Question about recursive JS - from freecodecamp - javascript

There is this function:
function countup(n) {
if (n < 1) {
return [];
} else {
const countArray = countup(n - 1);
countArray.push(n);
return countArray;
}
}
console.log(countup(5));
How is [] being pushed first? From the code, it looks like it is being counted down from n. Can someone explain?
Bonus question: Why write like this when I can write:
function countup(n){
let answer = []
for (let i = 1; i <= n; i++){
(i<n) ? answer.push(i)
: answer.push(n)
}
return answer
}
console.log(countup(5));
which is a shorter function. thank you in advance!

Even though the numbers are in descending order starting from n, they are actually being pushed smallest number first. This is because before the push() operation can happen we keep going down in the recursion stack when we call countup(n - 1) every layer of the recursion. This is why we reach a point where n < 1 i.e. 0 where an empty array is returned.
Next, we encounter the first array.push() operation and so the first number that is pushed into the array is actually 1 and not n. Next, the recursion layers start winding up and we push numbers 2, 3, and so on till n.
Yes you can achieve the same using a traditional for loop, but I guess they just want to explain how recursion works.

To understand recursions, sometimes you need to follow the execution of the code manually. Now the following line causes the recursive call and subtracts one from the number and then passes it to the recursive call.
const countArray = countup(n - 1);
Now imagine, if you passed 5 to the initial function call like so:
countup(5);
As 5 is not less than 1, the else block will be executed. The first line in the else block is the recursive call. So when your program encounters the recursive call, it stops execution of the current function until that recursive function call is concluded/returned. So the first recursive call will be:
const countArray = countup(4);
consequently, the same process will be repeated until n equals 0. As 0 is smaller than 1, the if condition will evaluate to true. and it will return an empty array.
After the recursive call, it pushes the number to the array returned by recursive call. so the array at that point will contain [0] and then
[0, 1] and it will keep adding numbers till the all recursive calls have been computed and then it will execute the rest of the intial function call.
Often times, recursion can be replaced by loops but thats not the case always. As a programmer one should be aware of this important topic.

Since countup() is fully divided by an if statement that depends on n, let's first see what exectutes for different vales of n.
To make it a lot easier to follow, we'll append our current n to countArray's variable name:
n == 5:
const countArray5 = countup(4);
countArray5.push(5);
return countArray5;
⋮
n == 1:
const countArray1 = countup(0);
countArray1.push(1);
return countArray1;
n == 0:
return [];
Now let's sequentially expand each inner call to countup(n):
countup(5) {
const countArray5 = countup(4);
countArray5.push(5);
return countArray5;
}
countup(5) {
const countArray5 = countup(4) {
const countArray4 = countup(3);
countArray4.push(4);
return countArray4;
};
countArray5.push(5);
return countArray5;
}
Expanding until countup(0):
countup(5) {
const countArray5 = countup(4) {
const countArray4 = countup(3) {
const countArray3 = countup(2) {
const countArray2 = countup(1) {
const countArray1 = countup(0) // countup(0) returns [] so countArray1 = [];
countArray1.push(1); // Then push '1' to countArray1;
return countArray1; // Return [1] at counterArray2 = countup(1) above
};
countArray2.push(2); // countArray2 was [1], now push '2' to make it [1,2]
return countArray2; // Return [1,2] at countArray3 = countup(2) above
};
countArray3.push(3); // Push '3' to countArray3 => [1,2,3]
return countArray3; // return [1,2,3] at countArray4 = countup(3)
};
countArray4.push(4); // Push '4' to countArray4 => [1,2,3,4]
return countArray4; // Return [1,2,3,4] at countArray5 = countup(4)
};
countArray5.push(5); // Push '5' to countArray5 => [1,2,3,4,5]
return countArray5; // countup(5) returns [1,2,3,4,5]
}

Related

why a double implementation of reducer method returns NAN when trying to find the second larger number in a number array in javascript?

