one argument——another function——and returns a "memoized" version of that function. A "memoized" version of a function caches and returns the results of its call so that when it is called again with the same input, it doesn’t run its computation but instead returns the results from cache. Note that previous results should be retrievable in any order without re-computation.
foo = function (x) {
console.log("calculating!");
return x + 5;
}
var memoizedFoo = memoize(foo);
memoizedFoo(5);
// calculating!
// 10
memoizedFoo(5);
// 10 (notice how 'calculating!' is not printed this time)
memoizedFoo(10);
// calculating!
// 15
I assume the question is how to write memoize. You can store the first result for a given argument in a container, and return a function that will use that container if it can.
Here's an ES5 example, which only works for argument values that can be usefully converted to strings, like strings, numbers, booleans:
function memoize(f) {
// Storage for this function's results
var values = Object.create(null);
return function(arg) {
// Already have it?
if (Object.hasOwnProperty.call(values, arg)) {
// Yes, return it
return values[arg];
}
// No, get it, remember it, and return it
return values[arg] = f(arg);
};
}
var foo = function (x) {
console.log("calculating!");
return x + 5;
};
var memoizedFoo = memoize(foo);
console.log(memoizedFoo(5));
// calculating!
// 10
console.log(memoizedFoo(5));
// 10
console.log(memoizedFoo(10));
// calculating!
// 15
If you need to support other kinds of arguments, you need to use another structure, or a Map polyfill. Which brings us to...
...in ES2015+, we can use Map, which makes it work with a broader range of argument values:
function memoize(f) {
// Storage for this function's results
const values = new Map();
return function(arg) {
// Already have it?
if (values.has(arg)) {
// Yes, return it
return values.get(arg);
}
// No, get it, remember it, and return it
const value = f(arg);
values.set(arg, value);
return value;
};
}
const foo = function (x) {
console.log("calculating!");
return x.foo + 5;
};
const memoizedFoo = memoize(foo);
const firstArg = {foo:5};
console.log(memoizedFoo(firstArg));
// calculating!
// 10
console.log(memoizedFoo(firstArg));
// 10
const secondArg = {foo:10};
console.log(memoizedFoo(secondArg));
// calculating!
// 15
// Note that maps consider equivalent but distinct objects as different (of course),
// so we don't get the memoized reslt from
// `firstArg` here
const thirdArg = {foo:5};
console.log(memoizedFoo(secondArg));
// calculating!
// 10
Related
I'm solving an exercise that is intended to use closures. You must create a function that returns a function that will store a value and, when you reuse it, add the new value to the saved one.
const firstValue = myFunction(3);
const secondValue = firstValue(4);
// result => 7
this is the code that I'm using to practice closures:
function addNumbers(num) {
let storage = 0
let n = num
function adding(n) {
storage += n;
return storage
}
return adding(n)
}
let firstAttemp = addNumbers(4)
let secondAttemp = firstAttemp(3)
console.log(firstAttemp)
this throw an error "Uncaught TypeError: firstAttemp is not a function"
const addNumbers = (a) => (b) => a + b
It's called currying, more details here.
P.S.
If you want to use function syntax, it will look like this:
function addNumbers(a) {
return function (b) {
return a + b
}
}
As #skara stated in their comment, return adding(n) returns the result of calling adding instead of returning the function so that it may be called later with firstAttemp(3).
Unfortunately though it still doesn't work because you don't actually assign the value passed to addNumber to be added later.
function addNumbers(num) {
let storage = 0;
let n = num;
function adding(n) {
storage += n;
return storage;
}
return adding;
}
let firstAttemp = addNumbers(4);
let secondAttemp = firstAttemp(3);
console.log(firstAttemp);
console.log(secondAttemp); // 3! 😢
You don't actually need to manually save the value of num to a variable as it is captured in the closure arround adding that is being returned.
function addNumbers(num) {
function adding(n) {
return num + n;
return storage;
}
return adding;
}
let firstAttemp = addNumbers(4);
let secondAttemp = firstAttemp(3);
console.log(secondAttemp); // 7 👍🏻
I have the following code and I am trying to prevent the calculation inside of complexFunction in case same arguments are passed.
const complexFunction = (arg1, arg2) => {
/* complex calculation in here */
console.log('complexFunction');
return true;
};
// this is the method we need to implement
const memo=()=>{
}
const memoComplex = memo(complexFunction);
memoComplex(1, 2); // complex function should be executed
memoComplex(1, 2); // complex function should not be invoked again, instead the cached result should be returned
memoComplex(1, 3); // complex function should be executed
You can simply use an array of objects which holds the arguments to check if they've been used before, the code below demonstrates the use
const usedArgs = [];
const memoComplex = (arg1, arg2) => {
/* complex calculation in here */
const value = arg1 + arg2;
usedArgs.push({ arg1: arg1, arg2: arg2, data: value });
return value;
};
const memo = (arg1, arg2) => {
const exists = usedArgs.find(x => x.arg1 == arg1 && x.arg2 == arg2);
if (exists) {
console.log('loading from cache')
return exists.data;
} else return memoComplex(arg1, arg2);
}
console.log(memo(1, 2)); // complex function should be executed
console.log(memo(1, 2)); // complex function should not be invoked again, instead the cached result should be returned
console.log(memo(1, 3)); // complex function should be executed
You could save the arguments passed and the result in Session Storage and on method call, look if you have already saved that in the storage, if that the case you could get it and return the result. Otherwise you would execute your method as always
sessionStorage.setItem('clé', 'valeur');
const complexFunction = (arg1, arg2) => {
// Get item return null if not found
let potentialResult = sessionStorage.getItem("#1" + arg1.toString() + "#2" + arg2.toString());
if(potentialResult !== null) {
// If we have a result in session storage, that mean we have already done the calc
// so we return the result
return potentialResult;
} else {
// If we don't have a result, we just execute the function and save the result
/* complex calculation in here */
let result = "Your result"; // Assuming result is the result of your function
// We save the result to avoid future calculation
sessionStorage.setItem("#1" + arg1.toString() + "#2" + arg2.toString(), result);
console.log('complexFunction');
return true;
}
};
But as said in the comments, this is more a hint than a solution, so please adapt it to your use case
I am currently studying Javascript and am doing the underbar project(re-writing the _underbar library).
I have solved all of them but one, as I am stuck on _.memoize
This is my current code
_.memoize = function(func) {
var cache = {};
var slice = Array.prototype.slice;
return function() {
var args = slice.call(arguments);
if (args in cache) {
return cache[args];
} else {
return (cache[args] = func.apply(this, args));
}
}
};
This is the test case I am failing to pass
// Here, we wrap a dummy function in a spy. A spy is a wrapper function (much like _.memoize
// or _.once) that keeps track of interesting information about the function it's spying on;
// e.g. whether or not the function has been called.
it('should run the memoized function twice when given an array and then given a list of arguments', function() {
// Be careful how you are checking if a set of arguments has been passed in already
var spy = sinon.spy(function() { return 'Dummy output'; });
var memoSpy = _.memoize(spy);
memoSpy([1, 2, 3]);
expect(spy).to.have.been.calledOnce;
memoSpy(1, 2, 3);
expect(spy).to.have.been.calledTwice;
});
Or in words ' It should run the memoized function twice when given an array and then given a list of arguments'
I tried to change the args by checking if the first was a array and if not making all the arguments an array, but to no luck. I also tried to re write the code to hold a result and use Fibonacci to store the cache but it would run the function twice.
How would I resolve this test case?
Thanks
Using cache[args] doesn't make much sense because args is an array, and using bracket notation will convert it to a string. You could make an array of arguments arrays to their return values, then compare the arrays:
const _ = {};
_.memoize = function(func) {
const allArgs = [];
return function(...args) {
// Try to find matching arguments in allArgs
const result = allArgs.find(
item => item.args.length === args.length && item.args.every(
(arg, i) => args[i] === arg
)
);
// If a matching argument array was found, return the result:
if (result) return result.returnValue;
const returnValue = func.apply(this, args);
allArgs.push({ args, returnValue });
return returnValue;
}
};
const memoizedSum = _.memoize((...args) => {
console.log('called with', args);
return args.reduce((a, b) => a + b, 0);
});
console.log(memoizedSum(1, 2, 3));
console.log(memoizedSum(1, 2, 3));
console.log(memoizedSum(4, 5, 6));
Another option with less computational complexity would be to create a Map for each argument. For example, a call with arguments of 1, 2, 3 could result in a data structure of:
Map([[
1, { nested: Map([[
2, { nested: Map([[
3, { result: 6 }
]])}
]])}
]])
Then you'd find whether a nested result exists for the given arguments by recursively iterating through the Maps with .get for each argument, instead of iterating through all arguments the function has been called with so far. The code would be complicated, but it'd be more efficient.
Your function looks good, just one little detail. In your last return you should cache it first and then return it:
cache[args] = func.apply(this, args);
return cache[args];
I am having trouble printing the correct result in NodeJS, why isn't my code printing the strings in the correct way ? I feel like console.log is not called at all. Why do I get :
[Function]
[Function]
[Function]
[Function]
Expected result:
Tigrou (buddy of Spider) was here!
Spider (buddy of Tigrou) was also here!
Tigrou (buddy of Spider) are in a boat...
1 (buddy of 2)3
The code I thought would work:
function build_sentence(...args)
{
var i = 0
var s = ""
for (let arg of args)
{
if (i == 1)
{
i++
s += "(buddy of " + arg + ") "
}
else
{
s += arg + " "
i++
}
}
return s
}
function mycurry(build_sentence)
{
return function(...args)
{
if (!args)
{
return build_sentence();
}
else
{
return mycurry(build_sentence.bind(this, ...args))
}
}
}
const curried = mycurry(build_sentence);
console.log(curried("Tigrou")("Spider")(" was here!"))
console.log(curried("Spider")("Tigrou")(" was also here!"))
console.log(curried("Tigrou", "Spider", " are in a boat... "))
console.log(curried(1)(2, 3))
Here's a full working solution for you (except the string spacing)
const build_sentence_n_params = 3;
function build_sentence(...args)
{
var i = 0
var s = ""
for (let arg of args)
{
if (i == 1)
{
i++
s += "(buddy of " + arg + ") "
}
else
{
s += arg + " "
i++
}
}
return s
}
// A generic n-parameter curry helper, that returns fn return value
// after n-parameters given, and continues currying with more parameters.
// Note that in this configuration, the caller would also need
// to know how many parameters to give before a value is returned.
function ncurry(fn, n)
{
// Note that we have to return an outer function here, rather than just
// returning `curry` function directly, so `params` is unique for each
// initial call to `curried`.
return function(...args) {
// Initial arguments (note: we can use this array without copy, since
// rest params will always be a new array)
const params = args;
// This function contains the return logic without having to duplicate it below
function next() {
// If we have all params, call the given function, otherwise
// return the curry function again for more parameters
return !n || params.length >= n ? fn.call(null, ...params) : curry;
}
// The outer function is only called once, but this curry
// function will be called for each additional time.
function curry(...args) {
// Accumulate additional arguments
params.push(...args)
return next();
};
return next();
};
}
function mycurry(build_sentence)
{
// Call the generic n-parameter curry helper to generate
// a specific curry function for our purposes.
return ncurry(build_sentence, build_sentence_n_params);
}
const curried = mycurry(build_sentence);
console.log(curried("Tigrou")("Spider")(" was here!"))
console.log(curried("Spider")("Tigrou")(" was also here!"))
console.log(curried("Tigrou", "Spider", " are in a boat... "))
console.log(curried(1)(2, 3))
Your method invocations are quite confusing so I took the liberty to simplify your code.
If you run the following:
const curried = build_sentence("Tigrou", "Spider", " was here!");
console.log(curried);
You will get your desired output:
Tigrou (buddy of Spider) was here!
Why you are using the mycurry method is beyond my understanding.
When debugging your code, the sentence is already built on the first invocation, and what happens is that the subsequent invocations are not really invocations of the inner functions.
the main issue about our code is that !args is all the time false, as [] to boolean is also true.
you have to use !args.length. however you should add an extra call to your curried function usage like curried(1)(2, 3)().
the other approach is using comparison of curried function number of required params (build_sentence.length) and the number of params passed (args.length), but it's not working with spread scenario.
upd:
ex.1 - using Function.prototype.length property
const curry = fn =>
(...args) =>
fn.length <= args.length
? fn(...args)
: curry(fn.bind(this, ...args));
const func1 = (a, b, c) => `${a},${b},${c}`;
const func2 = (...args) => args.join(',');
console.log(curry(func1)(1,3)(4));
console.log(curry(func1)(1,3,4));
console.log(curry(func1)(1)(3)(4));
console.log(curry(func2)(1));
in this case currying will work fine for func1 (function that has enumerable number of arguments) because func1.length = 3. however for func2 - func2.length = 0, so (curry(func1)) will be executed after first call.
ex.2 - using number of arguments passed
const curry = fn =>
(...args) =>
!args.length
? fn()
: curry(fn.bind(this, ...args));
const func = (...args) => args.join(',');
console.log(curry(func)(1)(2,3,4)(5)());
in this case function currying will only return result of fn executing, when called with no arguments. however it will handle innumerable arguments of curried function properly.
I know that the purpose of memoize is to cache values so code can be run faster by not having to re-calculate the same answer everytime. My issue stems from returning a function (i think). The google chrome debugger isn't that useful for me here because everytime I try to run this memoize function, it just goes from the argus variable (on line 4 i believe) all the way down to the semi-colon. Furthermore, result always returns an empty object instead of storing a value in result.
I start by defining a function:
function add(a,b){
return a+b;
}
This is my attempt at the memoize function:
_.memoize = function(func) {
var result = {};
var flag = 0;
var argus = Array.prototype.slice.call(arguments)
return function() {
if(result[key] === arguments){
flag = 1
}
else if(flag = 0){
result[argus] = func.apply(this, argus);
}
return result[argus];
};
};
I'd call memoize by doing _.memoize(add(2,5)) but the result doesn't get stored in the result object.
Am I even close to getting this memoize function working properly? Any guidance you guys can give here would be appreciated.
The biggest point you're missing is that _.memoize is called on the function first, and it returns a new function. You are calling it on the result of a function call (which is the number 7 in this case).
In order to get it to work, you need to rearrange a few things.
Also note that it's not wise to try to use an array itself as the index to an object. One approach to get around that would be to convert the arguments array to JSON and use that as the index on the results object:
function add(a, b) {
console.log('Called add(' + a + ', ' + b + ')');
return a + b;
}
var _ = {};
_.memoize = function(func) {
var results = {};
return function() {
var args = Array.prototype.slice.call(arguments);
var key = JSON.stringify(args);
if (!(key in results)) {
results[key] = func.apply(this, args);
}
return results[key];
};
};
var madd = _.memoize(add);
console.log(madd(2, 4));
console.log(madd(9, 7));
console.log(madd(2, 4));