Understanding a recursive function in JavaScript [duplicate] - javascript

So, I've already understood the idea of recursion in JavaScript (having a function that loops itself until it reaches a base condition, at which point it stops and returns the final result), but I'm having a bit of a headache when I apply that to the actual syntax of trying to apply it to create arrays.
Let's use this countdown exercise from freeCodeCamp as an example. This is the correct result:
function countdown(n) {
if (n < 1) {
return [];
} else {
const arr = countdown(n - 1);
arr.unshift(n);
return arr;
}
}
The idea here is obviously to have a function to count down from a given n to 1. I understand the idea, but I have a couple of questions:
1 - Why is the array declared as a constant and assigned to the recursion call? Shouldn't the array be better declared outside as a variable (since it changes value)– and doesn't its current position mean that it is declared (and over-written) every time the recursion takes place?
2 - I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
3 - Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
As it is, my understanding is that when n reaches 0, the loop should return an empty array, and not the full array that has been created with the recursion.
~ ~ ~
FINAL EDIT / UNDERSTANDING: Thanks to every answer! So, if my current understanding is correct, recursion does not loop the function, as I initially thought – instead, it sort of "opens" a call of "chained functions" that goes downwards until it finds and resolves the base case.
When that "base" case or function is resolved, the chain goes "up", so to say, resolving each recursive step above, .unshifting each n value, and returning the resulting array "upwards", until we reach the initial n value, at which point the final array is returned, and the function is exited.
I think I (sort of) got the point now! Actually writing recursive functions won't be easier though, but I have a better grasp of how the process actually takes place.

Why is the array declared as a constant and assigned to the recursion call?
It is not. The array is variable. Every call to this function will return a new array. No arrays get "overwritten". const in JavaScript is merely used to declare a constant variable which has lexical scope (that is, the variable arr ceases to exist after return arr) and is constant (can't be mutated / assigned to).
I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
You can't position it above since the recursive call is required to obtain arr in the first place. After you have counted down from n-1 to 1, you must prepend n at the first position, which is what unshift does.
Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
There is no such thing as an "else loop". The reason for returning the array there as well is that you want countdown(n) to return the array it has just generated, using the array countdown(n-1).
As it is, my understanding is that when n reaches 0, the loop should return an empty array, and not the full array that has been created with the recursion.
That's exactly what if (n < 1) return []; does.
That said, for the task at hand the recursive implementation given is highly suboptimal. First of all, you don't need the else since return will exit the function anyways:
function countdown(n) {
if (n < 1) return [];
const arr = countdown(n - 1);
arr.unshift(n);
return arr;
}
second, this is most readable (and performant) as a simple for loop:
function countdown(n) {
const arr = Array(n)
for (let i = 0; i < n; i++) arr[i] = n - i;
return arr;
}
(bonus: the array size being known ahead of time can be leveraged using the Array constructor to prevent JS having to resize the Array as you push elements).
third, the current implementation has a quadratic time complexity of O(n²) because arr.unshift is a linear-time operation which is applied n times to arrays of length 0 to n-1.
Recursion is extremely useful when you need the stack (f.E. in a depth-first traversal). Creating a countdown doesn't require a stack. This is not using but rather abusing recursion to implement a highly suboptimal algorithm. If you - for whatever reason (perhaps you are forced to use a purely functional language that has no loops, only recursion?) - wanted to replace the perfectly fine for loop with a recursive call at all cost, simply use tail recursion and lexical scope (closures):
function countdown(n) {
const arr = [];
function loop(n) {
if (n < 1) return;
arr.push(n);
loop(n-1);
}
loop(n);
return arr;
}
or if you don't like closures, what about an optional arr parameter? This allows turning the recursion around since the caller now has the arr before the callee and can thus append its number before subsequent recursive calls do the same:
function countdown(n, arr = []) {
if (n < 1) return arr;
arr.push(n);
countdown(n - 1, arr);
return arr;
}
same result, but (IMO) cleaner, and most importantly, significantly faster code. Try running all these functions on n = 1e6 and you will see that the freeCodeCamp one fails to complete in a reasonable timeframe whereas the other ones finish almost instantly.

It might help to clarify how the code is working a little. Hopefully, the explanation clears up your questions a bit. Let's start with the most basic case:
countdown(0) - When you call countdown(0) your if-block runs and returns an empty array []. So we see that countdown(0) outputs []. This is the base case.
Next, let's imagine we call the function again, this time as:
countdown(1) - In this scenario the else block runs. We see that it calls countdown(0), which means the interpreter executes the function again, passing n as 0. Above we saw what happens when countdown(0) is called, it returns an empty array [], so that's what gets assigned to arr within the else block. We then unshift n (which is 1 here) onto this empty array, which adds 1 to the start of the array stored in arr. This is then returned. So we say that countdown(1) outputs [1].
Now let's see what happens if we were to call the function again with 2:
countdown(2) - Again, the else block runs, triggering a call to countdown(1). We saw above what occurs when this is called, we get back an array with [1], which is stored in the local variable arr, and we then .unshift(2) so that 2 is added to the beginning of the array which we then return. So we see that countdown(2) returns [2, 1].
When thinking about recursion, it can be helpful to think of it as breaking down your input until it's at a small enough value that it can't be broken down anymore (your base case). Once you hit that, you work your way back up, stitching together your previous calls now that you have "solved" the sub-problems.
To answer your questions more directly:
arr is assigned to the recursive call so that we can build upon the results it returned. If countdown(1) returns [1], then we can build onto that result by storing it in arr and unshift 2 onto it to calculate countdown(2). It could have been made let, but const is commonly used if the variable doesn't need to be reassigned. We don't want to move arr outside of the function as then calling countdown() multiple times will modify the same global array. You would also need to change your code so that we can gradually "build-up" the result by further recursive calls if you did that also, which is more of a "loop mindset" than a "recursive mindset".
Moving the .unshift() component before the recursive call wouldn't work as you wouldn't have anything to unshift to. The recursive call is what provides you with the array result of countdown(n-1) so that you can build onto that later by using .unshift().
Returning your array in the else block is what allows you to obtain results when n is greater than 0. In the countdown(1) example above, we need to return arr so that it outputs [1]. This needs to be in the else block because we're returning arr which is defined in that else-block (and hence can't be accessed outside of it).

