JavaScript | 配列・オブジェクト:ループ処理 – reduce での集計

JavaScript JavaScript
スポンサーリンク
  1. reduce での集計の基本
  2. 基本構文と初期値の設計
    1. 使い方の基本形
    2. 初期値を省略した場合の挙動(原則おすすめしない)
  3. 数値集計の定番(合計・平均・最大最小)
    1. 合計・平均
    2. 最大・最小(境界に注意)
    3. 欠損や非数値をスキップして合計
  4. オブジェクト配列の集計(合計・カウント・グループ化)
    1. プロパティ合計(売上合計など)
    2. 条件付きカウント(在庫ありの件数)
    3. グルーピング(カテゴリごとにまとめる)
    4. ヒストグラム(頻度カウント)
  5. オブジェクト・Map の集計(entries/values の活用)
    1. Object.values で数値プロパティを合計
    2. Object.entries でキーも使った集計(フィルタ+合計)
    3. Map の集計(キー順に走査して辞書化)
  6. 応用レシピ(重み付き・条件付き・複合集計)
    1. 重み付き平均
    2. 条件付き集計(売上のうち税込・税抜を同時に)
    3. 1パスで抽出+変換+集約(filter+map を合成)
  7. 重要ポイントの深掘り(初期値・純粋性・疎配列・非同期)
    1. 初期値は“必ず”与える(型・空配列・エラー対策)
    2. リデューサーは“純粋”に(副作用を入れない)
    3. 疎配列(空スロット)はスキップされる
    4. 非同期の逐次連鎖は“Promise を acc に”
  8. 実践例(レポート・売上・入力検証)
    1. 月別売上の集計(辞書化)
    2. バリデーション+集計(最初の不正で打ち切らない設計)
    3. 上位カテゴリを抽出しつつ件数集計
  9. まとめ

reduce での集計の基本

reduce は「配列の全要素を順に処理して、ひとつの値へ“畳み込む(集約する)”」ためのメソッドです。合計・平均・最大最小だけでなく、辞書(オブジェクト)や Map を作る、グルーピングする、といった“構造的な集計”にも使えます。ここが重要です:reduce のカギは“アキュムレータ(蓄積値)”と“初期値”。この2つの設計次第で、正確さ・安全性・読みやすさが決まります。


基本構文と初期値の設計

使い方の基本形

const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => acc + n, 0);
console.log(sum); // 10
JavaScript
  • acc は“これまでの集計結果”。
  • n は“現在の要素”。
  • 第2引数の 0 は“初期値”。最初の acc に入ります。

ここが重要です:初期値は「期待する出力の型」で必ず与える(数値なら 0、文字列なら “”、配列なら []、オブジェクトなら {})。空配列でも安全に動き、型ブレを防げます。

初期値を省略した場合の挙動(原則おすすめしない)

const nums = [1, 2, 3];
const sum = nums.reduce((acc, n) => acc + n); // 初期値なし
console.log(sum); // 6
JavaScript

ここが重要です:初期値なしだと“先頭要素が初期 acc”になります。空配列では TypeError。要素型がオブジェクトなどだと、意図しない型で計算が始まるため、初心者は“必ず初期値を設定”しましょう。


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

合計・平均

const nums = [3, 7, 2, 9];
const sum = nums.reduce((acc, n) => acc + n, 0);        // 21
const avg = nums.length ? sum / nums.length : 0;        // 5.25
JavaScript

ここが重要です:平均は“合計÷件数”。欠損や非数値が混じるなら、reduce でフィルタリングを同時に行う設計が安全です。

最大・最小(境界に注意)

const nums = [3, 7, 2, 9];
const max = nums.reduce((acc, n) => Math.max(acc, n), -Infinity); // 9
const min = nums.reduce((acc, n) => Math.min(acc, n),  Infinity); // 2
JavaScript

ここが重要です:初期値に -Infinity/Infinity を使うと、空配列でも安全に結果が定義できます。

欠損や非数値をスキップして合計

