JavaScriptにおいて多次元配列は、複雑なデータ構造を管理するために非常に重要な役割を果たします。
多次元配列とは、配列の要素としてさらに配列が含まれている構造を指し、行列データや格子状のマップ、複雑なリスト形式の情報を扱う際に多用されます。
プログラミング初心者から中級者へとステップアップする過程で、この多次元配列を自由自在に操作できるスキルは必須と言えるでしょう。
本記事では、JavaScriptにおける多次元配列の作成方法から、効率的な初期化の手法、さらには実務で役立つ操作テクニックまで詳しく解説します。
最新のJavaScript仕様に基づいた、安全でパフォーマンスの高い実装方法を一緒に学んでいきましょう。
JavaScriptにおける多次元配列の基本構造
JavaScriptには、厳密な意味での「多次元配列」という専用の型は存在しません。
その代わりに、配列の要素に別の配列を格納することで多次元構造を実現します。
これを「配列の配列(Array of Arrays)」と呼ぶこともあります。
最も一般的なのは、行と列の概念を持つ2次元配列です。
例えば、3行3列のデータを表現する場合、外側の配列の中に3つの要素(内側の配列)が含まれる形になります。
まずは、最もシンプルなリテラル記法による2次元配列の定義を見てみましょう。
// 3x3の2次元配列を定義する例
const matrix = [
[1, 2, 3], // 0行目
[4, 5, 6], // 1行目
[7, 8, 9] // 2行目
];
console.log(matrix);
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
このように、ネスト(入れ子)構造にすることで、視覚的にも行列に近い形でデータを保持できます。
JavaScriptの配列は動的であるため、各行の要素数が異なる「ジャグ配列(不揃いな配列)」を作成することも可能です。
しかし、プログラムの予測可能性を高めるためには、可能な限り各次元のサイズを揃えて運用するのが一般的です。
多次元配列の要素にアクセスする方法
多次元配列内の特定の要素にアクセスするには、ブラケット記法([])を連結して使用します。
1つ目のブラケットで行(外側のインデックス)を指定し、2つ目のブラケットで列(内側のインデックス)を指定します。
インデックスは常に0から始まる点に注意してください。
const data = [
["A", "B", "C"],
["D", "E", "F"],
["G", "H", "I"]
];
// 1行目の2列目(インデックス[0][1])を取得
console.log(data[0][1]);
// 2行目の3列目(インデックス[1][2])を取得
console.log(data[1][2]);
// 値を書き換える場合
data[2][0] = "Z";
console.log(data[2][0]);
"B"
"F"
"Z"
多次元配列の階層が深くなるほど、インデックスの指定も増えていきます。
例えば、3次元配列であればarray[i][j][k]のように記述します。
存在しないインデックスに対してアクセスしようとすると、undefinedが返されます。
特に、親となる階層がundefinedの場合にさらにその子要素へアクセスしようとすると、エラー(TypeError)が発生します。
安全にアクセスするためには、オプショナルチェイニング(?.)を活用するのも有効な手段です。
const val = data[5]?.[0]; // data[5]がなくてもエラーにならない
console.log(val);
undefined
多次元配列の初期化における注意点と推奨手法
プログラムで動的に多次元配列を生成する場合、初期化の方法には注意が必要です。
よくある間違いとして、Array.fill()メソッドを誤用するケースが挙げられます。
fill()メソッドによる初期化の罠
以下のコードは、一見すると3×3の0埋めされた配列を作成するように見えますが、深刻な問題を抱えています。
// 誤った初期化方法
const rows = 3;
const cols = 3;
const badMatrix = Array(rows).fill(Array(cols).fill(0));
console.log("初期状態:", badMatrix);
// 0行0列目だけを書き換えたつもりが...
badMatrix[0][0] = 99;
console.log("変更後:", badMatrix);
初期状態: [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
変更後: [[99, 0, 0], [99, 0, 0], [99, 0, 0]]
結果を見ると、全ての行の1列目が99に書き換わってしまいました。
これは、fill()に渡した内側の配列が全ての要素で同じメモリ参照を共有しているためです。
一つの行を操作すると、全ての行が同時に変化してしまうという現象は、多次元配列における最も代表的なバグの原因です。
Array.from()を使用した安全な初期化
多次元配列を正しく初期化するには、各行を個別のインスタンスとして生成する必要があります。
最も推奨される方法は、Array.from()を使用する方法です。
const rows = 3;
const cols = 3;
// 第二引数のコールバック関数で、各行ごとに新しい配列を生成する
const goodMatrix = Array.from({ length: rows }, () => Array(cols).fill(0));
goodMatrix[0][0] = 99;
console.log(goodMatrix);
[[99, 0, 0], [0, 0, 0], [0, 0, 0]]
この方法であれば、各行が独立したメモリ領域を持つため、意図しない書き換えが発生しません。
また、複雑な初期値を設定したい場合も、コールバック関数の中で柔軟に制御できます。
多次元配列の反復処理(ループ操作)
多次元配列の全ての要素を処理するには、ループを入れ子にする「多重ループ」を使用します。
用途に合わせて、for文やforEach、あるいはmapなどの高階関数を使い分けましょう。
for…of文によるシンプルな反復
単純に全ての値を表示したり、計算に使用したりする場合は、for...of文が読みやすく効率的です。
const board = [
[1, 2],
[3, 4],
[5, 6]
];
for (const row of board) {
for (const cell of row) {
console.log("要素:", cell);
}
}
map()による配列の変換
既存の多次元配列を加工して、新しい多次元配列を作成する場合はmap()が適しています。
例えば、全ての数値を2倍にする処理は以下のようになります。
const original = [[1, 2], [3, 4]];
const doubled = original.map(row => row.map(value => value * 2));
console.log(doubled);
[[2, 4], [6, 8]]
map()を入れ子にすることで、構造を維持したまま各要素にアクセスして処理を適用できます。
これは関数型プログラミングのスタイルであり、元の配列を破壊せずに新しいデータを得られるというメリットがあります。
多次元配列を平坦化(フラット化)する方法
多次元配列を処理する際、一時的に1次元配列に変換したいケースがあります。
このような時には、JavaScript標準のflat()メソッドが非常に便利です。
const nested = [1, [2, [3, [4]]]];
// デフォルトでは1段階だけ平坦化
console.log(nested.flat());
// 階層を完全に平坦化する場合はInfinityを指定
console.log(nested.flat(Infinity));
[1, 2, [3, [4]]]
[1, 2, 3, 4]
flat()を使用することで、多次元構造を持つデータから特定の値を検索したり、合計値を計算したりする処理が大幅に簡略化されます。
また、要素を変換しながら平坦化を行いたい場合は、flatMap()というメソッドも用意されています。
実践的な活用例:多次元配列でのデータ管理
多次元配列の活用シーンとして代表的なのは、情報のマトリックス管理です。
ここでは、Webアプリケーションでよく見られる「ユーザーごとのスコア管理テーブル」を想定した例を紹介します。
const userScores = [
{ id: 1, scores: [80, 90, 70] },
{ id: 2, scores: [60, 85, 95] },
{ id: 3, scores: [100, 90, 80] }
];
// 各ユーザーの平均点を計算し、新しい配列に格納する
const userAverages = userScores.map(user => {
const sum = user.scores.reduce((acc, current) => acc + current, 0);
const average = sum / user.scores.length;
return { id: user.id, average: average.toFixed(1) };
});
console.table(userAverages);
上記のコードでは、オブジェクトのプロパティの中に配列が含まれる構造になっています。
実務では、純粋な多次元配列だけでなく、このようにオブジェクトと組み合わせた「複雑なデータ構造」を扱う機会が非常に多いです。
reduce()やmap()を組み合わせることで、多層階層のデータも宣言的に操作できるようになります。
多次元配列のコピーとイミュータビリティ
JavaScriptで配列を扱う際に避けられないのが「浅いコピー(Shallow Copy)」の問題です。
[...array](スプレッド構文)やslice()を使えば配列のコピーは可能ですが、多次元配列の場合は注意が必要です。
const original = [[1, 2], [3, 4]];
const copy = [...original];
copy[0][0] = 999;
console.log("コピー:", copy[0][0]);
console.log("オリジナル:", original[0][0]);
コピー: 999
オリジナル: 999
このように、スプレッド構文では内側の配列の参照までは切り離せません。
多次元配列を完全に独立した状態でコピーするには、「深いコピー(Deep Copy)」を行う必要があります。
現代のJavaScript環境では、ブラウザ標準のstructuredClone()を使用するのが最も簡単で安全です。
const original = [[1, 2], [3, 4]];
const deepCopy = structuredClone(original);
deepCopy[0][0] = 999;
console.log("コピー:", deepCopy[0][0]);
console.log("オリジナル:", original[0][0]);
コピー: 999
オリジナル: 1
データの不変性(イミュータビリティ)を維持することは、現代のフロントエンド開発(ReactやVue.jsなど)において不具合を防ぐための重要なプラクティスです。
多次元配列を更新する際は、常に新しい参照を作成する意識を持つことが重要です。
パフォーマンスと最適化
大規模な多次元配列を扱う場合、パフォーマンスが問題になることがあります。
JavaScriptの配列は、実際にはハッシュマップに近い内部構造を持っているため、非常に大きな行列計算には向いていない側面があります。
もし数万、数十万といった要素を持つ数値データを高速に処理する必要がある場合は、TypedArray(型付き配列)の検討も視野に入れましょう。
ただし、通常のWebアプリケーション開発において数千件程度のデータを扱う分には、標準の配列操作で十分な速度が得られます。
ループ処理を記述する際は、内側のループで計算が重複しないようにするなどの基本的な最適化を心がけましょう。
| 手法 | 用途 | メリット |
|---|---|---|
| Array.from() | 初期化 | 各要素が独立した参照を持つ。 |
| map() | データ変換 | 非破壊的で読みやすい記述が可能。 |
| flat() | 平坦化 | ネストを簡単に解消できる。 |
| structuredClone() | コピー | 深い階層まで完全に複製できる。 |
まとめ
JavaScriptの多次元配列は、データの構造化に欠かせない強力なツールです。
配列の中に配列を入れるというシンプルな概念ですが、初期化時の参照共有問題や、コピー時の挙動など、注意すべき点も多く存在します。
正しい初期化方法としてArray.from()を使いこなし、データの変更時にはstructuredClone()やmap()を活用することで、安全なプログラムを作成できます。
また、多重ループやflat()メソッドといった操作方法を習得すれば、複雑なデータ変換も容易に行えるようになるでしょう。
今回解説した基本原則と実践的なテクニックを活かし、より高度なJavaScriptプログラミングに挑戦してみてください。
日々のコーディングの中で多次元配列を適切に扱えるようになることが、効率的でメンテナンス性の高いコードへの第一歩となります。
