rangeOfNumbers with global variable

Dear community!

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;
};

You can do this using something called default params

Hello @CodinP,

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)

...
// recursive case
else {
    arr.push(startNum);
    rangeOfNumbers(startNum + 1, endNum);
  }

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 :slight_smile:

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

https://pythontutor.com/live.html#mode=edit

Thanks for your detailed reply!

  1. return: If I understood you and MDN correctly, it is necessary to have a return statement to avoid getting returned the return value “undefined”?

  2. 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?

Hello @CodinP,

Yes, that is the way I understand it.

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:

I used that technique in this anwser:

Cheers and happy coding :slight_smile:

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?

Thanks for correcting my nomenclature, I meant “initialize” instead of “declare”!

Hello @CodinP ,

This is how I understand it:

  1. you can create an array using array literal notation
  2. according to JavaScript Literals - w3resource :

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

Cheers and happy coding :slight_smile:

Hi @Diego_Perez ,

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)?

Hello @CodinP,

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:

[4]
[4].push(3) 
[4,3].push(2) 
[4,3,2].push(1) 
[4,3,2,1]

Because there is only one array.

If you look the code:

 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 :slight_smile:

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.

I didnt mean to correct you, the two terms are very much interchangeable so no worries :wink:

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.

1 Like

Ok, thanks again, I gotta dig more inside the DNA of Javascript to understand whats going on “behind the scenes”!

1 Like

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.

Thanks :slight_smile:

Cheers and happy coding

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.

1 Like

You are wrong:

Magic (programming) - Wikipedia

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.

1 Like