groupBy を書いてみたメモ。
こういうのが揃っているライブラリもあるけれど、 バニラで書きたいときもあるし、 色々と応用も効きそうだしということで書き留め。
何かゲームの個人記録があったとして、 各チームの平均点が知りたい、みたいなケース。
const records = [
{ player: 'a', team: 'red', score: 10 },
{ player: 'b', team: 'blue', score: 3 },
{ player: 'c', team: 'red', score: 4 },
{ player: 'd', team: 'green', score: 15 },
// ...
];
/*
const result = [
{ team: 'red', score: ... },
{ team: 'blue', score: ... },
{ team: 'green', score: ... },
// ...
]
*/
filter
で振り分けちゃおうか、という発想。
const result = ['red', 'blue', 'green']
.map(team => {
const list = records.filter(r => r.team === team);
return {
team,
score: list.reduce((sum, r) => sum + r.score, 0) / list.length
};
});
書くのは楽だけれど、filter
のループ回数が要素数×分割数 になるので、
件数多いときにちょっと使いたくない感じ。
チームも決め打ちになってしまっているので、拡張性に乏しい。
やはり汎用的な groupBy が欲しい。
グループに分け、結果を { key: values }
なオブジェクトで返す。
わりとよく見る実装。(underscore.js とか)
js
const groupBy = (array, getKey) =>
array.reduce((obj, cur, idx, src) => {
const key = getKey(cur, idx, src);
(obj[key] || (obj[key] = [])).push(cur);
return obj;
}, {});
ts
const groupBy = <K extends PropertyKey, V>(
array: readonly V[],
getKey: (cur: V, idx: number, src: readonly V[]) => K
) =>
array.reduce((obj, cur, idx, src) => {
const key = getKey(cur, idx, src);
(obj[key] || (obj[key] = []))!.push(cur);
return obj;
}, {} as Partial<Record<K, V[]>>);
振り分けのループ回数が要素数に抑えられている。
Usage
const groups = groupBy(records, r => r.team);
const result = Object.entries(groups)
.map(([team, list]) => ({
team,
score: list.reduce((sum, r) => sum + r.score, 0) / list.length
}));
単に振り分けるだけならいいのだけれど、その後になにか続けようとすると、ちょっと使い勝手が悪い。
他にも色々と不満点がある。