How to memoize with Map() - javascript

I am currently stuck on a problem with the memoize function in JavaScript. The last 2 tests don't pass. I will be more then happy for assistance.
The problem is the following:
Write a memoize function that takes in a required callback function and an optional resolver function. The memoize function returns a memoized version of the callback function, which is defined as follows:
All of the return values of the memoized function are cached. If the memoized callback is called with an existing cache key (defined below), then that cached value is returned without invoking the callback again.
The cache key is defined based on the optional resolver function. If a resolver function is not provided, then the cache key is the result of passing the memoized function arguments to JSON.stringify as an array. If a custom resolver function is provided, then the arguments should be individually passed to that function instead, and its return value will be the cache key (note that this can be of any type).
The memoized function should also have three methods: clear(): Clears out the cache. delete(...args): Deletes the cache entry corresponding to the passed arguments if one exists. has(...args): Returns a boolean of true if the cache has an entry corresponding to the passed arguments, otherwise false. For simplicity, you don't need to worry about binding a this context (i.e., you can assume that the callback doesn't reference this).
The callback function look like this:
const callback = (...args) => args;
And this is the input and the result that should be returned
const memoized = memoize(callback);
memoized(123); // calls callback, returns 123
memoized(123); // returns 123
memoized(123, 'abc'); // calls callback, returns [123, 'abc']
const memoized2 = memoize(callback, args => args[0]);
memoized2(123); // calls callback, returns 123
memoized2(123); // returns 123
memoized2(123, 'abc'); // returns 123
memoized('abc', 123); // calls callback, returns ['abc', 123]
memoized2('abc'); // returns ['abc', 123]
I've stored all arguments in cache and when the same argument is called the result will be returned from cache.
This is my code
function memoize(cb) {
const memo = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (!memo.has(args)) {
memo.set(key, cb(...args));
return memo.get(key);
}
return memo.get(key);
};
}
And this are the tests
const chai = require("chai");
const spies = require("chai-spies");
const { expect } = chai;
chai.use(spies);
const spy = () => chai.spy(() => {});
const { memoize } = require("../memoize.js");
describe("memoize", () => {
it("callback without parameters is never called twice", () => {
const callback = spy(() => {});
const memoized = memoize(callback);
expect(callback).to.have.been.called.exactly(0);
memoized();
expect(callback).to.have.been.called.exactly(1);
memoized();
expect(callback).to.have.been.called.exactly(1);
memoized();
memoized();
expect(callback).to.have.been.called.exactly(1);
});
it("callback with a single parameter is handled properly", () => {
const callback = spy((val) => val * 2);
const memoized = memoize(callback);
expect(callback).to.have.been.called.exactly(0);
const val1 = memoized(1);
expect(callback).to.have.been.called.exactly(1);
expect(val1).to.equal(2);
const val2 = memoized(1);
expect(callback).to.have.been.called.exactly(1);
expect(val2).to.equal(2);
const val3 = memoized(2);
expect(callback).to.have.been.called.exactly(2);
expect(val3).to.equal(4);
const val4 = memoized(2);
expect(callback).to.have.been.called.exactly(2);
expect(val4).to.equal(4);
const val5 = memoized(1);
expect(callback).to.have.been.called.exactly(2);
expect(val5).to.equal(2);
});
it("has function works as expected", () => {
const callback = spy((...args) => args);
const memoized = memoize(callback);
expect(memoized.has()).to.be.false;
expect(memoized.has(123)).to.be.false;
expect(memoized.has(123, "abc")).to.be.false;
memoized();
expect(memoized.has()).to.be.true;
memoized(123);
expect(memoized.has(123)).to.be.true;
memoized(123, "abc");
expect(memoized.has(123, "abc")).to.be.true;
expect(callback).to.have.been.called.exactly(3);
});
});

