JavaScript | 配列・オブジェクト:パフォーマンス・設計 – reduce の使いどころ

JavaScript JavaScript
スポンサーリンク

reduce とは何か

reduce は「配列を1回なめて、“累積器(accumulator)”に結果を積み上げ、最後に1つの値(数値・文字列・配列・オブジェクト)にまとめる」ための関数です。ここが重要です:reduce は“何でも作れる”汎用ツールですが、目的を明確にして累積器の初期値を正しく選ぶことで、読みやすく速いコードになります。

// 形:arr.reduce((acc, x, i) => 次のacc, 初期acc)
const sum = arr.reduce((acc, x) => acc + x, 0);
JavaScript

基本パターン(累積器の設計)

数値の集計(合計・平均・最小・最大)

const nums = [10, 20, 5];

const sum = nums.reduce((a, x) => a + x, 0);
const avg = nums.reduce((a, x, i) => a + x / nums.length, 0);
const min = nums.reduce((a, x) => (x < a ? x : a), Infinity);
const max = nums.reduce((a, x) => (x > a ? x : a), -Infinity);
JavaScript

ここが重要です:平均は「最後に割る」または「各要素で割って足す」のどちらでも書けますが、整数誤差・型を意識しやすい“最後に割る”が一般的です。

文字列連結・テンプレート生成

const parts = ["id=10", "q=book", "page=2"];
const query = parts.reduce((a, x, i) => a + (i ? "&" : "") + x, "");
JavaScript

ここが重要です:初期値を空文字にして、先頭だけ区切り文字を省く。累積器で“表現ルール”を一元管理できます。


map / filter を1パスに融合する(コスト削減)

filter+map を reduce 1回で

const out = arr.reduce((acc, x) => {
  if (!x.active) return acc;        // filter 相当
  acc.push(x.value * 2);            // map 相当
  return acc;
}, []);
JavaScript

ここが重要です:中間配列を作らず、走査も1回。大量データでは filter→map の二段チェーンより速く、GCが安定します。

条件付きで別の容器へ入れる(分類)

const ret = arr.reduce((acc, x) => {
  (x.score >= 80 ? acc.pass : acc.fail).push(x);
  return acc;
}, { pass: [], fail: [] });
JavaScript

ここが重要です:条件分岐を累積器に閉じ込め、後段が扱いやすい形(グループ化済み)で返せます。


辞書化・グループ化の定番(実務で最も活躍)

ID→レコードの辞書化(O(1)検索に)

const dict = list.reduce((acc, x) => {
  acc[String(x.id)] = x;
  return acc;
}, {});
// dict["123"] で即アクセス
JavaScript

ここが重要です:頻繁に検索するなら配列から辞書へ。reduce は「走査しながら蓄積」するのに最適です。

groupBy(キーごとに配列を作る)

const byCategory = items.reduce((acc, x) => {
  const k = x.category ?? "(unknown)";
  (acc[k] ??= []).push(x);
  return acc;
}, {});
// byCategory.book など
JavaScript

ここが重要です:??= で“初回だけ空配列を作る”。欠損キーを明示的なグループへ寄せると後続が簡単になります。


集計器でメモリ削減(配列を持たずに要約)

件数・合計・平均を同時に計算

const stats = items.reduce((acc, x) => {
  acc.count++;
  acc.sum += x.price ?? 0;
  return acc;
}, { count: 0, sum: 0 });
const avg = stats.count ? stats.sum / stats.count : 0;
JavaScript

ここが重要です:グループごとの配列を保持せず“統計だけ”積むと軽量。大規模データで効率が段違いになります。

多段集計(キー×キー)

const byCatMonth = orders.reduce((acc, o) => {
  const cat = o.category;
  const month = new Date(o.date).toISOString().slice(0, 7);
  const g1 = (acc[cat] ??= {});
  const g2 = (g1[month] ??= { count: 0, sum: 0 });
  g2.count++; g2.sum += o.amount ?? 0;
  return acc;
}, {});
JavaScript

ここが重要です:入れ子の累積器で“段付き”の結果をその場で作る。あとから変換不要で、そのまま表示・分析に使えます。