I would just add to the first part of LMD's answer (I can't comment because of no reputation):
When you define constant array, you are not saying that the array is constant and can't change. You are creating constant reference to that array. So you still can edit its elements, but can not overwrite it with new array.
More info here:
https://www.w3schools.com/jS/js_array_const.asp

1 - Why is the array declared as a constant and assigned to the recursion call? Shouldn't the array be better declared outside as a variable (since it changes value)– and doesn't its current position mean that it is declared (and over-written) every time the recursion takes place?
The const declaration is block-scoped. The reference only exists inside the else-block. There is not one generic arr that is shared by each call. Instead, inside of each call to countdown, we get an if-condition. If that fails, we create the else block and assign a brand-new arr variable. When this is returned, and the function ends, that reference is gone, and only the function return value remains.
2 - I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
First of all, be careful of "the function loops itself". Recursion is a different mechanism from looping; and although they have equal power, they are not at all the same.
But recall that the recursive call returns the result of calling this with n - 1. So
const arr = countdown(n - 1);
when called with n of 4 sets arr to [3, 2, 1]. Now that we have this value, we can do the unshift operation on it.
3 - Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
Again, you need to stop thinking in terms of looping.
Here's an alternative way of thinking about it: For any smaller value of n than our current value, we assume that countdown will magically provide us the correct answer, and then, when we call it, we will prepend our n value to the result.
Recursion is simply the trick of making that magic work. If we know the answer to our simpler problem(s) and we can use these to build the answers to our more complex ones, then all we need is a way to stop breaking down into smaller problems: our base cases.
So, for countdown (3), we call our magical countdown (2) function, which returns [2, 1] and use unshift to prepend 3 and get [3, 2, 1]. Of course if we wanted to dig into how that magical countdown (2) actually worked, we could and we'd see that it called the magical countdown (1), getting [1] and prepended 2 to get [2, 1], and so on. The magic, it turns out, is rather mundane.
But that's what recursion is, at its core: the notion that there is a sequence of simpler and simpler problems, bottoming out in some base case(s), and that we can build our more complex result based on those simpler ones. But while writing the logic to do this, it's easiest to think of those simpler ones as magical.

Related

Trouble understanding JS recursive countdown function from freeCodeCamp