So, I did this in order to find the second biggest value in an array (I know there are a lot of answers out there, I just want to know why this approach fails)
With a first reduce() method on the number array I find the largest number, then using a second reduce() method I tried to use an if statement to check return the biggest number only if the compared numbers are not the previous biggest one found. This is the code:
const arr = [1,6,2,7,3,9,5,8];
const biggest = arr.reduce((a,b)=> {
return Math.max(a,b)
})
console.log(biggest)
const secondBiggest = arr.reduce((a,b)=>{
if(a!= biggest && b!= biggest){
return Math.max(a,b)
}
})
console.log(secondBiggest) // --> NAN
In every iteration you need to return something from reduce function. so currently you just return when (a!= biggest && b!= biggest) is true. so you need to return the original value of accumulator a when the condition doesn't match as well (return a).
Learn more about reduce()
const arr = [1,6,2,7,3,9,5,8];
const biggest = arr.reduce((a,b)=> {
return Math.max(a,b)
})
console.log(biggest)
const secondBiggest = arr.reduce((a,b)=>{
if(a!= biggest && b!= biggest){
return Math.max(a,b)
}
return a; // if you don't return anything during the next iteration the accumulator will have `undefined`.
})
console.log(secondBiggest) // --> NAN
You want return your accumulator as is if the current one is the biggest
const arr = [1, 6, 2, 7, 3, 9, 5, 2];
const biggest = Math.max([...arr])
console.log(biggest)
const secondBiggest = arr.reduce((accumulator, current) => {
if (current != biggest) {
return Math.max(accumulator, b)
}
return a
})
console.log(secondBiggest)

Recursive sort in JS

