JavaScript | 配列・オブジェクト:実務パターン – グループ化

JavaScript JavaScript
スポンサーリンク

グループ化とは何か

グループ化は「配列の要素を“共通のキー”でまとめ、キーごとに要素の集合を作る」処理です。ここが重要です:グループの“鍵(キー)”をどう決めるかで実務の使いやすさが決まります。単純なキー(category、status)だけでなく、表記ゆれを正規化したキーや複数項目を組み合わせたキーを設計すると、集計・分析・表示が安定します。

const items = [
  { category: "book", price: 1200 },
  { category: "pen",  price: 200 },
  { category: "book", price: 800 }
];
// 期待する結果(category ごとの配列)
/*
{
  book: [{...}, {...}],
  pen:  [{...}]
}
*/
JavaScript

基本のグループ化(reduce で辞書を作る)

オブジェクト辞書でグループ化

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

// 使い方
const byCategory = groupBy(items, x => x.category);
// { book: [...], pen: [...] }
JavaScript

ここが重要です:acc[k] ??= [] は“まだ無ければ空配列を作る”という短い初期化パターン。キーが見つからなければ新規グループを作り、あれば既存グループへ追加します。

Map でグループ化(キー型を保持)

function groupByMap(list, keyFn) {
  const m = new Map();
  for (const x of list) {
    const k = keyFn(x);
    const g = m.get(k);
    if (g) g.push(x);
    else m.set(k, [x]);
  }
  return m;
}

// 取り出し
const m = groupByMap(items, x => x.category);
m.get("book"); // グループ配列
JavaScript

ここが重要です:オブジェクト辞書はキーが文字列化されます。数値キーやオブジェクトキー(日時 / 複合キー)をそのまま使いたいなら Map が安全です。


グループ化と集計(集計値・件数・平均)

件数・合計・平均を同時に取る

function groupStats(list, keyFn, valueFn = x => x) {
  return list.reduce((acc, x) => {
    const k = keyFn(x);
    const v = valueFn(x);
    const g = acc[k] ?? (acc[k] = { count: 0, sum: 0 });
    g.count += 1;
    g.sum += v;
    return acc;
  }, {});
}

const stats = groupStats(items, x => x.category, x => x.price);
// { book: { count: 2, sum: 2000 }, pen: { count: 1, sum: 200 } }
const avgBook = stats.book.sum / stats.book.count; // 平均
JavaScript

ここが重要です:グループ配列を作ってから別途集計するより、“集計器”を直接蓄積するほうがメモリ効率が良く、コードも短くなります。

トップN抽出などの後処理

function topNInGroups(groups, n, scoreFn) {
  const out = {};
  for (const [k, arr] of Object.entries(groups)) {
    out[k] = arr
      .slice()
      .sort((a, b) => scoreFn(b) - scoreFn(a))
      .slice(0, n);
  }
  return out;
}

// 例:価格上位1件
const top1 = topNInGroups(byCategory, 1, x => x.price);
JavaScript

ここが重要です:slice() でコピーしてから sort すれば、元配列を破壊せずにトップNを取れます(イミュータブルに保つ)。


キー設計(正規化・欠損・複合キー)

表記ゆれを吸収する正規化キー

const normalizeCategory = s =>
  s.trim().toLowerCase().replace(/[A-Za-z]/g, ch => 
    String.fromCharCode(ch.charCodeAt(0) - 0xFEE0)
);

const byNormalized = groupBy(items, x => normalizeCategory(x.category));
JavaScript

ここが重要です:前後空白、小文字化、全角→半角などを“キー関数”に閉じ込めると、実務で起こりがちな揺れを吸収できます。まず“何を同じとみなすか”を仕様として明確にすることが肝心です。

欠損キーへの安全策(未知グループ)

const bySafeKey = groupBy(items, x => x.category ?? "(unknown)");
JavaScript

欠損(null/undefined)をそのままキーにせず、明示的な“未知グループ”へ寄せると、後段処理が簡潔になります。

