JavaScript -- Unable to understand Array.Map & Array.reduce - javascript

I have been working on a problem on FCC (Free code camp) which requires to scan a 2D array and find largest element from each 1D array and put the result into a 1D array. I came across this solution on GitHub was unable to understand it.
function largestOfFour(arr) {
return arr.map(function(group){
return group.reduce(function(prev, current) {
return (current > prev) ? current : prev;
}, 0);
});
}
largestOfFour([[4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1]]);
OUTPUT IS [5, 27, 39, 1001]
My console.log(arr) gave me: 4,5,1,3,13,27,18,26,32,35,37,39,1000,1001,857,1
How did that 2D array get converted to 1D array when passed as a function parameter?
My console.log(group) gave me: 4,5,1,3 (first time), 13,27,18,26 (second time), 32, 35, 37, 39 (third time) and 1000, 1001, 857, 1 (fourth time).
How was the anonymous function able to take the parameter group as the individual 1D arrays inside the 2D.
Can someone please help.

When you use Array.map it will process each element within the array for example.
[1, 2, 3, 4].map(function (item) { return item; } );
Will just return a new array with [1, 2, 3, 4], but say we double each value we could do it like this.
[1, 2, 3, 4].map(function (item) { return 2 * item; });
Which returns [2, 4, 6, 8] back.
So in your example you have a 2 dimensional array. The Array.map works on the first level. So in each iteration the group is
1st -> [4, 5, 1, 3]
2nd -> [13, 27, 18, 26]
3rd -> [32, 35, 37, 39]
4th -> [1000, 1001, 857, 1]
So the 1st to 4th will then be passed into the Array.reduce function. Reduce just returns a single value based on the array passed in, or better put
The reduce() method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
What is happening in the reduce here is storing the maximum value, it begins with 0, and on the first iteration compares with the value 4. As 4 is larger than 0 it is stored and becomes our new max. It then tests against 5 and as 5 is bigger than 4 it stores that as the new max, and so on.
Once it finishes going through it produces a maximum for that array and gives a Number as the result. So now the first iteration of the map which contained an array is changed to a Number, in this case the first item of the Array produced by Array.map should be 5.
The final output I suspect would be
[5, 27, 39, 1001]

Well the array.map() returns a new array by changing the values of the array you pass to it, in this case we're returning a function itself (array.reduce).
arr.map(function(group))
arr.map takes in a 2D array called 'group' and reads each value inside it, in this case another array. So on the first run of 'map' the first array is [4, 5, 1, 3], we call reduce on it.
group.reduce(function(prev, current)
On the first run of 'reduce' we have prev = 4 and current = 5 ("it starts on second value"), then in:
return (current > prev) ? current : prev;
it asks is 5 bigger than 4?, if so return 5 else return 4. So it returns 5. 5 then is passed into 'prev' for the next run of 'reduce' we have prev = 5 (from last run) and current = 1.
return (1 > 5) ? 1 : 5;
It returns 5 since it's bigger than 1.
return (3 > 5) ? 3 : 5;
It returns 5 since it's bigger than 3. end of group.reduce(), the final returned value '5' is returned to arr.map(), the entire first array [4, 5, 1, 3] is replaced by a single value '5'. next run of arr.map() starts with the second array [13, 27, 18, 26].
I actually did that question on freecodecamp, and I didn't have to use map or reduce. I just read through using "double for loop / array.push / returning the array", although "map / reduce" is a lot cleaner and probably a better way of doing it. Hope that explains it

Related

How to splice multiple array elements and insert accordingly?

const data = response.data
console.log(data)
const temp = data.ssps_with_scated.splice(5, 1)(1, 3)[0]
data.ps_with_ed.splice(2, 1, 0, temp)
i am trying to achieve finally i got it. But issue is, i cant expect the array value same all the time. So i have decided to re-arrange the array values based on the ID.
Well,
splice(7,1)(21,3)
This code will cause an error. Since Array.prototpy.slice returns a new array.
It would be the same if you would do this:
const a = [1,2,3]
const b = a.splice(1,1);
b(2,1) // b.splice(...) is not a function
EDITED:
Maybe there is a faster/better solution but...
You can make it more general but for your case only:
const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21];
const first = array[7];
const second = array[21];
// Add elements on certain indices in array (second and third)
array.splice(2, 0, first, second)
// Remove first from the array (index is 7 + 2 because we added 2 elements)
array.splice(9, 1)
// Remove 21 from the array (index is 22 - 1 because we added 2 elements and removed 1, not: number 21 is on index 22)
array.splice(21, 1);
data shouldn't be a const since the value is getting updated. Splice can also only be called on one array at a time. If you need to call it on multiple arrays, create a loop or iterate over them.
If you want to inject the current value of the 7th position into the 2nd position... you'd need to do something like...
array.splice(2, 0, array[7])

Javascript recursions. Why the following code works with .unshift() instead of .push()?

