import type { NonNull } from './type'; import { isArray } from './alias'; import { equal } from './compare'; interface Cache { add?(key: K, value: V): NonNull; set(key: K, value: V): this; get(key: K): V | undefined; has(key: K): boolean; } export function memoize unknown, b = Parameters[0]>(f: f, memory?: Cache>): f; export function memoize unknown, b extends number = Parameters[0]>(f: f, memory: Record>, mask?: number): f; export function memoize unknown, b = Parameters[0]>(f: f, identify: (...as: Parameters) => b, memory?: Cache>): f; export function memoize unknown, b extends number = number>(f: f, identify: (...as: Parameters) => b, memory: Record>, mask?: number): f; export function memoize(f: (a: a) => z, memory?: Cache): typeof f; export function memoize(f: (a: a) => z, memory: Record, mask?: number): typeof f; export function memoize(f: (a: a) => z, identify: (a: a) => b, memory?: Cache): typeof f; export function memoize(f: (a: a) => z, identify: (a: a) => b, memory: Record, mask?: number): typeof f; export function memoize(f: (...as: as) => z, memory?: Cache): typeof f; export function memoize(f: (...as: as) => z, memory: Record, mask?: number): typeof f; export function memoize(f: (...as: as) => z, identify: (...as: as) => b, memory?: Cache): typeof f; export function memoize(f: (...as: as) => z, identify: (...as: as) => b, memory: Record, mask?: number): typeof f; export function memoize(f: (...as: as) => z, identify?: Cache | Record | ((...as: as) => b), memory?: Cache | Record | number, mask?: number): typeof f { if (typeof identify === 'object') { mask = memory as number; memory = identify; identify = undefined; } identify ??= (...as) => as[0] as b; switch (true) { case isArray(memory): return mask === undefined ? memoizeArray(f, identify, memory as z[]) : cacheArray(f, identify, memory as [b, z][], mask); case memory?.constructor === Object: return mask === undefined ? memoizeObject(f, identify, memory as Record) : cacheObject(f, identify, memory as Record, mask); default: return memoizeDict(f, identify, memory as Cache ?? new Map()); } } function memoizeArray( f: (...as: as) => z, identify: (...as: as) => b, memory: z[], ): typeof f { return (...as) => { const b = identify(...as) as number; let z = memory[b]; if (z !== undefined) return z!; z = f(...as); memory[b] = z; return z; }; } function cacheArray( f: (...as: as) => z, identify: (...as: as) => b, memory: (z | [b, z])[], mask: number, ): typeof f { const mask1 = mask >>>= 1; const mask2 = mask; const mem1 = memory; const mem2 = [] as typeof memory; return (...as) => { const b = identify(...as) as number; if (b <= mask1) { let z = mem1[b]; if (z !== undefined) return z!; z = f(...as); mem1[b] = z; return z; } else { const i = b & mask2; const t = mem2[i]; if (t && t[0] === b) return t[1]; const z = f(...as); mem2[i] = [b as b, z]; return z; } }; } function memoizeObject( f: (...as: as) => z, identify: (...as: as) => b, memory: Record, ): typeof f { let nullable = false; return (...as) => { const b = identify(...as) as number; let z = memory[b]; if (z !== undefined || nullable && b in memory) return z!; z = f(...as); nullable ||= z === undefined; memory[b] = z; return z; }; } function cacheObject( f: (...as: as) => z, identify: (...as: as) => b, memory: Record, mask: number, ): typeof f { const mask1 = mask >>>= 1; const mask2 = mask; const mem1 = memory; const mem2 = {} as typeof memory; let nullable = false; return (...as) => { const b = identify(...as) as number; if (b <= mask1) { let z = mem1[b]; if (z !== undefined || nullable && b in mem1) return z!; z = f(...as); nullable ||= z === undefined; mem1[b] = z; return z; } else { const i = b & mask2; const t = mem2[i]; if (t && t[0] === b) return t[1]; const z = f(...as); mem2[i] = [b as b, z]; return z; } }; } function memoizeDict( f: (...as: as) => z, identify: (...as: as) => b, memory: Cache, ): typeof f { let nullable = false; return (...as) => { const b = identify(...as); let z = memory.get(b); if (z !== undefined || nullable && memory.has(b)) return z!; z = f(...as); nullable ||= z === undefined; memory.add?.(b, z) ?? memory.set(b, z); return z; }; } export function reduce unknown, b = Parameters[0]>(f: f): f; export function reduce unknown, b = Parameters[0]>(f: f, identify?: (...as: Parameters) => b): f; export function reduce(f: (a: a) => z): typeof f; export function reduce(f: (a: a) => z, identify?: (a: a) => b): typeof f; export function reduce(f: (...as: as) => z): typeof f; export function reduce(f: (...as: as) => z, identify?: (...as: as) => b): typeof f; export function reduce(f: (...as: as) => z, identify: (...as: as) => b = (...as) => as[0] as b): typeof f { let key: b = {} as b; let val: z; return (...as) => { const b = identify(...as); if (!equal(key, b)) { key = b; val = f(...as); } return val; }; }