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, время: 17:32. |