Function Cache
Всем привет!
Задача в том, что: 0) Создается некоторая функция, принимающая N аргументов; 1) Создается кэширующая функция, которая принимает вышеуказанную в качестве фунарга и дальше с возвращенным ей значением мы можем работать следующим образом: - если такая функция-обертка вызывается с новым набором аргументов, то выполняется оригинальная функция; - в противном случае, если вызов с такими аргументами уже был, то возвращается закэшированный результат, без вызова. Вот оригинальное описание: "If you are calculating complex things or execute time-consuming API calls, you sometimes want to cache the results. In this case we want you to create a function wrapper, which takes a function and caches its results depending on the arguments, that were applied to the function." Мой код: function cache(func) { var cached_func = func, cached_func_data = []; return function() { var args_len = arguments.length, i, j; if (!cached_func_data.length) { return callCachedFunc(arguments); } for (i = 0; i < args_len; i++) { for (j = 0; j < cached_func_data[i].args.length; j++) { if (cached_func_data[i].args[i] === arguments[i]) { continue; } return cached_func_data[i].res; } return callCachedFunc(arguments); } function callCachedFunc(arguments) { var cache_obj = {}; cache_obj.args = Array.prototype.slice.call(arguments); cache_obj.res = cached_func.apply(null, arguments); cached_func_data.push(cache_obj); return cache_obj.res; } } } Прошу провести ревью кода. Решение не полностью корректно, но я не могу пока что, к большому своему огорчению, найти ошибку. Спасибо. |
Вот пример использования:
var complexFunction = function(arg1, arg2) { /* complex calculation in here */ }; var cachedFunction = cache(complexFunction); cachedFunction('foo', 'bar'); // complex function should be executed cachedFunction('foo', 'bar'); // complex function should not be invoked again, instead the cached result should be returned cachedFunction('foo', 'baz'); // should be executed, because the method wasn't invoked before with these arguments Заранее благодарен за любые комментарии по делу. |
|
nerv_,
чушь собачья a=Object.create({a: 1}) b=Object.create({a: 2}) fu=function(){ var f=function(arg){return arg.a} return function(){alert(JSON.stringify(arguments)); return f(arguments[0])} }() alert(fu(a)) alert(fu(b)) // {"0":{}} // 1 // {"0":{}} // 2 Закешировали, ага. |
я вчера было уже хотел выложить вариант с двумя массивами, но вариант nerv_ c cache[stamp] дал понять, где настоящий скил :)
в качестве отработки при помощи FineReader-a извлёк код nerv_ (шутка :D ) function f(a, b, c) { return a * b + c; }; function cacheFn(fn) { var cache = {}; return function () { var stamp = JSON.stringify(arguments); console.log(cache); if (!(stamp in cache)) { cache[stamp] = fn.apply(this, arguments); console.log("call: " + cache[stamp]); } console.log("cache: " + cache[stamp] + "\n\n"); return cache[stamp]; } } var cacheF = cacheFn(f); cacheF(2, 2, "asdf"); cacheF(2, 2, "asdf"); cacheF(2, 3, "asdf"); cacheF(2, 3, "asdf"); |
if (!cache.hasOwnProperty(stamp)) {/*... */} а так не лучше будет? |
Цитата:
|
Цитата:
Общепринятое название функции memorize (у меня на скрине опечатка, скопипастил название у lowdash), от memorization https://lodash.com/docs#memoize Читайте Стоян Стефанов - JavaScript. Шаблоны, там все есть Ну, и, разумеется, такая функция принимает только примитивы + json like objects. Если очень хочется пихать объекты с циклическими ссылками, то массивы. |
Цитата:
|
danik.js, значит в книжке опечатка)
https://yadi.sk/i/IlMM8krQdCoLf https://ru.wikipedia.org/wiki/%D0%9C...%D 0%B8%D1%8F или в википедии опечатка, т.к. запоминание согласно переводчику гугла это memorization от слова memory (насколько я понимаю) https://translate.google.ru/#en/ru/memorization |
Цитата:
|
krutoy, я рад, что ты открыл для себя функцию Object.create(). Жаль, что только ее :)
Цитата:
иди почитай что такое JSON |
nerv_,
Я ничего не понял. Что не так в моем коде? JSON.stringify берет только верхнюю (ближний хеш) часть объекта, которая объектом, безусловно не является. И ты так и не ответил, причем тут циклические ссылки. А Object.create тут не при чем, он взят в качестве примера. с юбым прототипом и их цепочкой, будет то же самое. |
Цитата:
идём от очевидного, где-то надо хранить - в переменной, массиве, объекте, до ключа объекта не догадались :no: |
Цитата:
|
Цитата:
|
bes,
Это идет, видимо, от общего, превратного понимания JS, в массе. JS является чистейшим ООП языком, в стиле смолтока или селфа, то есть, ООП в хорошем смысле этого слова. В подобных семантиках, надо рассматривать ключ объекта, как предикат, определяющий, может ли объект принять данное сообщение, "знает" ли он данное определение. |
bes,
Но, в целом, ф-ция -- говно, см мои комменты выше. Ее написал не "гений", как ты выразился, а, мягко говоря, чел далекий от понимания JS. |
Цитата:
Цитата:
Array.prototype.slice.call(arguments).toString() |
Цитата:
f=function(arg){ return [].slice.call(arguments).toString() } o1=Object.create({a: 1}) o2=Object.create({a: 2}) alert(f(o1)) alert(f(o2)) // [object Object] // [object Object] что мы будем сравнивать? А если так f=function(arg){ return [].slice.call(arguments[0]).toString() } o1=Object.create({a: 1}) o2=Object.create({a: 2}) alert(f(o1)) alert(f(o2)) вернем 2 пустые строки, в даном случае, то есть, те же яйца сбоку. |
krutoy, возьми мой пример и покажи, что не так
|
Цитата:
cache[stamp] = fn.apply(this, arguments); в ключ записывается не объект, а лишь текстовое представление его "верхушки", первого хеша. В дальнейшем, когда ты делаешь проверку, у тебя следующий "отпечаток", при сравнении строк даст true, хотя объекты, записанные туда, могут быть другими. o1={a: 1} o2={a: 1} textRepresent1=JSON.stringify(o1) textRepresent2=JSON.stringify(o2) console.log( o1===o2, textRepresent1===textRepresent2 ) // false true У тебя из кеша будет извлечено не то, что ты ожидаешь. |
Цитата:
Цитата:
бахать "сикретные свойства" примитивам для того , чтобы их "опознать" - это уже не мемоизация, а геморизация. ты взял этот способ из книги Фленегана?. (да, мы все его читали) |
Цитата:
Цитата:
Цитата:
|
Цитата:
и я не совсем понял, что ты имеешь в виду. Если ты расшитяешь именно примитив, то ты делаешь это так String.prototype.foo=1 и, в этом случае, у тебя все строки будут иметь св-во foo. Если же ты делаешь foo=new String("foo") foo.foo=1 alert(foo.foo) // 1 Ты расширяешь не примитив, а конкретный строковый объект. |
Цитата:
Цитата:
function f(obj) { return obj.a = 2; }; function cacheFn(fn) { var cache = {}; return function () { var stamp = JSON.stringify(arguments); console.log(cache); if (!(stamp in cache)) { cache[stamp] = fn.apply(this, arguments); console.log("call: " + cache[stamp]); } console.log("cache: " + cache[stamp] + "\n\n"); return cache[stamp]; } } var cacheF = cacheFn(f); var o1 = {a: 1}; var o2 = {a: 1}; cacheF(o1); cacheF(o2); console.log("o1:"); console.log(o1); console.log("o2:"); console.log(o2); |
Цитата:
Цитата:
|
Цитата:
|
bes,
Нет, ты опять не понял. Задача твоей кэширующей ф-ции в том, чтобы "понять", была ли данная функция (которая в замыкании), уже вызвана с данными аргументами. Именно этого она не делает, точней делает, но только с примитивами. Она не может по этой "псевдосигнатуре" объекта определить, был ли данный объект уже в качестве аргумента, или нет, потому что разные объекты могут давать одинаковый отпечаток. o1=Object.create({b: 2}) o2=Object.create({b: 3}) o1.a=1 o2.a=1 f=function(o){return o.a+o.b} function cacheFn(fn) { var cache = {}; return function () { var stamp = JSON.stringify(arguments); console.log(cache); if (!(stamp in cache)) { cache[stamp] = fn.apply(this, arguments); console.log("call: " + cache[stamp]); } console.log("cache: " + cache[stamp] + "\n\n"); return cache[stamp]; } } theCache=cacheFn(f) alert(theCache(o1)) alert(theCache(o2)) // {} // call: 3 // cache: 3 // // // 3 // { '{"0":{"a":1}}': 3 } // cache: 3 // // // 3 // |
Цитата:
|
krutoy, не вижу смысла с тобой спорить. Ты уже показал себя на форуме :)
bes, мой полный ответ был здесь. По сути на этом тема должна быть исчерпана. Даже если в функцию передается объект с параметрами, то: 1. либо он чистый [new Object()] 2. либо, в большинстве случаев его следует интерпретировать как чистый. Например: foo({bar: 1}); foo({bar: 1}); // какой смысл от того, что это другой объект, если это всего лишь "объект с параметрами"?т.е. просто глупо тут разницу показывать или нет смысла хранить в массиве ссылки на объекты (если реализация через массивы) На все var fn = function() { console.log(1); }; var foo = memoize(fn); foo({a:1}); // 1 foo({a:1}); // var bar = memoize(fn, function() { return Date.now(); }); bar({a:1}); // 1 bar({a:1}); // 1 /** * @param {Function} fn * @param {Function} [identify] */ function memoize(fn, identify) { var cache = {}; var getKey = identify || JSON.stringify; return function() { var key = getKey(arguments); if (!cache.hasOwnProperty(key)) { cache[key] = fn.apply(this, arguments); } return cache[key]; }; } |
Цитата:
|
Цитата:
в JS с функциями все в порядке, кроме того, что они медленные. учитывая моду на ФП (баззворд "функциональщина, йо"), в стандарте ES >= 7 я ожидаю появление плюх для комфортного написания кода в этой парадигме. кстати, если ты так против ФП и иммутабельности - глянь React, Morearty и ClojureScript. это всё не спроста появилось. ... и не спроста оно всё такое "революционное" лечись от ООП головного мозга :victory: (написано как троллинг, но сим не является) Цитата:
Цитата:
Цитата:
|
Цитата:
(function f(arg){return foo})(function_with_long_calculation()) Если ты знаешь, что функция function_with_long_calculation чистая, ты можешь редуцировать эту строку до foo в компилтайме. И при таком раскладе, бредореализация кэширования нерва действительно могла бы работать. В JS это все ни к чему, это экстремально динамичный язык, тут делается упор на выразительность и мозги программиста. А то что ты приводишь, правильно, это мода, когда многие недоумки слышали звон, да не знают где он, но зато знают, что это модно, поэтому пишут ахинею. В принципе, в JS наверняка код, написанный в ФП-стиле оптимизируется лучше, но это не значит, что надо на это дрочить постоянно. |
Цитата:
Про примитивы -- это еще вопрос, поскольку в JS любой примитив может быть вызван в объектном контексте. Насчет массивов -- не п*ди, массивы -- это 100% объекты. И наконец, 3-й раз спрашиваю, при чем тут циклические ссылки? |
Цитата:
Цитата:
Цитата:
Цитата:
Цитата:
function f(obj) { return obj.a + obj.b; } function isSame(args1, args2) { var len1 = args1.length; var len2 = args2.length; if ( len1 != len2 ) return false; for (var i = 0; i < len1; i++) { if ( args1[i] === args2[i] ) continue; return false; } return true; } function cacheFn(fn) { var cache = []; return function () { var len = cache.length; for (var i = 0; i < len; i++) { if ( isSame(cache[i].args, arguments) ) { console.log("cache: " + cache[i].res + "\n\n"); return cache[i].res; } } cache.push( {args: arguments, res: fn.apply(this, arguments)} ); console.log("call: " + cache[len].res + "\n\n") return cache[len].res; } } var cacheF = cacheFn(f); var o1 = Object.create({b: 2}) var o2 = Object.create({b: 3}) o1.a = 1 o2.a = 1 cacheF(o1); cacheF(o1); cacheF(o2); cacheF(o2); |
Цитата:
А ты и дальше не обращай, поскольку это тоже полуп*больство, см мой пост выше. http://javascript.ru/forum/misc/5217...tml#post345384 |
Цитата:
function f(obj) { return obj.a + obj.b; } function isSame(args1, args2) { var len1 = args1.length; var len2 = args2.length; if ( len1 != len2 ) return false; for (var i = 0; i < len1; i++) { if ( args1[i] === args2[i] ) continue; return false; } return true; } function cacheFn(fn) { var cache = []; return function () { var len = cache.length; for (var i = 0; i < len; i++) { if ( isSame(cache[i].args, arguments) ) { console.log("cache: " + cache[i].res + "\n\n"); return cache[i].res; } } cache.push( {args: arguments, res: fn.apply(this, arguments)} ); console.log("call: " + cache[len].res + "\n\n") return cache[len].res; } } var cacheF = cacheFn(f); var o1 = Object.create({b: 2}) var o2 = Object.create({b: 3}) o1.a = 1 o2.a = 1 cacheF(o1); cacheF(o1); cacheF(o2); cacheF(o2); o2.a = 10 cacheF(o2); // call: 3 // // // cache: 3 // // // call: 4 // // // cache: 4 // // // cache: 4 И не мучайся. В JS в общем случае, кэширование невозможно. |
Цитата:
function f(obj) { return obj.a + obj.b; } function isSame(args1, args2) { var len1 = args1.length; var len2 = args2.length; if ( len1 != len2 ) return false; for (var i = 0; i < len1; i++) { if ( args1[i] === args2[i] ) continue; return false; } return true; } function cacheFn(fn) { var cache = []; return function () { var len = cache.length; for (var i = 0; i < len; i++) { if ( isSame(cache[i].args, arguments) && JSON.stringify(arguments) == cache[i].strArg) { console.log("cache: " + cache[i].res + "\n\n"); return cache[i].res; } } cache.push( {args: arguments, strArg: JSON.stringify(arguments), res: fn.apply(this, arguments)} ); console.log("call: " + cache[len].res + "\n\n") return cache[len].res; } } var cacheF = cacheFn(f); var o1 = Object.create({b: 2}) var o2 = Object.create({b: 3}) o1.a = 1 o2.a = 1 //cacheF(o1); //cacheF(o1); cacheF(o2); cacheF(o2); console.log("----"); o2.a = 10 cacheF(o2); cacheF(o2); console.log("****"); o2.b = 10; cacheF(o2); cacheF(o2); есть ещё примеры? :) |
Цитата:
Но это не значит, что мы не можем кэшировать какие то частные случаи. Например, можно часть объектов сделать неизмняемыми. чтоб они работали как простые хэши, можно работать с примитивами, не вызывая их в объектном контексте, и т.п. Это сделать легко. |
Часовой пояс GMT +3, время: 10:25. |