JavaScript | 配列・オブジェクト:ループ処理 – ネスト配列の処理

JavaScript JavaScript
スポンサーリンク

ネスト配列とは何か

ネスト配列は「配列の中にさらに配列が入っている」入れ子構造です。例えば行列(2次元)、ツリー(多次元)、グループ化されたデータなどが該当します。ここが重要です:処理の設計は“どの深さまで扱うか”と“要素が配列か値かをどう区別するか”で決まります。1階層だけなら単純な二重ループ、深い・不定の階層なら再帰や flat/flatMap を使います。


1階層(2次元)の基本処理

二重 for/for…of で行・列を走査する

const matrix = [
  [1, 2, 3],
  [4, 5, 6]
];
for (let i = 0; i < matrix.length; i++) {
  const row = matrix[i];
  for (let j = 0; j < row.length; j++) {
    const cell = row[j];
    // (i, j) で位置を使いつつ処理
  }
}
JavaScript

ここが重要です:外側が“行”、内側が“列”。境界は i < matrix.length と j < row.length の“開区間”を守ると取りこぼし/はみ出しを防げます。

for…of で簡潔に(インデックス不要なら)

for (const row of matrix) {
  for (const cell of row) {
    // cell を処理
  }
}
JavaScript

ここが重要です:値中心で書けるため、インデックス管理のミスが減り、読みやすさが上がります。座標が必要なら entries() を使います。

1階層の平坦化(一覧へ)

const flattened = matrix.flat(); // [1, 2, 3, 4, 5, 6]
JavaScript

ここが重要です:flat() は既定で“深さ1”を展開します。「行列のセルを一直線にしたい」などで最短です。


変換・抽出・集約(1階層での定番レシピ)

条件抽出→変換(在庫アイテムのラベル化)

const groups = [
  { name: "A", items: [{n:"x",stock:1},{n:"y",stock:0}] },
  { name: "B", items: [{n:"z",stock:2}] }
];
const labels = groups
  .flatMap(g => g.items)                 // 1階層だけ展開
  .filter(it => it.stock > 0)            // 抽出
  .map(it => `${it.n} (${it.stock})`);   // 変換
// ["x (1)", "z (2)"]
JavaScript

ここが重要です:flatMap は「map の返り値(配列)を1階層だけ自動で展開」。ネストの“中身だけ集めて”から、filter/map を重ねるのが読みやすい定石です。

2次元集計(合計・平均)

const sum = matrix.reduce((acc, row) => acc + row.reduce((a, n) => a + n, 0), 0);
const count = matrix.reduce((acc, row) => acc + row.length, 0);
const avg = count ? sum / count : 0;
JavaScript

ここが重要です:外側と内側で reduce を“入れ子”にすれば、行列全体を1式で集約できます。


多階層・不定深さの処理(再帰・flat の活用)

再帰で“配列か値か”を見て辿る(万能)

function walk(xs, visit) {
  for (const x of xs) {
    if (Array.isArray(x)) walk(x, visit);   // 配列なら深く辿る
    else visit(x);                          // 値なら処理
  }
}
const nested = [1, [2, [3, 4]], 5];
const out = [];
walk(nested, v => out.push(v)); // 値を集める
// out: [1, 2, 3, 4, 5]
JavaScript

ここが重要です:不定深さに強い基本形。visit を差し替えれば“変換・検証・集計”を汎用的に書けます。

flat(Infinity) で完全平坦化してから処理

const deep = [1, [2, [3, [4]]]];
const flatAll = deep.flat(Infinity); // [1, 2, 3, 4]
const sum = flatAll.reduce((acc, n) => acc + n, 0);
JavaScript

ここが重要です:構造を意識せず“中身だけ扱いたい”なら一旦平坦化が最短。ただしデータ量が大きい場合はコストに注意。

深さ指定の平坦化

const nested = [1, [2, [3]]];
nested.flat(1); // [1, 2, [3]]
nested.flat(2); // [1, 2, 3]
JavaScript

ここが重要です:“必要最小の深さ”で展開すると意図が明確になり、不要な計算を避けられます。


ネストオブジェクト+配列の混在を処理する

パスで辿って値を抽出(安全に)

