アルゴリズム概要

O(n)
時間計算量
O(n)
空間計算量
0 〜 10⁵
配列サイズ
string
fnの戻り値型

問題の要約

Array.prototypegroupBy(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"}]
  }

最適化のポイント

  • reduce → for ループ:スタックフレームを n 回生成しない
  • length キャッシュ:毎ループの this.length 参照を排除
  • in 演算子 vs ??=:キーの存在確認におけるセマンティクスの違い(Object.create(null)との併用)
  • Object.create(null):プロトタイプ汚染を防ぐ純粋ハッシュマップ

ステップバイステップ解説

最適化 Before / After 比較

TypeScript 実装(最適化版)

// 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]}
 */

処理フローチャート

groupBy(fn) 開始 初期化 result=Object.create(null) / i=0 / len=this.length i < len ? No (ループ終了) Yes 要素とキーを取得 item = this[i] / key = fn(item) key in result ? Yes No result[key] = [item] result[key].push(item) i++ ループバック result を返却 Record<string, T[]> 終了

フローの説明:
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 オーバーヘッド

中間的なパフォーマンス