複合キー(複数条件でグループ化)

const byCatMonth = groupBy(orders, o => {
  const month = new Date(o.date).toISOString().slice(0, 7); // YYYY-MM
  return `${o.category}|${month}`;
});
JavaScript

ここが重要です:複合キーは区切り文字を決め、衝突しないように設計します。取り出し時は split("|") で分解できます。型を保ちたいなら Map のキーにタプル風オブジェクトを使う選択もあります。


ネストされたグループ化(段階的なまとめ)

二段グループ化(カテゴリ→月)

function groupBy2(list, k1, k2) {
  const out = {};
  for (const x of list) {
    const a = k1(x);
    const b = k2(x);
    ((out[a] ??= {})[b] ??= []).push(x);
  }
  return out;
}

// 使い方
const grouped = groupBy2(orders, o => o.category, o => new Date(o.date).getMonth());
// grouped[category][month] = [...]
JavaScript

ここが重要です:多段グループは“入れ子の辞書”になります。表示や集計を意識して、必要な段数のみ作るのが保守しやすいです。

グループ化→集計の一気通貫

function groupSum2(list, k1, k2, valueFn) {
  const out = {};
  for (const x of list) {
    const a = k1(x), b = k2(x), v = valueFn(x);
    const g1 = (out[a] ??= {});
    const g2 = (g1[b] ??= { count: 0, sum: 0 });
    g2.count++; g2.sum += v;
  }
  return out;
}
JavaScript

複数軸の集計を作るときは、配列を保持せず“統計だけ”を持つ設計にすると軽量です。


実務の安全設計(イミュータブル・性能・出力形式)

非破壊(イミュータブル)を徹底

グループ化後は元配列を変更しないのが原則です。ソートや追加は必ずコピーしてから行い、関数の返り値は新インスタンスにします。状態管理(UI)で差分検知が安定します。

大規模データの性能

  • まとめ取り: 1回の走査で“グループ化+集計”まで済ませると速く、メモリも節約できます。
  • 辞書 vs Map: キーが多様(数値・オブジェクト)なら Map、保存やJSON化が必要ならオブジェクト辞書。
  • ストリーム的処理: データが巨大ならチャンクごとにグループ化・集計し、最後に合成する戦略が有効です。

出力形式の選択

用途に合わせて“辞書(キー→配列)”“辞書(キー→集計器)”“配列(グループの配列)”を選びます。

// 配列出力の例(ビュー向け)
function groupAsArray(list, keyFn) {
  const dict = groupBy(list, keyFn);
  return Object.entries(dict).map(([key, rows]) => ({ key, rows }));
}
JavaScript

すぐ使えるレシピ(現場の定番コード)

基本の groupBy(辞書)

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

件数・合計の同時集計

const groupStats = (list, keyFn, valueFn = x => x) =>
  list.reduce((acc, x) => {
    const k = keyFn(x);
    const v = valueFn(x);
    const g = acc[k] ?? (acc[k] = { count: 0, sum: 0 });
    g.count++; g.sum += v;
    return acc;
  }, {});
JavaScript

多段グループ化

const groupBy2 = (list, k1, k2) => {
  const out = {};
  for (const x of list) {
    const a = k1(x), b = k2(x);
    ((out[a] ??= {})[b] ??= []).push(x);
  }
  return out;
};
JavaScript

まとめ

グループ化の核心は「適切なキー設計」と「一度の走査でまとめる」ことです。辞書(オブジェクト/Map)にキーごとに配列や集計器を蓄え、表記ゆれや欠損はキー関数で正規化して吸収する。多段グループは入れ子の辞書にし、必要な段だけ作る。イミュータブルを徹底し、巨大データは“グループ化+集計”を一気通貫で行い、出力形式は用途(表示・分析・保存)に合わせて選ぶ。これを押さえれば、初心者でも短く、速く、実務で通用するグループ化が書けます。

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