const data = {
  groups: [
    { name: "A", items: [{ id: 1 }, { id: 2 }] },
    { name: "B", items: [{ id: 3 }] }
  ]
};
const ids = data.groups.flatMap(g => g.items.map(it => it.id)); // [1,2,3]
JavaScript

ここが重要です:プロパティの存在が不安定なら、オプショナルチェーン(?.)と null 合体(??)で安全に書くと壊れにくくなります。

ネストした値をグループ集計

const grouped = data.groups.reduce((acc, g) => {
  acc[g.name] = g.items.map(it => it.id);
  return acc;
}, {});
// { A:[1,2], B:[3] }
JavaScript

ここが重要です:reduce は“辞書化・集約”に強い。ネストの“外側でまとめ、内側は map”が読みやすい構成です。


疎配列(穴)・undefined の扱い(挙動の理解)

flat/flatMap は空スロットをスキップする

const sparse = [1, , [2, , 3]];
console.log(sparse.flat()); // [1, 2, 3]
JavaScript

ここが重要です:疎配列の“穴”は消えます。穴を保持したい要件では、事前に Array.from で正規化するか、null を埋めて“値として扱う”設計にします。

for/for…of は穴も位置として回る(undefined)

const a = Array(3); // [ <3 empty items> ]
for (const v of a) console.log(v); // undefined, undefined, undefined
JavaScript

ここが重要です:ループの種類で挙動が違うため、要件に合わせて選びます。“穴は無視したい”なら map/filter/flat 系、“穴も認識したい”なら for の正規化を。


パフォーマンスと設計の指針

構造が分かっているなら最短の道具を

  • 1階層の抽出は flat/flatMap。
  • 行列の集約は入れ子の reduce。
  • 値だけ欲しいなら flat(Infinity)→1回の reduce。

ここが重要です:道具を適切に選ぶだけで、コードは短く、計算も軽くなります。

深い・巨大データは再帰に“ガード”を

  • Array.isArray で配列かを判定。
  • 必要なら最大深さや件数の上限を設ける。
  • 重い処理(正規表現、Collator)はループ外で準備して使い回す。

非破壊の原則を守る

ネスト構造は共有参照が多くなりがち。更新は“新インスタンス”を返す({ …obj }、[…arr])ことで、外部への副作用を防ぎます。


実践例(よくあるネスト処理を最短で)

メニュー(多段)を表示用のフラットリストに

const menu = [
  { label: "File", children: ["New", "Open"] },
  { label: "Edit", children: ["Copy", ["Paste", "Undo"]] }
];
const items = menu.flatMap(m => Array.isArray(m.children) ? m.children : []);
const flatOnce = items.flat(); // ["New", "Open", "Copy", "Paste", "Undo"]
JavaScript

コメントツリーから全ユーザー名を抽出

const comments = [
  { user: "A", replies: [{ user: "B" }, { user: "C", replies: [{ user: "D" }] }] }
];
function collectUsers(nodes) {
  const out = [];
  (function walk(xs) {
    for (const x of xs) {
      out.push(x.user);
      if (x.replies) walk(x.replies);
    }
  })(nodes);
  return out;
}
const users = collectUsers(comments); // ["A","B","C","D"]
JavaScript

行列の隣接セル合計(座標で処理)

function sumNeighbors(grid, r, c) {
  let sum = 0;
  for (let i = r - 1; i <= r + 1; i++) {
    for (let j = c - 1; j <= c + 1; j++) {
      if (i === r && j === c) continue;
      const v = grid[i]?.[j];
      if (Number.isFinite(v)) sum += v;
    }
  }
  return sum;
}
JavaScript

まとめ

ネスト配列の処理は「どの深さを相手にするか」を明確にし、適切な道具を選ぶことが核心です。1階層なら二重ループや flat/flatMap、全階層なら再帰や flat(Infinity)。抽出→変換→集約は“展開してから目的別メソッド”が読みやすく、辞書化やグルーピングは reduce が得意。疎配列の穴の挙動、オプショナルチェーンでの安全なアクセス、非破壊更新の原則、深いデータでのガードまで押さえておけば、初心者でも意図通りで壊れにくいネスト処理を短く正確に書けます。

タイトルとURLをコピーしました