记忆一个咖喱函数
Memoize a curried function
const f = (arg1) => (arg2) => { /* returns something */ }
是否可以记忆 f 关于 2 个参数,即:
f(1)(2);
f(1)(3); // Cache not hit
f(4)(2); // Cache not hit
f(1)(2); // Cache hit
有趣的问题 — 您可以为每个函数设置独立的缓存。外部函数的缓存将保存函数。每个内部函数都可以获得自己独立的缓存。因此调用 f(10)(1)
后跟 f(10)(2)
将导致调用内部函数的缓存版本。再次调用 f(10)(1)
会命中两个缓存:
function getCachedF() {
// outer cache holds functions keyed to argument
let outer_memo = {}
const f = (arg1) => {
if (!outer_memo.hasOwnProperty(arg1)) {
// Create inner function on outer cache
// each inner function needs its own cache
// because it will return different values
// given different outer function calls
let inner_memo = {}
console.log("outer cache miss")
outer_memo[arg1] = (arg2) => {
// just a normal memoized function
// cache is simple key:value pair
if (!inner_memo.hasOwnProperty(arg2)) {
console.log("inner cache miss")
inner_memo[arg2] = arg1 + arg2
}
return inner_memo[arg2]
}
}
return outer_memo[arg1]
}
return f
}
let f = getCachedF()
// both caches miss
console.log("3+5", f(3)(5))
// cached result
console.log("3+5", f(3)(5))
// only inside cache hit
console.log("3+8", f(3)(8))
// inside cache only hits if both args are the same
console.log("10+8", f(10)(8))
另一种选择是使用单个缓存,其键是两个参数的组合,但是内部函数总是必须被调用。
您可以将 Map
作为缓存并为所有以下参数获取嵌套映射。
此缓存适用于任意数量的参数并重用之前调用的值。
它通过采用柯里化函数和可选的 Map
来工作。如果未提供映射,则会创建一个新映射,用作 returned 闭包或最终结果的所有其他调用的基本缓存。
内部函数采用单个参数并检查此值是否在映射中。
如果不是,调用curried函数并检查returned值
if 函数,在函数上创建一个新的闭包和一个新的映射,
如果没有函数取结果,
作为地图新元素的值。
最后return来自地图的值。
const cached = (fn, map = new Map()) => arg => {
const inCache = map.has(arg);
const hint = inCache ? 'in cache' : 'not in cache';
console.log(arg, hint);
if (!inCache) {
const value = fn(arg);
const result = typeof value === 'function' ? cached(value, new Map()) : value;
map.set(arg, result);
}
return map.get(arg);
};
const f = a => b => c => a * b * c; // the original curried function
const g = cached(f); // its cached variant
console.log(g(1)(2)(5)); // not not not 10
console.log(g(1)(3)(4)); // in not not 12
console.log(g(4)(2)(3)); // not not not 24
console.log(g(1)(2)(6)); // in in not 12
console.log(g(4)(2)(3)); // in in in 24
.as-console-wrapper { max-height: 100% !important; top: 0; }
您不能将地图传递给每个函数。
你可以像下一个那样做:
const memoize = fn => {
const cache = {};
return (...args) => {
const curriedFn = fn(...args);
return (...next) => {
const key = // generate your key
if (key in cache) return cache[key];
return (cache[key] = curriedFn(...next));
}
}
}
这可能不是规范的记忆功能。
需要缓存其结果的函数被赋予一个 cache
函数,用于存储和检索以前的结果:
const sum = memo(cache => a => b => cache(`${a}+${b}`, () => a + b));
// ^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^
// A B C
A — cache
函数由 memo
函数提供。
(如有必要,记忆函数可以选择不缓存某些结果。)
B — 结果的唯一键。 (例如 cache['1+2'] = 3
)
C — thunk 结果 return
(所以我们可以在计算之前检查是否已经有了它。)
这既支持柯里化和非柯里化函数,也支持 return 作为值的函数。
memo
函数可以实现如下:
const memo = fn => {
const ns = Symbol();
const cache = (key, thunk) => cache[ns][key] ??= thunk();
cache[ns] = {};
return fn(cache);
};
我非常喜欢用于管理缓存的 logical nullish assignment 运算符:
a ??= answer()
当且仅当 a
尚未定义时,右侧的表达式被计算并分配给 a
。然后它 return 是 a
的值:
const answer = () => (console.log('returning the answer'), 42);
let a;
a ??= answer();
//=> LOG: returning the answer
//=> 42
a ??= answer();
//=> 42
a ??= 40;
//=> 42
我使用了一个符号来隐藏 cache
函数中设置的实际缓存。枚举对象的属性时,符号未 returned:
const foo = {};
const key1 = Symbol();
const key2 = 'bar';
foo[key1] = 42;
foo[key2] = 41;
Object.keys(foo);
//=> ['bar']
Object.entries(foo);
//=> [['bar', 41]]
演示
// curried memoized function
const sum = memo(cache => a => b =>
cache(`${a}+${b}`,
() => (console.log(`computing ${a}+${b}…`), a+b)));
console.log(sum(1)(2));
console.log(sum(1)(2));
console.log(sum(1)(2));
// non-curried memoized function
const mul = memo(cache => (a, b) =>
cache(`${a}*${b}`,
() => (console.log(`computing ${a}*${b}…`), a*b)));
console.log(mul(2, 3));
console.log(mul(2, 3));
console.log(mul(2, 3));
// function-returning function
const deferred_sum = memo(cache => a => b =>
cache(`${a}+${b}`,
() => (console.log(`defer computing ${a}+${b}…`), () => a+b)));
console.log(deferred_sum(1)(2)());
console.log(deferred_sum(1)(2)());
console.log(deferred_sum(1)(2)());
<script>
const memo = fn => {
const ns = Symbol();
const cache = (key, thunk) => cache[ns][key] ??= thunk();
cache[ns] = {};
return fn(cache);
};
</script>
const f = (arg1) => (arg2) => { /* returns something */ }
是否可以记忆 f 关于 2 个参数,即:
f(1)(2);
f(1)(3); // Cache not hit
f(4)(2); // Cache not hit
f(1)(2); // Cache hit
有趣的问题 — 您可以为每个函数设置独立的缓存。外部函数的缓存将保存函数。每个内部函数都可以获得自己独立的缓存。因此调用 f(10)(1)
后跟 f(10)(2)
将导致调用内部函数的缓存版本。再次调用 f(10)(1)
会命中两个缓存:
function getCachedF() {
// outer cache holds functions keyed to argument
let outer_memo = {}
const f = (arg1) => {
if (!outer_memo.hasOwnProperty(arg1)) {
// Create inner function on outer cache
// each inner function needs its own cache
// because it will return different values
// given different outer function calls
let inner_memo = {}
console.log("outer cache miss")
outer_memo[arg1] = (arg2) => {
// just a normal memoized function
// cache is simple key:value pair
if (!inner_memo.hasOwnProperty(arg2)) {
console.log("inner cache miss")
inner_memo[arg2] = arg1 + arg2
}
return inner_memo[arg2]
}
}
return outer_memo[arg1]
}
return f
}
let f = getCachedF()
// both caches miss
console.log("3+5", f(3)(5))
// cached result
console.log("3+5", f(3)(5))
// only inside cache hit
console.log("3+8", f(3)(8))
// inside cache only hits if both args are the same
console.log("10+8", f(10)(8))
另一种选择是使用单个缓存,其键是两个参数的组合,但是内部函数总是必须被调用。
您可以将 Map
作为缓存并为所有以下参数获取嵌套映射。
此缓存适用于任意数量的参数并重用之前调用的值。
它通过采用柯里化函数和可选的 Map
来工作。如果未提供映射,则会创建一个新映射,用作 returned 闭包或最终结果的所有其他调用的基本缓存。
内部函数采用单个参数并检查此值是否在映射中。
如果不是,调用curried函数并检查returned值
if 函数,在函数上创建一个新的闭包和一个新的映射,
如果没有函数取结果,
作为地图新元素的值。
最后return来自地图的值。
const cached = (fn, map = new Map()) => arg => {
const inCache = map.has(arg);
const hint = inCache ? 'in cache' : 'not in cache';
console.log(arg, hint);
if (!inCache) {
const value = fn(arg);
const result = typeof value === 'function' ? cached(value, new Map()) : value;
map.set(arg, result);
}
return map.get(arg);
};
const f = a => b => c => a * b * c; // the original curried function
const g = cached(f); // its cached variant
console.log(g(1)(2)(5)); // not not not 10
console.log(g(1)(3)(4)); // in not not 12
console.log(g(4)(2)(3)); // not not not 24
console.log(g(1)(2)(6)); // in in not 12
console.log(g(4)(2)(3)); // in in in 24
.as-console-wrapper { max-height: 100% !important; top: 0; }
您不能将地图传递给每个函数。 你可以像下一个那样做:
const memoize = fn => {
const cache = {};
return (...args) => {
const curriedFn = fn(...args);
return (...next) => {
const key = // generate your key
if (key in cache) return cache[key];
return (cache[key] = curriedFn(...next));
}
}
}
这可能不是规范的记忆功能。
需要缓存其结果的函数被赋予一个 cache
函数,用于存储和检索以前的结果:
const sum = memo(cache => a => b => cache(`${a}+${b}`, () => a + b));
// ^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^
// A B C
A —
cache
函数由memo
函数提供。
(如有必要,记忆函数可以选择不缓存某些结果。)B — 结果的唯一键。 (例如
cache['1+2'] = 3
)C — thunk 结果 return
(所以我们可以在计算之前检查是否已经有了它。)
这既支持柯里化和非柯里化函数,也支持 return 作为值的函数。
memo
函数可以实现如下:
const memo = fn => {
const ns = Symbol();
const cache = (key, thunk) => cache[ns][key] ??= thunk();
cache[ns] = {};
return fn(cache);
};
我非常喜欢用于管理缓存的 logical nullish assignment 运算符:
a ??= answer()
当且仅当 a
尚未定义时,右侧的表达式被计算并分配给 a
。然后它 return 是 a
的值:
const answer = () => (console.log('returning the answer'), 42);
let a;
a ??= answer();
//=> LOG: returning the answer
//=> 42
a ??= answer();
//=> 42
a ??= 40;
//=> 42
我使用了一个符号来隐藏 cache
函数中设置的实际缓存。枚举对象的属性时,符号未 returned:
const foo = {};
const key1 = Symbol();
const key2 = 'bar';
foo[key1] = 42;
foo[key2] = 41;
Object.keys(foo);
//=> ['bar']
Object.entries(foo);
//=> [['bar', 41]]
演示
// curried memoized function
const sum = memo(cache => a => b =>
cache(`${a}+${b}`,
() => (console.log(`computing ${a}+${b}…`), a+b)));
console.log(sum(1)(2));
console.log(sum(1)(2));
console.log(sum(1)(2));
// non-curried memoized function
const mul = memo(cache => (a, b) =>
cache(`${a}*${b}`,
() => (console.log(`computing ${a}*${b}…`), a*b)));
console.log(mul(2, 3));
console.log(mul(2, 3));
console.log(mul(2, 3));
// function-returning function
const deferred_sum = memo(cache => a => b =>
cache(`${a}+${b}`,
() => (console.log(`defer computing ${a}+${b}…`), () => a+b)));
console.log(deferred_sum(1)(2)());
console.log(deferred_sum(1)(2)());
console.log(deferred_sum(1)(2)());
<script>
const memo = fn => {
const ns = Symbol();
const cache = (key, thunk) => cache[ns][key] ??= thunk();
cache[ns] = {};
return fn(cache);
};
</script>