繁体   English   中英

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

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

我有一个基本的记忆 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)
  }
}

它不适用于递归调用自身的函数。 例如:

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

const memoizedFib = memo(fibonacci)
memoizedFib(20)

fibonacci内部,它仍然进行大量重复计算。

我想避免这种情况的一种方法是将记忆插入到 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;
};

我想知道是否有办法让高阶 util function 与fibonacci之类的递归函数一起工作?

这是一个非记忆递归 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 }

直接定义一个memoised function

您可以定义 function 和 memoise,这将使所有递归调用都使用 memoised 版本:

外观如何

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

演示

 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 }

用记忆替换原来的

您也可以稍后通过替换它的原始绑定来记忆它。 为此,不应将 function 定义为const 这仍然会使以后的调用使用记忆版本:

外观如何

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

fibonacci = memo(fibonacci);

或者

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

fibonacci = memo(fibonacci);

演示

 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 }


警告

请注意,这仅适用于引用您可以控制的绑定的递归函数。 并不是所有的递归函数都是这样的。 几个例子:

命名为 function 的表达式

例如,如果 function 定义了一个本地名称,该名称只能用于引用自身:

外观如何

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

fibonacci = memo(fibonacci);

演示

 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 }

这是因为 function 表达式的名称在 function 的正文中可用,不能从外部覆盖。 实际上,它与制作它相同:

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

fibonacci = memo(fibonacci);

内部函数

它也不适用于使用内部递归助手的函数:

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);

演示

 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 }

模块/其他范围

如果您无权访问 function 的定义,那么您就无法真正控制它用于调用自身的绑定。 使用模块时最容易看到:

外观如何

斐波那契.js

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

索引.js

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

//assume memo() exists here

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

这不会影响递归 function,因为它仍然引用模块 scope 中的自身。

注意:这并未涵盖@VLAZ 的回答中提到的所有可能性。 只是说明一种可能的方式。 此外,使用eval()也不是一个好主意


有了这个我们就不必替换原来的 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));

为了支持非箭头函数,我们可以检索它们的代码并使用 eval 将它们转换为箭头函数。

记忆 function 在参数未以独特方式进行字符串化或其中包含连字符时无法正常工作

@trincott是对的,一个解决方案是拥有一个基于Map的各种“链接列表”,我在下面演示

@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