My brain believes that I should use .push() instead of .unshift() but freeCodeCamp and my console tell that this is the right way of doing it. I just don't understand how come my numbers go from 10 to 20 using .unshift(), and 20 to 10 using .push();
The way I read the code:
if startNum is bigger than endNum return an empty array. Otherwise, create a variable called arr and assign to it the current startNum increased by 1 each time the code runs and return it into the array.
What am I doing wrong?
function rangeOfNumbers(startNum, endNum) {
if (startNum > endNum) {
return [];
} else {
const arr = rangeOfNumbers(startNum + 1, endNum);
arr.unshift(startNum);
return arr;
}
}
console.log(rangeOfNumbers(10, 20));
The console: (11) [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
One way to think of it is to imagine that the recursive call already worked properly. So rangeOfNumbers(11, 20) would yield [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]. And so const arr = rangeOfNumbers(startNum + 1, endNum) means that arr is [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]. Now you have startNum and arr and need to combine them. push would add startNum to the end. unshift properly adds it to the beginning.
But while we're here, I'd like to suggest a more elegant way to express that same algorithm:
const rangeOfNumbers = (start, end) =>
start > end
? []
: [start, ... rangeOfNumbers (start + 1, end)]
Instead of if-else statements we have a single conditional expression. And we don't mutate a variable like arr in the process. I think it also more cleanly expresses the algorithm. Of course tastes may vary, and, if you're just learning JS, this may involve syntax you haven't seen yet. But if you do understand it, you might agree with me about its elegance.
unshift inserts an element into the beginning of the array.
Since you're iterating from the startNum to the endNum, consider the next to last recursive call:
const arr = rangeOfNumbers(startNum + 1, endNum);
arr.unshift(startNum);
return arr;
where startNum is 19 and endNum is 20. The recursive call returns an array with one element: [20]. Then you need to insert the 19 element in the proper position.
To do this, you'll need to insert the 19 at the beginning, to get:
[19, 20]
So, insert 19 at the beginning with unshift.
If you used push instead, from the original [20], you'd get:
[20, 19] // 19 added to end
and then
[20, 19, 18] // 18 added to end
and so on, which isn't in the right order.
You could use push if you inserted elements before the recursive call.
function rangeOfNumbers(startNum, endNum, arr = []) {
arr.push(startNum);
if (startNum < endNum) {
rangeOfNumbers(startNum + 1, endNum, arr);
}
return arr;
}
console.log(rangeOfNumbers(10, 20));

Return Largest Numbers in ArraysPassed using reduce method

There is a question in freecodecamp which is as follows:
Return an array consisting of the largest number from each provided sub-array. For simplicity, the provided array will contain exactly 4 sub-arrays.
Remember, you can iterate through an array with a simple for loop, and access each member with array syntax arr[i].
Thus largestOfFour([[4, 5, 1, 3], [13, 27, 18, 26], [32, 35, 37, 39], [1000, 1001, 857, 1]]);
should return [ 5, 27, 39, 1001 ].
There are numerous methods to do the above map() method being the shortest. However I am trying to achieve the same using reduce method but unable to do that. My method is as follows.
function largestOfFour(arr) {
return arr.reduce((maxArray,item) => maxArray.push(Math.max(...item)),[]);
}
But there is error in console which says
maxArray.push is not a function
Kindly find the bug.
.push(...) returns the new length of the array, not the array itself, so at the second loop, you're pushing to a number, instead of the array, hence you need to change your arrow function:
const largestOfFour = arr => arr.reduce(
(max, current) => (max.push(Math.max(...current)), max),
[]
);
That would work.

How to cycle through an array of values to optimize number of calls to a function?

I have an array of rows from a Google Spreadsheet interspersed with numbers between the bounds of 1 and 800:
var rowPositions = [1,3,4,5,9,10,11,12...795,799,800]
And, for efficiency's sake, I need to make an API call to the function deleteRows, which accepts two parameters:
deleteRows(positionOfFirstRow, howManyRowsShouldBeDeletedStartingFromFirstRow)
Google Documentation for deleteRows.
How can I create a function in Javascript that calls deleteRows a minimal number of times for a given array? IE, using the aforementioned array as an example, the function calls deleteRows to remove the row at position 1, then calls it again to delete 3,4,5, then again to delete 10,11,12, etc...?
In other words, how can I transform that rowPositions array into a two-dimensional one, ex.:
var 2DrowPositions = [[1,1],[3,2],[5,1],[9,4]]
which I can then use to call the deleteRows function once per provided coordinate.
If you are given an array of items to delete like [1, 3, 4, 5, 7, 8, 9, 15, 18, 19, 20, 23] you can step through that array and look for consecutive numbers. Keep track of how many consecutive numbers you see and when you hit a non-consecutive number, save the old one and start again.
Here's one way to do that, which is reasonably easy to read:
let del = [1, 3, 4, 5, 7, 8, 9, 15, 18, 19, 20, 23]
let res = []
let curr = {start: del[0], count: 0}
del.forEach((item, i, arr) => {
if (i === 0 || arr[i] === 1 + arr[i-1]) curr.count++
else {
res.push(curr)
curr = {start: arr[i], count:1}
}
})
res.push(curr)
console.log(res)
This will return an array of objects of the form {start, count} which you can use in your function with something like:
res.forEach(r => deleteRows(r.start, r.count))

Filter and map in JavaScript

I'm doing "experiments" on a little example to understand the use of filter() and map() in JavaScript, and I have a question, based on the following code.
var numbers = [1, 4, 9, 25, 36, 49];
var roots = numbers.filter(function() {
for (i = 0; i < numbers.length; i++) {
if (numbers[i] > 10) {
var index = numbers.indexOf(numbers[i]);
numbers.splice(index, 1);
}
}
return numbers;
}).map(Math.sqrt);
console.log(numbers);
// -> [1, 4, 9]
console.log(roots);
// -> [1, 2, 3]
Why when I put numbers[i] > 10 as a condition the output is correct, whereas if I put numbers[i] < 10 the outcome is the following
console.log(numbers);
// -> [25, 36, 49]
console.log(roots);
// -> [1, 5, 7]
Where the numbers array is correct, but the roots array is messed up?
Your filter function is mutating the numbers array on which its operating; it should just return true or false depending on whether you want the item in the filtered results or not. For example
var roots = numbers.filter(function(n) {
return n > 10;
}).map(Math.sqrt);
In this case numbers is never changed, but roots contains all elements in numbers that are greater than 10.
example fiddle
You are using filter but inside the callback, you are doing your own filter ... which I doubt is intentional. If you break down what your code is doing conversationally, it is basically "for each element in the array, iterate through the entire array (for loop) and in that loop iterate through the entire array again (indexOf)...". At this point you can probably determine something is wrong. Also Array.splice mutates the original array which is usually undesirable.
Here is an example of how you might want to use filter and map
var numbers = [1, 4, 9, 25, 36, 49];
// get numbers greater than 10
numbers.filter(n => n > 10)
// => [25, 36, 49]
// get numbers greater than 10 and take the square root of each number
numbers.filter(n => n > 10).map(Math.sqrt)
// => [5, 6, 7]
Note that these methods are chainable but never mutate the original array. This gives you a great amount of flexibility for performing array computations.
This code is not doing what you think it's doing. It'll be acting very strangely because you're mutating the numbers array as you go along in the for loop.
The correct use of filter is to return true/false for each value, not return a new array. Return true to keep an element, false to remove it.
This is what you're after:
var numbers = [1, 4, 9, 25, 36, 49];
// notice "number" is taken as an argument
var roots = numbers.filter(function(number) {
// return true to include this number, false to reject it
return number < 10;
}).map(Math.sqrt);
console.log(numbers);
// NOTE: has not been changed
// -> [1, 4, 9, 25, 36, 49]
console.log(roots);
// -> [1, 2, 3]
To get an understanding of where your results are coming from, let's walk through each iteration of your code:
filter will run for each element of the array. the for loop will then go through and modify the array.
for number > 10
filter: value of 1
for the first 3 iterations of the for loop, nothing happens, then:
i = 3, so numbers[3] = 25. this passes the condition in your for loop, causing the splice, which will change numbers to [1, 4, 9, 36, 49]
this is where it gets weird #1
In the next iteration of the for loop, i will be 4, but the array has been changed. You may expect the value of 36 to be checked next, but because the array has been modified, and 36 is now in the place of 25, it will be missed. The next value to be checked is in fact 49. Similar reasoning shows that 49 will also be removed.
When the for loop is complete, you are left with numbers = [1, 4, 9, 36]
You then return numbers, which is truthy, which filter interprets as an instruction to keep 1 in the array.
filter: values 4, and 9
The same reasoning can be followed to show that the values of 4 and 9 will remain in the numbers array, and 36 will be removed.
Now, because 25, 36, and 49 have been removed from the array, filter has completed its job, leaving [1, 4, 9] to be passed to map giving [1, 2, 3].
Result
It's actually provided the inverse of the correct answer.
for number < 10
This is where it get really weird.
filter: value of 1
the for loop will remove 1, skip 4 (since it now takes the place of 1), remove 9 (which is in place of 4), skip 25, and keep the rest. This returns a value of [4, 25, 36, 49], which is truthy, and intepreted as keeping 1.
result of filter so far is [1].
filter: value of 25 (since it's now in the 2nd position in the array as 1 was removed)
the for loop will remove the 4, skip 25, and keep the rest. This returns a value of [25, 36, 49], which is truthy, so interpreted as keeping 25.
result of the filter so far is now [1, 25]
filter: value of 49 (since it's now in the 3rd position in the array as 1, 4, and 9 have been removed)
the for loop will keep all of the values since they're all greater than 10, meaning that the return value will be [25, 36, 49], which is truthy, which means that 49 is kept.
result of the filter will be [1, 25, 49], mapped with Math.sqrt is [1, 5, 7]
Result
0_o
Conclusion
Never, ever modify an array as you're looping or using array functions on it.

Categories

Resources