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;
}, {});
JavaScripttoDict(ID→レコード)
const toDict = (list, key = "id") =>
list.reduce((acc, x) => ((acc[String(x[key])] = x), acc), {});
JavaScriptfilterMap(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 で書けます。
