グループ化とは何か
グループ化は「配列の要素を“共通のキー”でまとめ、キーごとに要素の集合を作る」処理です。ここが重要です:グループの“鍵(キー)”をどう決めるかで実務の使いやすさが決まります。単純なキー(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)にキーごとに配列や集計器を蓄え、表記ゆれや欠損はキー関数で正規化して吸収する。多段グループは入れ子の辞書にし、必要な段だけ作る。イミュータブルを徹底し、巨大データは“グループ化+集計”を一気通貫で行い、出力形式は用途(表示・分析・保存)に合わせて選ぶ。これを押さえれば、初心者でも短く、速く、実務で通用するグループ化が書けます。