const xs = [1, undefined, "3", null, 2];
const sum = xs.reduce((acc, x) => {
  const n = Number(x);
  return Number.isFinite(n) ? acc + n : acc;
}, 0); // 6
JavaScript

ここが重要です:Number.isFinite で“加算対象だけ”足す。集計が安定します。


オブジェクト配列の集計(合計・カウント・グループ化)

プロパティ合計(売上合計など)

const orders = [
  { id: 1, amount: 1200 },
  { id: 2, amount: 800 },
  { id: 3, amount: 600 }
];
const total = orders.reduce((acc, o) => acc + (o.amount ?? 0), 0); // 2600
JavaScript

ここが重要です:欠損(null/undefined)は ?? で 0 に。入力が不安定でも安全です。

条件付きカウント(在庫ありの件数)

const products = [
  { name: "A", stock: 0 },
  { name: "B", stock: 5 },
  { name: "C", stock: 3 }
];
const countInStock = products.reduce((acc, p) => acc + (p.stock > 0 ? 1 : 0), 0); // 2
JavaScript

ここが重要です:ブール条件を 1/0 に落とし込めば、読みやすく高速です。

グルーピング(カテゴリごとにまとめる)

const items = [
  { cat: "fruit", name: "apple" },
  { cat: "fruit", name: "banana" },
  { cat: "veg",   name: "carrot" }
];
const grouped = items.reduce((acc, item) => {
  (acc[item.cat] ??= []).push(item);
  return acc;
}, {});
/*
{
  fruit: [{...}, {...}],
  veg:   [{...}]
}
*/
JavaScript

ここが重要です:null 合体代入(??=)で“初期化+追加”を1行に。多カテゴリ集計が簡潔になります。

ヒストグラム(頻度カウント)

const tags = ["js", "web", "js"];
const freq = tags.reduce((acc, t) => {
  acc[t] = (acc[t] ?? 0) + 1;
  return acc;
}, {});
// { js: 2, web: 1 }
JavaScript

ここが重要です:辞書(オブジェクト)に“出現回数”を蓄積するのは reduce の王道パターンです。


オブジェクト・Map の集計(entries/values の活用)

Object.values で数値プロパティを合計

const report = { Q1: 120, Q2: 80, Q3: 200 };
const total = Object.values(report).reduce((acc, n) => acc + n, 0); // 400
JavaScript

Object.entries でキーも使った集計(フィルタ+合計)

const report = { apples: 10, bananas: 5, cars: 2 };
const fruitTotal = Object.entries(report).reduce((acc, [k, v]) => {
  if (["apples", "bananas"].includes(k)) acc += v;
  return acc;
}, 0); // 15
JavaScript

Map の集計(キー順に走査して辞書化)

const m = new Map([["a", 1], ["b", 2]]);
const obj = Array.from(m).reduce((acc, [k, v]) => {
  acc[k] = v * 10; // 変換しながら投入
  return acc;
}, {});
// { a: 10, b: 20 }
JavaScript

ここが重要です:オブジェクトは entries/values、Map は Array.from(m) などで配列にして reduce。集計対象を“配列化”するのがコツです。


応用レシピ(重み付き・条件付き・複合集計)

重み付き平均

const rows = [
  { value: 80, weight: 2 },
  { value: 90, weight: 1 }
];
const { wsum, w } = rows.reduce((acc, r) => ({
  wsum: acc.wsum + r.value * r.weight,
  w:    acc.w + r.weight
}), { wsum: 0, w: 0 });
const wavg = w ? wsum / w : 0; // 83.33...
JavaScript

ここが重要です:複数の累積が必要なら、acc を“オブジェクト”にしてまとめると見通しが良くなります。

条件付き集計(売上のうち税込・税抜を同時に)

const orders = [{amount:1000},{amount:2500}];
const out = orders.reduce((acc, o) => {
  acc.net += o.amount;
  acc.gross += Math.round(o.amount * 1.1);
  return acc;
}, { net: 0, gross: 0 });
// { net: 3500, gross: 3850 }
JavaScript

1パスで抽出+変換+集約(filter+map を合成)