非破壊更新の合成(複数変更を1度に)

複数パッチを reduce で適用

const applyPatches = (state, patches) =>
  patches.reduce((s, p) => {
    if (p.type === "setUser") {
      return { ...s, user: { ...(s.user ?? {}), ...p.value } };
    }
    if (p.type === "setItems") {
      return { ...s, items: p.value };
    }
    return s;
  }, state);

const next = applyPatches(prevState, [
  { type: "setUser", value: { name: "Alice" } },
  { type: "setItems", value: [{ id: 1 }] }
]);
JavaScript

ここが重要です:累積器(状態)に対して“パッチを順に適用”。一連の変更が分かりやすく、テストもしやすい。


エラー処理・検証の取りまとめ(合格だけ通す)

検証しながら成功だけ集める

const result = inputs.reduce((acc, x) => {
  const ok = x.name && /^\S+@\S+$/.test(x.email ?? "");
  if (!ok) {
    acc.errors.push({ id: x.id, reason: "invalid" });
  } else {
    acc.valid.push({ id: x.id, name: x.name, email: x.email.trim() });
  }
  return acc;
}, { valid: [], errors: [] });
JavaScript

ここが重要です:成功・失敗を同時に蓄積。呼び出し側が分岐しやすい形で返せます。


reduce の落とし穴と回避策(重要ポイントの深掘り)

初期値を誤ると例外・型崩れ

空配列で arr.reduce((a, x) => a + x) のように初期値なしだと TypeError。常に“ゼロ値”を渡す習慣を持つ(数値なら 0、文字列なら “”、配列なら []、オブジェクトなら {})。

const sum = arr.reduce((a, x) => a + x, 0);
JavaScript

可読性の低下(やりすぎ問題)

reduce は“何でもできる”が、複雑すぎるロジックを1本に詰めると読めなくなります。役割を分ける、ヘルパー関数を使う、map/filter で十分な場面では素直に使う。

破壊的な累積でバグ

累積器を破壊的に変更すると他所で参照している場合に副作用。基本は“新インスタンスを返す(イミュータブル)”か、“統計値のような自前オブジェクトにだけ書き込む”。


使いどころの指針(いつ reduce を選ぶか)

reduce を選ぶと良い場面

  • 1パスで複数のことをしたい: 変換・選別・集計を同時に。
  • 入出力の形が“非配列”: 数値、文字列、辞書、グループ化、統計など。
  • 中間配列を作りたくない: メモリ・GCを抑えたい大規模処理。

他の選択が良い場面

  • 単純変換だけ: map のほうが意図が明快。
  • 単純選別だけ: filter のほうが読みやすい。
  • 早期終了が重要: find / some / every が適切。

すぐ使えるレシピ(短く実務向け)

groupBy(辞書化)

const groupBy = (list, keyFn) =>
  list.reduce((acc, x) => {
    const k = keyFn(x);
    (acc[k] ??= []).push(x);
    return acc;
  }, {});
JavaScript

toDict(ID→レコード)

const toDict = (list, key = "id") =>
  list.reduce((acc, x) => ((acc[String(x[key])] = x), acc), {});
JavaScript

filterMap(1パス融合)

const filterMap = (arr, pred, mapFn) =>
  arr.reduce((acc, x) => (pred(x) ? (acc.push(mapFn(x)), acc) : acc), []);
JavaScript

統計(count/sum/avg)

const stats = arr.reduce((a, x) => {
  a.count++; a.sum += x;
  return a;
}, { count: 0, sum: 0 });
const avg = stats.sum / (stats.count || 1);
JavaScript

まとめ

reduce の核心は「累積器を設計し、配列を1回で“欲しい最終形”へ着地させる」ことです。filter+map を1パスに融合してコストを下げ、辞書化・グループ化・統計など“非配列の出力”に強い。初期値は必ず与え、イミュータブルを意識し、複雑になりすぎたら役割を分割する。使いどころを見極めれば、初心者でも読みやすくて速い実務コードを reduce で書けます。

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