簡體   English   中英

在JavaScript中記憶任何給定的遞歸函數

[英]memoize any given recursive function in JavaScript

我感興趣的是我們有一些函數f的場景,它是遞歸的,我們沒有提供源代碼。

我想要一個函數memoizer:Function - > Function,它接受說f並返回一個函數g,使得g = f(在某種意義上它們返回給定相同參數的相同值),當調用時首先檢查被調用的參數是否為在它的'緩存'(它之前已經計算過的結果的內存)中,如果這樣返回結果,否則它應該計算f,如果f用一些參數調用自己,這無異於用這些參數調用g,我想首先檢查g的緩存是否包含這些參數,如果是,則返回結果,否則......

這很容易(在Javascript中)給出f的源代碼,我只是以明顯的方式定義memoize並做類似的事情

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

但這根本不吸引我(主要是因為我可能想要一個相同功能的memoized和非memoized版本然后我必須寫兩次相同的功能)如果我不知道將無法工作如何實施f。

如果我不清楚我在問什么,

我想要一個函數memoize,它具有如下函數

fact = n => n === 0 ? 1 : n * fact(n - 1);

並且返回一些新函數g,使得所有n的fact(n)= g(n)並且例如當計算g(10)時存儲fact(0),...,fact(10)的值,這是計算g(10)時計算,然后如果我要求說g(7)它在緩存中找到結果並將其返回給我。

我認為概念上可以檢測f何時被調用,因為我有它的地址,也許我可以用一個新函數替換所有對f的調用,我計算f並存儲結果,然后將值傳遞到它所在的位置通常去。 但我不知道該怎么做(這聽起來很不愉快)。

我可能想要一個相同功能的memoized和non memoized版本然后我必須寫兩次相同的功能

是的,你需要。 函數內部對fact(n - 1)的遞歸調用只能引用一個fact函數 - 一個是memoized或一個是unmemoized函數。

因此,為避免代碼重復,您需要做的是使用Y組合器定義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);

也許我可以使用新函數替換對f的所有調用,其中我計算f並存儲結果,然后將值傳遞到通常的位置。

這實際上很容易做到,正如Bergi在評論中提到的那樣。

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

在我有限的經驗中,我們可以訪問JavaScript源代碼。 因此,我們可以嘗試為memoized函數生成新的源代碼。

 // 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) } 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM