JavaScript | 配列・オブジェクト:ネスト構造の扱い – ネスト配列

JavaScript JavaScript
スポンサーリンク

ネスト配列とは何か

ネスト配列は「配列の中にさらに配列が入っている構造」です。2次元(行・列の表)、階層(ツリーの子配列)、グループ化(カテゴリごとの要素)など、現実のデータを自然に表現できます。ここが重要です:扱いの基本は“アクセス(インデックスで辿る)”“更新(非破壊で差し替える)”“反復(map・for…of)”“整形(flat/flatMap/reduce)”を押さえることです。


ネスト配列へのアクセス(インデックスで辿る)

2次元配列(行・列)を辿る

const grid = [
  [1, 2, 3],   // row 0
  [4, 5, 6],   // row 1
  [7, 8, 9]    // row 2
];
const value = grid[1][2]; // 6(row=1 の col=2)
JavaScript

固定の位置を読むだけなら、このインデックスの連鎖で十分です。動的に辿る場合は安全にガードを入れます。

“無いかも”に備える(欠損ガード)

function getAt(grid, r, c) {
  if (!Array.isArray(grid[r])) return undefined;
  return grid[r][c];
}
JavaScript

ここが重要です:インデックスが範囲外だと undefined。必ずガードを入れてからアクセスし、落ちないコードにします。


ネスト配列の更新(非破壊で差し替える)

2次元配列の要素を安全に更新

const grid = [
  [1, 2, 3],
  [4, 5, 6]
];
// row=1, col=0 を 400 に変更(非破壊)
const next = grid.map((row, ri) =>
  ri === 1 ? row.map((v, ci) => (ci === 0 ? 400 : v)) : row
);
JavaScript

ここが重要です:直接代入は共有状態で危険です。map で“該当行を作り直し、該当列だけ置き換える”のが定番の非破壊更新です。

子配列の置き換え(丸ごと差し替え)

const lists = [
  ["a", "b"],
  ["c"]
];
const nextLists = lists.map((group, i) =>
  i === 0 ? [...group, "z"] : group
);
JavaScript

部分的な追加・削除はスプレッドや filter を組み合わせ、元を壊さず新インスタンスを返します。


反復と整形(map・flat・flatMap・reduce)

2次元を1次元へ(flat)

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

深さがさらに多い場合は flat(depth) を指定します。ここが重要です:flat は“穴(空スロット)を無視し、存在する要素を平坦化”します。

変換+平坦化(flatMap)

const rows = ["ab", "cd"];
const chars = rows.flatMap(s => s.split("")); // ["a","b","c","d"]
JavaScript

map 後の配列を自動で 1 段平坦化します。生成・展開が同時に欲しいときに最短です。

集計・検索(reduce / find / some / every)

const groups = [[1,2], [3,4,5]];
const sum = groups.flat().reduce((a, n) => a + n, 0); // 15

const found = groups.flat().find(n => n > 3); // 4
const anyLarge = groups.some(g => g.some(n => n >= 5)); // true
const allPositive = groups.every(g => g.every(n => n > 0)); // true
JavaScript

ここが重要です:some/every は“ネストの中で条件が満たされるか”の短絡評価に向きます。flat は“形を単純化してから処理”するため、可読性が高まります。


ツリー構造(子配列)を扱う基本

子を持つレコードの反復(再帰)

const tree = {
  name: "root",
  children: [
    { name: "A", children: [] },
    { name: "B", children: [{ name: "B1", children: [] }] }
  ]
};

function walk(node, visit) {
  visit(node);
  for (const child of node.children ?? []) {
    walk(child, visit);
  }
}

walk(tree, n => console.log(n.name)); // root, A, B, B1
JavaScript

ここが重要です:再帰は“子が無い場合(undefined)”も考慮して安全に辿ります。更新は“該当ノードだけ新しく作り直す”非破壊パターンが基本です。

ノード更新(探して置き換える)

function updateByName(node, target, patch) {
  if (node.name === target) return { ...node, ...patch };
  const children = (node.children ?? []).map(ch => updateByName(ch, target, patch));
  return { ...node, children };
}
const nextTree = updateByName(tree, "B", { flag: true });
JavaScript

該当ノードを見つけたらスプレッドで置き換え、その他はそのまま返します。変更箇所だけが新インスタンスになり、差分検知が安定します。


2次元データの定番操作(行・列・検索・挿入)

行の挿入・削除

const grid = [[1,2], [3,4]];
const insertRow = (g, idx, row) => [...g.slice(0, idx), row, ...g.slice(idx)];
const removeRow = (g, idx) => [...g.slice(0, idx), ...g.slice(idx + 1)];

const g1 = insertRow(grid, 1, [9,9]); // [[1,2],[9,9],[3,4]]
const g2 = removeRow(grid, 0);        // [[3,4]]
JavaScript

列の挿入・削除

const insertCol = (g, idx, value = null) =>
  g.map(row => [...row.slice(0, idx), value, ...row.slice(idx)]);
const removeCol = (g, idx) =>
  g.map(row => [...row.slice(0, idx), ...row.slice(idx + 1)]);
JavaScript

ここが重要です:列操作は“各行に対して同じ位置に挿入・削除”する、という思考で map します。

行・列の検索

const findRowIndex = (g, pred) => g.findIndex(pred);
const findColIndex = (g, colPred) => (g[0] ?? []).findIndex(colPred);

const r = findRowIndex(grid, row => row.includes(4)); // 行番号
const c = findColIndex(grid, v => v === 2);           // 列番号
JavaScript

浅いコピーと深いコピー(ネスト配列の独立度)

浅いコピーでは“内側は共有”

const nested = [[1], [2]];
const shallow = [...nested];        // 行配列の参照をコピー
shallow[0][0] = 9;
nested[0][0]; // 9(同じ参照)
JavaScript

行ごとに独立させるなら、もう一段 map を入れて“各行もコピー”します。

const safe = nested.map(row => [...row]); // 行配列までコピー
JavaScript

深いコピーが必要なら structuredClone

const deep = structuredClone(nested); // 全階層を独立
JavaScript

ここが重要です:更新範囲が狭いときは“必要な階層だけコピー”、全体の独立が必要なら深いコピーを選び、性能と安全性のバランスを取ります。


欠損・境界・不変性の安全設計(重要ポイントの深掘り)

インデックス境界を常に確認する

function at2D(g, r, c) {
  return Array.isArray(g[r]) ? g[r][c] : undefined;
}
JavaScript

境界外アクセスは undefined。常にガードを通すことで、例外なく安全に扱えます。

非破壊(イミュータブル)更新を徹底する

共有状態(UI・ストア)では直接代入を避け、map・slice・スプレッドで新インスタンスを返します。差分検知の一貫性が高まり、バグを避けられます。

反復は for…of/map を使う(for…in は使わない)

配列に for…in を使うと、順序保証が弱く、追加プロパティまで回る可能性があります。意図が明確な for…of/map/filter/reduce を選びます。


実践レシピ(よくあるネスト配列の処理)

グループ化されたデータの平坦化と集計

const groups = [
  [{ id: 1, score: 10 }, { id: 2, score: 20 }],
  [{ id: 3, score: 5 }]
];
const total = groups.flat().reduce((a, r) => a + r.score, 0); // 35
JavaScript

多段ネストの取り出し(安全な経路)

function pickPath(arr, path) {
  let cur = arr;
  for (const idx of path) {
    if (!Array.isArray(cur) || cur[idx] === undefined) return undefined;
    cur = cur[idx];
  }
  return cur;
}
pickPath([[1],[2,[3]]], [1, 1, 0]); // 3
JavaScript

条件に合う要素をネストごと置換

function updateNested(groups, pred, patch) {
  return groups.map(lst =>
    lst.map(item => (pred(item) ? { ...item, ...patch } : item))
  );
}
const next = updateNested(groups, it => it.id === 2, { score: 99 });
JavaScript

まとめ

ネスト配列は“2次元・階層・グループ化”を自然に表現できる強力な構造です。アクセスはインデックスを安全に辿り、更新は非破壊(map・slice・スプレッド)で“該当部分だけ作り直す”。整形は flat/flatMap/reduce を使い、検索・検証は some/every/find を組み合わせる。浅いコピーは内側が共有なので“必要な階層までコピー”し、全体独立には structuredClone。境界チェックと反復方法の選択を徹底すれば、初心者でも複雑なネスト配列を短く明快に、しかも安全に扱えます。

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