I struggle with to things concerning the “Range Of Numbers”-Challenge:
1) Global array
I could only solve the challenge using a globally accessible array. If I understood guidlines from previous challenges correctly, local variables are preferable. But I couldn’t manage to declare an array that is not swept empty each time the recursion call happens.
I wonder if you could give me some hints on how to solve the challenge with a local array?
2) second “return”-statement
I don’t understand why I need this (right before the function is closed).
In the final sequence, when startNum equals endNum (which inevitably happens), the final content of the array is returned anyway…
Thanks for your help!
const arr = []
function rangeOfNumbers(startNum, endNum) {
// const arr = []; at this line I declared an array but obiously it was emptied
if (startNum === endNum) {
arr.push(endNum);
return arr;
}
else {
arr.push(startNum);
rangeOfNumbers(startNum + 1, endNum);
}
return arr;
};
Hints:
1.- (Recursion in) JS uses some abstractions: call stack and nested calls
2.- The order in which elements come off a stack gives rise to its alternative name, LIFO (last in, first out)
3.- In your code the “last element” comes from this (we can call it: the base case):
// base case
if(startNum === endNum) {
arr.push(endNum);
return arr;
}
4.- In the base case, is possible to return “a local array”
5.- In your code, when the base case is evaluated to false: this is executed (we can call it: the recursive case)
6.- If you use “a local array” you will have to change the code of the recursive case
the final content of the array is returned anyway…
Are you sure? if I delete the “second” return statement I got undefined.
According to MDN that is the correct behavior.
By default, functions return undefined. To return any other value, the function must have a return statement that specifies the value to return.
Cheers and happy coding
Notes:
I wrote a short faq about this exercise, if you search the forum you can find it (be aware, the faq is the solution with some comments and “visualization” of the execution of the code)
You can use python tutor to “see” the code execution
return: If I understood you and MDN correctly, it is necessary to have a return statement to avoid getting returned the return value “undefined”?
I still can’t figure out where to declare the array (inside the recursive else-statement) upon which the push- and the recursive function are called after the declaration.
I have a vague guess that the problem lies within the arrayalways being re-set to the value it was declared once the recursive call gets executed. How can this be avoided?
How many times is the base case executed?
How many times is the recursive case executed?
If something is executed “only one time”, it can be “re-seted”?
If you read my first answer:
1.- (Recursion in) JS uses some abstractions: call stack and nested calls
2.- The order in which elements come off a stack gives rise to its alternative name, LIFO (last in, first out)
What information did you find about nested function calls?
If JS uses the abstractions “call stack” and “nested functions calls”, why you want to return an array from the recursive case?
Why not return an array from the base case?
Did you used python tutor to see the execution of the code?
Then you have an Hypothesis, and you can make experiments:
The recursion is a way to repeat the same code action over and over, until you reach a result you are satisfied with, very similar to how you would make a loop. You simply put the logic you want to repeat inside the recursive function, isntead of within a loop and also, make sure to put a final condition, to return a solid base value, as you cant return the function over and over, or it would produce an infinite loop. Remember, the function will keep calling on itself, until a point you reach the aimed result; it will keep repeating the same action, only with different parameters. Just like when you add a range of numbers ,one to another. Like 2+3+4+5; in every step, you add 1 to the previous number.
The point with recursion is for the said function to call itself and utilize the returned value. I dont see how in the current case, calling on itself and receive undefined can be of any use.
You will initialize the array, the base value, in the base conditon, the final call of your function. In the remaining calls, you will be working with what the function has returned as a value; but ofc, it depends on what you mean by declare the array
@Diego_Perez
Thanks for your sources (videos, faq, pics), I dug through some of it and will continue to do so.
I think one mistake I made was not knowing one can assign the content of an array to a function just like you would do with a variable.
´´´
let arr = rangeOfNumbers(startNum + 1, endNum);
´´´
I don’t understand one part of your faq: In the base case, you just return [endNum]. How is [endNum] automatically attached to the array that results from the recursive call without applying a push.method?
an array literal is a list of expressions, each of which represents an array element, enclosed in a pair of square brackets [] . When an array is created using an array literal, it is initialized with the specified values as its elements, and its length is set to the number of arguments specified. If no value is supplied it creates an empty array with zero length.
As you can see, you can create an array with elements (non empty) without push, an example:
// an array with one element without using push
let arr = [4];
console.log(arr); // [ 4 ]
console.log(Array.isArray(arr)); // true
console.log(arr.length); // 1
function returningAnEmptyArray() {
return [];
}
console.log( returningAnEmptyArray() ) // []
console.log( Array.isArray(returningAnEmptyArray())) // true
// an array with one element without using push
function returningAnArrayWithOneElement(elem) {
return [elem];
}
console.log( returningAnArrayWithOneElement(4)) // [ 4 ]
console.log( Array.isArray(returningAnArrayWithOneElement(4)) ) // true
I understand the part where on how to create an array. I don’t understand how and where the array from the recursive case and the base case get mingled (using your code which works obviously!).
If you call the function with the arguments 1, 4, the recursive case would stop after returning [1, 2, 3]; then the code jumps to the base case, where [4] is returned.
How does this result in returning [1,2,3,4] and not [1,2,3] [4] (two seperate arrays)?
My comments below are about this code (I will use this version, and not the unshift one, because with this code is easier to see the call stack):
function rangeOfNumbers(startNum, endNum) {
if(startNum === endNum) {
return [endNum];
} else {
let arr = rangeOfNumbers(startNum + 1, endNum);
arr.push(startNum);
return arr;
}
}
let result = rangeOfNumbers(1,4);
console.log(result);
// [ 4, 3, 2, 1 ]
If you use push(arr.push(startNum)), that is not correct. The code returns [ 4, 3, 2, 1 ].
The code returns [ 4, 3, 2, 1 ]: because JS uses nested function calls and a call stack.
That is not correct because JS uses nested function calls and a call stack. What happens is something like this:
Nested Function Calls:
// recursive case
let arr = rangeOfNumbers(1, 4);
let arr = rangeOfNumbers(2, 4);
let arr = rangeOfNumbers(3, 4);
// base case
let arr = rangeOfNumbers(4, 4);
Call Stack:
// base case
rangeOfNumbers(4, 4); (last in, first out)
// recursive case
rangeOfNumbers(3, 4);
rangeOfNumbers(2, 4);
rangeOfNumbers(1, 4);
Because JS uses a call stack and nested function calls we get something like this:
let arr = rangeOfNumbers(startNum + 1, endNum);
arr.push(startNum);
return arr;
let arr is an array only after the base case is executed ( if(startNum === endNum) is evaluated as true an the code between the { } is executed). So, there is only one array (not two).
Later you push startNum to that array (arr).
Then you return the array (arr).
Because JS uses a call stack and nested function calls we get something like this:
return [4] // base case (last in, first out)
[4].push(3) // recursive case
return [4,3] // recursive case
[4,3].push(2) // recursive case
return [4,3,2] // recursive case
[4,3,2].push(1) // recursive case
return [4,3,2,1] // recursive case
Cheers and happy coding
Notes:
When a function makes a nested call, the following happens:
The current function is paused.
The execution context associated with it is remembered in a special data structure called execution context stack.
The nested call executes.
After it ends, the old execution context is retrieved from the stack, and the outer function is resumed from where it stopped.
They are sometimes used interchangeably, but technically they really aren’t.
Declaring a variable tells the interpreter or compiler that it exists. Initializing the variable tells the interpreter or compiler what tho initial value of the variable is. They are often done together, but they are separate things.
let myVar; // declared, not initialized
let myOtherVar = 42; // declared and initialized
Sometimes you really need to know the terminology to understand documentation or other descriptions of code.
Yes, I think that is a good idea. As far I can see you understand recursion and you are just having difficulties with the implementation (JS). Sometimes JS uses too much magic, and make everything more difficult to understand.
Just to be clear, there really is no magic going on here. You don’t need to do anything special with JS to get recursion to work. Recursion is just a method for solving a problem. It is not unique to JS and it doesn’t require any special syntax or understanding of JS to implement it. If you understand the basics of how functions work (i.e. pass in inputs and get back a value) then you have enough information to understand and implement recursion.
Yes, there is a little “trick” to it in order to get the recursion to stop. And it can sometimes take a little extra thinking to understand how you use the return value from the recursive call to build up the answer. But you don’t need to take a deep dive into JS to understand that. You just need practice thinking about it and coding it.
In the context of computer programming, magic is an informal term for abstraction; it is used to describe code that handles complex tasks while hiding that complexity to present a simple interface. The term is somewhat tongue-in-cheek, and often carries bad connotations, implying that the true behavior of the code is not immediately apparent.
If you think that my explanation is wrong just say it clearly or even better: write a better explanation.
Please don’t quote me for this sort of thing again.
@bbsmooth@Diego_Perez I think the take-away message for any beginning programmer (like me) is to understand as much as possible about the language and logic you use and to reduce the “black-box”-amount of one’s understanding of what’s happening when you execute your code - even if the result is the desired outcome.
I guess you both know the book “Humble Pi” by Matt Parker - even the best coders and engineers make “simple” mistakes such as not taking into consideration whether to start a count from 0 or 1 XD.