I have this solution for a memoization function.
const slice = Array.prototype.slice
function memoize(fn){
const cache = {}
return (...args) => {
const params = slice.call(args)
console.log(params)
if(cache[params]){
console.log('cached')
return cache[params]
} else{
let result = fn(...args)
cache[params] = result
console.log('not cached')
return result
}
}
}
cache[params] is the point. cache is an object and params is an array. Will this always work? After some researching I am still not confident this code is correct.
Such memoization can work for very simple cases, but it does not work in many other cases, for instance when:
the arguments are objects. They will often stringify to "[object Object]", and so different objects are treated as if they are the same.
the arguments are strings but cannot be distinguished because of the poor way they are separated when the arguments array is stringified (comma delimiter)
Some demos of failures:
Arguments are Objects
const slice = Array.prototype.slice
function memoize(fn){
const cache = {}
return (...args) => {
const params = slice.call(args)
if(cache[params]){
return cache[params]
} else{
let result = fn(...args)
cache[params] = result
return result
}
}
}
// The function we will test with:
function sum(a) {
let total = 0;
for (let value of a) total += value;
return total;
}
function test() {
console.log(sum(new Set([1,2,3]))); // should be 6
console.log(sum(new Set([2,4,6]))); // should be 12
}
console.log("Run test without memoization...");
test(); // Perform test without memoization
sum = memoize(sum); // Memoize the function
console.log("Run test WITH memoization...");
test(); // Test again, but now with memoization
Arguments are strings that have commas:
const slice = Array.prototype.slice
function memoize(fn){
const cache = {}
return (...args) => {
const params = slice.call(args)
if(cache[params]){
return cache[params]
} else{
let result = fn(...args)
cache[params] = result
return result
}
}
}
// The function we will test with:
function compareString(a, b) {
return a.localeCompare(b); // returns -1, 0 or 1.
}
function test() {
console.log(compareString("b,a", "c")); // should be -1
// Change the arguments such that the concatenation with comma remains the same
console.log(compareString("b", "a,c")); // should be 1
}
console.log("Run test without memoization...");
test(); // Perform test without memoization
compareString = memoize(compareString); // Memoize the function
console.log("Run test WITH memoization...");
test(); // Test again, but now with memoization
Other remarks on the code
Calling slice is useless, as args is already a new array.
if(cache[params]) will evaluate to false when the already cached value is a falsy value, like 0, "", false. Doing if (params in cache) would avoid that issue
params will be stringified, which (as shown above) is not guaranteed to uniquely identify an array of arguments.
Improvement
If we can require that the arguments passed to our function are immutable, then we can use these immutable values or references as keys in a Map.
This Map would become a tree of Maps when there are multiple arguments, so that when a lookup is made for the first argument in the main Map, it returns a nested Map, and then in that Map the next argument would be used as key, ...etc, until the deep Map is found for the last argument. In that final Map, a reserved key is used to retrieve the cached value. This reserved key can be Symbol that is only known by the memoize function, so that it can never collide with an argument value.
Disclaimer: This will not work when objects can mutate between calls.
Here is how that looks:
function memoize(fn){
const end = Symbol("end"); // A unique reference, only known here
const cache = new Map;
return (...args) => {
let node = args.reduce((node, arg) => {
if (!node.has(arg)) node.set(arg, new Map);
return node.get(arg);
}, cache);
if (!node.has(end)) node.set(end, fn(...args));
return node.get(end);
}
}
// The function we will test with:
let numCalls = 0;
function length(a) { // Length of a linked list
numCalls++; // Keep track of the number of calls made
return a ? length(a.next) + 1 : 0;
}
function test() {
numCalls = 0; // Reset the number of calls
let head = { next: { next: { next: {}}}}; // Linked list with 4 nodes
console.log(length(head)); // should be 4
// Now exclude the head node:
console.log(length(head.next)); // should be 3
console.log("number of calls: ", numCalls);
}
console.log("Run test without memoization...");
test(); // Perform test without memoization
length = memoize(length); // Memoize the function
console.log("Run test WITH memoization...");
test(); // Test again, but now with memoization
Again, this cannot be used when objects mutate between calls. But for all other scenarios it should work fine.
Object keys are supposed to be string/symbols.
Depending on how you input array affects the output, you can .join() the array and use it as your cache key:
const slice = Array.prototype.slice
function memoize(fn){
const cache = {}
return (...args) => {
const params = slice.call(args)
let paramsString = params.sort().join('');
console.log(paramsString)
if(cache[paramsString]){
console.log('cached')
return cache[paramsString]
} else{
let result = fn(...args)
cache[paramsString] = result
console.log('not cached')
return result
}
}
}
If the order does not matter then you can .sort(). If order is important then you can remove the sort and just join. This is not perfect but works for simple cases.
Related
onceCopy function (testFunc) {
const copyFunc = (a) => {
const copyFunc2 = (b) => {
return testFunc(a);
};
return copyFunc2;
};
return copyFunc;
};
So the function returns the inner function upon first invocation.
Then returns the inner function of the inner function of the second invocation.
Then the second inner function (third invocation) actually returns the passed argument in the parent function and only invokes it with the character we gave it on the second invocation.
Ideally I want to achieve what I'm achieving over many invocations after only the first one if that makes sense.
Edit: Yes sorry, _.once.
Edit: so first invocation onceCopy SHOULD hold a copy of the Func passed
Second invocation SHOULD trigger the copy and gives an ouput
Third invocation SHOULD give the result of the second invocation so should the fourth, fifth, sixth and so on...
My function does do this, but on the second invocation it stores a function (copyFunc2) again, but I just made that because I need somewhere to store "a".
so like we have
function multiplyBy3 (a) {return a*3}
and then once copy stores a copy of multiplyBy3
const actualFunction = onceCopy(multiplyBy3)
then upon second and third invocation what I want
actualFunction(1) = 3
actualFunction(66) = 3
so the passed function ONLY RUNS ONCE
Cant explain more than this, its in the lodash docs.
I'm not familiar with the function you're trying to reimplement, so feel free to correct me if I misunderstood. To wrap a function and ensure it's only called once you don't need multiple nested wrappings, only one with some state.
You need to keep track of whether you already have a result to return (hasResult) and if so, what that result is (result). Keeping these two variables separate allows you to cover the case when result is undefined while keeping the code easy to read and understand.
function once(wrappedFunction) {
let hasResult = false;
let result;
return (...args) => {
if (hasResult) {
return result;
}
result = wrappedFunction.apply(this, args);
hasResult = true;
return result;
}
}
// An example function to wrap
function multiply(a, b) {
return a * b;
}
const demoFunction = once(multiply)
console.log(demoFunction(1, 2)); // 2
console.log(demoFunction(3, 4)); // 2
console.log(demoFunction(5, 6)); // 2
Is this what you were looking for?
The initial function return is kept on subsequent calls. I used a second variable called in case the first call returns undefined, which should also be returned on subsequent calls.
const once = (onceFn) => {
let called;
let value;
return (...args) => {
if (called) return value;
called = true;
return (value = onceFn(...args));
};
};
function multiplyBy3(a) {
return a * 3;
}
const fn = once(multiplyBy3);
console.log(fn(3)); // 9
console.log(fn(66)); // 9
After calling the function for the 1st time, and getting the result, create a new function that returns the result, and use it whenever the wrapped function is called:
const once = fn => {
let func
return (...args) => {
if(func) return func()
const result = fn(...args)
func = () => result
return result
}
}
const multiply = (a, b) => a * b
const demoFunction = once(multiply)
console.log(demoFunction(1, 2)); // 2
console.log(demoFunction(3, 4)); // 2
console.log(demoFunction(5, 6)); // 2
The Lodash flow() function works, as shown in this question, by giving it a bunch of functions in an array. The result of the previous function will be applied to the next one.
And this is exactly what is problematic for my use case, as it throws away any arguments provided. I need to pass them on to the next function in line, instead of just the return value *).
*) Yes, the return value should get returned modified and be passed on the next function
Using flow() is just what I came up with. Any other solution achieving the goal is ok as well.
What it does:
_.flow( [ firstFn, secondFn ] )
( first, second, third ) => firstFn( first, second, third ) => return first * first
( first ) => secondFn( first )
What it should do:
_.flow( [ firstFn, secondFn ] )
( first, second, third ) => firstFn( first, second, third ) => return first * first
( first, second, third ) => secondFn( resultFromFirstFn, second, third )
Here's some code to demo it:
const checkLog = ( val, attr, group ) => {
console.log( val, attr, group )
return val
}
// This basically is "easy to use"-API and what will be mostly used.
const Callbacks = {
demo: [ _.trim, _.toLower, checkLog ],
}
// _.cond() is just a switch
// [ <condition-to-exec-cb>, <cb-if-cond-true> ]
const cond = _.cond( _.map(
[
[ _.stubTrue, Callbacks.demo ],
],
// Monkey patching _.flow() to each array of cond/cb above
cb => _.set( cb, '1', _.flow( _.last( cb ) ) )
) )
const res = _.map( {
" city": " Sample City",
"street": "Sample Street",
}, cond )
console.log( res )
Note: In case you wonder about why I write it like this: There's a single line comment that points to which part will get altered and extended a lot in the future and this must, due to the target group working there, be simple. So it's just an array of stacked callbacks.
The problem with _.flow() is that it expects everything after the first argument to be a unary function so it can pass the result through all of them. This presents a challenge when you want to apply the same set of arguments to all function and only alter the first one. There are a few ways you could go about this.
Using Lodash
You could leverage _.partialRight to do a partial allocation of every function. partialRight will apply arguments from right to left depending on how many arguments the function takes.
const fn = (a, b, c, d) => console.log(a, b, c, d);
// behaves similar to (a, b) => console.log(a, b, "yak", "zebra")
const newFn = _.partialRight(fn, "yak", "zebra");
newFn("alpaca", "beaver");
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
However you can still pass arguments left to right and this would push the partially applied arguments to right:
const fn = (a, b, c, d) => console.log(a, b, c, d);
// behaves similar to (a, b) => console.log(a, b, "yak", "zebra")
const newFn = _.partialRight(fn, "yak", "zebra");
newFn("alpaca", "beaver", "cat");
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
Assuming all of the function will have at most take n amount of arguments we can:
Get an array of n arguments to be applied to all functions.
Get all functions.
Do partialRight on all functions using all but the first argument. Since we've applied n-1 arguments to each, now all of them can be called as if they are unary.
Use flow on the new functions from step 3.
Start the chain using the first argument.
This will then call all functions such that the first argument is the last result and the second and onwards arguments would be based on the initial ones thus always the same for all functions
function nAryFlow(argsArray, ...fns) { //1. and 2. - get arguments and functions
const start = argsArray[0];
const rest = argsArray.slice(1);
const convertedFns = fns.map(f => _.partialRight(f, ...rest)) //3. Turn all functions into unary ones
return _.flow( //4. `flow` through the functions
...convertedFns
)(start); //5. use the initial fist argument to kick things off
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const args = ["Alice", "Bob", "Carol"];
const result = nAryFlow(args, fn1, fn2, fn3);
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
Without using Lodash
All of the above can very easily be done without Lodash. Instead, we can use Array#reduce to go through all of the functions and apply the arguments. This time, we're directly applying them instead of pre-processing the functions but the overall operation and effect is the same:
The first function takes all the arguments.
Any further function takes the result of the last function and the second argument onwards from the start:
function nAryFlow(argsArray, ...fns) {
const start = argsArray[0];
const rest = argsArray.slice(1);
return fns.reduce((last, f) => f(last, ...rest), start);
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const args = ["Alice", "Bob", "Carol"];
const result = nAryFlow(args, fn1, fn2, fn3);
console.log(result)
Variation using more higher order functions
Just as a variation, this could be split up into multiple higher order functions which might produce a nicer syntax for some situations nAryFlow(f1, f2, f3, fN)(arg1, arg2, arg3, argN):
function nAryFlow(...fns) {
return function (...args) {
const start = args[0];
const rest = args.slice(1);
return fns.reduce((last, f) => f(last, ...rest), start);
}
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const chain = nAryFlow(fn1, fn2, fn3);
const result1 = chain("Alice", "Bob", "Carol");
const result2 = chain("Rachel Green", "Monica Geller", "Chandler Bing");
console.log(result1);
console.log(result2);
Using a Functor
For a slightly different take here, you can use an algebraic structure called Functor to ease the syntax. The essential thing about functors is that they have a .map() method that accepts a function. If you're reminded of Array#map then you're not wrong.
The basic idea is that the functor holds a value and allows it to be modified with functions via .map(). It can then dictate how the function is applied to its value. The result of .map is always the same type of functor as what was already mapped, so you can always continue mapping and be sure that the application would be the same every time. With arrays you always get a new array of the same length with every member transformed. Other functors can apply the function given to .map() different to what an array does but it would always be consistent.
So, background done, here is how this functor can look like:
class NAryFlow {
constructor(argsArray) {
this.value = argsArray[0];
this.rest = argsArray.slice(1);
}
static of(argsArray) {
return new NAryFlow(argsArray);
}
map(fn) {
return NAryFlow.of(
[ fn(this.value, ...this.rest), ...this.rest ]
);
}
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const result = NAryFlow.of(["Alice", "Bob", "Carol"])
.map(fn1)
.map(fn2)
.map(fn3)
.value;
console.log(result)
A similar idea as the two others above - we take the arguments, and apply them all to each function we give to .map(). Every next time we call .map() the first argument would be the last result.
And here is a slight variation using ES6 getters. I think it has a slightly better syntax but wanted to keep the previous implementation simpler.
class NAryFlow {
constructor(argsArray) {
this.args = argsArray;
}
static of(argsArray) {
return new NAryFlow(argsArray);
}
map(fn) {
return NAryFlow.of(
[ fn(...this.args), ...this.rest ]
);
}
get value() {
return this.args[0];
}
get rest() {
return this.args.slice(1);
}
}
const fn1 = (who1, who2, who3) => `${who1}, ${who2}, and ${who3} are best friends.`;
const fn2 = (friends, who2, who3) => `${friends} Especially ${who2} and ${who3}.`;
const fn3 = (story, who2) => `${story} They all met at a party thrown by ${who2}`;
const result = NAryFlow.of(["Alice", "Bob", "Carol"])
.map(fn1)
.map(fn2)
.map(fn3)
.value;
console.log(result)
flow doesn't throw any arguments, it's just how javascript functions work, they don't respect the a particular signature, you can call a function that only takes 1 parameter with 100 parameters and that won't throw an error.
Also flow is defined such that it passes the initial parameters all to the first function regardless of whether it's going to use them all or not.
You can create your own flow function(since it is a simple one) that only passes the exact amount of parameters a function needs and keep the rest for the rest of functions. To know how many parameters a function takes, you can check its length property like so:
function flow(funcs) {
const length = funcs.length;
for(let func of funcs) {
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}
}
return function(...args) {
let result = args[0];
for(let func of funcs) {
result = func.apply(this, args.slice(0, func.length)); // call this function with only as many args as it needs
args = [result, ...args.slice(func.length)]; // args becomes the result + the rest of args after the previous call
}
return result;
}
}
Note: This won't work if one of the functions uses the rest parameters, in that case length will return 0. You can't know how many parameters that functions takes. If you wan to call that function with all the available parameters, then just change the for loop above to:
for(let func of funcs) {
result = func.apply(this, args.slice(0, func.length || +Infinity)); // if 'func.length' is 0, '+Infinity' is used instead which will use all the available args
args = [result, ...args.slice(func.length || +Infinity)]; // same goes here, '.slice(+Infinity)' will result in an empty array. 'result' will always be present which is the expected behavior
}
Demo:
function flow(funcs) {
const length = funcs.length;
for(let func of funcs) {
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}
}
return function(...args) {
let result = args[0];
for(let func of funcs) {
result = func.apply(this, args.slice(0, func.length)); // call this function with only as many args as it needs
args = [result, ...args.slice(func.length)]; // args becomes the result + the rest of args after the previous call
}
return result;
}
}
function square(n) {
return n * n;
}
function add(a, b, c) {
return a + b + c;
}
console.log(flow([square, add])(2, 3, 4));
Lets say I have a generator:
function* source() {
yield "hello"; yield "world";
}
I create the iterable, iterate with a for-loop, and then break out of the loop before the iterator fully completes (returns done).
function run() {
for (let item of source()) {
console.log(item);
break;
}
}
Question: How can I find out, from the iterable-side, that the iterator terminated early?
There doesn't seem to be any feedback if you try to do this directly in the generator itself:
function* source2() {
try {
let result = yield "hello";
console.log("foo");
} catch (err) {
console.log("bar");
}
}
... neither "foo" nor "bar" is logged.
Edit: See Newer accepted answer. I will keep this as it does/did work, and I was pretty happy at the time I was able to hack a solution. However, as you can see in the accepted answer, the finally solution is so simple now it has been identified.
I noticed that typescript defines Iterator (lib.es2015) as:
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
I intercepted these methods and logged calls and it does appear that if an iterator is terminated early --at least via a for-loop-- then the return method is called. It will also be called if the consumer throws an error. If the loop is allowed to fully iterate the iterator return is not called.
Return hack
So, I made a bit of a hack to allow capturing another iterable - so I don't have to re-implement the iterator.
function terminated(iterable, cb) {
return {
[Symbol.iterator]() {
const it = iterable[Symbol.iterator]();
it.return = function (value) {
cb(value);
return { done: true, value: undefined };
}
return it;
}
}
}
function* source() {
yield "hello"; yield "world";
}
function source2(){
return terminated(source(), () => { console.log("foo") });
}
for (let item of source2()) {
console.log(item);
break;
}
and it works!
hello
foo
remove the break and you get:
hello
world
Check after each yield
While typing this answer, I realised a better problem/solution is to find out in the original generator method.
The only way I can see to pass information back to the original iterable is to use next(value). So if we pick some unique value (say Symbol.for("terminated")) to signal the termination, and we alter the above return-hack to call it.next(Symbol.for("terminated")):
function* source() {
let terminated = yield "hello";
if (terminated == Symbol.for("terminated")) {
console.log("FooBar!");
return;
}
yield "world";
}
function terminator(iterable) {
return {
[Symbol.iterator]() {
const it = iterable[Symbol.iterator]();
const $return = it.return;
it.return = function (value) {
it.next(Symbol.for("terminated"));
return $return.call(it)
}
return it;
}
}
}
for (let item of terminator(source())) {
console.log(item);
break;
}
Success!
hello
FooBar!
Chaining Cascades Return
If you chain some extra transform iterators, then the return call cascades through them all:
function* chain(source) {
for (let item of source) { yield item; }
}
for (let item of chain(chain(terminator(source())))) {
console.log(item);
break
}
hello
FooBar!
Package
I've wrapped the above solution as a package. It supports both [Symbol.iterator] and [Symbol.asyncIterator]. The async iterator case is of particular interest to me, especially when some resource needs to be disposed of correctly.
There's a much simpler way to do this: use a finally block.
function *source() {
let i;
try {
for(i = 0; i < 5; i++)
yield i;
}
finally {
if(i !== 5)
console.log(' terminated early');
}
}
console.log('First:')
for(const val of source()) {
console.log(` ${val}`);
}
console.log('Second:')
for(const val of source()) {
console.log(` ${val}`);
if(val > 2)
break;
}
...yields:
First:
0
1
2
3
4
Second:
0
1
2
3
terminated early
I've run into a similar need to figure out when an iterator terminates early. The accepted answer is really clever and probably the best way to solve the problem generically, but I think this solution could also be helpful for other uses cases.
Say, for example, you have an infinite iterable, such as the fibonacci sequence described in MDN's Iterators and Generators docs.
In any sort of loop there needs to be a condition set to break out of the loop early, like in the solution already given. But what if you want to destructure the iterable in order to create an array of values? In that case, you'd want to limit the number of iterations, essentially setting a maximum length on the iterable.
To do this, I wrote a function called limitIterable, which takes as arguments an iterable, an iteration limit, and an optional callback function to be executed in the event that the iterator terminates early. The return value is a Generator object (which is both an iterator and an iterable) created using an Immediately Invoked (Generator) Function Expression.
When the generator is executed, whether in a for..of loop, with destructuring, or by calling the next() method, it will check to see if iterator.next().done === true or iterationCount < iterationLimit. In the case of an infinite iterable like the fibonacci sequence, the latter will always cause the while loop to be exited. However, note that one could also set an iterationLimit that is greater than the length of some finite iterable, and everything will still work.
In either case, once the while loop is exited the most recent result will be checked to see if the iterator is done. If so, the original iterable's return value will be used. If not, the optional callback function is executed and used as a return value.
Note that this code also allows the user to pass in values to next(), which will in turn be passed to the original iterable (see the example using MDN's fibonacci sequence inside the code snippet attached). It also allows for additional calls to next() beyond the set iterationLimit within the callback function.
Run the code snippet to see the results of a few possible use cases! Here's the limitIterable function code by itself:
function limitIterable(iterable, iterationLimit, callback = (itCount, result, it) => undefined) {
// callback will be executed if iterator terminates early
if (!(Symbol.iterator in Object(iterable))) {
throw new Error('First argument must be iterable');
}
if (iterationLimit < 1 || !Number.isInteger(iterationLimit)) {
throw new Error('Second argument must be an integer greater than or equal to 1');
}
if (!(callback instanceof Function)) {
throw new Error('Third argument must be a function');
}
return (function* () {
const iterator = iterable[Symbol.iterator]();
// value passed to the first invocation of next() is always ignored, so no need to pass argument to next() outside of while loop
let result = iterator.next();
let iterationCount = 0;
while (!result.done && iterationCount < iterationLimit) {
const nextArg = yield result.value;
result = iterator.next(nextArg);
iterationCount++;
}
if (result.done) {
// iterator has been fully consumed, so result.value will be the iterator's return value (the value present alongside done: true)
return result.value;
} else {
// iteration was terminated before completion (note that iterator will still accept calls to next() inside the callback function)
return callback(iterationCount, result, iterator);
}
})();
}
function limitIterable(iterable, iterationLimit, callback = (itCount, result, it) => undefined) {
// callback will be executed if iterator terminates early
if (!(Symbol.iterator in Object(iterable))) {
throw new Error('First argument must be iterable');
}
if (iterationLimit < 1 || !Number.isInteger(iterationLimit)) {
throw new Error('Second argument must be an integer greater than or equal to 1');
}
if (!(callback instanceof Function)) {
throw new Error('Third argument must be a function');
}
return (function* () {
const iterator = iterable[Symbol.iterator]();
// value passed to the first invocation of next() is always ignored, so no need to pass argument to next() outside of while loop
let result = iterator.next();
let iterationCount = 0;
while (!result.done && iterationCount < iterationLimit) {
const nextArg = yield result.value;
result = iterator.next(nextArg);
iterationCount++;
}
if (result.done) {
// iterator has been fully consumed, so result.value will be the iterator's return value (the value present alongside done: true)
return result.value;
} else {
// iteration was terminated before completion (note that iterator will still accept calls to next() inside the callback function)
return callback(iterationCount, result, iterator);
}
})();
}
// EXAMPLE USAGE //
// fibonacci function from:
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#Advanced_generators
function* fibonacci() {
let fn1 = 0;
let fn2 = 1;
while (true) {
let current = fn1;
fn1 = fn2;
fn2 = current + fn1;
let reset = yield current;
if (reset) {
fn1 = 0;
fn2 = 1;
}
}
}
console.log('String iterable with 26 characters terminated early after 10 iterations, destructured into an array. Callback reached.');
const itString = limitIterable('abcdefghijklmnopqrstuvwxyz', 10, () => console.log('callback: string terminated early'));
console.log([...itString]);
console.log('Array iterable with length 3 terminates before limit of 4 is reached. Callback not reached.');
const itArray = limitIterable([1,2,3], 4, () => console.log('callback: array terminated early?'));
for (const val of itArray) {
console.log(val);
}
const fib = fibonacci();
const fibLimited = limitIterable(fibonacci(), 9, (itCount) => console.warn(`Iteration terminated early at fibLimited. ${itCount} iterations completed.`));
console.log('Fibonacci sequences are equivalent up to 9 iterations, as shown in MDN docs linked above.');
console.log('Limited fibonacci: 11 calls to next() but limited to 9 iterations; reset on 8th call')
console.log(fibLimited.next().value);
console.log(fibLimited.next().value);
console.log(fibLimited.next().value);
console.log(fibLimited.next().value);
console.log(fibLimited.next().value);
console.log(fibLimited.next().value);
console.log(fibLimited.next().value);
console.log(fibLimited.next(true).value);
console.log(fibLimited.next().value);
console.log(fibLimited.next().value);
console.log(fibLimited.next().value);
console.log('Original (infinite) fibonacci: 11 calls to next(); reset on 8th call')
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next(true).value);
console.log(fib.next().value);
console.log(fib.next().value);
console.log(fib.next().value);
I am having multiple problems with this function. It's part of a bonus question for a Data Structures and Algorithms class and I've invested so much time in this one problem, that I'd really like to get it to work and understand what's going on.
There is one main problem, which has caused several little ones...this problem's name is JavaScript. We've never programmed in JavaScript before, but for some reason we have to use it.
The function has to pass tests (this one and fibonacci), which are structured like this:
var fn = (n) => 2 * n
var m_fn = memoize(fn)
expect(m_fn(18)).to.equal(fn(18))
So I have to pass the function I want to memoize as a parameter of the memoize function and the memoize function has to return a function. I am not allowed to do it any other way.
I did all of the reading and researched the memoize function, but all of the implementations take a different approach.
Basically, I understand what I have to do, but I don't quite understand HOW. I know what the memoize function should do, but I don't understand how to adjust the original function using my memoize function. This is what I have so far/what I don't have:
I know it's wrong. But I think I'm missing something major. I am supposed to return a function, but I am returning values...
In the test, it's writen var m_fn = memoize(fn), so the memoize function passes fn, then returns a new function, but in my memoize, I am returning values for fn(n), so I AM doing something wrong...
/**
* Creates a memoized version of the function fn. It is assumed that fn is a referentially transparent
* function.
* #param {function} fn Some referentially transparent function that takes a basic datatype (i.e. number / string)
* #returns {function} A new function that is the memoized version of fn. It never calculates the result of
* a function twice.
*/
memoize: (fn) => { //here we enter the function that we want to memoize
var memory = []; //we need to create an array to hold the previously calculated values, length n (parameter of fn)
if(this.n > memory.length){ //Check to see if this particular value is in the array already.
return memory[this.n]; //How do I access the integer parameter that was passed through fn though? Is this correct?
} else{ // if not, we want to save it and return it
var result = fn(this.n);
memory.push(result);
return result;
}
}
Indeed, you need to return a function.
Secondly, an array is not the ideal structure for memory, because it takes linear time to find an argument value in it. I would suggest to use a Map for this, which is ideal for such purposes. It has has(), get() and set() methods which run in near-constant time:
function memoize(fn) {
var memory = new Map();
return function(arg) {
if (memory.has(arg)) {
console.log('using memory');
return memory.get(arg);
} else {
var result = fn(arg);
memory.set(arg, result);
return result;
}
};
}
var fn = (n) => 2 * n
var m_fn = memoize(fn)
console.log(fn(18));
console.log(m_fn(18));
console.log(m_fn(18)); // outputs also "using memory"
You could use a Map as memory.
var memoize = f =>
(map => v => (!map.has(v) && map.set(v, f(v)), map.get(v)))(new Map),
fn = (n) => 2 * n,
m_fn = memoize(fn);
console.log(m_fn(18), fn(18));
Looking at your code and in-code comments and assuming I'm interpreting correctly, you're really close to the solution. As you've said in the question, you need to return a function that returns the values rather than returning the values.
See comments for explanation:
function memoize(f) {
// An array in which to remember objects with the input arg and result
var memory = [];
// This is the function that will use that array; this is the
// return value of memoize
return function(arg) {
// This code runs when the function returned by memoize is called
// It's *here* that we want to process the argument, check the `memory`
// array, call `f` if necessary, etc.
var entry;
// See if we have a previously-saved result for `arg`
var entry = memory.find(entry => entry.arg === arg);
if (!entry) {
// No -- call `fn`, remember the `arg` and result in an object
// we store in memory``
entry = {arg, result: f(arg)};
memory.push(entry);
}
// We have it (now), return the result
return entry.result;
};
}
function fn(arg) {
console.log("fn called with " + arg);
return 2 * arg;
}
var m_fn = memoize(fn);
console.log(m_fn(18));
console.log(m_fn(18));
console.log(m_fn(20));
console.log(m_fn(20));
Note: There was an arrow function in your code, so I've assumed it's okay to use ES2015 features above. There's not actually very much of it, though, just the arrow function passed to memory.find, the assumption that Array#find will be available, and the syntax used to create the entry object (in ES5 we' need entry = {arg: arg, result: f(arg)} instead).
Note that if we can assume that arg will be a string or number or other value that can reliably be converted to string, we can use an object to store the data rather than an array.
And actually, given this is ES2015, we can use a Map:
function memoize(f) {
// An Map in which to remember objects with the input arg and result
const memory = new Map();
// This is the function that will use that array; this is the
// return value of memoize
return function(arg) {
// This code runs when the function returned by memoize is called
// It's *here* that we want to process the argument, check the `memory`
// array, call `f` if necessary, etc.
let result;
// See if we have a previously-saved result for `arg`
if (!memory.has(arg)) {
// No -- call `fn`, remember the `arg` and result in an object
// we store in memory``
result = f(arg);
memory.set(arg, result);
} else {
// Yes, get it
result = memory.get(arg);
}
// We have it (now), return the result
return result;
};
}
function fn(arg) {
console.log("fn called with " + arg);
return 2 * arg;
}
var m_fn = memoize(fn);
console.log(m_fn(18));
console.log(m_fn(18));
console.log(m_fn(20));
console.log(m_fn(20));
Note that in both cases, I've written the code verbosely to allow for comments and easy comprehension. The ES2015 version with Map, in particular, can be quite a lot shorter.
I found some code online. I've squashed the original code down into this little excerpt, that when ran, will print 1-20 to the console.
var NumbersFromOne = {
*[Symbol.iterator] () {
for (let i = 1;; ++i) yield i;
}
};
var take = function* (numberToTake, iterable) {
let remaining = numberToTake;
for (let value of NumbersFromOne) {
if (remaining-- <= 0) break;
yield value;
}
}
var printToTwenty = take(20, NumbersFromOne)
console.log(...printToTwenty);
Now, I understand that take() is a GeneratorFunction.
When take() is called, it is given an iterator.
The code "...printToTwenty" uses the spread operator to iterate through that function.
I understand that NumbersFromOne is an object.
I've come here looking for an explanation of what this part means:
*[Symbol.iterator] () {}
Declaring generator functions is done like this: function* () {}
So I'm assuming this isn't declaring a generator function.
* also doesn't represent the function name
* also can't be replaced with another operator (/, -, +)
What is the deal with that syntax, and why is the * before [Symbol.iterator]
If placed after, it will not run.
I had considered that *[Symbol.iterator] () is a way to overwrite the existing iterator property, but then wouldn't it say this[Symbol.iterator].
Thanks!
There are a few things that might make this code look complicated:
It uses the object property shorthand notation. What you're seeing here is actually the following:
var NumbersFromOne = {
[Symbol.iterator]: function* () {
for (let i = 1;; ++i) yield i;
}
};
Symbol.iterator creates a custom iterator for your NumbersFromOne object.
So your code basically means that the iterator of NumbersFromOne is defined as a generator. Instead of manually having to define a function which returns a next and other properties:
var NumbersFromOne = {
[Symbol.iterator]: function () {
var i = 1;
return {
next: function() {
return { value: i++, done: false };
}
};
}
};
Returning the generator creates the next function automatically for. This allows you to yield when you need to.
It can then be called as:
const it = NumbersFromOne[Symbol.iterator]();
it.next(); // 1
it.next(); // 2
it.next(); // 3
// ...
Note: Written this way, this iterator never ends! So if you were to call it in a for ... of loop without an end-condition, it would freeze your program.