配列
いくつかの値を並べ、添字で参照できるようにしたものを、配列と呼びます。数学で言うところの数列のようなものです。
配列をプログラム中に直接書く場合の表記は、カンマで区切った値のリストを角カッコ [ ]
で囲います。
例:
[1, 2, 3] // 配列の例1
["a", true, 42] // 配列の例2
[[1, 2], [3, 2]] // 配列の例3
[] // 空の配列
添字によるアクセス
配列を表す式の後に [ ]
で数値を続ける(ブラケット演算子)ことによって、配列の所定の位置の値を取得できます。配列の要素の位置は0から始まる整数で指定します。
let a = ["a", true, 42];
console.log(a[0]); // => "a"
console.log(a[1]); // => true
console.log(a[2]); // => 42
配列の長さ
length プロパティーで配列の長さを取得できます。
// 続き
console.log(a.length); // => 3
配列の同一性と破壊的な操作・非破壊的な操作
数値や文字列は、内容によって同一性が決まりました。しかし、配列の場合は内容が同じでも同一とは判定されません。
console.log(123 === 123); // => true
console.log("hoge" === "hoge"); // => true
let a = [1, 2, 3];
console.log(a === a); // => true (実体が同じ)
console.log(a === [1, 2, 3]); // => false (内容が同じでも実体が同じではない)
配列の操作には、配列の実体を変更する操作と、既存の配列は変更せずに新しい配列を作って返す操作の2種類があります。ここでは、前者を「破壊的な操作」、後者を「非破壊的な操作」と呼ぶことにします。
配列の操作(破壊的)
ブラケット演算子 ( [ ]
) で、配列の特定の位置の要素を更新(代入)することもできます:
let a = [1, 2, 3];
a[1] = "Hello world!"
console.log(a); // => [ 1, 'Hello world!', 3 ]
push / pop メソッドは、末尾への要素の追加・削除を行います。
let a = [1, 2, 3];
a.push(4); // 末尾に追加
console.log(a); // => [ 1, 2, 3, 4 ]
a.pop(); // 末尾から削除
console.log(a); // => [ 1, 2, 3 ]
unshift / shift メソッドは、先頭への要素の追加・削除を行います。
let a = ["a", "b", "c"];
a.unshift("x"); // 先頭に追加
console.log(a); // => [ 'x', 'a', 'b', 'c' ]
a.shift(); // 先頭を削除
console.log(a); // => [ 'a', 'b', 'c' ]
reverse メソッドは、配列の要素を逆順にします。
let a = [42, true, "foo"];
a.reverse(); // 逆順にする
console.log(a); // => [ 'foo', true, 42 ]
sort メソッドは、配列の中身を小さい順に並べ替えます。
let a = [1, 9, 3, 2];
a.sort();
console.log(a); // => [ 1, 2, 3, 9 ]
sort メソッドでは、要素の比較方法(結果の並び順)を自分で指定することもできますが、ここでは解説しません。
配列の操作(非破壊的)
concat メソッドで、配列の末尾に要素を追加した新しい配列を作ることができます。
let a = [1, 2, 3];
console.log(a.concat(4)); // => [ 1, 2, 3, 4 ]
console.log(a.concat(4, 5)); // => [ 1, 2, 3, 4, 5 ]
console.log(a); // => [ 1, 2, 3 ]
concat に複数の値を渡すことによって、複数の値を一気に追加することもできます。
join メソッドでは、配列の要素を文字列として連結することができます。区切り文字はデフォルトではカンマですが、引数で指定することで任意の文字列を区切り文字として使うことができます。
let a = ["a", "b", "x"];
console.log(a.join()); // => 'a,b,x' (カンマ区切り)
console.log(a.join("")); // => 'abx' (区切り文字なし)
console.log(a.join("+")); // => 'a+b+x' ('+' 区切り)
配列の操作(関数を使うもの)
配列操作の関数(メソッド)には、要素に関する関数を引数として受け取るものもあります。後述するように for 文でもこういう処理をかけますが、こちらの方がメソッド一発で済むので簡潔になる場合が多いと思います。これらの関数は全て非破壊的です。
every メソッドと some メソッドで、「全ての要素が条件を満たすか」「一個以上の要素が条件を満たすか」を調べることができます。条件は関数として与えるので、無名関数(アロー表記)を使うと便利です。
> [1, 2, 3].every((x) => x % 2 === 0) // 全ての要素が2で割り切れるか?→No
false
> [1, 2, 3].some((x) => x % 2 === 0) // 2で割り切れる要素があるか?→Yes
true
> [1, 2, 3].every((x) => x > 0) // 全ての要素が0より大きいか?→Yes
true
> [1, 2, 3].some((x) => x > 4) // 4より大きい要素があるか?→No
false
map メソッドで、それぞれの要素を関数によって変換した配列を返します。これも引数が関数なので、無名関数を使うと便利です。
> [1, 2, 3].map((x) => x * x) // それぞれの要素を2乗した配列を作る
[ 1, 4, 9 ]
> [1, 2, 3].map((x) => x.toString(2)) // それぞれの要素を2進数として文字列化したものを作る
[ '1', '10', '11' ]
> [1, 2, 3].map((x) => Math.sqrt(x)) // それぞれの要素(数値)の平方根をとる
[ 1, 1.4142135623730951, 1.7320508075688772 ]
> ["1", "2", "3"].map((x) => parseInt(x)) // それぞれの要素(文字列)を整数として解釈する
[ 1, 2, 3 ]
forEach メソッドで、配列のそれぞれの要素に対して処理を行うことができます。
// test-foreach.js
let a = [1, 2, 3];
a.forEach((x) => {
console.log(x);
});
$ node test-foreach.js
1
2
3
「配列のそれぞれの要素に対して処理を行う」場合には、後述する for 文を使うという方法もあります。
配列の要素のうち、条件を満たすものを抽出した新しい配列を作るには、 filter メソッドを使います。
> [1, 2, 3, 7, 10].filter((x) => x % 2 === 0) // 配列の要素のうち、偶数であるものを取り出す
[ 2, 10 ]
> ["Hello", "Goodbye", "Good morning", "Bonsoir"].filter((x) => x.startsWith("Good")) // "Good" から始まるものを取り出す
[ 'Goodbye', 'Good morning' ]
reduce メソッドは、配列の要素に対して順番に関数を適用し、一つの値にします。具体的には、配列 a
の要素数を n (=a.length
) とすると、 a.reduce(f, b)
は大雑把に言って
f(... f(f(b, a[0]), a[1]), ... a[n-1])
と等価になります。 これを使うと、配列の要素の総和の計算は次のように書けます:
> [1, 2, 3].reduce((x, y) => x+y, 0) // ((0+1)+2)+3 と等価
6
問題:reduce メソッドを使って、配列の要素の総乗を計算せよ。
reduceRight メソッドは reduce メソッドと似ていますが、配列の末尾から先に計算します。
配列 a
の要素数を n (=a.length
) とすると、 a.reduceRight(f, b)
は大雑把に言って
f(... f(f(b, a[n-1]), a[n-2]), ... a[0])
と等価になります。 これを使うと、配列の要素の総和の計算は次のように書けます:
> [1, 2, 3].reduce((x, y) => x+y, 0) // ((0+3)+2)+1 と等価
6
これらのメソッドの引数とする配列には、要素の値が引数として渡されますが、そのほかに要素のインデックスと配列自身も渡されます。
let a = [1, 2, 3];
a.forEach((x, i, b) => {
x === a[i]; // => true (第2引数は、要素のインデックスが渡される)
a === b; // => true (第3引数には、処理対象の配列自身が渡される)
console.log(`a[${i}] = ${x}`);
});
このことを忘れると、思わぬ落とし穴にはまることがあります。例えば、 parseInt 関数には第2引数として基数を渡せるという仕様があるので、配列の map メソッドと組み合わせると
> ["1", "2", "3"].map(parseInt)
[ 1, NaN, NaN ]
という、予想外の実行結果となります(先ほどの、 (x) => parseInt(x)
を使った場合と比べてみましょう)。
この「予想外の実行結果」に関する詳しい説明はしません。気になる方は、parseInt("1", 0)
, parseInt("2", 1)
, parseInt("3", 2)
の結果がそれぞれどうなるか、自分で調べてみてください。
配列と for 文
配列の要素に関して順番に処理を行うには、 forEach メソッドを使う方法もありますが、ベタに for 文を使うこともできます。
let a = ["a", "b", "c"]; // 処理したい配列
for (let i = 0; i < a.length; ++i) {
console.log(a[i]); // 処理
}
let b = [1, 2, 3];
let sum = 0;
for (let i = 0; i < b.length; ++i) {
sum += b[i];
}
console.log(sum);
for-of 文を使うと、配列の添字の変数(上のコード例だと i
)が不要になって、もう少し簡潔になります。
let a = ["a", "b", "c"];
for (let x of a) {
console.log(x);
}
let b = [1, 2, 3];
let sum = 0;
for (let x of b) {
sum += x;
}
console.log(sum);
ちなみに、for-of 文は ECMAScript 6 で導入された、比較的新しい機能です。