I was asked in an interview to write a program/algo to sort an array of number using recursion.
Though I vaguely answered it, I tried and came up with following code:
You can use following JSFiddle link to play around.
function sort(arr) {
if (arr.length === 2) {
const v1 = arr[0];
const v2 = arr[1];
const isGreater = (
(isString(v1) && isString(v2) && v1.toString().toLocaleCompare(v2) > 0) ||
(isNumber(v1) && isNumber(v2) && v1 > v2)
);
return isGreater ? [ v2, v1 ] : [ v1, v2 ];
} else {
const last = arr.pop();
const ret = sort(arr);
const newLast = ret.peekLast();
if (newLast < last) {
return [ ...ret, last ];
} else {
return sort( [ last, ...ret ] );
}
}
}
function isString(value) { return typeof value === 'string'; }
function isNumber(value) { return Number.isFinite(value); }
Array.prototype.peekLast = function () { return this.slice().pop(); }
//console.log(sort([1,2,3,4,5]))
console.log(sort([5,4,3,2,1]))
The algo I implemented is:
Take the array and check if its length is greater than 2.
If yes,
Remove last element and store it in a variable.
Again call same function without last element till it has 2 items.
Accept array returned from recursive call and peek the last element.
If newLast value is greater than previousLast
Push previousLast as first element and again call itself with this array.
If not, push previousLast to array and return it.
Else,
For number and string check equality and return correct order.
For anything else, return same value
Question is, is there a better way to implement (algo wise)?
Note: I'm not expecting code improvements. Objective of this question is improvement in algo part or any general stuff I have missed.
I also know, current code does not support:
Sort order. It will sort ascending only.
May break for Date objects, and does not support Objects in general.
Thanks!
I think most interviewers would expect you to respond with quicksort or merge sort (or both) given that question. Of the two, quicksort, is easier to remember and recreate in a pinch because the merge step of merge sort is easy to mess up.
Quicksort is a really beautiful algorithm and is a natural fit for javascript's functional tools. It is worth really understanding if you'll be doing interviews:
const arr = [6, 1, 5, 3, 9, 6, 7, 10, 16, 4, 0, 12, 2]
function qsort(arr){
if (arr.length < 2) return arr
// choose a pivot, p
// the choice of pivot can effect worst-case performance
// for this, we'll just use the first element.
const [p, ...rest] = arr
// partition array into element greater and lesser that the pivot
// this can be optimized so you don't loop through the array twice
const low = rest.filter(n => n <= p)
const high = rest.filter(n => n > p)
// recurse on both partitions and reassemble as recursion unwinds
return [...qsort(low), p, ...qsort(high)]
}
console.log(qsort(arr).join(', '))
I see a vein of intermediate value creation that is not inconsequential.
peekLast calls Array.prototype.slice which makes a copy of the array. You copy an entire array just to return the last element.
Array.prototype.peekLast = function () { return this.slice().pop(); }
Array.prototype.peekLast = function () { return this[this.length]; }
This gives you the same result every time without the need to copy.
Use of spread arguments in expressions like [ ...arr, x ] copies arr entirely.
arr.concat([ x ]) does the same thing without making copy (or mutation) of arr
You call peekLast and use ...x once per element in the input. Calling sort on a list of just 100 items will copy over 10,000 elements, for these operations alone. A list of just 1,000 items will copy over 1,000,000 elements. Room for algorithm improvment? For sure.
Mark Meyer starts you off on the right foot. If you're going to use recursion, it's best writing your program in functional style, as it will yield the best results. Mixing imperative style (statements, mutations, reassignments, other side effects, etc) with recursion is a recipe for a migraine.
Mark's algorithm, however great a "code improvement", your question is asking for "algorithm improvements". Under this lens, Mark's algorithm suffers from similar intermediate value creation by use of many ...x expressions.
Another lurking offense is the double use of .filter on the same array, rest. This creates an inefficient process as it iterates entirely through rest two (2) times per element. This is a symptom of reaching for low-hanging built-in functions that do close to what you want, but not exactly what you want. A better function would iterate through the array once and return both results.
The inefficiencies in Mark's program are mostly forgivable because of the dramatic improvement in code quality. His program is much more readable than yours because he's using functional style, which is where recursion comes from. The inefficiencies are also very easy to fix, so maybe that's an exercise for you?
Let's see if that gets your brain going. We'll see what answers other people submit before smothering you with too much information.
Your code will fail if we have duplicate elements because of this line.
if (newLast < last) {
It will go into infinite recursion
Refer the snippet with the duplicate array passed as input
function sort(arr) {
if (arr.length === 2) {
const v1 = arr[0];
const v2 = arr[1];
const isGreater = (
(isString(v1) && isString(v2) && v1.toString().toLocaleCompare(v2) > 0) ||
(isNumber(v1) && isNumber(v2) && v1 > v2)
);
return isGreater ? [ v2, v1 ] : [ v1, v2 ];
} else {
const last = arr.pop();
const ret = sort(arr);
const newLast = ret.peekLast();
debugger;
if (newLast < last) {
return [ ...ret, last ];
} else {
return sort( [ last, ...ret ] );
}
}
}
function isString(value) { return typeof value === 'string'; }
function isNumber(value) { return Number.isFinite(value); }
Array.prototype.peekLast = function () { return this.slice().pop(); }
//console.log(sort([1,2,3,4,5]))
console.log(sort([3,3,5,2]))
this one work for me to sort an array recursively:
var array = [3,1,8,2,4,9,16,28];
const sum = (arr, i=0)=> {
if(i === arr.length) return arr;
if(arr[i+1] < arr[i]){
const x = arr[i+1];
arr[i+1] = arr[i];
arr[i] = x;
}
return sum(arr,i+1);
}
console.log(sum(array))
function swap(arr, firstIndex, secondIndex){
let a= arr[firstIndex];
arr[firstIndex] = arr[secondIndex];
arr[secondIndex] = a;
return arr;
}
function sortArr(arr, index=0){
if(index == arr.length) return arr;
for(let i=0;i<arr.length; i++){
if(arr[i] > arr[i+1]){
arr = swap(arr, i, i+1);
}
}
return sortArr(arr, index+1);
}
console.log(sortArr([4,1,3,2,0]));
function quicksort(num){
if (num.length < 2){
return num
}
let pivot = num[0];
let slicedArr = num.slice(1);
let left = [];
let right = [];
for(let i = 0; i < slicedArr.length; i++){
if(slicedArr[i] <= pivot){
left.push(slicedArr[i])
}else{
right.push(slicedArr[i])
}
}
return [...quicksort(left), pivot, ...quicksort(right)]
}

Why does this tail recursive loop cause stack overflow in javascript / node?

exports.tailLoop = function(A) {
const asc = A.sort((a,b) => a - b)
function tailRecur (rest) {
if (!rest.length) return 0
const pair = rest.splice(0,2)
if (pair[0] != pair[1]){
return pair[0]
} else {
return tailRecur(rest)
}
}
return tailRecur(asc)
}
I have also tried this with:
first = rest.shift()
next = rest.shift()
instead of the splice method.
When I input an array with 1 million elements it fails.
Am I not understanding tail recursion correctly or does tail recursion not work on sizes of 1 million (note sort works fine on a 1 million sized array)
To answer the comment question: how to deal with large inputs in node — You can always find ways to turn a recursive function into a non-recursive function. Sometimes it's not as elegant, but in this case, you are basically using recursion for a loop, which would be faster and easier to understand as a simple loop.
Something like:
function nonRec(A){
const asc = A.sort((a,b) => a - b)
while (asc.length){
const pair = asc.splice(0,2)
if (pair[0] != pair[1])
return pair[0]
}
return 0
}
a = [1, 2, 3, 2, 4, 2, 2, 1, 3]
console.log(nonRec(a))
#Mark has already answered the question, so this is merely a refactor of OP's code.
Your code is basically just checking for the two successive items that are equal by looping the array two items a time, this can be drastically optimized by using a for loop to get rid of the expensive calls to splice:
exports.tailLoop = function(A) {
const asc = A.sort((a,b) => a - b);
for(let i = 0; i < asc.length; i += 2) {
if(asc[i] != asc[i + 1]) {
return asc[i];
}
}
return 0;
}
You could try increasing NodeJS maximum call stack, not sure whether this will help in this case.
Another way to skip from the maximum call stack is to change your code from synchronous to asynchronous.
tailLoop = function(A) {
let resolver;
const promise = new Promise((res,_rej)=>{
resolver = res
})
const asc = A.sort((a,b) => a - b)
function tailRecur (rest) {
if (!rest.length) return 0
const pair = rest.splice(0,2)
if (pair[0] != pair[1]){
resolver(pair[0])
} else {
setImmediate(()=>{
tailRecur(rest)
})
}
}
tailRecur(asc)
return promise
}
now it won't exceed maximum call stack.
const a = []
for(let i=0;i<10000;i++){
for(let j=0;j<100;j++){
a.push(0)
}
}
a.push(1)
tailLoop(a).then(result=>{
console.log(result) //1
})
By the way, the code above takes minutes to get the result...
I think you could find a better method/algorithm to solve this problem.

How to display only the latest data received?

Hey I am trying to show only the latest message of the day,
Thing is I am not sure how to do that as my code only picks up the one I first wrote..
it's not even the first object in the array.. but it still takes it.
*Note that this code runs every few seconds in intervals to check for new data received.
Below is the response I am using my logic on and the code with the logic
isMOTD = false;
var i = 0;
var MOTD = "";
while (messages[i].text && isMOTD == false) {
i++;
isMOTD = messages[i].text.includes("MOTD");
if (isMOTD)
MOTD = messages[i].text;
}
if (isMOTD) {
console.log(MOTD+' ' +'THIS IS MSG OF THE DAY')
$('.content', el).html(MOTD);
}
}
};
I would do something like this:
var MOTD = messages.filter(message => message.text.includes("MOTD"))
.reduce((a, b) => a.ts > b.ts ? a : b, {ts: -1, text: ''})
.text;
$('.content', el).html(MOTD);
The .filter() creates a new array which only includes messages with MOTD in them.
the .reduce() is going through that filtered array and keeping only the message who's timestamp is highest. I also have it default to an empty string if there are no strings that contain MOTD
And then .text, to deal with just the string, not the timestamps.
EDIT: i've been requested to add some more explanation.
First: arrow functions. EcmaScript 2015 (one of the newer versions of javascript) gave a new way to write functions. When you see an =>, think "function". Rather than doing this:
function (a, b) {
return a + b;
}
you can do this:
(a, b) => {
return a + b;
}
Or if there's just one statement as in this case, you can leave off the curly brackets, the semicolon, and the return:
(a, b) => a + b
Second: .filter. All arrays have a function on them called .filter. As the name suggests, the purpose is to filter the array. You pass in a function that describes how you want it to be filtered, and then it creates a new array with just the matching elements.
So consider the following:
var myArray = [1, 2, 3, 15, 18, 200];
var evenNumbers = myArray.filter(function (num) {
return num % 2 == 0
});
It will loop through the array, and for each element of the array, it calls the function i specified. If the function returns true, then that element is included. If the function returns false, then the element is not included. So for this sample code, evenNumbers will equal [2, 18, 200]
For your case, the filter that i'm doing is:
messages.filter(function (message) {
return message.text.includes("MOTD");
});
So the array that's returned by this will contain all messages who's text includes "MOTD". messages that lack "MOTD" are excluded.
Third: .reduce. All arrays have this function as well. Reduce is quite a versatile function, but the general purpose is to take your array, and in some way boil it down (or "reduce" it) to a single value. You pass in some function that you want to be called for every element of the array. When your function returns a value, that value gets carried forward and used the next time the function is run. Here's an example where i want to sum up all the numbers in an array:
var myArray = [1, 2, 3, 4];
var total = myArray.reduce(function (sumSoFar, current) {
var newSum = sumSoFar + current;
return newSum;
}, 0); //<--- this 0 is the initial value
So here's how it works: It's going to start with the initial value of 0, and then it calls the function. SumSoFar will be 0 at this point (because of the intial value), and current is 1 (because we're starting with the first element of the array, which has a value of 1). The function adds them together, and then returns a new sum of 1. Then the function gets called again, but now sumSoFar is 1 (because that's what was returned the last time), and current is 2 (because we're looking at the second element). It adds 1 + 2, and returns a new sum of 3. And so it continues, adding 3 + 3 to get 6, then adding 6 + 4 to get 10. We're done going the the array, so the final value is 10.
In your case, i want to step through the array and find only the message with the most recent timestamp. I start with an initial value of {ts: -1, text: ''}, because in case there are no messages with "MOTD" in them, i want to have the empty string be the result.
.reduce(function (mostRecentMessageSoFar, currentMessage) {
if (mostRecentMessageSoFar.ts > currentMessage.ts) {
return mostRecentMessageSoFar;
} else {
return currentMessage;
}
}, {ts: -1, text: ''});
So this will walk its way through the array (and remember, we're walking through the filtered array, so they all have "MOTD" in them), it is looking for the most recent message. Any time currentMessage is more recent than the best we've found so far, it switches to that one being the best so far. And in the end, we get the most recent message from the entire array.
That final message is an object that looks something like this:
{
type: 'message',
user: 'U0GL3BR52',
text: 'SOLVE MORE CASES #MOTD',
ts: 1505236695.000533
}
Only the text needs to be put into the dom, so i access that with .text
So, in long form, my solution is this:
var MOTDMessages = messages.filter(function (message) {
return message.text.includes("MOTD");
});
var MostRecentMOTDMessage = MOTDMessages.reduce(
function (mostRecentMessageSoFar, currentMessage) {
if (mostRecentMessageSoFar.ts > currentMessage.ts) {
return mostRecentMessageSoFar;
} else {
return currentMessage;
}
}, {ts: -1, text: ''});
var MOTDText = MostRecentMOTDMessage.text;
$('.content', el).html(MOTDText);
Just sort your message after time descending:
messages.sort((a,b) => b.ts - a.ts );
Then just take the first one:
messages[0]

First common element across multiple arrays in Javascript

I need to find the first common element across a group of arrays. The number of arrays may vary, but they are always in sequential order (small->large). My arrays are all properties of myObj.
This is what I have so far:
function compare(myObj,v,j) {
if (myObj[j].indexOf(v)>-1) return true;
else return false;
}
function leastCommon ([1,5]) {
var myObj = { //This is filled by code, but the finished result looks like this
1: [1, 2,...,60,...10k]
2: [2, 4,...,60,...20k]
3: [3, 6,...,60,...30k]
4: [4, 8,...,60,...40k]
5: [5,10,...,60,...50k]
};
var key = [1,2,3,4,5]; //also filled by code
var lcm = myObj[key[key.length-1]].forEach(function(v) { //Iterate through last/largest multiple array
var j=key[key.length-2];
while (j>=0) {
if (compare(myObj,v,j)) { //check to see if it is in the next lower array, if yes, check the next one.
j--;
}
if (j>0 && (compare(myObj,v,j+1))===true) return v; //before the loop exits, return the match
}
});
return lcm;
}
I'm not sure what is wrong, but it is returning undefined.
Note: yes, I know a forEach returns undefined, and I tried modifying my code, and I get a "potential infinite loop" error from my editor. Modified code looks like this:
function leastCommon ([1,5]) {
var myObj = { //This is filled by code, but the finished result looks like this
1: [1, 2,...,60,...10k]
2: [2, 4,...,60,...20k]
3: [3, 6,...,60,...30k]
4: [4, 8,...,60,...40k]
5: [5,10,...,60,...50k]
};
var key = [1,2,3,4,5]; //also filled by code
var lcm = 0;
myObj[key[key.length-1]].forEach(function(v) { //Iterate through last/largest multiple array
var j=key[key.length-2];
while (j>=0) {
if (compare(myObj,v,j)) { //check to see if it is in the next lower array, if yes, check the next one.
j--;
}
if (j>0 && (compare(myObj,v,j+1))===true) lcm = v; //before the loop exits, set lcm = v
}
});
return lcm;
}
I would not use forEach since there is no way to exit from the method when you find the first match/failure. You would need to keep looping. Instead you should look into a regular for loop with every and indexOf. This code also assumes that the array is sorted so smallest number comes first. If not, a simple sort() with a clone of the array can solve that.
//pass in arrays, assumes array is sorted
function getFirstCommon (arrs) {
//Loop over the elements in the first array
for (var i=0; i<arrs[0].length; i++) {
//get the value for the current index
var val = arrs[0][i];
//make sure every array has the value
//if every does not find it, it returns false
var test = arrs.every( function (arr) {
//check the array to see if it has the element
return arr.indexOf(val)!==-1;
});
//If we find it, than return the current value
if (test) {
return val;
}
}
//if nothing was found, return null
return null;
}
//test numbers
var nums = [
[1,2,3,4,5,6],
[2,3,4,5,6],
[3,4,5,6],
[4,5,6,7,8,9],
[6,7,8,9]
];
console.log(getFirstCommon(nums)); //6
var nums2 = [
[1,2,3,4,5,6],
[2,3,4,5,6],
[3,4,5,6],
[4,5,6,7,8,9],
[5,7,8,9]
];
console.log(getFirstCommon(nums2)); //5
var nums3 = [
[1,2,3,4,5,6],
[7,8,9,10,11,12],
[7,8,9,10,11,12]
];
console.log(getFirstCommon(nums3)); //null
The code could be improved where it does not check itself
First of all, you do have an infinite loop. If first compare() fails, you never decrease j, and keep checking the same number.
Second: you key array never decreases, so you always compare two last arrays.

Categories

Resources