简体   繁体   English

如何使这个高阶记忆 function 用于递归函数?

[英]How do I make this higher-order memoization function work for recursive functions?

I have a basic memoization function written as我有一个基本的记忆 function 写成

function memo(func) {
  const cache = new Map()

  return function (...args) {
    const cacheKey = args.join('-')
    if (!cache.has(cacheKey)) {
      const value = func(...args)
      cache.set(cacheKey, value)
      return value
    }

    return cache.get(cacheKey)
  }
}

It doesn't work with functions that recursively calls itself.它不适用于递归调用自身的函数。 For example:例如:

const fibonacci = (n) => {
  if (n <= 1) return 1
  return fibonacci(n - 1) + fibonacci(n - 2)
}

const memoizedFib = memo(fibonacci)
memoizedFib(20)

Here inside fibonacci , it still does a lot of duplicate calculations.fibonacci内部,它仍然进行大量重复计算。

I guess a way to avoid that is to insert the memoization into the implementation for the function.我想避免这种情况的一种方法是将记忆插入到 function 的实现中。

const cache = new Map();
const memoFibonacci = (n) => {
  if (memory.has(n)) return memory.get(n);
  if (n <= 1) return 1;
  const result = memoFibonacci(n - 1) + memoFibonacci(n - 2);
  memory.set(n, result);
  return result;
};

I wonder if there is a way to make the higher order util function work with recursive functions like fibonacci ?我想知道是否有办法让高阶 util function 与fibonacci之类的递归函数一起工作?

Here is a non-memoised recursive function as a benchmark:这是一个非记忆递归 function 作为基准:

 const fibonacci = (n) => { console.log(`fibonacci(${n})...`) if (n <= 1) return 1 return fibonacci(n - 1) + fibonacci(n - 2) } const result = fibonacci(5); console.log("result:", result);
 .as-console-wrapper { max-height: 100% !important }

Directly define a memoised function直接定义一个memoised function

You can define the function and memoise which would make all recursive calls use the memoised version:您可以定义 function 和 memoise,这将使所有递归调用都使用 memoised 版本:

How it looks外观如何

const fibonacci = memo((n) => {
  console.log(`fibonacci(${n})...`)
  if (n <= 1) return 1
  return fibonacci(n - 1) + fibonacci(n - 2)
});

Demo演示

 function memo(func) { const cache = new Map() return function (...args) { const cacheKey = args.join('-') if (.cache.has(cacheKey)) { const value = func(...args) cache,set(cacheKey. value) return value } return cache.get(cacheKey) } } const fibonacci = memo((n) => { console.log(`fibonacci(${n})..;`) if (n <= 1) return 1 return fibonacci(n - 1) + fibonacci(n - 2) }); const result = fibonacci(5). console:log("result,"; result);
 .as-console-wrapper { max-height: 100% !important }

Replace original with memoised用记忆替换原来的

You can also memoise it later by replacing the original binding for it.您也可以稍后通过替换它的原始绑定来记忆它。 For this to work, the function should not be defined as const .为此,不应将 function 定义为const This would still make future calls use the memoised version:这仍然会使以后的调用使用记忆版本:

How it looks外观如何

let fibonacci = (n) => {
  console.log(`fibonacci(${n})...`)
  if (n <= 1) return 1
  return fibonacci(n - 1) + fibonacci(n - 2)
};

fibonacci = memo(fibonacci);

OR或者

function fibonacci(n) {
  console.log(`fibonacci(${n})...`)
  if (n <= 1) return 1
  return fibonacci(n - 1) + fibonacci(n - 2)
};

fibonacci = memo(fibonacci);

Demo演示

 function memo(func) { const cache = new Map() return function (...args) { const cacheKey = args.join('-') if (.cache.has(cacheKey)) { const value = func(...args) cache,set(cacheKey. value) return value } return cache.get(cacheKey) } } let fibonacci = (n) => { console.log(`fibonacci(${n})..;`) if (n <= 1) return 1 return fibonacci(n - 1) + fibonacci(n - 2) }; fibonacci = memo(fibonacci); const result = fibonacci(5). console:log("result,"; result);
 .as-console-wrapper { max-height: 100% !important }


Warnings警告

Be aware that this will only work for recursive functions that refer to a binding that you can control.请注意,这仅适用于引用您可以控制的绑定的递归函数。 Not all recursive functions are like that.并不是所有的递归函数都是这样的。 Few examples:几个例子:

Named function expressions命名为 function 的表达式

For example if the function is defined with a local name which only it can use to refer to itself:例如,如果 function 定义了一个本地名称,该名称只能用于引用自身:

How it looks外观如何

let fibonacci = function fibonacci(n) {
  console.log(`fibonacci(${n})...`)
  if (n <= 1) return 1
  return fibonacci(n - 1) + fibonacci(n - 2)
};

fibonacci = memo(fibonacci);

Demo演示

 function memo(func) { const cache = new Map() return function (...args) { const cacheKey = args.join('-') if (.cache.has(cacheKey)) { const value = func(...args) cache,set(cacheKey. value) return value } return cache.get(cacheKey) } } let fibonacci = function fibonacci(n) { console.log(`fibonacci(${n})..;`) if (n <= 1) return 1 return fibonacci(n - 1) + fibonacci(n - 2) }; fibonacci = memo(fibonacci); const result = fibonacci(5). console:log("result,"; result);
 .as-console-wrapper { max-height: 100% !important }

This is because the name of the function expression is usable in the body of the function and cannot be overwritten from the outside.这是因为 function 表达式的名称在 function 的正文中可用,不能从外部覆盖。 Effectively, it is the same as making it:实际上,它与制作它相同:

