JavaScript Tips | 配列ユーティリティ:reduce ヘルパー

JavaScript JavaScript
スポンサーリンク

そもそも reduce って何をする関数か

Array.prototype.reduce は、「配列を 1 つの値に“畳み込む”ための関数」です。
合計を出したり、オブジェクトに集計したり、別の配列に変形したり――「最終的に 1 つの結果を作る」ときに使います。

ざっくりいうと、

array.reduce((acc, item) => {
  // acc に「これまでの結果」
  // item に「今見ている要素」
  // 新しい acc を返す
}, 初期値);
JavaScript

という形で、「acc(蓄積値)」を更新しながら、配列を頭から順に処理していくイメージです。


reduce が難しく感じる理由と、ヘルパーを作る意味

初心者が reduce を難しく感じるポイントはだいたいここです。

  • acc(蓄積値)が何なのか分かりにくい
  • 初期値を何にすればいいか迷う
  • 1 行に詰め込みすぎて、何をしているのか読めない

業務では、reduce を使う場面が多いのに、毎回「acc って何だっけ?」から始まるとしんどい。
そこで、「よくあるパターンを reduce ヘルパーとして切り出しておく」と、コードが一気に読みやすくなります。


合計を出すヘルパー:sumBy

実装と考え方

「配列の数値を合計したい」「オブジェクト配列の特定プロパティの合計を出したい」――これは reduce の超定番です。

function sumBy(array, selector) {
  if (!Array.isArray(array)) {
    return 0;
  }
  if (typeof selector !== "function") {
    selector = (x) => x;
  }

  return array.reduce((acc, item) => {
    const value = selector(item);
    const num = typeof value === "number" ? value : 0;
    return acc + num;
  }, 0);
}
JavaScript

重要なポイントをかみ砕きます。

  • selector は「item から“足したい値”を取り出す関数」
  • selector が渡されなければ「そのまま値を使う」
  • 値が数値でなければ 0 とみなして安全側に倒す
  • 初期値は 0(合計のスタート地点)

例題:売上合計を出す

const sales = [
  { id: 1, amount: 1000 },
  { id: 2, amount: 2500 },
  { id: 3, amount: 1500 },
];

const total = sumBy(sales, (s) => s.amount);
// 5000
JavaScript

「売上配列から合計金額を出す」という業務でありがちな処理が、sumBy で一行になります。


集計ヘルパー:groupCount(件数集計)

実装と考え方

「ステータスごとの件数」「カテゴリごとの件数」など、「グループごとに何件あるか」を集計するのも reduce の定番です。

function groupCount(array, keySelector) {
  if (!Array.isArray(array)) {
    return {};
  }
  if (typeof keySelector !== "function") {
    keySelector = (x) => String(x);
  }

  return array.reduce((acc, item) => {
    const key = keySelector(item);
    if (!Object.prototype.hasOwnProperty.call(acc, key)) {
      acc[key] = 0;
    }
    acc[key] += 1;
    return acc;
  }, {});
}
JavaScript

ここでの重要ポイントは、

  • keySelector が「どのキーで集計するか」を決める
  • acc は「集計結果のオブジェクト」
  • 初期値は {}(空の集計表)
  • キーがまだなければ 0 からスタートして、1 件ずつ足していく

例題:ステータスごとの件数を集計する

const tasks = [
  { id: 1, status: "todo" },
  { id: 2, status: "doing" },
  { id: 3, status: "done" },
  { id: 4, status: "doing" },
];

const counts = groupCount(tasks, (t) => t.status);

/*
{
  todo: 1,
  doing: 2,
  done: 1
}
*/
JavaScript

「ステータスごとの件数」を reduce で書くとゴチャつきがちですが、
groupCount にすると「何をしたいか」が一目で分かります。


グループ化ヘルパー:groupBy(配列を“分類”する)

実装と考え方

「カテゴリごとに配列を分けたい」「日付ごとにログをまとめたい」――これは 「グループ化」です。

function groupBy(array, keySelector) {
  if (!Array.isArray(array)) {
    return {};
  }
  if (typeof keySelector !== "function") {
    keySelector = (x) => String(x);
  }

  return array.reduce((acc, item) => {
    const key = keySelector(item);
    if (!Object.prototype.hasOwnProperty.call(acc, key)) {
      acc[key] = [];
    }
    acc[key].push(item);
    return acc;
  }, {});
}
JavaScript

