ネスト Map トライ構造 による O(k) キャッシュ実装
任意の関数
fn
を受け取り、
同じ引数列を2回渡した際に fn
を再実行せずキャッシュから結果を返す
ラッパー関数を生成します。
===(参照同一性)
undefined
を返すケースも正しくキャッシュ
JSON.stringify
キー化は不可(参照同一性が失われる)
undefined
戻り値も
has()
で正確に判定
改善版:TrieNode オブジェクトを廃止し Map 自体をノードとして使用
type Fn = (...params: any) => any;
function memoize(fn: Fn): Fn {
// Symbol をクロージャ内に閉じ込め、外部アクセスを防止
// いかなる引数値とも衝突しないことをコンパイル時・実行時両方で保証
const RESULT = Symbol('result');
// ノード = Map 自体(TrieNodeオブジェクト不要)
// 再帰型: 次の引数への経路 or 結果値を保持
type CacheMap = Map<unknown, CacheMap | unknown>;
const root: CacheMap = new Map();
return function (...args: unknown[]): unknown {
let node = root;
// 各引数を順にトライを辿る
// 経路がなければ新規 Map ノードを作成
for (const arg of args) {
if (!node.has(arg)) {
node.set(arg, new Map() as CacheMap);
}
// 直前ブロックで set 済み → non-null assertion は安全
node = node.get(arg) as CacheMap;
}
// 終端ノードにキャッシュがあればそれを返す
// ※ has() で判定 → fn が undefined を返す場合も正確に動作
if (node.has(RESULT)) {
return node.get(RESULT); // キャッシュヒット: fn を呼ばない
}
// キャッシュミス: fn を実行して終端ノードに格納
const result: unknown = fn(...args);
node.set(RESULT, result);
return result;
};
}
root
から引数を1つずつ Map でたどるhas(RESULT) をチェック| 比較項目 | 旧実装(TrieNode obj) | 新実装(Map 直接) |
|---|---|---|
| ノード構造 | {children:Map, hasResult, result} | Map のみ |
| オブジェクト生成 | Object + Map(2重) | Map のみ |
| キャッシュ確認 | node.hasResult(boolean) | map.has(RESULT)(Symbol) |
| undefined 戻り値 | hasResult フラグが必要 | has() で自然に対応 |
| V8 JIT 親和性 | Hidden Class 最適化が複雑 | 均一な Map 形状で最適化しやすい |