I've been toyin with concept of memoization and there are obviously different implementations of it. I put this together and it seems to work fine:
Function.prototype.memoized = function(a) {
debugger
if (typeof cache === "undefined") cache = [];
if (cache[a]) {
return cache[a];
} else {
cache[a] = this(a);
return cache[a];
}
}
Function.prototype.memoize=function() {
t=this;
return function() {
// Resig seems to use this:
return t.memoized.apply(t,arguments);
// why not simple this:
//return t.memoized(arguments[0]);
}
}
myTest = (function fibonacci(n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}).memoize();
console.log(myTest(2));
My question is however why in some implementations I see
return t.memoized.apply(t,arguments); rather than simple return t.memoized(arguments[0]); inside of proptotype.memoize? I cannot see any advantage apart from passing multiple arguments which are not used anyway. I there any advantage to apply?
EDIT:
Updated the code and I believe this takes care of major problems so cache as global on window (what was I thinking??) and recursive fibonacci not memoizing calls to itself. Are there any other major issue with my implementation?
Function.prototype.memoized = function(a) {
if (typeof this.cache === "undefined") this.cache = [];
if (this.cache[a]) {
return this.cache[a];
} else {
this.cache[a] = this(a);
return this.cache[a];
}
}
Function.prototype.memoize=function() {
t=this;
return function() {
//return t.memoized.apply(t,arguments);
return t.memoized(arguments[0]);
}
}
myTest = (function fibonacci(n) {
//return a * 3;
return n < 2 ? n : fibonacci.memoized(n - 1) + fibonacci.memoized(n - 2);
}).memoize();
console.log(myTest(2));
BTW this is learning experience for me and doing it purely for fun, it's not assignment or anything like that uni related.
In general, JavaScript functions are variadic, which means that you would need to pay attention to all of them actually. Some memoize implementations do this by using JSON.stringify(arguments) for the cache key.
In this case, it does not make any sense, since the memoized method does only use a single argument as well.
However, there are more serious bugs in your code than this little inaccuracy. cache is a global variable, while it should be bound to the specific memoized function. Also, your fib implementation is not memoized in the recursive call, where it actually is the most important.
The edited version looks good (except for the global t variable). In personal, I would have shortened/simplified the code a little bit however:
Function.prototype.memoize=function() {
var t = this;
this.cache = [];
return function(a) {
var cache = t.cache;
if (typeof cache[a] == "undefined")
cache[a] = t(a);
return cache[a];
};
}
var fibonacci = (function (n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}).memoize();
console.log(fibonacci(2));
Related
I am stumped with this memoize problem. I need to create a function that will check to see if a value has already been calculated for a given argument, return the previous result, or run the calculation and return that value.
I have spent hours on this and while I am new to JS. I cannot get my head around how to do this. I cannot use any built in functions and would really like to understand what I need to do.
Here is what I have so far, which is so wrong it feels like pseudo-code at this point. I have searched existing memoize questions out here but I cannot seem to make any solution work yet. Any help is much appreciated.
myMemoizeFunc = function(passedFunc) {
var firstRun = passedFunc;
function check(passedFunc){
if(firstRun === undefined){
return passedFunc;
}else{return firstRun;}
}
};
Sorry, I should have been more clear. Here are my specific requirements:
myMemoizeFunc must return a function that will check if the calculation has already been calculated for the given arg and return that val if possible. The passedFunc is a function that holds the result of a calculation.
I understand this may seem like a duplicate, but I am marking as not so, as I am having some serious difficulty understanding what I should do here, and need further help than is given in other posts.
This is what my thought process is bringing me towards but again, I am way off.
myMemoizeFunc = function(passedFunc) {
var allValues = [];
return function(){
for(var i = 0; i < myValues.length; i++){
if(myValues[i] === passedFunc){
return i;
}
else{
myValues.push(passedFunc);
return passedFunc;
}
}
}
};
I should not be returning i or passedFunc here, but what else could I do within the if/else while checking for a value? I have been looking at this problem for so long, I am starting to implement code that is ridiculous and need some fresh advice.
I think the main trick for this is to make an object that stores arguments that have been passed in before as keys with the result of the function as the value.
For memoizing functions of a single argument, I would implement it like so:
var myMemoizeFunc = function (passedFunc) {
var cache = {};
return function (x) {
if (x in cache) return cache[x];
return cache[x] = passedFunc(x);
};
};
Then you could use this to memoize any function that takes a single argument, say for example, a recursive function for calculating factorials:
var factorial = myMemoizeFunc(function(n) {
if(n < 2) return 1;
return n * factorial(n-1);
});
Consider this an extension on the answer of Peter Olson.
For a variable number of arguments you could use something like this.
Note: This example is not optimal if you intent to pass complex arguments (arrays, objects, functions). Be sure to read further and not copy/paste blindly.
function memo(fn) {
const cache = {};
function get(args) {
let node = cache;
for (const arg of args) {
if (!("next" in node)) node.next = new Map();
if (!node.next.has(arg)) node.next.set(arg, {});
node = node.next.get(arg);
}
return node;
}
return function (...args) {
const cache = get(args);
if ("item" in cache) return cache.item;
cache.item = fn(...args);
return cache.item;
}
}
This builds the following cache tree structure:
const memoizedFn = memo(fn);
memoizedFn();
memoizedFn(1);
memoizedFn(1, 2);
memoizedFn(2, 1);
cache = {
item: fn(),
next: Map{ // <- Map contents depicted as object
1: {
item: fn(1),
next: Map{
2: { item: fn(1, 2) }
}
},
2: {
next: Map{
1: { item: fn(2, 1) }
}
}
}
}
This solution leaks memory when passing complex arguments (arrays, object, functions) that are no longer referenced afterwards.
memoizedFn({ a: 1 })
Because { a: 1 } is not referenced after the memoizedFn call it would normally be garbage collected. However now it can't be because cache still holds a reference. It can only be garbage collected once memoizedFn itself is garbage collected.
I showed the above first because it shows the base concept and demonstrates the cache structure in a somewhat simple form. To clean up cache that would normally be garbage collected we should use a WeakMap instead of a Map for complex objects.
For those unfamiliar with WeakMap, the keys are a "weak" reference. This means that the keys do not count towards active references towards an object. Once an object is no longer referenced (not counting weak references) it will be garbage collected. This will in turn remove the key/value pair from the WeakMap instance.
const memo = (function () {
const primitives = new Set([
"undefined",
"boolean",
"number",
"bigint",
"string",
"symbol"
]);
function typeOf(item) {
const type = typeof item;
if (primitives.has(type)) return "primitive";
return item === null ? "primitive" : "complex";
}
const map = {
"primitive": Map,
"complex": WeakMap
};
return function (fn) {
const cache = {};
function get(args) {
let node = cache;
for (const arg of args) {
const type = typeOf(arg);
if (!(type in node)) node[type] = new map[type];
if (!node[type].has(arg)) node[type].set(arg, {});
node = node[type].get(arg);
}
return node;
}
return function (...args) {
const cache = get(args);
if ("item" in cache) return cache.item;
cache.item = fn(...args);
return cache.item;
}
}
})();
const fib = memo((n) => {
console.log("fib called with", n);
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib(n - 2);
});
// heavy operation with complex object
const heavyFn = memo((obj) => {
console.log("heavyFn called with", obj);
// heavy operation
return obj.value * 2;
});
// multiple complex arguments
const map = memo((iterable, mapFn) => {
console.log("map called with", iterable, mapFn);
const result = [];
for (const item of iterable) result.push(mapFn(item));
return result;
});
console.log("### simple argument demonstration ###");
console.log("fib(3)", "//=>", fib(3));
console.log("fib(6)", "//=>", fib(6));
console.log("fib(5)", "//=>", fib(5));
console.log("### exlanation of when cache is garbage collected ###");
(function () {
const item = { value: 7 };
// item stays in memo cache until it is garbade collected
console.log("heavyFn(item)", "//=>", heavyFn(item));
console.log("heavyFn(item)", "//=>", heavyFn(item));
// Does not use the cached item. Although the object has the same contents
// it is a different instance, so not considdered the same.
console.log("heavyFn({ value: 7 })", "//=>", heavyFn({ value: 7 }));
// { value: 7 } is garbade collected (and removed from the memo cache)
})();
// item is garbade collected (and removed from memo cache) it is no longer in scope
console.log("### multiple complex arguments demonstration ###");
console.log("map([1], n => n * 2)", "//=>", map([1], n => n * 2));
// Does not use cache. Although the array and function have the same contents
// they are new instances, so not considdered the same.
console.log("map([1], n => n * 2)", "//=>", map([1], n => n * 2));
const ns = [1, 2];
const double = n => n * 2;
console.log("map(ns, double)", "//=>", map(ns, double));
// Does use cache, same instances are passed.
console.log("map(ns, double)", "//=>", map(ns, double));
// Does use cache, same instances are passed.
ns.push(3);
console.log("mutated ns", ns);
console.log("map(ns, double)", "//=>", map(ns, double));
The structure stays essentially the same, but depending on the type of the argument it will look in either the primitive: Map{} or complex: WeakMap{} object.
const memoizedFn = memo(fn);
memoizedFn();
memoizedFn(1);
memoizedFn(1, 2);
memoizedFn({ value: 2 }, 1);
cache = {
item: fn(),
primitive: Map{
1: {
item: fn(1),
primitive: Map{
2: { item: fn(1, 2) }
}
}
},
complex: WeakMap{
{ value: 2 }: { // <- cleared if { value: 2 } is garbage collected
primitive: Map{
1: { item: fn({ value: 2 }, 1) }
}
}
}
}
This solution does not memoize any errors thrown. Arguments are considered equal based on Map key equality. If you also need to memoize any errors thrown I hope that this answer gave you the building blocks to do so.
There are a number of memoization libraries available. Doing memoization efficiently is not as straight forward as it seems. I suggest a library be used. Two of the fastest are:
https://github.com/anywhichway/iMemoized
https://github.com/planttheidea/moize
See here for a comprehensive(-ish) list of memoization libraries: https://stackoverflow.com/a/61402805/2441655
Just for fun, I'm looking for a way to create a function, array, with the following behavior:
array() // []
array(2) // ugly function thing
array(2)() // [2]
array(2)(3)() // [2,3] etc
The closest I can come is
function array(x) {
if (x == null)
return []
return function() {
// same as above?!
// I don't want some inelegant solution involving a lot of additional parameters
}
}
Is there a way to do this in ECMA5? If not, prove that the syntax can't accomodate such a function.
Yes, "same as above". This is solved by a "recursive"1 call:
function makeArrayAppender(arr) {
return function array() {
var args = Array.prototype.slice.call(arguments);
if (!args.length)
return arr;
else
return makeArrayAppender(arr.concat(args));
};
}
var array = makeArrayAppender([]);
1: As the function is called from the returned "thunk" function, not from the call itself, it's not really recursive. It's more like a tail-call-optimised function, being invoked manually in-a-row without filling the stack
I think this should do exactly what you are looking for. The self-executing function scopes off r and rr, which are basically static variables using this implementation. Of course, you need to reset r after assigning it to rr, so you can return the Array when ra has an undefined argument, which then stops the recursive behavior.
var array = (function(){
var r = [], rr;
function ra(a){
if(a === undefined){
rr = r; r = []
return rr;
}
else{
r.push(a);
return ra;
}
}
return ra;
})();
console.log(array()); console.log(array(5)()); console.log(array());
console.log(array(7)(2)());
I have a few places in my code that are very similar to this snippet:
tag_iter = hold_tags_el.firstChild;
do {
if (tag_iter === null) {
hold_tags_el.appendChild(paragraph_el);
break;
}
if (par_el.innerHTML < tag_iter.innerHTML) {
hold_tags_el.insertBefore(paragraph_el, tag_iter);
break;
}
if (tag_iter === hold_tags_el.lastChild) {
NS.insertAfter(tag_iter, paragraph_el);
break;
}
tag_iter = tag_iter.nextSibling;
} while (tag_iter !== null);
This can be abstracted to:
tag_iter = ref_el.firstChild;
do {
// loop logic
tag_iter = tag_iter.nextSibling;
} while (tag_iter !== null);
In a function form this would look like:
The Call:
eachChild(par_el, function (tag_iter, par_el) {
// loop logic
});
The Definition:
NS.eachChild = function (par_el, func, context) {
var iter_el = par_el.firstChild,
result;
do {
result = func.call(context, iter_el, par_el);
if (result) {
break;
}
iter_el = iter_el.nextSibling;
} while (iter_el !== null);
}
Is there a library that implements this pattern / idiom?
What improvements can be made to eachChild?
Are there any errors in eachChild?
Applying the idiom we have:
Snippet A
NS.eachChild(el, function(tag_iter, par_el){
// first
if (tag_iter === null) {
par_el.appendChild(paragraph_el);
return true;
}
// middle
if (par_el.innerHTML < tag_iter.innerHTML) {
par_el.insertBefore(paragraph_el, tag_iter);
return true;
}
// last
if (tag_iter === hold_tags_el.lastChild) {
par_el.appendChild(paragraph_el);
return true;
}
});
What improvements can be made?
Many. Your snippet with its do-while loop and the many breaks is overly complicated and hard to understand. It can be simplified to
var tag_iter = hold_tags_el.firstChild,
search = par_el.innerHTML;
while (tag_iter !== null && search >= tag_iter.innerHTML)
tag_iter = tag_iter.nextSibling;
hold_tags_el.insertBefore(paragraph_el, tag_iter);
Notice that insertBefore with null as second argument, insertAfter(lastChild) and appendChild do exactly the same thing.
With that simplification, you don't need that eachChild function any more. But maybe a little different one:
NS.findChild = function(parent, condition) {
var child = parent.firstChild;
for (var i=0; child!==null && condition(child, i); i++)
child = child.nextSibling;
return child;
};
// then simply:
var el = NS.findChild(hold_tags_el, function(tag_iter) {
return tag_iter.innerHTML < par_el.innerHTML;
});
hold_tags_el.insertBefore(paragraph_el, el);
Is there a library that implements this pattern / idiom?
I don't know any. But there are many libs with generic iterator methods (some of them with break functionality) that can easily be applied on childNodes collections.
Are there any errors in eachChild?
It calls the callback even when there is no firstChild (with null as argument). That's at least unconventional, if not wrong - not what you would expect from an iteration. If you think to need it, this should better be made a separate case (a separate callback); otherwise it requires an extra condition in the callback. However in the given usecase you do not need it, as that is a search - see the findChild function above - where eachChild is inappropriate.
What improvements can be made to eachChild?
Additionally to parEl maybe a counter argument might be nice - check the signature of the standard forEach Array method.
I'm trying to follow the rule and avoid repeating the same code.
I have this single function but depending on the input I want it to either return an array of objects or an object (not an array of just one object)
e.g.(the actual function is much longer and more elaborate than this one obviously, there are just the last few lines after a much longer calculation)
function (nameParameter, ageParameter, inputType)
{
if (inputType === "asObject")
{
var x = {};
x.name = nameParameter;
x.age = ageParameter;
return x;
}
else if (inputType === "asArray")
{
var y = [];
y.push(nameParameter);
y.push(ageParameter);
return y;
}
};
Is this possible and if so is it good practice? Is there some other way around it?
Otherwise I'll have to create two distinct function with almost the exact same code.
Don't do this. Implement one version and add a wrapper function that converts the the other format you may want. That way the caller always gets consistent behaviour, and theres still no code duplication.
function asObject(nameParameter, ageParameter)
{
//Lots of work here.
var x = {};
x.name = nameParameter;
x.age = ageParameter;
return x;
};
function asArray(nameParameter, ageParameter)
{
//Just defer to the other version and repack its response.
var o = asObject(nameParameter, ageParameter);
var y = [o.nameParameter,o.ageParameter ];
return y;
}
You can simplify your code by declaring the object and array with the values already set, but in my opinion if you have this strict type of coding it is not necessary to keep this function... Anyway, here is a simplified version:
function (nameParameter, ageParameter, inputType) {
var ret;
if (inputType === "asObject") {
ret = {
name: nameParameter,
age: ageParameter
};
} else if (inputType === "asArray") {
ret = [nameParameter, ageParameter];
}
return ret;
};
I left it without name and with a semicolon at the end because I guess it has been declared through a variable.
Yes; that will work fine.
Javascript is not strongly-typed; functions can return whatever they want, whenever they want.
if ( typeof inputType == 'object') {
//object part of code
} else {
//array part of code
}
I'd like be able to call a function like item_edit.say hello passed as a string on the window object (like the last line of the following):
var arc={ view: { item_edit: {} } };
arc.view.item_edit={
say_hello: function(){
alert('hello there');
}
}
var f_name='say_hello';
var g_name='item_edit.say_hello';
var str=window.arc.view.item_edit[f_name](); // <- this works
var str2=window.arc.view[g_name](); // <- this is what I'm interested in; curently doesn't work
any ideas on how to get this to work?
thx in advance
edit #1
I guess I should add that probably don't want to be doing eval although the more I look at it, that might be what makes sense (and is in fact what eval was made to do).
Sure. The Google closure library does something like this in its goog.provide function when not optimized by the compiler.
function callDotted(obj, path, args) {
var parts = path ? path.split('.') : [];
var i, n = parts.length;
for (i = 0; i < n - 1; ++i) {
obj = obj[parts[i]];
}
var fn = i < n ? obj[parts[i]] : obj;
return fn.apply(obj, args);
}
and then on browsers where Date.now returns the current timestamp,
callDotted(window, 'Date.now', [])
returns the current timestamp.
Here's one way using .reduce().
var str2 = g_name.split('.').reduce(function(obj, key) {
return obj[key];
}, window.arc.view);
You'll need to shim it for older browsers, and introduce safety checks if you want.
If you do this a lot, I'd add the function to your library so you can reuse it.
function keyToObj(obj, key) {
return obj[key];
}
Then use it like this:
var str2 = g_name.split('.').reduce(keyToObj, window.arc.view);
As #MikeSamuel pointed out, there's an issue with the this value of the executed function when using this approach.
To resolve this, we could make another version that's suited specifically for method invocations.
function keyToMethod(obj, key, i, arr) {
return i === arr.length - 1 && typeof obj[key] === "function"
? function() {
return obj[key].apply(obj, arguments);
}
: obj[key];
}
Now our function returns a function that invokes the method from the proper object.
var str2 = g_name.split('.').reduce(keyToMethod, window.arc.view)();
We could further enhance the returned function to check to see if the this value is the default value, and use the provided value if not.
How about this:
var str2 = eval('window.arc.view.' + g_name + '()');