So, I've already understood the idea of recursion in JavaScript (having a function that loops itself until it reaches a base condition, at which point it stops and returns the final result), but I'm having a bit of a headache when I apply that to the actual syntax of trying to apply it to create arrays.
Let's use this countdown exercise from freeCodeCamp as an example. This is the correct result:
function countdown(n) {
if (n < 1) {
return [];
} else {
const arr = countdown(n - 1);
arr.unshift(n);
return arr;
}
}
The idea here is obviously to have a function to count down from a given n to 1. I understand the idea, but I have a couple of questions:
1 - Why is the array declared as a constant and assigned to the recursion call? Shouldn't the array be better declared outside as a variable (since it changes value)– and doesn't its current position mean that it is declared (and over-written) every time the recursion takes place?
2 - I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
3 - Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
As it is, my understanding is that when n reaches 0, the loop should return an empty array, and not the full array that has been created with the recursion.
~ ~ ~
FINAL EDIT / UNDERSTANDING: Thanks to every answer! So, if my current understanding is correct, recursion does not loop the function, as I initially thought – instead, it sort of "opens" a call of "chained functions" that goes downwards until it finds and resolves the base case.
When that "base" case or function is resolved, the chain goes "up", so to say, resolving each recursive step above, .unshifting each n value, and returning the resulting array "upwards", until we reach the initial n value, at which point the final array is returned, and the function is exited.
I think I (sort of) got the point now! Actually writing recursive functions won't be easier though, but I have a better grasp of how the process actually takes place.
Why is the array declared as a constant and assigned to the recursion call?
It is not. The array is variable. Every call to this function will return a new array. No arrays get "overwritten". const in JavaScript is merely used to declare a constant variable which has lexical scope (that is, the variable arr ceases to exist after return arr) and is constant (can't be mutated / assigned to).
I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
You can't position it above since the recursive call is required to obtain arr in the first place. After you have counted down from n-1 to 1, you must prepend n at the first position, which is what unshift does.
Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
There is no such thing as an "else loop". The reason for returning the array there as well is that you want countdown(n) to return the array it has just generated, using the array countdown(n-1).
As it is, my understanding is that when n reaches 0, the loop should return an empty array, and not the full array that has been created with the recursion.
That's exactly what if (n < 1) return []; does.
That said, for the task at hand the recursive implementation given is highly suboptimal. First of all, you don't need the else since return will exit the function anyways:
function countdown(n) {
if (n < 1) return [];
const arr = countdown(n - 1);
arr.unshift(n);
return arr;
}
second, this is most readable (and performant) as a simple for loop:
function countdown(n) {
const arr = Array(n)
for (let i = 0; i < n; i++) arr[i] = n - i;
return arr;
}
(bonus: the array size being known ahead of time can be leveraged using the Array constructor to prevent JS having to resize the Array as you push elements).
third, the current implementation has a quadratic time complexity of O(n²) because arr.unshift is a linear-time operation which is applied n times to arrays of length 0 to n-1.
Recursion is extremely useful when you need the stack (f.E. in a depth-first traversal). Creating a countdown doesn't require a stack. This is not using but rather abusing recursion to implement a highly suboptimal algorithm. If you - for whatever reason (perhaps you are forced to use a purely functional language that has no loops, only recursion?) - wanted to replace the perfectly fine for loop with a recursive call at all cost, simply use tail recursion and lexical scope (closures):
function countdown(n) {
const arr = [];
function loop(n) {
if (n < 1) return;
arr.push(n);
loop(n-1);
}
loop(n);
return arr;
}
or if you don't like closures, what about an optional arr parameter? This allows turning the recursion around since the caller now has the arr before the callee and can thus append its number before subsequent recursive calls do the same:
function countdown(n, arr = []) {
if (n < 1) return arr;
arr.push(n);
countdown(n - 1, arr);
return arr;
}
same result, but (IMO) cleaner, and most importantly, significantly faster code. Try running all these functions on n = 1e6 and you will see that the freeCodeCamp one fails to complete in a reasonable timeframe whereas the other ones finish almost instantly.
It might help to clarify how the code is working a little. Hopefully, the explanation clears up your questions a bit. Let's start with the most basic case:
countdown(0) - When you call countdown(0) your if-block runs and returns an empty array []. So we see that countdown(0) outputs []. This is the base case.
Next, let's imagine we call the function again, this time as:
countdown(1) - In this scenario the else block runs. We see that it calls countdown(0), which means the interpreter executes the function again, passing n as 0. Above we saw what happens when countdown(0) is called, it returns an empty array [], so that's what gets assigned to arr within the else block. We then unshift n (which is 1 here) onto this empty array, which adds 1 to the start of the array stored in arr. This is then returned. So we say that countdown(1) outputs [1].
Now let's see what happens if we were to call the function again with 2:
countdown(2) - Again, the else block runs, triggering a call to countdown(1). We saw above what occurs when this is called, we get back an array with [1], which is stored in the local variable arr, and we then .unshift(2) so that 2 is added to the beginning of the array which we then return. So we see that countdown(2) returns [2, 1].
When thinking about recursion, it can be helpful to think of it as breaking down your input until it's at a small enough value that it can't be broken down anymore (your base case). Once you hit that, you work your way back up, stitching together your previous calls now that you have "solved" the sub-problems.
To answer your questions more directly:
arr is assigned to the recursive call so that we can build upon the results it returned. If countdown(1) returns [1], then we can build onto that result by storing it in arr and unshift 2 onto it to calculate countdown(2). It could have been made let, but const is commonly used if the variable doesn't need to be reassigned. We don't want to move arr outside of the function as then calling countdown() multiple times will modify the same global array. You would also need to change your code so that we can gradually "build-up" the result by further recursive calls if you did that also, which is more of a "loop mindset" than a "recursive mindset".
Moving the .unshift() component before the recursive call wouldn't work as you wouldn't have anything to unshift to. The recursive call is what provides you with the array result of countdown(n-1) so that you can build onto that later by using .unshift().
Returning your array in the else block is what allows you to obtain results when n is greater than 0. In the countdown(1) example above, we need to return arr so that it outputs [1]. This needs to be in the else block because we're returning arr which is defined in that else-block (and hence can't be accessed outside of it).
I would just add to the first part of LMD's answer (I can't comment because of no reputation):
When you define constant array, you are not saying that the array is constant and can't change. You are creating constant reference to that array. So you still can edit its elements, but can not overwrite it with new array.
More info here:
https://www.w3schools.com/jS/js_array_const.asp
1 - Why is the array declared as a constant and assigned to the recursion call? Shouldn't the array be better declared outside as a variable (since it changes value)– and doesn't its current position mean that it is declared (and over-written) every time the recursion takes place?
The const declaration is block-scoped. The reference only exists inside the else-block. There is not one generic arr that is shared by each call. Instead, inside of each call to countdown, we get an if-condition. If that fails, we create the else block and assign a brand-new arr variable. When this is returned, and the function ends, that reference is gone, and only the function return value remains.
2 - I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
First of all, be careful of "the function loops itself". Recursion is a different mechanism from looping; and although they have equal power, they are not at all the same.
But recall that the recursive call returns the result of calling this with n - 1. So
const arr = countdown(n - 1);
when called with n of 4 sets arr to [3, 2, 1]. Now that we have this value, we can do the unshift operation on it.
3 - Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
Again, you need to stop thinking in terms of looping.
Here's an alternative way of thinking about it: For any smaller value of n than our current value, we assume that countdown will magically provide us the correct answer, and then, when we call it, we will prepend our n value to the result.
Recursion is simply the trick of making that magic work. If we know the answer to our simpler problem(s) and we can use these to build the answers to our more complex ones, then all we need is a way to stop breaking down into smaller problems: our base cases.
So, for countdown (3), we call our magical countdown (2) function, which returns [2, 1] and use unshift to prepend 3 and get [3, 2, 1]. Of course if we wanted to dig into how that magical countdown (2) actually worked, we could and we'd see that it called the magical countdown (1), getting [1] and prepended 2 to get [2, 1], and so on. The magic, it turns out, is rather mundane.
But that's what recursion is, at its core: the notion that there is a sequence of simpler and simpler problems, bottoming out in some base case(s), and that we can build our more complex result based on those simpler ones. But while writing the logic to do this, it's easiest to think of those simpler ones as magical.