The solution I am gonna give solves 12 tests instead of 3, including your tests.
Try this:
const defaultResolver = (...args) => JSON.stringify(args);
export function memoize(fn, resolver = defaultResolver) {
const cache = new Map();
const memoized = (...args) => {
const key = resolver(...args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
memoized.clear = () => cache.clear();
memoized.delete = (...args) => {
const key = resolver(...args);
cache.delete(key);
};
memoized.has = (...args) => {
const key = resolver(...args);
return cache.has(key);
}
return memoized;
}

Related

Why doesn't promisify want the argument for the callback function?

The below script works, but I don't understand why one doesn't have to
const a = promisify(opA());
instead of the (correct)
const a = promisify(opA);
I mean opA is a function, so why not opA()?
'use strict'
const { promisify } = require('util')
const opA = (cb) => {
setTimeout(() => {
cb(null, 'A')
}, 500)
}
const opB = (cb) => {
setTimeout(() => {
cb(null, 'B')
}, 250)
}
const opC = (cb) => {
setTimeout(() => {
cb(null, 'C')
}, 125)
}
const a = promisify(opA);
const b = promisify(opB);
const c = promisify(opC);
(async function() {
try {
console.log(await a());
console.log(await b());
console.log(await c());
} catch(err) {
print(err);
};
})();
I mean opA is a function, so why not opA()?
Because opA is a reference to the function itself. The promise will use that reference to execute that function at a later time.
Alternatively, opA() executes the function (without any arguments) now and passes the result of that function call to the promise. Since your opA function doesn't return anything, it would pass undefined to the promise. The promise would then have nothing to execute later after it completes its operation(s). The setTimeout would then also fail because cb is also undefined, because no arguments were passed to opA().
Any time you do see a structure like that, either (1) it's a bug or (2) the function intentionally builds and returns a callback function intended for the promise. For example, if you write a function which returns opA then you can invoke that function and pass its result to promisify.
An important clue to this behavior is here:
const opA = ...
opA is a variable, not unlike any other variable. It contains a value or a reference to something. In this case it's a reference to a function. You could re-assign that variable, pass it as a function argument, set it as a property on an object, etc. just like any other.

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

How to await data from async/await debounced axios call

I'm trying to return a debounced search result from an API request using lodash's debounce function but keep getting undefined from the call.
Here's my code, please help;
const searchSuggestionsRequest = async (input) => {
const params = {
userInput: encodeURIComponent(input),
};
const { data } = await axios.get(`${BASE_URL}/api/location`, { params });
return data;
};
const debouncedSuggestionsRequest = _.debounce(searchSuggestionsRequest, 500);
const fetchSearchSuggestions = (input) => {
return debouncedSuggestionsRequest(input);
};
handleSearchSuggestions = async (input) => {
const searchObj = await fetchSearchSuggestions(input);
console.log('searchObj', searchObj);
};
handleSearchSuggestions()
You are expecting the debounce function to return the result of your original function, or in your case the resolved promise. But that is not how the debounce function works.
The debounce function wraps your function with its own code in which it checks if any new call files in our not. After a certain amount of time eventually your function is initiated. But it cannot return the result of that function.
You need to define a more global scope (or at least a scope overlapping your functions) variable, and set that variable in your function where you get the axios result.
You problem remains that you cannot await for the result, so your console.log will still be undefined. Personally I develop in Vue, and I can set a reactivity watcher on the variable.

Does the parameter of Observable.create() function have to define explicitely an observer?

I came across this code from here: https://medium.com/#mohandere/rxjs-5-in-5-minutes-1c3b4ed0d8cc
function multiplyByTen(input) {
var output = Rx.Observable.create(function subscribe(observer) {
input.subscribe({
next: (v) => observer.next(10 * v),
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
return output;
}
var input = Rx.Observable.from([1, 2, 3, 4]);
var output = multiplyByTen(input);
output.subscribe(x => console.log(x));
// Result In:
// 10
// 20
// 30
// 40
I just do not see where the "observer" parameter for the subscribe function inside the multiplyByTen function comes from? Does it have to be explicitely defined or is it just some "default" object that gets passed to the create function in case it was not defined before? And why is it called function subscribe(observer) inside the function? Does it override the default .subscribe() function or could it also be an anonymous function?
It is just an RxJS construct to allow you to dispatch new values to the subscriber functions. Basically, this is how RxJS have designed the library to work. This allows you to control the dispatch of new values. One example of usage is when you have some asynchronous operation and you want to send a new value to the subscribers when it has resolved/completed.
You can read more about how it works here: https://www.learnrxjs.io/operators/creation/create.html
Also, check out this simple synchronous example in JsBin (taken from the docs link above)
/*
Create an observable that emits 'Hello' and 'World' on
subscription.
*/
const hello = Rx.Observable.create(function(observer) {
observer.next('Hello');
observer.next('World');
});
const subscribe = hello.subscribe(val => console.log(val));
// prints.. the follwoing:
// Hello
// World
The asynchronous case I mentioned above is portrayed here: (from official RxJS jsBin examples - https://jsbin.com/lodilohate/1/edit?js,console):
// RxJS v6+
import { Observable } from 'rxjs';
/*
Increment value every 1s, emit even numbers.
*/
const evenNumbers = Observable.create(function(observer) {
let value = 0;
const interval = setInterval(() => {
if (value % 2 === 0) {
observer.next(value);
}
value++;
}, 1000);
return () => clearInterval(interval);
});
//output: 0...2...4...6...8
const subscribe = evenNumbers.subscribe(val => console.log(val));
//unsubscribe after 10 seconds
setTimeout(() => {
subscribe.unsubscribe();
}, 10000);

How to pass an arg to the function inside lodash's once function?

So I am calling a function that calls lodash's once function:
if (isPageTwo) {
sendSegmentData(sendEnhancedTrackEvent);
}
And I have the functions defined here:
const pageTwoSegmentEvent = (sendEnhancedTrackEvent) => {
const enhanceableData = {
name: 'Page loaded',
properties: {
...defaultProps,
cid: getCid(),
epid: getEpid(),
name: 'ReviewExperienceModernDoubleStep'
}
};
sendEnhancedTrackEvent(enhanceableData);
}
const sendSegmentData = (sendEnhancedTrackEvent) => {
once(() => {
pageTwoSegmentEvent(sendEnhancedTrackEvent);
});
}
I am trying to pass the sendEnhancedTrackEvent callback function to the pageTwoSegmentEvent function but I guess the way I'm trying to pass it through the once function pageTwoSegmentEvent never gets called. Does anyone know how to do this?
The _.once() method takes a function (func), and returns a function that invokes the wrapped function (func) a single time. According to the docs:
The func is invoked with the this binding and arguments of the created
function.
Which means that whatever arguments you pass to the new function, will be passed to the wrapped func.
In your case:
sendSegmentData has the sendEnhancedTrackEvent param
When sendSegmentData is invoked, it calls once(() => { pageTwoSegmentEvent(sendEnhancedTrackEvent); });, which creates a new function. The new function is not returned or called.
To create sendSegmentData, call once on pageTwoSegmentEvent directly. This will return a new function, that will pass whatever arguments in gets to pageTwoSegmentEvent.
Example:
const { once } = _
const pageTwoSegmentEvent = (sendEnhancedTrackEvent) => console.log(sendEnhancedTrackEvent)
const sendSegmentData = once(pageTwoSegmentEvent)
sendSegmentData('1')
sendSegmentData('2')
sendSegmentData('3')
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
_.once returns the function that you need to invoke. No matter how many times you call this function it will only be invoked once.
Assuming once is an alias to _.once, try changing it to this:
const sendSegmentData = once((sendEnhancedTrackEvent) => {
pageTwoSegmentEvent(sendEnhancedTrackEvent);
});
...
// somewhere else call it
sendSegmentData(theSegmentedData);

Categories