Writing _.Memoize from underbar in Javascript - javascript

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];

Related

Memoize callback function

I was trying this memoization on
function getData(n, m, callback){
callback(n+m);
};
let memoizedData = memoize(getData);
console.log(memoizedData(5, 4, (result)=>{})) // 9;
I want to cache the result i,e, 9 so whenever next call happens it takes from cache.
I understand memoization if getData would have return n+m instead of putting to the callback.
Any help will be appreciated.
Edits: Want to implement this via vanillaJs.
Edited the above question for more clarity
function getData(n, m, callback){
callback(n+m);
};
let memoizedData = memoize(getData);
memoizedData(5, 4, (result)=>{console.log(result)}) // 9;
memoizedData(5, 4, (result)=>{console.log(result)}) // 9 - returned from cache
If I understand correctly, you are asking for an implementation of memoize. As to your console.log: this is strange, since the result is not returned, but passed as an argument to a callback. So you should actually print within that callback. You could even pass console.log as callback.
For the implementation of memoize, I will assume that the function to memoize will always have a callback as last parameter.
First, memoize should return a function which accept the same arguments as the function that is memoized.
The idea is to maintain a Map, keyed by the arguments that are passed to the function (excluding the callback argument), and with the corresponding result. One way to build the key is to form the JSON representation of the arguments.
When memoize is called, and the arguments form a key that is already present in the map, then call the callback with the corresponding value from the map. If not present, then call the function with the same set of arguments, except for the callback. Let the callback be a function that stores the result in the map, and then passes the result on to the callback that was passed to memoize.
Here is how you could write it:
function memoize(func) {
let map = new Map; // for memoization
return function(...args) {
let callback = args.pop(); // last argument must be a function
let key = JSON.stringify(args);
let result = map.get(key);
if (result !== undefined) {
console.log(`getting result from map.get('${key}')`);
return callback(result);
}
console.log(`calling ${func.name}(${args.join(", ")}, callback)`);
func(...args, result => {
console.log(`writing result with map.set('${key}', ${result})`);
map.set(key, result);
callback(result);
});
}
}
function getData(n, m, callback){
callback(n+m);
}
let memoizedData = memoize(getData);
memoizedData(5, 4, console.log) // 9 calling getData;
memoizedData(5, 4, console.log) // 9 from memory;
Note that this:
memoizedData(5, 4, console.log)
...is just "short" for:
memoizedData(5, 4, (result) => console.log(result))
The only difference is that there is an extra wrapper function around console.log, but practically there is no real difference.
If your asking how to implement memoization you may implement it like this
import { useMemo, useCallback } from 'react';
export default function Index() {
const getData = useCallback((n, m, callback) => callback(n + m), []);
let memoizedData = useMemo(() => getData(5, 4, (result) => result), [
getData,
]);
console.log(memoizedData);
return null;
}

NodeJS output is function instead of printed strings

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.

Passing function to map

I was trying to understand this simple code snippet
function fnWrapper (fn) {
return function printResults (...args) {
const results = args.map(fn)
console.log(results)
}
}
const squarePrinter = fnWrapper(x => x*x)
const cubePrinter = fnWrapper(x => x*x*x)
const nums = [1, 2]
squarePrinter(1, 2)
cubePrinter(1, 2)
While, almost everything make sense, I am unable to comprehend this part args.map(fn)
i.e map should give an element but how are we able to pass our fn function and getting the desired result directly
As per map documentation it takes callback function as parameter and returns A new array with each element being the result of the callback function.
For example
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
So in your case
squarePrinter(1, 2)
const results = args.map(fn)
is equivalent to
const results = [1, 2].map(function(x) {
return x*x;
})
map function takes a callback function, which gets executed with the array elements on each iteration.
You might think of the above code like
args.map((x) => x*x);
which is nothing but the fn function provided as argument to the fnWrapper
A typical implementation of map would be something like
Array.prototype.map = function (callback) {
const resultArray = [];
for (let index = 0; index < this.length; index++) {
resultArray.push(callback(this[index], index, this));
}
return resultArray;
}
args.map(fn) will take each argument and perform the operations on those arguments to create a new list.
More Like
squarePrinter(1, 2) => [1,2].map(x => x*x)
Map is a higher order function which can take callback function as argument, in simple form map() implementation would seem like this:
Array.prototype.myMap = function(callback) {
arr = [];
for (let i = 0; i < this.length; i++)
//Map function take 3 parameter: the item, items's index and the array it self
arr.push(callback(this[i], i, this));
return arr;
};

Function invocation every secoтd time?

Recently I got test task at the project, it is fairly simple:
const _ = require('underscore');
// Challenge:
//
// Write a function that accepts as argument a "function of one variable", and returns a new
// function. The returned function should invoke the original parameter function on every "odd"
// invocation, returning undefined on even invocations.
//
// Test case:
// function to wrap via alternate(): doubleIt, which takes a number and returns twice the input.
// input to returned function: 1,2,3,4,9,9,9,10,10,10
// expected output: 2, undefined, 6, undefined, 18, undefined, 18, undefined, 20, undefined
const input = [1,2,3,4,9,9,9,10,10,10];
const doubleIt = x => x * 2;
const alternate = (fn) => {
// Implement me!
//
// The returned function should only invoke fn on every
// other invocation, returning undefined the other times.
}
var wrapped = alternate(doubleIt)
_.forEach(input, (x) => console.log(wrapped(x)))
// expected output: 2, undefined, 6, undefined, 18, undefined, 18, undefined, 20, undefined
And my solution was:
const alternate = (fn) => {
let odd = false;
return (x) => {
odd = !odd;
if (odd) {
return fn(x);
}
return undefined;
};
};
// An alternate solution if ternary operator (?) is allowed according to coding standards used on the project.
// Sometimes it's treated as bad practise.
const alternateShort = (fn) => {
let odd = false;
return (x) => (odd = !odd) ? fn(x) : undefined;
};
And I got the reply that tech lead didn't like my solution at all and I'm not hired to the project.
I'm really confused, do you have any idea what else he could expect?

I'm trying to rewrite memoize in javascript (for underscore), can someone explain this?

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

Categories