JavaScript Map Reduce: weird behavior - javascript

var t = [-12, 57, 22, 12, -120, -3];
t.map(Math.abs).reduce(function(current, previousResult) {
return Math.min(current, previousResult);
}); // returns 3
t.map(Math.abs).reduce(Math.min); // returns NaN
I don't understand why the second form doesn't work. Any explanations are welcomed.
EDIT: Technical context: Chrome and Firefox JavaScript engine. See ES5 reduce http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.21

Math.min accepts multiple arguments. This is exactly the same reason this doesn't work for parseInt or other functions like that. you need to bind the parameters yourself.
reduce feeds the values like index and array to Math.min
We can confirm this if we follow the following steps:
First, we proxy Math.min:
var oldMath = Math.min;
Math.min = function (){
console.log(arguments)
return oldMath.apply(Math, arguments);
}
Then we run the second version:
[-12, 57, 22, 12, -120, -3].reduce(Math.min);
Which logs:
[-12, 57, 1, Array[6]]
Since Array[6] is not a number, the result is NaN
Here is a very similar example from MDN:
["1", "2", "3"].map(parseInt);
While one could expect [1, 2, 3]
The actual result is [1, NaN, NaN]
parseInt is often used with one argument, but takes two. The second being the radix
To the callback function, Array.prototype.map passes 3 arguments: the element, the index, the array
The third argument is ignored by parseInt, but not the second one, hence the possible confusion.

reduce's callback is called passing four arguments: previousValue, currentValue, index and array. And because Math.min is a variadic function, your code:
t.map(Math.abs).reduce(Math.min); // returns NaN
is equivalent to:
t.map(Math.abs).reduce(function(current, previousResult, index, array) {
return Math.min(current, previousResult, index, array);
});
That's why the result is NaN: the last parameter, array, is not a number.
You can also solve this kind of issue with a high-ordered function like this one:
function binary (fn) {
return function (a, b) {
return fn.call(this, a, b);
}
}
And then:
t.map(Math.abs).reduce(binary(Math.min));
will works.

Related

Why does [...].reduce(Math.min,0) return NaN? [duplicate]

In short, this works:
[1, 2, 3].reduce(function (a, b) { return Math.max(a, b); });
=> 3
But this doesn't:
[1, 2, 3].reduce(Math.max);
=> NaN
Pure puzzlement.
This is in Firefox 3.5.9, which I presume is using the mozilla standard implementation of reduce, FWIW.
Math.max can be used as a higher-order function. The problem is .reduce will call the function with 4 arguments:
Math.max(accumulator, value, index, the_array)
here is the_array is an array, so Math.max returns NaN. I don't think there's simpler way to discard the last 2 arguments.
Math.max.apply(Math, [1, 2, 3]);
//3
Not exactly a higher order use, but this also works:
Math.max(...myArray);

Someone please explain the Function.apply.bind(Math.max, null) algorithm

suppose we have this code
function largestOfFour(arr) {
return arr.map(Function.apply.bind(Math.max, null));
}
where arr is an array of arrays.
first,why must i use apply()?
I understand that when using the method Math.max() to operate on an array i must add the apply() method also. So i'll have something like this Math.max.apply(null, arr) why? what does apply() do?
In this code arr.map(Function.apply.bind(Math.max, null)) what does bind() really do?
Please give an explanation i can understand,i really appreciate this.
Looking at the entire expression:
arr.map(Function.apply.bind(Math.max, null));
map expects its first argument to be a function, which is returned by:
Function.apply.bind(Math.max, null);
Function.apply is a shorter version of Function.prototype.apply.
Calling bind on it returns a special version of apply whose this is set to Math.max and when called, will have as it's first parameter (i.e. the value that would normally be used as this) set to null, since it's not going to be used.
So each element in arr will effectively be called using:
Math.max.apply(null, member);
The use of apply means the values in member are passed as parameters, as if:
Math.max(member[0],member[1] ... member[n]);
So the expression returns the maximum value in each array. Those values are returned to map, which puts them into a new array.
var arr = [[1,2,3],[4,5,6]];
console.log(
arr.map(Function.apply.bind(Math.max, null)) //[3, 6]
);
and is effectively the same as:
var arr = [[1, 2, 3],[4, 5, 6]];
console.log(
arr.map(function(a) {return Math.max.apply(null, a)}) //[3, 6]
);
Though using recent features you might use destructing with rest parameter syntax:
var arr = [[1, 2, 3],[4, 5, 6]];
console.log(
arr.map(a => Math.max(...a)) // [3, 6]
);
Simply put, .apply calls a function with the set of arguments(array-like) passed to it.
EG:
const add = (...args) => args.reduce((acc, next) => acc + next);
I can call the add function with any number of arguments using the .apply method like this.
add.apply(null, [4, 2, 6, 76, 9]) // => 97.
You call also use .call but instead of passing in array-like arguments, you simply pass in the values
add.call(null, 4, 2, 6, 76, 9) // => 97.
With .bind, the difference is that it creates a new function with call be called later.
const addFunc = add.bind(null, 4, 2, 6, 76, 9);
addFunc() // -> 97.
So, as it applies to the function we defined, it also applies to inbuild functions like Math.max, Math.min, etc.
Hope this helps!
The Function.apply.bind(Math.max, null) creates a function definition when invoked takes null as the first parameter by default and any provided parameters will come second. So as a callback to arr.map this function (due to bind statement) will be bound to Math.max however the Function.apply's first parameter will be null and second is going the be the sub array item of the main array (of which the items are to be passed as arguments to Math.max function).
This is an old trick and in ES6 terms arr.map(s => Math.max(...s)); would do the same job much more clearly.

Doesn't the ES6 spread operator flatten an array?

Not react specific, so hope ok to ask, but I thought the spread operator flattens an array?
So with the following sum function which sums the args, you can use .apply to pass in the values:
function sum() {
return arguments.reduce((total, number) => total + number, 0);
}
var values = [2, 4, 8, 12, 16];
console.log(sum.apply(null, values));
I thought you could just addat the function and use the spread operator to flatten the array so call could be used. (I know you wouldn't use call in this instance, but I was just surprised as I thought the spread flattened the array:
function sum() {
return [...arguments].reduce((total, number) => total + number, 0);
}
var values = [2, 4, 8, 12, 16];
console.log(sum.call(null, values));
This returns the string 02,4,8,12,16
This happens because arguments is array-like of array. The array becomes converted to string.
As call documentation says,
While the syntax of this function is almost identical to that of apply(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.
This should work as expected with:
sum(...values);
sum.call(null, ...values);
sum.apply(null, values);
By the way, arguments and call aren't welcome in ES6.
The arguments is an array like structure so while passing the array as an argument the structure would be like a nested array. So applying spread operator results an array in the format [[2, 4, 8, 12, 16]](i.e, [...[[2, 4, 8, 12, 16]]]) and the reduce method apply 0 + [2, 4, 8, 12, 16] and which results "02,4,8,12,16".
To make it work you need to pass the array values as arguments using Function#apply or get the first argument.
function sum() {
return [...arguments].reduce((total, number) => total + number, 0);
}
var values = [2, 4, 8, 12, 16];
console.log(sum.apply(null, values));
function sum() {
return [...arguments][0].reduce((total, number) => total + number, 0);
}
var values = [2, 4, 8, 12, 16];
console.log(sum.call(null, values));
Refer : What is the difference between call and apply?

Why does using Array.map(parseInt) on an array of strings produce different results

I was watching a talk on destroy all software title The Birth and Death of Javascript
during the talk Gary Bernhardt pointed out a JavaScript quirky features, were given an array of integer strings,
javascript
var a = ['10','10','10','10']
console.log(a.map(parseInt)); // [10, NaN, 2, 3, 4]
Array.map() takes a function and returns a new array with the result of applying this function on each operator.
I found the behavior incredibly bizarre at first, doesn't parseInt parse a number to an integer?
why would it be NaN? and then not 10!!
JavaScript is often the subject of parody, for its seemingly unexpected results.
var a = []+{} // [Object object]
var b = {}+[] // 0
However there is consistency in its madness, and I suspected the parseInt behavior must have some reason behind it.
Getting to the bottom of what's happening
I first thought of debugging parseInt, but since couldn't debug a native function, I thought of wrapping it around another function that basically does the same thing.
var a = ['10','10','10','10']
var intParse = function (x) {
return parseInt(x);
};
console.log(a.map(parseInt)); // [10, NaN, 2, 3, 4]
console.log(a.map(intParse)); // [10,10,10,10]
Ok so it seems like everything is working fine
But just for the sake of brevity I decided to try some more observations
var a;
(a = Array(13).join('10,').split(',')).pop() // try array of 13 '10's
a.map(parseInt); // [10, NaN, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
(a = Array(10).join('100,').split(',')).pop() // try array of 10 '100's
a.map(parseInt); // [100, NaN, 4, 9, 16, 25, 36, 49, 64, 81]
(a = Array(10).join('7,').split(',')).pop() // try array of 10 '6's
a.map(parseInt); // [7, NaN, NaN, NaN, NaN, NaN, NaN, 6, 6, 6]
Maybe it's not that weird after all
At this point as weird as the results may seem, they are consistent (in some way), there certainly seems to be a pattern.
It then hit me.
Array.map(callback) the callback takes 3 parameters, (key, index, array), so what if parseInt doesn't just take one parameter but 2 instead.
That would certainly had an effect on its results
Turns out The parseInt() function parses a string argument and returns an integer of the specified radix or base.
Syntax
parseInt(string, radix);
the radix is the base of the number
parseInt("10", 0) // 10, zero meant decimal
parseInt("10", 1) // NaN, since only 0 is allowed in base 1
parseInt("10", 2) // 2, '10' in binary
parseInt("10", 3) // 3, '10' in ternary
//...
Since the second argument in map's callback is the index the radix kept changing according to the index.
This explains why my intParse function worked.
I had specifically defined that it uses 'parseInt' with just x.
I thought this was what's happening inside map
var intParse = function (x) { return parseInt(x);}
When in fact this is what was happening
var intParse = function (x, r, array) { return parseInt(x, r);}
What I should've done when wrapping the function was
to not assuming the arguments that where being passed like so
var a = ['10','10','10','10']
var intParse = function () {
return parseInt.apply(this, arguments);
}
console.log(a.map(parseInt)); // [10, NaN, 2, 3, 4]
console.log(a.map(intParse)); // [10, NaN, 2, 3, 4]
Lessons learned
This was a nice exploration, I think I wound up learning a bit more than I thought I would about parseInt.
More importantly tho I was reminded that when programs act in an unexpected way, it is most likely for a reason.
Finally, if one wants to properly wrap a function use .apply(this, arguments)

Javascript - sorting arrays containing positive and negative "decimal" numbers

I originally had the following callback passed as a parameter to the javascript array sort() function:
function sortNumber(a,b) {
return a-b;
}
However this doesn't work when my array contains positive and negative decimal numbers (i.e. -107.578, 97.453 etc.) How would I modify this to sort properly?
I don't see any problems with that function. Here's my test code:
var nums = [10, 5, 40, 25, -3412,4212, -107.578, 97.453];
function sortNumber(a,b){
return a - b;
}
alert( nums.sort(sortNumber) );
Can you show some more of your code? It might be a problem with the array.

Categories

Resources