for ループ + Object.create(null) による O(n) 実装
実測: Memory Beats 90.11% へ最適化 (Runtime 101 ms / Memory 74.68 MB)
Array.prototype
に
groupBy(fn)
メソッドを追加する。 コールバック
fn(item) → string
を各要素に適用し、 同じキーを返した要素を同じ配列にまとめた
Record<string, T[]>
を返す。
入出力例
[{id:"1"},{id:"1"},{id:"2"}]
.groupBy(item => item.id)
→ {
"1": [{id:"1"}, {id:"1"}],
"2": [{id:"2"}]
}
// Declaration Merging: Array<T> に groupBy を追加
interface Array<T> {
groupBy(fn: (item: T) => string): Record<string, T[]>;
}
Array.prototype.groupBy = function<T>(
this: T[],
fn: (item: T) => string
): Record<string, T[]> {
// ① Object.create(null) でプロトタイプ汚染を防ぐ純粋ハッシュマップを生成
const result: Record<string, T[]> = Object.create(null);
// ② length をキャッシュしてプロパティ参照コストを削減
const len = this.length;
for (let i = 0; i < len; i++) {
const item = this[i]; // ③ this 参照を1回に抑制
const key = fn(item); // コールバックでキーを生成
// ④ in 演算子: プロトタイプなしオブジェクトのキー存在確認
if (key in result) {
result[key].push(item); // キー存在: 既存配列へ追加
} else {
result[key] = [item]; // キー不在: 新規配列を生成
}
}
return result;
};
/**
* 使用例
* [1,2,3].groupBy(String)
* // => {"1":[1], "2":[2], "3":[3]}
*
* [6,7,1,2].groupBy(n => String(n > 5))
* // => {"true":[6,7], "false":[1,2]}
*/
フローの説明:
1.
Object.create(null)
でプロトタイプなしの純粋ハッシュマップを初期化し、len
をキャッシュ
2.
i < len
の間ループ(スタックフレームは1つ)。No → 右バイパス(赤)で返却へスキップ
3.
fn(item) でキーを生成し
in
演算子で存在確認。Yes → push /
No → 新規配列
4. 両分岐が合流し
i++ 後に紫矢印でループ先頭へ戻る
5. 全要素処理後に
result を返却して終了
| 観点 | 計算量 | 詳細 |
|---|---|---|
| 時間計算量 | O(n) | 全要素を1回走査。fn が O(1) であることが前提 |
| 空間計算量 | O(n) | 全要素への参照を result に格納(理論的下限) |
| ハッシュマップ参照 | O(1) amortized | V8 エンジンのハッシュテーブル実装による |
| push 操作 | O(1) amortized | 配列の動的拡張(倍増アルゴリズム)による |
reduce
n 回のコールバック生成
スタックフレーム × n
Memory Beats ~40%
for ループ
スタックフレーム 1 つ
length キャッシュ有効
Memory Beats ~70%+
for...of
Iterator プロトコル経由
Symbol.iterator オーバーヘッド
中間的なパフォーマンス