JSの配列についてのまとめ
JSの配列についてのまとめ
JSはフロントエンドにもバックエンドにも動作環境がある言語です。
そのJSにおける配列は、メソッドが充実しておりとても使い勝手が良いです。
TypeScriptなどのaltJSにおいても、これらのJSのコアの知見はそのまま流用できます。
という事で、とても汎用的な知見になると思い、まとめました。
配列の操作
配列は、特定の位置をindexで指定し代入と参照を行うことで変数同様に扱えます。
しかし、JavaScriptの配列はいくつか特定の用途に使えるメソッドを持っていますので、これらを紹介していきます。
先頭あるいは最後の要素に対する操作
配列arrayの先頭要素はarray[0]を指し、最後の要素はarray[array.length – 1]を指します。
配列にはこれらの要素に対する操作を行うメソッドが定義されています。
どれらも対象の配列に対して破壊的な変更を行うメソッドであることに注意してください。
操作一覧
先頭あるいは最後の要素に対する操作は、キューやスタックを実装するに当たって基本的な操作の単位になります。
そのため、これらの操作のメソッド名はコンピュータサイエンスでよく使われる用語がそのまま使われています。
- push
- pop
- unshift
- shift
メソッドの解説
push
pushを用いることで、末尾に要素を追加できます。
その返り値は変更後の配列の長さです。
pop
popを用いることで、末尾から要素を取得できます。
返り値は、変更前の配列の最後の要素です。
取得した要素は配列から削除されます。
unshift
unshiftを用いることで、先頭へ要素を挿入できます。
その返り値は、変更後の配列の長さです。
shift
shiftを用いることで、先頭の要素を取得できます。
返り値は、変更前の配列の最初の要素です。
取得した要素は配列から削除されます。
サンプルコード
let array = ['b', 'c', 'd'];
array.push('e'); // => 4
array; // => ['b', 'c', 'd', 'e']
array.pop(); // => 'e'
array; // => ['b', 'c', 'd']
array.unshift('a'); // => 4
array; // => ['a', 'b', 'c', 'd']
array.shift(); // => 'a'
array; // => ['b', 'c', 'd']
複数の要素の操作
配列に対して複数の要素を追加したい場合には、concatメソッドを使います。
配列から部分配列を取得したい場合、sliceメソッドを使います。
配列の任意の場所の要素を変更したい場合、spliceメソッドを使います。
ES2015以降では、配列内での要素の削除を行うcopyWithinメソッドと、複数の要素の値を一度に指定するfillが利用できます。
また、先頭あるいは最後の要素に対する操作で紹介した、pushとunshiftも、可変長引数をとるために複数の要素の追加/挿入を行うことができます。
その場合、pushは末尾に複数要素を追加し、unshiftは先頭に複数の要素を挿入します。
ただし、pushとunshiftの引数に配列を渡しても分解は行われないので注意をしてください。
これらは、配列に対して一度に複数の要素を操作すると考えることができます。
元の配列に変更を加えるものもあれば、変更は加えずに新たな配列を作るものもあります。
concat, sliceメソッドは、元の配列に変更を加えない安全なメソッドです。
元の配列に対して変更を加えず、常に元の要素がコピー後に変更された、新たな配列を返します。
対して、splice, copywithin, fillメソッドは、破壊的なメソッドです。
呼び出し元の配列に対して変更を加えます。
またpushとunshiftも、単一要素を追加/挿入する場合と同様に破壊的な変更を加えます。
let array = [11, 12, 13];
let concatenated = array.concat(14, 15);
let sliced = array.slice(0, 2)
array; // => [11, 12, 13]
concatenated; // => [11, 12, 13, 14, 15]
sliced; // => [11, 12]
メソッドの解説
concat
concatは可変長引数で指定された要素が末尾に追加された新たな配列を返します。
引数に配列を渡すと、配列を分解したのちに、末尾への追加を行います。
引数の配列の要素が配列の場合、分解は行わずに追加を行います。
呼び出した際に配列を破壊しないのは意外ですが、数値や文字列と同様だと考えれば納得できます。
let array = [11, 12, 13];
// 複数の引数を渡した場合
array.concat(14, 15); // => [11, 12, 13, 14, 15]
// 引数に配列を渡した場合
array.concat([14, 15]); // => [11, 12, 13, 14, 15]
// 引数に配列と、スカラ値を渡した場合
array.concat([14, 15], 16); // => [11, 12, 13, 14, 15, 16]
// 引数に多次元配列を渡した場合
array.concat([14, [15, 16]]); // => [11, 12, 13, 14, [15, 16]]
slice
sliceは配列の部分配列を生成します。
sliceは第1引数に指定した位置から第2引数で指定した位置までの要素からなる新たな配列を返します。
第2引数が省略された場合、引数で渡されたindexの要素から最後の要素までになります。
引数に負の数を渡した場合、最後の要素から数えた位置を指定することになります。
最後に、第1引数はindexの要素ですが、第2引数はindexの要素の1つ前なので気をつけてください。
これは覚えづらいように思いますが、自然言語で表現するとそんなこともありません。
ちなみにsliceに日本語訳を当てると薄く切るとなりますが、元の配列に変更は加えないために少々ミスリードだと思います。
let array = [11, 12, 13, 14, 15];
// 最初から2番目まで
array.slice(0, 2); // => [11, 12]
// 最初から最後まで
array.slice(0);// => [11, 12, 13, 14, 15]
// indexが1の要素から最後まで
array.slice(1); // => [12, 13, 14, 15]
// 最後から2番目以降
array.slice(-2); // => [14, 15]
// 最初から、最後の一つ前まで
array.slice(0, -1) // => [11, 12, 13, 14]
// indexが1の要素から最後の一つ前まで
array.slice(1, -1); // => [12, 13, 14]
splice
spliceは配列の任意の場所を指定した上で要素を変更することができる破壊的な操作です。
引数は3つあり、それぞれ以下を意味します。
- 第1引数: 変更を開始する要素のindex
- 第2引数: 取り除く要素の数
- 第3引数以降: 追加する要素
返り値は、取り除かれた要素の配列を返します。
元の配列に対し、要素の削除が行われなかった場合、空の配列が返ります。
concatとは異なり、第3引数以降に配列を指定しても分解は行いません。
let array = [1, 5, 7];
// array[1]に2, 3, 4が挿入される
let removed = array.splice(1, 0, 2, 3, 4);
array; // => [ 1, 2, 3, 4, 5, 7 ]
removed; // => []
// array[5]に6が挿入され、以降一つずつ後ろへずれる
removed = array.splice(5, 0, 6);
array; // => [ 1, 2, 3, 4, 5, 6, 7 ]
removed; // => []
// array[1]から2つの要素を削除する
removed = array.splice(1, 2);
array; // => [ 1, 4, 5, 6, 7 ]
removed; // => [ 2, 3 ]
// array[2]から1つの要素を削除して、'a', 'b'を挿入する
removed = array.splice(2, 1, 'a', 'b');
array; // => [ 1, 4, 'a', 'b', 6, 7 ]
removed; // => [ 5 ]
// array[2]から2つの要素を削除して、[1, 2]を挿入する
removed = array.splice(2, 2, [1, 2]);
array; // => [ 1, 4, [1, 2], 6, 7 ]
removed; // => [ 'a', 'b' ]
copyWithin
copyWithinメソッドは、配列から一連の要素を指定し、それらを配列の他の部分へコピーし、元あった要素を上書きします。
引数は以下のようになります。
- 第1引数: どこにコピーをするか(ターゲット)
- 第2引数: どこからコピーするか
- [第3引数: どこまでコピーするか]
sliceと同様、範囲指定を行う第2引数と第3引数には負の数を指定することができ、その場合は配列の最後の要素から数えられます。
返り値は、呼び出し元の配列自身となります。
let array = [11, 12, 13, 14];
// array[1]の位置から置き換えを行う。array[2]から最後までをコピーする。
array.copyWithin(1, 2); // => [11, 13, 14, 14]
array; // => [11, 13, 14, 14]
// array[2]の位置から書き換える。array[0]からarray[2]の前までをコピーする。
array.copyWithin(2, 0, 2); // => [11, 13, 11, 13]
// array[0]の位置から書き換える。最後から3番めの要素から最後の要素の1つ前までをコピーする
array.copyWithin(0, -3, -1); // => [13, 11, 11, 13]
fill
fillメソドは、複数の要素の値を一度に指定することができます。
Arrayコンストラクタと併用すれば、複数の要素の初期値を指定することができます。
配列の一部だけの値を指定することもできます。(負数の扱いは、splice, copyWithinなどと同様です)
第1引数に、設定する値を渡します。
第2引数には設定する要素の開始位置、第3引数は終了位置です。
返り値は、呼び出し元の配列自身となります。
// 大きさ5の配列を生成し、全体を1で初期化する
let array = new Array(5).fill(1);
array; // => [1, 1, 1, 1, 1]
array.fill('a'); // => ['a', 'a', 'a', 'a', 'a']
array.fill('b', 1); // => ['a', 'b', 'b', 'b', 'b']
array.fill('c', 2, 4) // => ['a', 'b', 'c', 'c', 'b']
配列全体に対する操作
配列の並び替え
配列全体に対して、並び替えを行いたい場合は、以下のメソッドを用います。
- reverse
- sort
名前の通り、sortは並び替えを行い、reverseは配列を逆転させます。
これらのメソッドは、呼び出し元の配列を直接書き換えます。
どちらも、返り値は呼び出し元の配列自身となります。
sortの引数による並び替えの実装
sortの引数には並び替えに用いる関数を指定することもできます。
以下の条件を満たす関数を引数に渡すことができます。
- 並び替えの関数は、2つの引数を受け取る
- 並び替えの関数は、真偽値または数値を返す
並び替えの際には、sort内のアルゴリズムによって上記関数の返り値によって並び替えを行います。
具体的には以下のようになります。
- 真偽値の場合、偽が返った時に並び替えを行う
- 数値の場合、負の値が返った時に並び替えを行う
サンプルコード
let array = [1, 2, 3, 4, 5];
array.reverse(); // => [5, 4, 3, 2, 1];
array; // => [5, 4, 3, 2, 1];
array.reverse(); // => [1, 2, 3, 4, 5];
let array = [5, 4, 3, 2, 1];
array.sort(); // => [1, 2, 3, 4, 5];
let nameArray = [ { name: 'Suzanne' }, { name: 'Jim' }, { name: 'Trever' }];
nameArray.sort((a, b) => a.name > b.name ); // nameの値でソート
nameArray; // => [ { name: 'Jim' }, { name: 'Suzanne' }, { name: 'Trever' } ]
nameArray.sort((a, b) => a.name[1] < b.name[1]) // nameの2文字目で逆順ソート
nameArray; // => [ { name: 'Suzanne' }, { name: 'Trever' }, { name: 'Jim' } ]
要素の検索
配列の要素の探索のための方法はいくつか存在します。
- indexOf, lastIndexOfを用いて、要素のindexを取得する
- findIndexを用いて、要素のindexを取得する
- findを用いて、要素を取得する
- someとeveryを用いて、要素が存在するか確認する
indexOfとlastIndexOf
1つ目はindexOfまたはlastIndexOfを利用する方法です。
indexOfは指定した値に厳密に等しい要素(===で等しい要素)を最初に持つindexを返します。
lastIndexOfは同様に、最後のindexを返します。
配列の一部だけを検索対象にしたい場合、第2引数で開始位置を指定します。
どちらも、要素が見つからなかった場合には、-1を返します。
const o = { name: 'k4h4shi' };
const array = [1, 5, "a", o, true, 5];
array.indexOf(5); // => 1
array.lastIndexOf(5) // => 5
array.indexOf('a') // => 2
array.lastIndexOf('a') // => 2
array.indexOf({ name: 'k4h4shi' }); // => -1
array.indexOf(o) // => 3
array.indexOf(5, 3); // => 5
findIndex
findIndexは、indexOfと同様の挙動をしますが、引数に要素の比較に用いる関数を渡します。
ただし、findLastIndexというようなメソッドは存在しません。
const array = [ { id: 1, name: 'k4h4shi' }, { id: 3, name: 'hoge' } ];
array.findIndex(o => o.id === 1); // => 0
array.findIndex(o => o.name === 'hoge'); // => 1
array.findIndex(o => o === {}) // -1
find
findは、findIndex同様に比較に用いる関数を渡すことができます。
返り値は、indexではなく見つかった要素自体が帰ります。
要素が見つからなかった場合には、undefinedが返ります。
const array = [ { id: 1, name: 'k4h4shi' }, { id: 3, name: 'hoge' } ];
array.find(o => o.id === 1); // => { id: 1, name: 'k4h4shi' }
array.find(o => o.name === 'hoge'); // => { id: 3, name: 'hoge' }
array.find(o => o === {}) // => undefined
someとevery
findによって、要素が見つかるかどうかを確認したい場合は、someを用います。
someは、引数に渡した関数による比較で真が返る要素が一つでもある場合、trueを返し、そうでなければfalseを返します。
everyは、引数に渡した関数による比較が、全ての要素に対し真を返す時にtrue、そうでなければfalseを返します。
const array = [ { id: 1, name: 'k4h4shi' }, { id: 3, name: 'qiitaro' } ];
array.some(o => o.id === 1); // => true
array.some(o => o.name === 'qiitaro'); // => true
array.some(o => o === {}) // => false
array.every(o => o.id === 1); // => false
array.every(o => o.name === 'qiitaro'); // => false
array.every(o => o === {}) // => false
array.every(o => o.name.includes('i')) // => true
配列内の全ての要素に対する操作
配列の全ての要素それぞれに対し、共通的に処理を行いたい場合はfor of文をを使います。
しかしそれ以外にも、JavaScriptの配列にはそういった処理を簡単に書けるメソッドが用意されています。
配列=>配列
配列の全ての要素に対してある操作を行い、結果として配列を返すメソッドは、mapとfilterです。
どちらのメソッドも返り値としてコピーを返すため、元の配列を変更しない安全なメソッドです。
map
mapは配列内の要素を変換します。
変換後の形式は自由に指定できます。
例えば、数値を保持するオブジェクトを要素に持つ配列を、その数値を要素に持つ配列に変換したいと言った場合に使えます。
mapの呼び出し時には、要素を変換するための関数を引数に渡します。
指定した関数が呼び出される時、その関数には以下の3つの要素が渡されます。
- 第1引数: 要素そのもの
- 第2引数: その要素のindex
- 第3引数: 配列そのもの
const cart = [ { name: 'iPhone', price: 54800 }, { name: 'iPad', price: 86400 } ];
const names = cart.map(x => x.name);
names; // => [ 'iPhone', 'iPad' ]
cart; // => [ { name: 'iPhone', price: 54800 }, { name: 'iPad', price: 86400 } ]
const prices = cart.map(x => x.price);
prices; // => [ 54800, 86400 ]
const discountPrices = prices.map(x => x * 0.8);
discountPrices; // => [ 43840, 69120 ]
// 二つの配列の要素を合わせたオブジェクトを要素に持つ配列も作れる
const dicountCart = names.map((e, i) => ({ name: e, price: discountPrices[i] }));
dicountCart; // => [ { name: 'iPhone', price: 43840 }, { name: 'iPad', price: 69120 } ]
filter
filterは配列内の要素の中で、ある条件に合致するもののみを取得した場合に使えます。
const cards = [];
for (let mark of ['heart', 'clover', 'diamond', 'spade']) {
for (let num = 1; num <= 13; num++) {
cards.push({mark, num});
}
}
let chosen = cards.filter(e => e.num === 2);
chosen; // => [ { mark: 'heart', num: 2 }, { mark: 'clover', num: 2 }, { mark: 'diamond', num: 2 }, { mark: 'spade', num: 2 } ]
chosen = cards.filter(e => e.mark === 'diamond');
chosen; // => diamondのcardのみ
chosen = cards.filter(e => e.num > 10);
chosen; // => 絵札のみ
配列=>スカラ値
配列の要素全体にある要素を適用し、結果として一つの値に変換するメソッドは複数あります。
例えば前述したfindやfindIndex, someやeveryなどもその一つと考えることが出来ると思います。
まだ紹介していないものを以下に列挙します。
- reduce
- join
reduce
配列全体を一つの値に変換したい、と言った場合はreduceを使います。
具体的にいえば、配列の全要素の合計や平均を計算したりしたいと言った場合です。
mapやfilterの場合、結果を得るための関数を渡す際の第1引数は配列の要素でしたが、reduceの場合はアキュムレータとなります。
アキュムレータとは、最終的に配列が変換される先です。
以降の引数は、mapやfilterと同様です。
reduceの第1引数は変換に用いる関数ですが、reduceの第2引数でアキュムレータの初期値を設定することができます。
アキュムレータの初期値がundefinedだった場合はreduceは異なる方法で処理を行います。
その場合、まずアキュムレータに格納される最初の要素は配列の最初の要素となり、2番目の要素から関数を呼び出し始めます。
const array = [5, 7, 2, 4];
const sum = array.reduce((a, e) => a += x, 0);
sum; // => 18
const sum2 = array.reduce((a, e) => a + x, 0); // +=の=は省略可能
sum2 // => 18
const sum3 = array.reduce((a, e) => a + x); // この場合第2引数を設定しない場合も、結果は変わらない
sum3 // => 18
join
配列の各要素をまとめて一つの文字列にしたい場合には、joinを用います。
第1引数はセパレータです、デフォルトは’,’となっています。
const party = ['桃太郎', 'キジ', '犬', '猿'];
const s = party.join();
s; // => "桃太郎,キジ,犬,猿"