Solving a closure problem
Last week we had a question on closures. I think I got the answer mostly right because I had previously seen code for memoize()and knew it’d apply, but not because I actually understood each line of code itself.
I want to break down how to solve this problem and the thought process for each line.
So a prompt can be as follows:
nTimes takes an integer and a function, and returns a function that will invoke the
original function the first n times the new function is invoked. The nth + 1 invocation
(and all subsequent invocation) of the new function will return the result of
the last func invocation. The new function should invoke func with any arguments
passed to the new function.
var add = function(a, b) {
return a + b;
}
var add3Times = nTimes(3, add);
add3Times(1, 5) // returns 6
add3Times(4, 9) // returns 13
add3Times(6, 5) // returns 11
add3Times(2, 3) // returns 11
add3Times(9, 8) // returns 11
Simply based on the part of the first sentence “nTimes takes an integer and returns a function“, we know we’re probably looking at a closure problem. Why? A closure is a function returned from an outer function and remembers the values in the outer function’s execution context, even after the outer function has finished execution. As further proof the problem requires a closure, the second part states we must remember how many times the passed in function is ran.
So then the first level of understanding, our inputs and outputs, can be something like this:
var nTimes = function(n, func) {
// Inputs
// n: the number of times a function can be ran before we start returning last value
// func: the function that will be passed to nTimes, to be ran n times
// Returns
// A closure function that has access to the number of times func has ran, and can execute func
return function() {
// Inputs: nothing
// Outputs:
// The return value from executing func with given parameters, if number of invokations is less than n
// The last returned value if the number of executions, if number of invokations is greater than or equal to n
// NOTE: after n calls, the returned value is always the same regardless of input parameter
}
};
CAUTION: the last noted line. My mistake in originally solving this problem was glossing over this because it made no sense to me, and it didn’t occur to me this was what was desired. In the example, the output was 11 for both (2, 3) and (9, 8). Neither of those sum to 11. But we’re not coding what we think is right, we’re coding what’s requested.
Okay, so now we know in detail what our inputs and outputs look like, let’s begin pseudocoding.
var nTimes = function(n, func) {
// Inputs
// n: the number of times a function can be ran before we start returning last value
// func: the function that will be passed to nTimes, to be ran n times
// Returns
// A closure function that has access to the number of times func has ran, and can execute func
// Pseudocode
// Define a counter to track number of times func has been invoked
// Declare a temp variable to store the last value returned from func
return function() {
// Inputs: nothing
// Outputs:
// The return value from executing func with given parameters, if number of invokations is less than n
// The last returned value if the number of executions, if number of invokations is greater than or equal to n
// NOTE: after n calls, the returned value is always the same regardless of input parameter
// Pseudocode
// If number of invokations is less than n
// Invoke function and store return value in temp
// Return temp
// Else, return temp
}
};
Now the logic flow is clear! We can code.
var nTimes = function(n, func) {
// Inputs
// n: the number of times a function can be ran before we start returning last value
// func: the function that will be passed to nTimes, to be ran n times
// Returns
// A closure function that has access to the number of times func has ran, and can execute func
// Pseudocode
// Define a counter to track number of times func has been invoked
// Define a temp variable to store the last value returned from func
var counter = 0;
var temp;
return function() {
// Inputs: nothing
// Outputs:
// The return value from executing func with given parameters, if number of invokations is less than n
// The last returned value if the number of executions, if number of invokations is greater than or equal to n
// NOTE: after n calls, the returned value is always the same regardless of input parameter
// Pseudocode
// If number of invokations is less than n
// Invoke function and store return value in temp
// Incremenet counter
// Return temp
// Return temp
if (counter < n) {
temp = func.apply(null, arguments);
counter++;
return temp;
}
return temp;
}
};
Almost 20 lines of analysis and pseudocode for only about 10 lines of code!
The code itself is pretty simple with the exception of apply().
Here’s the line:
temp = func.apply(null, arguments);
One might ask, why do we need to use apply()? And why not use call()?
The answer is that func() is a callback function, and you don’t know the number of arguments it receives. In the given example our add(a,b) function has 2 arguments, but it could be a function with any number of arguments.
apply() should be used when the number arguments is known, and since there’s no object we need to force binding to, we leave the this parameter as null.