const products = [
  { name: "A", stock: 0, price: 100 },
  { name: "B", stock: 5, price: 200 },
  { name: "C", stock: 3, price: 150 }
];
const labels = products.reduce((acc, p) => {
  if (p.stock > 0) acc.push(`${p.name} (${p.price})`);
  return acc;
}, []);
// ["B (200)", "C (150)"]
JavaScript

ここが重要です:複数メソッドを重ねるより、reduce 1本で最適化できる場面があります。可読性とのバランスを取りましょう。


重要ポイントの深掘り(初期値・純粋性・疎配列・非同期)

初期値は“必ず”与える(型・空配列・エラー対策)

  • 空配列でも動くように、出力型の初期値を指定する。
  • 初期値なしは空配列で TypeError。型の起点が先頭要素になり、意図がブレます。

リデューサーは“純粋”に(副作用を入れない)

// NG:外部を書き換える副作用は分離
const sum = nums.reduce((acc, n) => { console.log(n); return acc + n; }, 0);
JavaScript

ここが重要です:入力→出力だけに集中させると、テスト容易性・再利用性が向上します。ログ等は別工程へ。

疎配列(空スロット)はスキップされる

const sparse = Array(2); // [ <2 empty items> ]
sparse.reduce((acc, x) => acc + (x ?? 0), 0); // 0(穴は処理されない)
JavaScript

ここが重要です:穴を明示的に扱いたいなら、前処理で正規化(Array.from など)。初期値なしは空配列で TypeError になるので注意。

非同期の逐次連鎖は“Promise を acc に”

const tasks = [a, b, c]; // 各 task は (acc) => Promise
const result = await tasks.reduce(async (prev, task) => {
  const acc = await prev;
  return task(acc);
}, Promise.resolve(initial));
JavaScript

ここが重要です:reduce 自体は同期。順序保証の非同期連鎖は“Promise をアキュムレータにする”パターンが定番です。並列は map+Promise.all。


実践例(レポート・売上・入力検証)

月別売上の集計(辞書化)

const sales = [
  { month: "2025-01", amount: 1000 },
  { month: "2025-01", amount: 500 },
  { month: "2025-02", amount: 800 }
];
const byMonth = sales.reduce((acc, s) => {
  acc[s.month] = (acc[s.month] ?? 0) + s.amount;
  return acc;
}, {});
// { "2025-01": 1500, "2025-02": 800 }
JavaScript

バリデーション+集計(最初の不正で打ち切らない設計)

const nums = [1, -1, 2, "x"];
const report = nums.reduce((acc, x, i) => {
  const n = Number(x);
  if (!Number.isFinite(n) || n < 0) acc.errors.push({ index: i, value: x });
  else acc.sum += n;
  return acc;
}, { sum: 0, errors: [] });
// { sum: 3, errors: [ {index:1,value:-1}, {index:3,value:"x"} ] }
JavaScript

ここが重要です:エラーと正常を同時に集計するなら、acc をオブジェクトにして“両方の器”を用意します。

上位カテゴリを抽出しつつ件数集計

const posts = [
  { cat: "tech" }, { cat: "life" }, { cat: "tech" }, { cat: "news" }
];
const topCats = posts.reduce((acc, p) => {
  acc.count[p.cat] = (acc.count[p.cat] ?? 0) + 1;
  if (!acc.list.includes(p.cat)) acc.list.push(p.cat);
  return acc;
}, { count: {}, list: [] });
// { count: { tech:2, life:1, news:1 }, list: ["tech","life","news"] }
JavaScript

まとめ

reduce は「配列を“ひとつの値(数値・文字列・配列・オブジェクト・Promise など)”へ畳み込む」ための集計メソッドです。初期値を必ず設定し、リデューサーを純粋に保ち、疎配列のスキップ挙動を理解することが最重要。数値の合計・平均・最大最小、辞書化、頻度カウント、グルーピング、複合集計まで幅広く書けます。非同期の逐次連鎖は“Promise を acc に”、並列は map+Promise.all。これらの指針を守れば、初心者でも意図通りで壊れにくい集計ロジックを短く正確に設計できます。

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