Could you explain me this freecodecamp recursion function, please?

Here's the code
function rangeOfNumbers(startNum, endNum) {
return startNum === endNum
? [startNum]
: rangeOfNumbers(startNum, endNum - 1).concat(endNum);
}
I understand that until startNum equals endNum it will recall itself, but the thing that I don't understand is where the value is stored?
Say for example it's rangeOfNumbers(3,6)
So it's gonna be like this:
6-1
5-1
4-1
Right? And each time the numbers are added to the array and we get [3,4,5,6], but I don't understand how and where it stores this array.
If I'm not mistaken, concat merges two or more arrays, but there are no arrays.
I just want to have a full understanding of it. Otherwise, I won't remember it and won't be able to use it.
As soon as the breaking condition is meet (startNum === endNum), an array is returned ([startNum]). Such object has a concat function which bubbles up another array and so on until the first call.
In resume: The array starts at the breaking condition and endNum is concatenated on each return value, which again, is an array.
If I'm not mistaken, concat merges two or more arrays, but there are
no arrays.
You are absolutely right about this. endNum is not an array. However, if you read further down in the docs, the argument supplied to concat can either be an array or value(s).
Parameters
valueN Optional
Arrays and/or values to concatenate into a new array. If all valueN parameters are omitted, concat returns
a shallow copy of the existing array on which it is called. See the
description below for more details.
Javascript functions are variadic by design, so it looks like the concat method takes advantage of this to accept individual arguments to append to a copy of the existing array.
I understand that until startNum equals endNum it will recall itself,
but the thing that I don't understand is where the value is stored?
It may help if you write the function using explicit variable names
function rangeOfNumbers(startNum, endNum) {
if (startNum === endNum) {
return [startNum];
}
const currentRange = rangeOfNumbers(startNum, endNum - 1);
return currentRange.concat(endNum);
}
As you can see, the value (or endNum) is stored inside the array returned by calling the function for the range [startNum, endNum - 1].
The trickiest part about recursion lies in the fact that you don't see where those intermediate values are stored, until as if by magic, we get to the base case and lo and behold, we have an array!
The answer is that the array returned by rangeOfNumbers(startNum, endNum - 1) is kept in stack memory until it needs to be returned. The discussion of stack vs heap will be quite off-topic here, but it is pretty much covered here:
What and where are the stack and heap?
When a block (something that starts with { and contains statements) is entered into, a new "variable environment" is created. You could think of this as something that maps each identifier for that block execution to its value.
Each time a function is called, a new such environment is created.
In this case, the parameters startNum and endNum are stored in an environment the first time the function is called. Then, when the interpreter runs across
rangeOfNumbers(startNum, endNum - 1).concat(endNum);
The currently running function (the one linked to the environment just described) gets suspended, and a new function is put onto the call stack, creating another environment. The process repeats itself until the end of the recursive logic is reached and [startNum] is returned (or the stack is blown). At that point, you have a bunch of rangeOfNumbers functions in progress, each with their own environments. At that point, you could imagine it as something like
rangeOfNumbers { startNum: 3, endNum: 5 } (this is the intial call; currently suspended)
rangeOfNumbers { startNum: 4, endNum: 5 } (currently suspended)
rangeOfNumbers { startNum: 5, endNum: 5 } (executing, about to return)
The innermost function returns its [startNum] and terminates, and so the last function resumes, now with the useable return value:
: rangeOfNumbers(startNum, endNum - 1).concat(endNum);
gets evaluated to, when the endNum is 5:
: [5].concat(endNum);
The process continues up the stack until all of the recursive calls are finished, and you just have the initial
rangeOfNumbers { startNum: 3, endNum: 5 }
which then finishes itself.
So, while the recursive calls are going on, the values from the prior calls are stored in each of those calls' environments.
If I'm not mistaken, concat merges two or more arrays, but there are no arrays.
[startNum] is an array returned at the innermost point of recursion. concat can also create a new array by taking one array as an argument, and the value to append as another. For example, [5].concat(4) evaluates to [5, 4].

Why does my variable get reinitialized to its original value everytime I go through the recursive iteration, but somehow keep the new value

So I just solved a code challenge on AlgoExpert and I was trying to get a deeper understanding of why my code works so i used PythonTutor to visualize the execution of the code, and I am curious to know why each time a recursive call is made arraySum re-initializes to 0 but somehow keeps the value it had previously that consisted of the sum of elements in the array.
Here is the problem I solved
Here is my code:
function productSum(array, multiplier=1) {
let arraySum = 0;
array.forEach(el => Array.isArray(el) ? arraySum+=productSum(el, multiplier+1) : arraySum += el)
return arraySum * multiplier
}
productSum([5, 2, [7, -1], 3, [6, [-13, 8], 4]])
Here is a link to the visualization in PythonTutor
It becomes clear when you realise every execution of productSum has its own arraySum variable. Although the name is each time the same, it really is a variable that is unrelated to the other variables out there (in the recursion tree) that happen to have the same name.
Each time a recursive call is made, the caller's productSum ends up on a stack frame, from where it is restored when the recursive call returns, and then the returned value is added to its own productSum.
And so a value is accumulating while backtracking out of recursion, and all those different productSum variables each play a little role in carrying an intermediate result, until it was returned to the caller, who accumulated it further, ...etc.
Your algorithm contains recursive calling: your productSum at times calls itself again with different arguments, at a deeper level in your data structure.
Each call gets its own call frame, with its own unique scope. Your let arraySum = 0 is defined within the function (scope), therefore every call has its own arraySum (initialized to 0) apart from other calls.
I've made another diagram, where every box/oval is a call frame:
Didn't display the multiplication part.
Every call frame has its own scope with its own array/multiplier/arraySum variables. Every call starts with arraySum = 0, uses .forEach to add to this variable, then returns the value stored in the variable. That's why in the Python visualizer you see arraySum become 0 at times. It's not that an actual arraySum somewhere becomes 0, it's because you're looking at a brand new function call, right after the let arraySum = 0 statement.
Changing arraySum in one scope has no affect on the arraySum of another scope, even if both scopes are for the same function (but different function call!).

How to write a function to filter an array in javascript?

I'm wanting to write a function to filter objects of an array on some condition. I know that when you pass an array to a function, the changes you make to the array inside the function also change the array outside the function (if you are able to make the distinction in the first place anyway). For example,
a=[1,2,3]
function addItem(list){
list.push(4)
}
addItem(a)
// now a = [1,2,3,4]
Also, even though the filter method only returns a new array and doesn't change the array it was called it, it can be used to change that array by writing a = a.filter(someFunction). I imagine there is some subtlety I'm missing here because when I try to combine these two facts to write a function filtering an array on some condition, the original array isn't changed at all. Here is an example:
function isEven(n){
return (n%2 == 0)
}
function filterOdds(list){
list = list.filter(isEven)
}
a=[1,2,3,4]
filterOdds(a)
// a is still [1,2,3,4]
I suppose I really have two questions:
Why doesn't the above code change a to [2,4]? and
How can I write a function to filter lists as I'm trying to do above? Of course, you could get the job done without the function, but what I really want is to filter multiple arrays in a single function. This just seemed like the relevant simplification.
In your second code, you are reassigning the argument only. Reassigning a variable will, in almost all cases, have no side-effects; all it'll do is change what further references to that variable in that scope will refer to. If there happens to be an outer variable which gets passed as an argument, that outer variable will not get reassigned regardless of what happens inside the function, unless the function deliberately reassigns the outer references.
You could implement this by not passing the list argument - have filterOdds reassign the outer variable:
function isEven(n) {
return (n % 2 == 0)
}
function filterOdds() {
list = list.filter(isEven)
}
list = [1, 2, 3, 4]
filterOdds()
console.log(list);
If you want a more generic solution that'll work for any array passed, since dynamically reassigning an outer variable inside a function isn't possible, your only decent remaining option would be to return the new array and, when calling the function, assign the result:
function isEven(n) {
return (n % 2 == 0)
}
function filterOdds(list) {
return list.filter(isEven)
}
a = [1, 2, 3, 4]
a = filterOdds(a)
console.log(a);

Mind boggling Function for Looping

If there's anyone who can help me understand this one, I will be impressed. It's #28 from John Resig's advanced JavaScript.
Here's a function for looping, and, if you look at the tutorial, it seems to run 3 times producing the 6 Pass below.
Can you please explain in plain language in as much detail as possible what is happening in this program, explaining along the way:
fn.call(array, array[i], i) Why are there these 3 parameters to the function and how do they all work. Does the function first deal with array, then array[i], and then i? What's happening when all that is going on?
Also, in the function, I understand that i++ goes up every time that it goes through the array.length, but what triggers num++ to increase it's value, and in what way is value == num++
In function(value, i), what is value? Is value alternately 0,1,2 of the loop array? If so, how do those loop array numbers get passed as a parameter in function(value, i)
this instanceof Array what is this trying to show? How?.
Code:
function loop(array, fn){
for ( var i = 0; i < array.length; i++ )
fn.call( array, array[i], i );
}
var num = 0;
loop([0, 1, 2], function(value, i){
assert(value == num++, "Make sure the contents are as we expect it.");
assert(this instanceof Array, "The context should be the full array.");
});
PASS Make sure the contents are as we expect it.
PASS The context should be the full array.
PASS Make sure the contents are as we expect it.
PASS The context should be the full array.
PASS Make sure the contents are as we expect it.
PASS The context should be the full array.
The function is an anonymous function, that is, a function without a name. The full second argument to loop is
function(value, i) {
assert(value == num++, "Make sure the contents are as we expect it.");
assert(this instanceOf Array, "The context should be the full array.");
}
which is a complete anonymous function which takes three arguments: this (the object for which it is a method), the current value, and the loop counter.
loop iterates over the array, and uses fn.call to invoke the anonymous function, passing it three arguments; here, the array object must be explicit, because call can't know what context the function reference it's being invoked on should be invoked in (that is, what to make this in the call).
The anonymous function, as invoked by loop, receives the array as this. The second ASSERT verifies this. It also expects that the array's value is [0, 1, 2] and verifies this by incrementing num on each call and comparing that to the passed array element.
So, following the execution chain:
num is declared and initialized to 0.
loop([0, 1, 2], function ...) is invoked.
loop invokes fn, the anonymous function, with the array (as this), its first element, and i which indicates the element offset. (i is never actually used.)
The anonymous function ASSERTs that it was passed the expected first element, 0, by comparing against num and incrementing num afterward.
The anonymous function ASSERTs that its this is an Array.
loop invokes fn as in #3, but with the second array element.
The anonymous function again performs its ASSERTs, this time comparing the passed second array element, which is expected to be 1, against num (which, because of the post-increment in step 4, is 1).
loop invokes fn as before with the third array element.
The anonymous function does its ASSERTs again, this time comparing the expected array element 2 against num whose value is now 2.
fn is a function pointer to the anonymous function that was created when invoking loop.
a) There are three parameters because the first one is a reference to the object you are going to be acting on, i.e. the array, the 2nd parameter is just current value in the iteration of the array, and the third value is location of that element in that array.
b) when you call num++, it increments the value of num. The post increment returns the value (aka assigns to value) and then increments num.
c) value is the 2nd parameter passed in by fn.call a couple lines up.
d) this refers to the array that you passed into the first parmater in your fn.call() invocation.
I think an easier way to understand what's going on is to remove the usage of call and instead invoke the method directly - the old fashioned way.
function loop(array, fn) {
for(var i = 0; i < array.length; i++)
fn(array[i], i);
}
var num = 0;
loop([0, 1, 2], function(value, i) {
assert(value == num, "Make sure the contents are as we expect it.");
assert(this instanceof Array, "The context should be the full array.");
num++;
});
If you run this code on the source url, you would see the this instanceof Array test fails each time, since this is not pointing to an instance of Array anymore.
To make this point to an instance of Array, we need to invoke the method using call or apply.
fn.call(array, array[i], i);
The first parameter is what this will point to. All other parameters here are passed as arguments to the function being called. Note that we can cheat here to pass the test. Instead of passing the array object that we are looping over, you can pass any Array object to make the second test pass.
fn.call([], array[i], i);
fn.call([1,2,3,4,5], array[i], i);
I don't actually know javascript but I believe I understand whats going on...
The first four lines are defining a function called loop. Loop takes in two variables, an array and a function. The only reason why I believe the second parameter is function is because the coder called call on the variable which probably calls the function. The function loops through the elements of the array and passes them to the function somehow through the call method. You should probably look this part up.
After the definition, num is defined to start at zero.
Then comes the fun part. The function loop is called with the parameters [0, 1, 2] and an anonymous function that was created on the spot. function(value, i) and everything after it is the definition of this anonymous function. It has no name but it just defined for this function call. The function checks to make sure that value equals num and increments num after it and then checks to makes sure the variable is an array.

Categories