ポイントは、

  • acc は「グループごとの配列を持つオブジェクト」
  • 初期値は {}
  • キーがまだなければ [] からスタート
  • 各グループの配列に push していく

例題:ステータスごとにタスクをグループ化する

const tasks = [
  { id: 1, status: "todo" },
  { id: 2, status: "doing" },
  { id: 3, status: "done" },
  { id: 4, status: "doing" },
];

const grouped = groupBy(tasks, (t) => t.status);

/*
{
  todo:  [{ id: 1, status: "todo" }],
  doing: [{ id: 2, status: "doing" }, { id: 4, status: "doing" }],
  done:  [{ id: 3, status: "done" }]
}
*/
JavaScript

「ステータスごとにタスクをまとめたい」という業務でよくある処理が、
groupBy でかなり読みやすく書けます。


reduce ヘルパーで一番大事な“acc のイメージ”

reduce を使うとき、acc(蓄積値)が何なのかを最初に決めるのが超重要です。

  • 合計なら acc は「数値」、初期値は 0
  • 件数集計なら acc は「オブジェクト」、初期値は {}
  • グループ化なら acc は「オブジェクト(キーごとに配列)」、初期値は {}

そして、「1 要素処理するたびに acc をどう更新するか」を、
“日本語で説明できるレベル”まで分解してからコードにするのがコツです。

例えば groupCount なら、

  1. acc は「集計表」
  2. 要素を 1 つ見る
  3. キーを決める
  4. そのキーのカウンタを 1 増やす

というストーリーが頭にあると、reduce がただの「難しい関数」ではなく、
「ストーリーをコードにしたもの」に見えてきます。


手を動かして reduce ヘルパーの感覚をつかむ

次のコードをコンソールで実行して、挙動を自分の目で確認してみてください。

function sumBy(array, selector) {
  if (!Array.isArray(array)) return 0;
  if (typeof selector !== "function") selector = (x) => x;
  return array.reduce((acc, item) => {
    const value = selector(item);
    const num = typeof value === "number" ? value : 0;
    return acc + num;
  }, 0);
}

function groupCount(array, keySelector) {
  if (!Array.isArray(array)) return {};
  if (typeof keySelector !== "function") keySelector = (x) => String(x);
  return array.reduce((acc, item) => {
    const key = keySelector(item);
    if (!Object.prototype.hasOwnProperty.call(acc, key)) {
      acc[key] = 0;
    }
    acc[key] += 1;
    return acc;
  }, {});
}

function groupBy(array, keySelector) {
  if (!Array.isArray(array)) return {};
  if (typeof keySelector !== "function") keySelector = (x) => String(x);
  return array.reduce((acc, item) => {
    const key = keySelector(item);
    if (!Object.prototype.hasOwnProperty.call(acc, key)) {
      acc[key] = [];
    }
    acc[key].push(item);
    return acc;
  }, {});
}

const sales = [
  { id: 1, amount: 1000 },
  { id: 2, amount: 2500 },
  { id: 3, amount: 1500 },
];

console.log(sumBy(sales, (s) => s.amount)); // 5000

const tasks = [
  { id: 1, status: "todo" },
  { id: 2, status: "doing" },
  { id: 3, status: "done" },
  { id: 4, status: "doing" },
];

console.log(groupCount(tasks, (t) => t.status));
console.log(groupBy(tasks, (t) => t.status));
JavaScript

「acc がどう育っていくか」「初期値がどう効いているか」を意識しながら眺めてみると、
reduce がだいぶ“怖くないもの”になってくるはずです。


まとめ:reduce ヘルパーで「よくある集計」を名前付きにする

業務コードでは、「合計」「件数集計」「グループ化」みたいな reduce パターンが何度も出てきます。
そのたびに生の reduce を書くのではなく、

export function sumBy(...) { ... }
export function groupCount(...) { ... }
export function groupBy(...) { ... }
JavaScript

のようなヘルパーをプロジェクトに置いておく。
そして、「集計したくなったらまずヘルパーを探す」と決めておく。

そうすると、

  • 何をしたいコードなのかが関数名で分かる
  • reduce の“お作法”を毎回思い出さなくて済む
  • 初心者でも「sumBy なら合計」「groupBy ならグループ化」と直感的に使える

という状態に近づいていきます。

reduce 自体は強力で抽象的な道具なので、
“よく使う形”をヘルパーとして名前付きにしてしまうのが、業務レベルでの付き合い方として一番現実的です。

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