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].
Related
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.
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.
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!).
This question already has answers here:
How does sort function work in JavaScript, along with compare function
(7 answers)
Closed 3 years ago.
I understand that the .sort() method sorts an array alphabetically, as if it was a string. Even if your array is made up entirely of numbers.
I also understand that to sort an array numerically, the code is:
myArray.sort(function (a,b) {
return a-b;
});
The problem is I don't understand WHY this works. The function I wrote is supposed to take two arguments, 'a' and 'b', but where are these arguments coming from? The array? How does it know to do that? Then, the function returns some number. If 'a' and 'b' were 10 and 5, respectively, my function would be returning '5'. Now I'm doing myArray.sort(5); How the heck does that magically sort my entire array numerically???
Thanks for the help.
The function I wrote is supposed to take two arguments, 'a' and 'b', but where are these arguments coming from?
To determine what order array elements belong in, the Array.sort function picks pairs of elements from your array and passes them into your comparison function as a and b. The result returned by your function determines what order the two elements get placed in - if it is negative, then it knows to put a before b; if it is positive, it knows to put a after b. The actual number doesn't matter, just whether it's negative or positive.
A somewhat less confusing way of writing your example might be:
function myComparisonFunction(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
myArray.sort(myComparisonFunction);
The sort function operates by (cleverly) comparing pairs of array entries. Based on the result of the comparison, it knows the order for a particular pair of values. How does it know? Because it trusts the sort callback function to return
-1 (or any negative number) if the first entry should go before the second;
1 (or any positive non-zero number) if the second entry should go before the first;
0 if the entries are the same (meaning that the order doesn't matter*)
By repeatedly comparing pairs of array entries, and doing so systematically, the sort function eventually determines the overall correct ordering according to the inherent rules imposed by the comparator function.
The nature of the systematic process used by the JavaScript sort implementation is unspecified. It's a good bet that modern JavaScript runtimes use a fairly well-optimized quicksort.
* when two entries in the array are equal according to the comparison function, two things might happen: the sort function may leave them in the order in which they originally appeared, or it may swap them. By the simple logic of the sort process, either of those actions are OK; if you're sorting a list of names, and "Thomas" appears twice, then it doesn't matter which "Thomas" is first and which is second since the strings are identical and indistinguishable. However, if a sort comparator function is plucking properties from pairs of objects, sometimes it does matter that entries remain in original order if their comparison returns 0. A sort function that ensures that behavior is called stable. Whether a JavaScript implementation is stable or not is undefined by the spec.
Now I'm doing myArray.sort(5);
That's not true. Passing in a function that (sometimes) returns 5 is not at all the same thing as passing in 5.
What happens is, you pass in a function that sort can call whenever it needs to know which of two values should be considered greater. sort will pass two arguments to that function. If the function returns a negative value, that means "the first argument is less than the second argument"; if it returns zero, that means "the two arguments should be considered equal"; if it returns a positive value, that means "the first argument is greater than the second argument". (This might seem a bit hackish and arbitrary — and maybe it is — but it has a long history in C-like languages.) So, for numeric comparisons, it's convenient to just return a-b, since that meets these requirements.
Note that you cannot predict exactly which comparisons sort will decide to perform, so you need to make sure to pass in a function that behaves consistently. For example, if your function says that 1 is less than 2 and 2 is less than 3, then your function also needs to say that 2 is greater than 1, that 3 is greater than 2, that 1 is less than 3, and that 3 is greater than 1. Otherwise, you might get random-seeming results.
(You may be interested in the MDN page on Array.prototype.sort, by the way. It gives a number of other examples, which might help clarify this for you.)
This comparator function, as used in Array.sort (ES5 spec., MDN), should return a number where..
if a < b, return less than 0
if a > b, return more than 0
else (a == b) return 0
So, let's look at some cases:
a b a-b meaning (see above)
---- ---- ---- ------
10 5 5 "a > b"
5 10 -5 "a < b"
5 5 0 "a == b"
The comparator function defines ordering between elements which is used in a Comparison Sort algorithm:
A comparison sort is a type of sorting algorithm that only reads the list elements through a single abstract comparison operation (often a "less than or equal to" operator or a three-way comparison [aka "comparator"]) that determines which of two elements should occur first in the final sorted list.
(Note: The sort by Array.sort is not guaranteed to be stable.)
So it runs through and applies whatever algorithm it wants to sort the values in your array. All it needs to know is how to tell which one is "bigger" or which one should be forward indexed.
I guess it helps to have some more familiarity with how javascript works. With javascript you can assign variables as functions and as such you can pass functions as parameters. It comes in handy in situations like this and for asynchronous callbacks (if you've played with jquery you've likely seen this in ajax calls and they're called for after various transformations are completed...like if you want one element to move 500px and then as soon as it completes it you move it down 500px, you put the down move function in the call back).
long story short, the a and b values come from your array, which values it pulls out are up to the sort function.
the awesome thing about the comparison function of sort is say you have an object that looks like this:
sales:[{sale:{"brand":"Nike", "person":"Eric", "total":5000}}, {sale:{...}}]
you can do use a sorting function to sort by brand then total like this:
sales.sort(function(a,b){
if (a.brand==b.brand){
return a.brand>b.brand ? 1 : -1;
} else {
return a.total-b.total;
});
I tend to think stuff like that is really neat...it lets the language use the most efficient sorting algorithm it feels like using and all you have to do is show it how to determine if one is "bigger" than another.
a and b two consecutive values in array to compare
and subtraction can be 0, less than zero and greater than zero..
if greater than zero, it means prior element is larger so it needs to be pushed further down
if it is less than zero, it means it needs not to be pushed down..
In your example, you're NOT doing myArray.sort(5);, because you pass a function (that is used to guide the sorting algorithm), not a number.
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.