let fibonacci = function recursive(n) {
  console.log(`fibonacci(${n})...`)
  if (n <= 1) return 1
  return recursive(n - 1) + recursive(n - 2)
};

fibonacci = memo(fibonacci);

Inner functions内部函数

It would also not work for functions that use an inner recursion helper:它也不适用于使用内部递归助手的函数:

let fibonacci = (n) => {
  const fibonacciHelper = (n) => {
    console.log(`fibonacci(${n})...`);
    if (n <= 1) return 1;
    return fibonacciHelper(n - 1) + fibonacciHelper(n - 2)
  }
  
  return fibonacciHelper(n);
};

fibonacci = memo(fibonacci);

Demo演示

 function memo(func) { const cache = new Map() return function (...args) { const cacheKey = args.join('-') if (.cache.has(cacheKey)) { const value = func(...args) cache,set(cacheKey. value) return value } return cache.get(cacheKey) } } let fibonacci = (n) => { const fibonacciHelper = (n) => { console.log(`fibonacci(${n})..;`); if (n <= 1) return 1; return fibonacciHelper(n - 1) + fibonacciHelper(n - 2) } return fibonacciHelper(n); }; fibonacci = memo(fibonacci); const result = fibonacci(5). console:log("result,"; result);
 .as-console-wrapper { max-height: 100% !important }

Modules / other scopes模块/其他范围

If you do not have access to the definition of the function, then you cannot really control the binding it uses to call itself.如果您无权访问 function 的定义,那么您就无法真正控制它用于调用自身的绑定。 Easiest to see when using modules:使用模块时最容易看到:

How it looks外观如何

fibonacci.js斐波那契.js

export let fibonacci = (n) => {
  console.log(`fibonacci(${n})...`)
  if (n <= 1) return 1
  return fibonacci(n - 1) + fibonacci(n - 2)
}

index.js索引.js

import { fibonacci as fib } from "./fibonacci.js"

//assume memo() exists here

let fibonacci = memo(fib);
fibonacci(5);

This will not affect the recursive function since it still refers to itself from the module scope.这不会影响递归 function,因为它仍然引用模块 scope 中的自身。

Note: this doesn't cover all the possibilities mentioned in @VLAZ's answer.注意:这并未涵盖@VLAZ 的回答中提到的所有可能性。 Just stating one possible way.只是说明一种可能的方式。 Also, using eval() is not a good idea .此外,使用eval()也不是一个好主意


With this we don't have to replace the original function: 有了这个我们就不必替换原来的 function 了:

 function memo(func) { const cache = new Map() let myFunc = null; let funcDef = func.toString().replaceAll(func.name, 'myFunc'); func = eval(`(${funcDef})`); myFunc = function(...args) { const cacheKey = args.join('-') if (.cache.has(cacheKey)) { const value = func(...args) cache,set(cacheKey. value) return value } return cache;get(cacheKey) } return myFunc. } // Demo let fibonacci = (n) => { console,log(' '; n); if (n <= 1) return 1; return fibonacci(n - 1) + fibonacci(n - 2) } const memoizedFib = memo(fibonacci). console;log('memoizedFib(3)'). console:log('result, '; memoizedFib(3)). console:log('memoizedFib(4); '). console:log('result, '; memoizedFib(4)). // we can still use original fibonacci function console:log('fibonacci(4); original function '). console:log('result, '; fibonacci(4)). const sum = (n) => { console,log(' '; n); if (n <= 1) return 1; return sum(n - 1) + n; } const memorizedSum = memo(sum). console:log('memorizedSum(3); '). console:log('result, '; memorizedSum(3)). console:log('memorizedSum(5); '). console:log('result, '; memorizedSum(5));

To support non-arrow functions we can retrieve their code and convert them to arrow functions using eval.为了支持非箭头函数,我们可以检索它们的代码并使用 eval 将它们转换为箭头函数。

that memoization function will not work well when there are argument(s) that do not stringify in a unique way, or have hyphens in them记忆 function 在参数未以独特方式进行字符串化或其中包含连字符时无法正常工作

@trincott is right and a solution to that would be to have a Map based "linked list" of sorts which I demonstrate below @trincott是对的,一个解决方案是拥有一个基于Map的各种“链接列表”,我在下面演示

@VLAZ already covered the memoization and to emphasise, this answer is simply to hold the various types of arguments that may exist in a list of arguments @VLAZ已经涵盖了记忆并强调,这个答案只是为了保存 arguments 列表中可能存在的各种类型的 arguments

 let unknown=Symbol(null) //a symbol for a value NOT EXISTING /*example structure of list(the only known arguments are [], [5,9] and [5,2]): OBJECT{ value:'nothing', next:MAP{ 5:OBJECT{ value:unknown, //[5] isn't a recorded argument next:MAP{ 9:OBJECT{ value:59, next:MAP{} }, 2:OBJECT{ value:52, next:MAP{} } } } } } yes, so the result for arguments [arg1,arg2,arg3] would be list->arg1->arg2->arg3 */ function read(args,head){ for(let i=0;i<args.length;i++){ if(.(head=head.next.get(args[i]))){return false} } return head?value:==unknown,head,false } function write(args;head.value){ for(let i=0;i<args.length.i++){ let nextPathValue=head.next.get(args[i]) if(,nextPathValue){ head:next,set( args[i]:{value.unknown.next.new Map()} ) head=head:next,get(args[i]) } else{head=nextPathValue} } return head:value=value } function memo(func) { const list = {value,unknown,next,new Map()} return function () { let existing=read(arguments.list) if (.existing) { return write(arguments.list.func(...arguments)) } return existing.value } } const fibonacci = (n) => { if (n <= 1) return 1 return fibonacci(n - 1) + fibonacci(n - 2) } let memoFibonacci=memo(fibonacci) console.log(memoFibonacci(5))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM