Why does the callback function for reduce take four parameters? - javascript

In studying the reduce method I'm not quite sure why the callback passed in needs the third and fourth parameters, index and array. In the example from the MDN:
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array) {
return previousValue + currentValue;
});
A lot of other uses for the array reduce method or underscore reduce function I've studied only make use of the first two parameters for the callback: previousValue (sometimes seen as accumulator) and currentValue (aka elem, the value of the current index).
Why is reduce sometimes written with callback taking four parameters and sometimes written with only previousValue and currentValue?
What would be a case where the index and array parameters are needed?
Should all four parameters always be given in a function definition for reduce in case an application of reduce requires the third or fourth parameters?

here is a (slightly) less-contrived example to sum up unique values in an array, skipping duplicates, using the index and array arguments to find unique values:
[0, 1, 2, 3, 2, 1, 0].reduce(function(previousValue, currentValue, index, array) {
return array.indexOf(currentValue) === index ? // value used already?
previousValue + currentValue : // not used yet, add to sum
previousValue; // used already, skip currentValue
}); // == 6 ( 0+1+2+3 )
live demo: http://pagedemos.com/fn764n659ama/1/
side note: [].reduce() runs quite a bit faster in V8 by specifying all four arguments as formal parameters on the callback, regardless of if they are used by the code inside the function.

If you only need to use the first two parameters, it is perfectly fine to leave the last two out in the function arguments list. In that case the last two arguments will just be ignored. To sum the array, this is a perfectly acceptable way to do it:
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue) {
return previousValue + currentValue;
});
The last two parameters, index and array just give you extra information in case you need it. For example, say you wanted to sum up the all the elements in the array multiplied by their mirror element, that is, given the array [0, 1, 2, 3, 4] you wanted to output
(0 * 4) + (1 * 3) + (2 * 2) + (3 * 1) + (4 * 0)
then you would have to use the last two parameters:
[0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array) {
return previousValue + currentValue * array[array.length - index - 1];
});
Admittedly, this is a somewhat contrived example, but I'm having a hard time coming up with an example that doesn't seem contrived. In any event, the last two parameters do occasionally come in handy.

Related

Problem with getting javascript to forget a variables value

I'm trying to solve a coding challenge
it gives me an array and waits for the answer ,then gives me the next array and so on.
https://www.codewars.com/kata/5648b12ce68d9daa6b000099/train/javascript
I am trying to take this input: var busStops = [[10,0],[3,5],[5,8]] and return 5.
the code is supposed to add the first in each pair to a total while subtracting the second in each pair from the total eg: 10 - 0 + 3 - 5 + 5 - 8 = 5
First my code loops through the inner arrays and outer array ,pushing it into myarr as a regular array eg: [10,0,3,5,5,8].
It then adds the value if it is index is 0 or even and subtracts it if the index is odd.
This actually works!
Until it is given a second array eg: [[3,0],[9,1],[4,10],[12,2],[6,1],[7,10]]
It is still calculating the total correctly but is still remembering the total from the first array meaning it is returning 22 instead of 17
Why?
There is a var answer = 0 that is being executed ahead of the second loop
It should forget the value of the previous answer.
Right?
Edit: I figured out my problem. I just needed to empty myarr after the total was calculated!
let myarr = [];
var number = function (busStops) {
for (var i = 0; i < busStops.length; i++) {
for (var j = 0; j < busStops[i].length; j++) {
/*console.log(busStops[i][j]);*/
myarr.push(busStops[i][j]);
}
}
console.log("test");
var answer = 0;
console.log("test again");
for (let t = 0; t < myarr.length; t++) {
if (t == 0 || t % 2 == 0) {
answer = answer + myarr[t];
} else {
answer = answer - myarr[t];
}
}
console.log(answer);
return answer;
};
The task at your hand tries to somehow find a value (an integer) from an array of arrays (multidimensional array). That task seems to be reducing that multidimensional array into a single integer.
Luckily, JavaScript has some powerful array methods and one of them is the reduce method:
The reduce() method executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value. Source: MDN
The reduce method tries to reduce an array's elements into a single value and in your case we want to reduce that multidimensional array into a single value that is the number persons who are still in the bus.
Before typing some code, let's dig a bit deeper into the reduce method:
It accepts 2 parameters, a callback function that acts as the reducer and the initial value to be used in the first iteration of the reduce method.
The reducer callback function, on its own, accepts 4 parameters that are supplied by the reduce method. You may learn more about those parameters here as am only going to focus on the first 2 parameters the reducer accepts:
previousValue: hold the value from the previous reducer call. On first call, it contains the value you set to the initial value parameter of the reduce method or, if you didn't supply an initial value, previousValue shall hold the value of your array's first element (arr[0]).
currentValue: hold the current reduce iteration's item.
Now, let's get back to the task we have, we need to calculate the number of persons who are still in the bus based on a supplied multidimensional array. Each item in that multidimensional array is an array of two values where the result we need at the end is: the sum of the differences between each array, in the multidimensional array, first and second values (sum = multiDim[i][0] - multiDim[i][1] + multiDim[i + 1][0] + multiDim[i + 1][1] etc...).
To solve this task, we'll reduce that multidimensional array into a single number (let's call it result) by using a simple reducer function that will start by an initial value of 0 (as we're calculating a sum in our case) and will add, to the result, the difference between the first and the second values of the array supplied by the reduce at each iteration.
To illustrate, here's a live demo:
/**
* a function that calculates and returns the number of person who are still in the bus or precisely, the sum of the differences between "c[0]" and "c[1]"
* busArray: the supplied multidimensional array to reduce
* the reducer accepts two parameters:
* r: the result from the last call to the reducer function (initially set to 0, the initial value (see second parameter passed to the "reduce" method))
c: hold the current iteration's array.
*/
const calculateWhoAreInTheBus = busArray => busArray.reduce((r, c) => r += c[0] - c[1], 0);
/** test the created "calculateWhoAreInTheBus" function */
console.log(calculateWhoAreInTheBus([
[10, 0],
[3, 5],
[5, 8]
])); // should print: 5
console.log(calculateWhoAreInTheBus([
[3, 0],
[9, 1],
[4, 10],
[12, 2],
[6, 1],
[7, 10]
])); // should print: 17
console.log(calculateWhoAreInTheBus([
[3, 0],
[9, 1],
[4, 8],
[12, 2],
[6, 1],
[7, 8]
])); // should print: 21
console.log(calculateWhoAreInTheBus([
[0, 0],
[0, 0]
])); // should print: 0
I would advice you to use Array.prototype.reduce instead. For example like this:
const reducer = (previous, current) => previous + current[0] - current[1];
const answer = busStops.reduce(reducer, 0);
It is very brief (although this is not a goal in and of itself) and the reducer function does almost trivial work, so it does not complicate unneccesarily. Best of all it encapsulates the functionality with a minimal need of extra variables.
Othwerwise you could simplify your function a bit but use the let keyword to keep variables locked to scope like:
function number(busStops) {
let answer = 0;
for (let bs of busStops) {
answer += bs[0] - bs[1];
}
return answer;
}

Why recursion behave differently in the two examples?

I have two examples of recursion, one calculate the sum of all elements of the array and the second return the count, both examples behave differently and i can't relate!
First Example:
const sum = (list) => {
if (list.length === 0) {
return 0;
}
return list[0] + sum(list.slice(1));
};
console.log(sum([1, 2, 3, 4])); // 10
The second:
const count = (list) => {
if (list.length === 0) {
return 0;
}
return 1 + count(list.slice(1));
};
console.log(count([0, 1, 2, 3, 4, 5])); // 6
Why the first recursion go through all array elements adding every element while the other added 1 to each element then returned just the final value?? i assumed it would do the same and the difference would be just adding 1 to the sum!!
The recursion is the same.
It visits all elements of the array and returns zero if no element is available.
For each element, it returns either the item for summing or one for counting.
Look at return 1 + count(list.slice(1)); in the count function. It would just ignore the 1st element in the list you pass recursively to the function and always use 1. Versus the sum function does consider that element. That effectively returns the count of times the function has been called which is 6
1st - sum, 2nd - count,
If you wish them to do the same thing you should use only one of them.

How are parameters get mapped in reduce function?

The MDN defines reduce() as below:
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
Let's not take anything for granted and just analyse the syntax of below statements:
const array1 = [1, 2, 3, 4];
const reducer = (y, x) => y + x;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
reducer is a function that takes two parameters and return their sum
reducer is executed on each element of the array, but 'each element of the array' is only ONE parameter, why can i assume that the SUM is the other parameter and cached somewhere waiting for the next add operation? and why can i assume reduce is returning the sum at the end?
Could someone answer these questions? From someone coming from other language background e.g. C which also has function concept. I am often confused by Javascript's syntax.
and that's how i get more confused when i see:
const pipeline = [
array => { array.pop(); return array; },
array => array.reverse()
];
pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);
because again, according to MDN, The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.
only this time,
reducer: (xs, f) => f(xs)
accumulator: [1, 2, 3]
array1: pipeline
then how do we explain its behaviour similar to our first example in English?
reducer is executed on each element of the array, but 'each element of the array' is only ONE parameter, why can i assume that the SUM is the other parameter and cached somewhere waiting for the next add operation?
The callback provided is called multiple times, once for each item in the array (or, for length - 1 times, in case no initial value is provided). You could easily implement this yourself:
const array1 = [1, 2, 3, 4];
const reducer = (y, x) => y + x;
Array.prototype.myReduce = function(callback, initialValue) {
let accum = initialValue === undefined
? this.shift()
: initialValue;
for (let i = 0; i < this.length; i++) {
accum = callback(accum, this[i], i, this);
}
return accum;
}
// 1 + 2 + 3 + 4
console.log(array1.myReduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.myReduce(reducer, 5));
Just because you pass one function to .reduce (or to any other function) doesn't put a limit on how many times that function can be called.
and why can i assume reduce is returning the sum at the end?
That's just how the method is defined - like with the implementation above, the accumulator (or accum) gets reassigned for every iteration, and passed to the next invocation of the callback.
The browser's native implementation of the method isn't actually written in Javascript like above, but its functionality is the same (for the most part).
The pipeline works the same way. For every element of the array provided, the accumulator is reassigned, and the next element is called with the new accumulator. Here, there's an array of functions which are being called, and each function's return value is being used as the next accumulator, and the value returned by the last function call is what the whole .reduce call resolves to.
const pipeline = [
array => { array.pop(); return array; },
array => array.reverse()
];
pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);
Item 1: initial value (accumulator) is [1, 2, 3]. Plugging into array => { array.pop(); return array; } and you .pop() its last value (the 3, resulting in [1, 2], then you return the array.
Item 2: Accumulator (return value of last iteration) is [1, 2]. Plug it into array => array.reverse(), and you get the same array, reversed: [2, 1].
There are no more items in the array, so this [2, 1] is the value that the whole reduce call evaluates to.

Can anybody explain this snippet of code:

I have got this block of code and I could not get the r.concat part because concat usually is used on a whole array not on a single element of it.
function doubleOddNumbers(numbers) {
return numbers.reduce((r, n) => n % 2 ? r.concat(n * 2) : r, [])
}
Here is the code annotated:
function doubleOddNumbers(numbers) {
return numbers.reduce( // reduce iterates over numbers and passes an accumulator from iteration to iteration
(r, n) => // the reducer function called for each element, r is the accumulator, n is the element
n % 2 // if the element is odd
? r.concat(n * 2) // then append its double to the accumulator
: r // otherwise return the accumulator unchanged
, []) // start with an empty array for the accumulator
}
Here is the MDN documentation on reduce and concat.
I think the missunderstanding comes from this usage of reduce:
[1, 2, 3].reduce((a, b) => a + b, 0); // 6
In this example, both the value of the array b, the accumulator a and the initial value 0 are numbers. But it doesn't have to be like this, the accumulator and the arrays values can have different types. If we change the line above to:
[1, 2, 3].reduce((a, b) => a + b, "") // "123"
As the initial accumulator is an empty string, the first time reduce executes it will concat "" + 1, which will result in "1" that gets passed to the next reduce step.
Now in your case, the initial accumulator value is an empty array.Therefore r will be an array, whilst n is a number. Now the reducer will either return r itself, or it will concatenate n * 2 to the array, which will also result in an array passed to the next reducer step.
[1, 2, 3].reduce((acc, el) => acc.concat(el), [])
That said, the code shown is just a complete missuse of the .reduce function. That you weren't able to understand the code does not mean that you are dumb, but it rather means that the code shown is badly written. I would write it as:
numbers
.filter(n => n % 2) // only.take odd numbers
.map(n => n * 2) // double them
As "numbers" is an array (of numbers), you can just start with the specification of the Array.reduce function here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
Every reduce works like so:
arrayToReduce.reduce((memo, currentElement) => { /* operations using the currentElement that return the new memo value */}, initialValue);
What happens:
You start with an initial value in memory (initialValue above) e.g. an empty array.
For each element of the array to reduce (e.g. arrayToReduce above), you execute a function which receives the current memorized value ("memo" above) and the current element in the array. The function will examine the current element and will compute a new memorized value. E.g. in your example, for odd numbers, you double the number and add it to the memorized array, then you return the memorized array; for even numbers you do nothing, so you return the memorized array unchanged.
The last value returned by the function is the eventual result of the reduce operation i.e. the array containing the odd numbers doubled.

Why is this sorting algorithm producing inconsistent results across browsers?

I'm trying to implement a (pretty simple indeed) absolute deviation sorting algorithm in Javascript. Absolute deviation is defined as the absolute value of the difference between one element and the average of all elements. For example, given the elements 1, 4, 5 and 9, the average would be (1 + 4 + 5 + 9) / 4 = 4.75, and so the absolute deviation of each element would be calculated as follows:
absDev(1) = |1 - 4.75| = 3.75
absDev(4) = |4 - 4.75| = 0.75
absDev(5) = |5 - 4.75| = 0.25
absDev(9) = |9 - 4.75| = 4.25
Sorting the elements by ascending absolute deviance would hence give the sequence 5, 4, 1, 9. So far so good, my current Javascript implementation is giving me different results in different browsers.
Here it is: http://jsfiddle.net/WVvuu/
In Firefox and Safari, I'm getting the expected result 5, 4, 1, 9
In Chrome and Opera, I'm getting 4, 5, 1, 9
In IE 10, I'm getting 1, 4, 5, 9
I guess there must be probably some very simple mistake in my code but I can't seem to find it. I'd like to understand what's wrong with it and why I'm getting a different result when I change my browser. I'd appreciate it if someone could kindly explain what I'm missing. Again, this is the code:
var array = [1, 4, 5, 9];
function absDev(x) {
return Math.abs(x - average(array));
}
function average(array) {
var sum = array.reduce(function(previousValue, currentValue) {
return previousValue + currentValue;
}, 0);
return sum / array.length;
}
array.sort(function(x, y) {
return absDev(x) - absDev(y);
});
alert("Sorted array: " + array);
I suspect it's because the state of the array while the sort is going on isn't necessarily consistent. You really shouldn't be recomputing the average on each comparison anyway:
array.sort(function(array) {
var avg = array.reduce(function(previousValue, currentValue) {
return previousValue + currentValue;
}, 0);
avg /= array.length;
return function(x, y) {
return Math.abs(x - avg) - Math.abs(y - avg);
};
}(array));
See if that works better. (edit — it gives me the correct answer in Chrome.)
In more detail, my suspicion is that the sort functions where you're seeing weird results may perform swaps on the array in place, and there may be intervals during which one or more original array values is either missing or replicated while the sort mechanism is doing its thing. Thus, your averaging function sees an array (sometimes) with a different list of values, meaning that the average comes out different (sometimes).
The sorting function is recalculating the average of the array for every step of the sort. Different browsers may not keep the array fully intact during the sort process. You can see the effects of this if you add console.log(previousValue, currentValue); into the array.reduce function.
You need to calculate the average of the array first, and store that into a variable. Then pass that variable into the sort function. Here are the changes I made to your code:
var array = [1, 4, 5, 9];
var mean = average(array);
function absDev(x, mean) {
return Math.abs(x - mean);
}
function average(array) {
var sum = array.reduce(function(previousValue, currentValue) {
console.log(previousValue, currentValue);
return previousValue + currentValue;
}, 0);
return sum / array.length;
}
array.sort(function(x, y) {
return absDev(x, mean) - absDev(y, mean);
});
console.log("Sorted array: " + array);
The array state while the sort is in progress is specified to be implementation defined, so you cannot trust that the items are in the array in a particular way during the sort. You must pre-calculate the average before starting the sorting process.
Reference
http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.11
Quote
Perform an implementation-dependent sequence of calls to the [[Get]] ,
[[Put]], and [[Delete]] internal methods of obj

Categories