我被这个记忆问题难住了。我需要创建一个函数,该函数将检查是否已经为给定参数计算了值,返回先前的结果,或者运行计算并返回该值。
我在这上面花了几个小时,而且我还是 JS 新手。我不知道该怎么做。我无法使用任何内置函数,并且真的很想了解我需要做什么。
这是我到目前为止所得到的,这是错误的,在这一点上感觉就像是伪代码。我已经在这里搜索了现有的记忆问题,但我似乎还无法使任何解决方案发挥作用。非常感谢任何帮助。
myMemoizeFunc = function(passedFunc) {
var firstRun = passedFunc;
function check(passedFunc){
if(firstRun === undefined){
return passedFunc;
}else{return firstRun;}
}
};
抱歉,我应该说得更清楚。以下是我的具体要求: myMemoizeFunc 必须返回一个函数,该函数将检查是否已经针对给定的 arg 进行了计算,并在可能的情况下返回该 val。 PassedFunc 是保存计算结果的函数。 我知道这可能看起来像重复,但我标记为不是这样,因为我在理解我应该在这里做什么时遇到了一些严重的困难,并且需要比其他帖子中提供的更多帮助。 这就是我的思维过程引导我走向的方向,但我又离目标很远了。
myMemoizeFunc = function(passedFunc) {
var allValues = [];
return function(){
for(var i = 0; i < myValues.length; i++){
if(myValues[i] === passedFunc){
return i;
}
else{
myValues.push(passedFunc);
return passedFunc;
}
}
}
};
我不应该在这里返回 i 或 passFunc,但是在检查值时我还能在 if/else 中做什么?我已经研究这个问题很长时间了,我开始实现一些荒谬的代码,需要一些新的建议。
我认为主要技巧是创建一个对象来存储之前作为键传入的参数,并以函数的结果作为值。
为了记忆单个参数的函数,我会像这样实现它:
var myMemoizeFunc = function (passedFunc) {
var cache = {};
return function (x) {
if (x in cache) return cache[x];
return cache[x] = passedFunc(x);
};
};
然后您可以使用它来记忆任何采用单个参数的函数,例如用于计算阶乘的递归函数:
var factorial = myMemoizeFunc(function(n) {
if(n < 2) return 1;
return n * factorial(n-1);
});
将此视为对 Peter Olson 答案的扩展。
对于可变数量的参数,您可以使用类似的方法。
注意: 如果您打算传递复杂的参数(数组、对象、函数),则此示例不是最佳选择。请务必进一步阅读,不要盲目复制/粘贴。
function memo(fn) {
const cache = {};
function get(args) {
let node = cache;
for (const arg of args) {
if (!("next" in node)) node.next = new Map();
if (!node.next.has(arg)) node.next.set(arg, {});
node = node.next.get(arg);
}
return node;
}
return function (...args) {
const cache = get(args);
if ("item" in cache) return cache.item;
cache.item = fn(...args);
return cache.item;
}
}
这构建了以下缓存树结构:
const memoizedFn = memo(fn);
memoizedFn();
memoizedFn(1);
memoizedFn(1, 2);
memoizedFn(2, 1);
cache = {
item: fn(),
next: Map{ // <- Map contents depicted as object
1: {
item: fn(1),
next: Map{
2: { item: fn(1, 2) }
}
},
2: {
next: Map{
1: { item: fn(2, 1) }
}
}
}
}
此解决方案在传递随后不再引用的复杂参数(数组、对象、函数)时会泄漏内存。
memoizedFn({ a: 1 })
因为
{ a: 1 }
在 memoizedFn
调用之后没有被引用,所以通常会被垃圾收集。但现在不可能了,因为 cache
仍然保留着引用。它只能被垃圾收集一次memoizedFn
本身被垃圾收集。
我首先展示了上面的内容,因为它展示了基本概念并以某种简单的形式演示了缓存结构。为了清理通常会被垃圾收集的缓存,我们应该使用
WeakMap
而不是复杂对象的 Map
。
对于那些不熟悉
的人来说,这些键是一个“弱”参考。这意味着这些键“不”计入对对象的活动引用。一旦一个对象不再被引用(不包括弱引用),它将被垃圾收集。这将从WeakMap
WeakMap
实例中删除键/值对。
const memo = (function () {
const primitives = new Set([
"undefined",
"boolean",
"number",
"bigint",
"string",
"symbol"
]);
function typeOf(item) {
const type = typeof item;
if (primitives.has(type)) return "primitive";
return item === null ? "primitive" : "complex";
}
const map = {
"primitive": Map,
"complex": WeakMap
};
return function (fn) {
const cache = {};
function get(args) {
let node = cache;
for (const arg of args) {
const type = typeOf(arg);
if (!(type in node)) node[type] = new map[type];
if (!node[type].has(arg)) node[type].set(arg, {});
node = node[type].get(arg);
}
return node;
}
return function (...args) {
const cache = get(args);
if ("item" in cache) return cache.item;
cache.item = fn(...args);
return cache.item;
}
}
})();
const fib = memo((n) => {
console.log("fib called with", n);
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib(n - 2);
});
// heavy operation with complex object
const heavyFn = memo((obj) => {
console.log("heavyFn called with", obj);
// heavy operation
return obj.value * 2;
});
// multiple complex arguments
const map = memo((iterable, mapFn) => {
console.log("map called with", iterable, mapFn);
const result = [];
for (const item of iterable) result.push(mapFn(item));
return result;
});
console.log("### simple argument demonstration ###");
console.log("fib(3)", "//=>", fib(3));
console.log("fib(6)", "//=>", fib(6));
console.log("fib(5)", "//=>", fib(5));
console.log("### exlanation of when cache is garbage collected ###");
(function () {
const item = { value: 7 };
// item stays in memo cache until it is garbade collected
console.log("heavyFn(item)", "//=>", heavyFn(item));
console.log("heavyFn(item)", "//=>", heavyFn(item));
// Does not use the cached item. Although the object has the same contents
// it is a different instance, so not considdered the same.
console.log("heavyFn({ value: 7 })", "//=>", heavyFn({ value: 7 }));
// { value: 7 } is garbade collected (and removed from the memo cache)
})();
// item is garbade collected (and removed from memo cache) it is no longer in scope
console.log("### multiple complex arguments demonstration ###");
console.log("map([1], n => n * 2)", "//=>", map([1], n => n * 2));
// Does not use cache. Although the array and function have the same contents
// they are new instances, so not considdered the same.
console.log("map([1], n => n * 2)", "//=>", map([1], n => n * 2));
const ns = [1, 2];
const double = n => n * 2;
console.log("map(ns, double)", "//=>", map(ns, double));
// Does use cache, same instances are passed.
console.log("map(ns, double)", "//=>", map(ns, double));
// Does use cache, same instances are passed.
ns.push(3);
console.log("mutated ns", ns);
console.log("map(ns, double)", "//=>", map(ns, double));
结构本质上保持不变,但根据参数的类型,它将在
primitive: Map{}
或
complex: WeakMap{}
对象中查找。const memoizedFn = memo(fn);
memoizedFn();
memoizedFn(1);
memoizedFn(1, 2);
memoizedFn({ value: 2 }, 1);
cache = {
item: fn(),
primitive: Map{
1: {
item: fn(1),
primitive: Map{
2: { item: fn(1, 2) }
}
}
},
complex: WeakMap{
{ value: 2 }: { // <- cleared if { value: 2 } is garbage collected
primitive: Map{
1: { item: fn({ value: 2 }, 1) }
}
}
}
}
此解决方案不会记住引发的任何错误。基于
Map
键相等
,参数被视为相等。如果您还需要记住抛出的任何错误,我希望这个答案为您提供了这样做的基础。
const memorized = (fn) => {
let cache = {}
const makeKey = (...args) => {
return args.map((o) => JSON.stringify(o)).join('_')
}
return function (...args) {
const key = makeKey(args)
if (cache[key]) {
console.log('return from cache')
return cache[key]
} else {
console.log('calculating')
const newValue = fn(...args)
cache[key] = newValue
return newValue
}
}
}
const sum = (a, b, c)=> a + b + c
const square = a => a*a
const sumMemorized = memorized(sum)
const squareMemorized = memorized(square)
sumMemorized(1,2,3)
sumMemorized(1,2,3)
sumMemorized(1,2,3)
sumMemorized(1,2,3)
sumMemorized(1,2,3)
squareMemorized(2)
squareMemorized(3)
const memoize = (func) => {
const results = {};
return (...args) => {
const argsKey = JSON.stringify(args);
if (!results[argsKey]) {
results[argsKey] = func(...args);
}
return results[argsKey];
};
};
const clumsysquare = memoize((num) => {
let result = 0;
for (let i = 1; i <= num; i++) {
for (let j = 1; j <= num; j++) {
result++;
}
}
return result;
});
console.log(clumsysquare(9467));
console.log(clumsysquare(9467));
console.log(clumsysquare(9467)); console.log(clumsysquare(9467));
使用记忆的重要性和执行时间大大减少