记住JavaScript中任何给定的递归函数

时间:2018-10-18 19:56:09

标签: javascript algorithm

我对以下情况感兴趣:我们有一些递归的函数f,但没有提供源代码。

我想要一个函数备注器:Function->接受f并返回g的函数,使得g = f(在给定相同参数的情况下它们返回相同的值),该函数在调用时首先检查是否被调用的参数位于其“缓存”(其之前已计算出的结果的内存)中,如果是,则从中返回结果,否则它将计算f,如果f用某些参数调用自身,这等于用这些参数调用g并我想让f首先检查g的缓存是否包含那些参数,如果是,则从中返回结果,否则...

在给定f的源代码的情况下,这很容易(用Javascript编写),我只是以明显的方式定义了备忘录,并执行

let f = memoize((...args) => {/* source code of f */});

但这根本不吸引我(主要是因为我可能想要同一个功能的记忆版本和非记忆版本,然后我必须编写两次相同的函数),如果我不这样做,将无法正常工作不知道如何实现f。

如果不清楚我在问什么,

我想要一个函数备忘录,该函数带有

之类的函数
fact = n => n === 0 ? 1 : n * fact(n - 1);

并返回一些新函数g,使得所有n的事实(n)= g(n),例如在计算g(10)时存储事实(0),...,事实(10)的值),它们是在计算g(10)时计算出来的,然后如果我要说g(7),它将在缓存中找到结果并将其返回给我。

我认为从概念上讲可以检测到何时调用f,因为我有它的地址,也许我可以用新函数替换对f的所有调用,在该函数中我计算f并存储结果,然后将值传递给通常会去哪里。但是我不知道该怎么做(听起来很不愉快)。

3 个答案:

答案 0 :(得分:4)

  

我可能想要一个相同功能的记忆版本和非记忆版本,然后我必须两次编写相同的功能

是的,您需要。该函数内部对fact(n - 1)的递归调用只能引用一个fact函数-一个已记忆或未记忆的函数。

因此,为避免代码重复,您需要做的是用Y combinator定义fact

const makeFact = rec => n => n === 0 ? 1 : n * rec(n - 1);
//               ^^^                           ^^^

const factA = Y(makeFact);
const factB = memoizingY(makeFact);

function Y(make) {
    const f = make((...args) => f(...args)); // const f = make(f) is easier to understand
    return f;                                // but doesn't work with eager evaluation
}

我将memoizingY的定义留给读者练习:-)


可能更简单的方法:

const makeFact = annotate => {
    const f = annotate(n => n === 0 ? 1 : n * f(n - 1));
    return f;
}

const factA = makeFact(identity);
const factB = makeFact(memoize);

答案 1 :(得分:2)

  

也许我可以用一个新函数替换所有对f的调用,在该函数中我可以计算f并存储结果,然后将值传递到通常可以到达的位置。

正如Bergi在comment中所提到的,这实际上很容易做到。

// https://stackoverflow.com/questions/24488862/implementing-automatic-memoization-returns-a-closured-function-in-javascript/ 
function memoize(func) {
  var memo = {};
  var slice = Array.prototype.slice;

  return function() {
    var args = slice.call(arguments);

    if (args in memo)
      return memo[args];
    else
      return (memo[args] = func.apply(this, args));

  }
}

function fib(n) {
  if (n <= 1) return 1;
  return fib(n - 1) + fib(n - 2);
}

fib = memoize(fib);

console.log(fib(100));

答案 2 :(得分:1)

根据我的有限经验,我们确实可以访问JavaScript源代码。因此,我们可以尝试为该记忆功能生成新的源代码。

// Redefine Function.prototype.bind
// to provide access to bound objects. 
// https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
var _bind = Function.prototype.apply.bind(Function.prototype.bind);
Object.defineProperty(Function.prototype, 'bind', {
    value: function(obj) {
        var boundFunction = _bind(this, arguments);
        boundFunction.boundObject = obj;
        return boundFunction;
    }
});

// Assumes the parameters for the function,
// f, can be consistently mapped.
function memo(f){
  if (!(f instanceof Function))
    throw TypeError('Argument is not an instance of Function.');
    
  // Generate random variable names
  // to avoid conflicts with unknown
  // source code
  function randomKey(numBytes=8){        
      let ranges = [[48, 10], [65, 26], [97, 26]]; 
      let key = '_';

      for (let i=0; i<numBytes; i++){     
          let idx = Math.floor(Math.random() * ranges.length);
          key += String.fromCharCode(ranges[idx][0] + Math.random() * ranges[idx][1]);
      }   

      return key;
  }

  let fName = f.name;
  let boundObject;
  let fCode;
  
  const nativeCodeStr = '(){[nativecode]}';
  
  // Possible Proxy
  try {
    fCode = f.toString();

  } catch(error){
    if (error.constructor == TypeError){
      if (Function(`return ${ fName }.toString()`)() != nativeCodeStr){
        throw TypeError(`Possible Proxy detected: function has a name but no accessible source code. Consider memoizing the target function, ${ fName }.`);
      
      } else {
        throw TypeError(`Function has a name but no accessible source code. Applying toString() to its name, ${ fName }, returns '[native code]'.`);
      }
    
    } else {
      throw Error('Unexpected error calling toString on the argument.');
    }
  }

  if (!fName){
    throw Error('Function name is falsy.');

  // Bound functions
  // Assumes we've monkey-patched
  // Function.prototype.bind previously
  } else if (fCode.replace(/^[^(]+|\s+/g, '') == nativeCodeStr){
    if (/^bound /.test(fName)){
      fName = fName.substr(6);
      boundObject = f.boundObject;
      // Bound functions return '[native code]' for
      // their toString method call so get the code
      // from the original function.
      fCode = Function(`return ${ fName }.toString()`)();
    
    } else {
      throw Error("Cannot access source code, '[native code]' provided.");
    }
  }

  const fNameRegex = new RegExp('(\\W)' + fName + '(\\W)', 'g');
  const cacheName = randomKey();
  const recursionName = randomKey();
  const keyName = randomKey();

  fCode = fCode.replace(/[^\(]+/,'')
    .replace(fNameRegex, '$1' + recursionName + '$2')
    .replace(/return/g, `return ${ cacheName }[${ keyName }] =`)
    .replace(/{/, `{\n  const ${ keyName } = Array.from(arguments);\n\n  if (${ cacheName }[${ keyName }])\n    return ${ cacheName }[${ keyName }];\n`);
  
  const code = `function(){\nconst ${ cacheName } = {};\n\nfunction ${ recursionName + fCode }\n\nreturn ${ recursionName }.apply(${ recursionName }, arguments);}`;

  let g = Function('"use strict";return ' + code)();

  if (boundObject){
    let h = (g).bind(boundObject);
    h.toString = () => code;
    return h;

  } else {
    return g;
  }
} // End memo function


function fib(n) {
  if (n <= 1) return 1;
  return fib(n - 1) + fib(n - 2);
}

const h = fib.bind({a: 37});
const g = memo(h);
   
console.log(`g(100): ${ g(100) }`);
console.log(`g.boundObject:`, g.boundObject);
console.log(`g.toString():`, g.toString());

try{ 
  memo(function(){});

} catch(e){
  console.log('Caught error memoizing anonymous function.', e)
}

const p = new Proxy(fib, {
  apply: function(target, that, args){
    console.log('Proxied fib called.');
    return target.apply(target, args);
  }
});

console.log('Calling proxied fib.');
console.log(`p(2):`, p(2));

let memoP;

try {
  memoP = memo(p);
  
} catch (e){
  console.log('Caught error memoizing proxied function.', e)
}