Bug in Array.prototype.includes? - javascript

I encountered strange behavior of Array.prototype.includes in one edge case.
Given that Array.prototype.includes works on bound context, one might use it like this (which is working)
expect(Array.prototype.includes.call([1, 2], 1))).toBe(true)
simply put, we bound array [1, 2] and test 1 for inclusion.
Then consider, that many Array.prototype methods are able to bound context to provided callback, so for example Array.prototype.some can be combined with Object.prototype.hasOwnProperty like this
expect(["foo", "bar"].some(Object.prototype.hasOwnProperty, { foo: 0 })).toBe(true)
Here, .some accepts two parameters, (callback, [thisArg]), where optional thisArg, when provided, is bound to callback, thus previous example binds { foo: 0 } to callback Object.prototype.hasOwnProperty and then tests all items in ["foo", "bar"] if at least one is own property of { foo: 0 }. This example is also working.
But something strange happen, if you try to use Array.prototype.includes as callback.
[0, 1].some(Array.prototype.includes, [1]) // => false
here we bind array [1] to Array.prototype.includes and we test every item of [0, 1] if at least one is included. But this case returns false, which is against our expectation.
Strangely, if bound array contains other number than 1 or contains more than one item, the test passes
[0, 1].some(Array.prototype.includes, [0]) // => true
[0, 1].some(Array.prototype.includes, [1, 1]) // => true
// but
[0, 1].some(Array.prototype.includes, [1]) // => false
It seems like array [1] is handled improperly.
Tested in
Node v.11.11.0
Node v.8.11.3
and Chrome 73
I tested basically just V8 engine. Can anyone report output in Chakra?

It's not a bug in includes. :-)
The problem is that includes accepts an optional second parameter, which is the index at which to start searching, and some provides three arguments to its callback: The item, its index, and the object being searched.
So with
[0, 1].some(Array.prototype.includes, [1])
These calls are made (effectively):
[1].includes(0, 0) - false, the array doesn't contain 0
[1].includes(1, 1) - false, the array contains 1 at index 0, but the search starts at index 1
This comes up periodically. For instance, when trying to use parseInt as a callback directly to convert an array of strings into an array of numbers, because of parseInt's second parameter (the number base, or radix, to use):
console.log(["6", "9", "7"].map(parseInt));
The 9 and 7 fail becaue the calls are (effectively):
parseInt("6", 0) - works because parseInt ignores the invalid radix 0.
parseInt("9", 1) - NaN because parseInt always returns NaN for radix 1
parseInt("7", 2) - NaN because "7" is not a valid digit in base 2 (binary)
The moral of the story: Remember the not-commonly-used arguments that map, some, forEach, and various other methods provide to callbacks. :-)
In one codebase I was working in, they had a clamp function that accepted a function and ensured that, regardless of how many arguments it was called with, it would only pass on the desired number of arguments. If you were using includes like this a lot, you could create a clamped includes:
function clamped(fn, count) {
return function(...args) {
return fn.apply(this, args.slice(0, count));
}
}
const includes = clamped(Array.prototype.includes, 1);
console.log([0, 1].some(includes, [1])); // true
console.log([0, 1].some(includes, [3])); // false
The handy thing about that is that that includes is reusable.
Or of course, just use a wrapper function:
console.log([0, 1].some(function(entry) {
return this.includes(entry);
}, [1])); // true
console.log([0, 1].some(function(entry) {
return this.includes(entry);
}, [3])); // false
These are all meant to be general solutions, of course. If you specifically want to know if array a contains any of the entries in array b, there are more specific implementations you can build to handle that efficiently depending on the characteristics of a and b.

Related

How does this code to create an array of sequential numbers work?

I found this code online and it works; however, I can't work out how!
Can anyone please explain how this code works?
const arr_seq = Array.apply(null, {
length: 10
}).map(Number.call, Number);
console.log(arr_seq)
Output is:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Also, can anyone explain why this code, which I thought might do exactly the same thing, instead creates an array filled with undefined?
const arr_undef = Array(n).map(Number.call, Number);
Array.apply expects an array(-like) value for its second argument. It will then create an argument for each slot in this array-like object.
Since this code passes { length: 5 } as argument, the apply method will call Array with 5 values. But when reading { length: 5 }[i] for i in the range 0..4, it will always get undefined. So Array.apply(null, { length: 5 }) will translate to Array(undefined, undefined, undefined, undefined, undefined).
Then the .map call has a second argument, which is the thisArg argument. This ensures that not just call is called, but Number.call, i.e. with the right this setting. With Number.call the first argument will receive those undefined values (explained in the previous paragraph) and as second argument, the index of the mapping. So we get these calls:
Number.call(undefined, 0)
Number.call(undefined, 1)
Number.call(undefined, 2)
Number.call(undefined, 3)
Number.call(undefined, 4)
This gives the same result as:
Number(0)
Number(1)
Number(2)
Number(3)
Number(4)
And .map will return an array with those values.
Newer ways to do this
Since ECMAScript 2015, JavaScript has Array.keys() and spread syntax for array literals. So we can now achieve the same in a less cryptic way:
console.log([...Array(5).keys()]);
Array(n) creates an empty array of length 10; the map/Number.call part requires something to be there to get the index:
const arr = Array(5).fill(null).map(Number.call, Number);
console.log(arr);

the DOM doesn't work with me when I try min and max and it show NaN [duplicate]

I am trying to get the highest number from a simple array:
data = [4, 2, 6, 1, 3, 7, 5, 3];
alert(Math.max(data));
I have read that if even one of the values in the array can't be converted to number, it will return NaN, but in my case, I have double-checked with typeof to make sure they are all numbers, so what can be my problem?
The reason why your code doesn't work is because Math.max is expecting each parameter to be a valid number. This is indicated in the documentation as follows:
If at least one of arguments cannot be converted to a number, the result is NaN.
In your instance you are only providing 1 argument, and that 1 value is an array not a number (it doesn't go as far as checking what is in an array, it just stops at knowing it isn't a valid number).
One possible solution is to explicitly call the function by passing an array of arguments. Like so:
Math.max.apply(Math, data);
What this effectively does is the same as if you manually specified each argument without an array:
Math.max(4, 2, 6, 1, 3, 7, 5, 3);
And as you can see, each argument is now a valid number, so it will work as expected.
Spreading an array
You can also spread the array. This essentially treats the array as if each item is being passed as it's own argument.
Math.max(...data);
if you see doc for Math.max you can see next description
Because max() is a static method of Math, you always use it as Math.max(), rather than as a method of a Math object you created (Math is not a constructor).
If no arguments are given, the result is -Infinity.
If at least one of arguments cannot be converted to a number, the result is NaN.
When you call Math.max with array parameter like
Math.max([1,2,3])
you call this function with one parameter - [1,2,3] and javascript try convert it to number and get ("1,2,3" -> NaN) fail.
So result as expected - NaN
NOTE: if array with just one number - all work correctly
Math.max([23]) // return 23
because [23] -> "23" -> 23 and covert to Number is done.
If you want get max element from array you should use apply function, like
Math.max.apply(Math,[1,2,3])
or you can use the new spread operator
Math.max(...[1,2,3])
It's not working because you are passing an array as the parameter instead of comma separated numbers. Try spreading the array like this:
data = [4, 2, 6, 1, 3, 7, 5, 3];
alert(Math.max(...data));
If you have to use the Math.max function and one number of the array might be undefined, you could use:
x = Math.max(undefined || 0, 5)
console.log(x) // prints 5
You will need to use the spread operator while passing array as an argument to the Math.max() method.
alert(Math.max(...data)); //this should resolve it.

What exactly `Function.prototype.length` means?

There was a Function.prototype.arity property purposed for getting number of arguments function expects. Now it is obsolete (since JS 1.4), and the same goal has Function.prototype.length.
But recently I've found an article in the documentation about Array.prototype.reduce method. And it clearly says that the method has property length equal to 1:
The length property of the reduce method is 1.
This exact article has a header with number of arguments, and there are two of them:
Array.prototype.reduce ( callbackfn [ , initialValue ] )
callbackfn and initialValue (optional).
So it is not clear to me, what exactly the purpose of length property.
If it is used to give useful information to developer, then it actually does not.
If it is just a technical automatically-generated property that just indicates number of arguments in function definition, then why don't maintain its consistency?
Array.prototype.reduce's length is 1 because the second parameter is optional.*
So it is not clear to me, what exactly the purpose of length property.
To tell the developer how many declared parameters it has prior to the first parameter with a default value (if any) or the rest parameter (if any), whichever is earliest in the parameter list. Or as the spec puts it:
The value of the length property is an integer that indicates the typical number of arguments expected by the function.
The exact algorithm is in the spec's Static Semantics: ExpectedArgumentCount section.
If it is used to give useful information to developer, then it actually does not.
Well, that's a matter of opinion. :-)
When you have a language like JavaScript where functions can only express an expectation but may be called with fewer or more arguments, and particularly when you add the concepts of default parameter values and rest parameters, it's not surprising that the arity of function is a bit of a soft concept.
Some fun examples:
function ex1(a) { } // length is 1, of course
function ex2(a, b = 42) { } // length is 1, `b` has a default
function ex3(a, b = 42, c) { } // length is 1, `b` has a default and
// `c` is after `b`
function ex4(a, ...rest) { } // length is 1 yet again, rest parameter doesn't count
* In ES5, its declaration in JavaScript would be:
function reduce(callback) {
// ...
}
...and then it would use arguments.length to determine whether you'd supposed an initialValue.
In ES2015+ (aka "ES6"+), it would either still be like that, or be like this:
function reduce(callback, ...args) {
// ...
}
...and use args.length to see if there was an initial value.
Or possibly like this:
const omitted = {};
function reduce(callback, initialValue = omitted) {
// ...
}
...and then use initialValue === omitted to know whether you'd supplied an initial value. (The default value of initialValue can't be undefined or null or similar because the function has to branch based on whether the argument was provided [not what its value is]. But we can do that with object identity.)

Why is math.max() returning NaN on an array of integers?

I am trying to get the highest number from a simple array:
data = [4, 2, 6, 1, 3, 7, 5, 3];
alert(Math.max(data));
I have read that if even one of the values in the array can't be converted to number, it will return NaN, but in my case, I have double-checked with typeof to make sure they are all numbers, so what can be my problem?
The reason why your code doesn't work is because Math.max is expecting each parameter to be a valid number. This is indicated in the documentation as follows:
If at least one of arguments cannot be converted to a number, the result is NaN.
In your instance you are only providing 1 argument, and that 1 value is an array not a number (it doesn't go as far as checking what is in an array, it just stops at knowing it isn't a valid number).
One possible solution is to explicitly call the function by passing an array of arguments. Like so:
Math.max.apply(Math, data);
What this effectively does is the same as if you manually specified each argument without an array:
Math.max(4, 2, 6, 1, 3, 7, 5, 3);
And as you can see, each argument is now a valid number, so it will work as expected.
Spreading an array
You can also spread the array. This essentially treats the array as if each item is being passed as it's own argument.
Math.max(...data);
if you see doc for Math.max you can see next description
Because max() is a static method of Math, you always use it as Math.max(), rather than as a method of a Math object you created (Math is not a constructor).
If no arguments are given, the result is -Infinity.
If at least one of arguments cannot be converted to a number, the result is NaN.
When you call Math.max with array parameter like
Math.max([1,2,3])
you call this function with one parameter - [1,2,3] and javascript try convert it to number and get ("1,2,3" -> NaN) fail.
So result as expected - NaN
NOTE: if array with just one number - all work correctly
Math.max([23]) // return 23
because [23] -> "23" -> 23 and covert to Number is done.
If you want get max element from array you should use apply function, like
Math.max.apply(Math,[1,2,3])
or you can use the new spread operator
Math.max(...[1,2,3])
It's not working because you are passing an array as the parameter instead of comma separated numbers. Try spreading the array like this:
data = [4, 2, 6, 1, 3, 7, 5, 3];
alert(Math.max(...data));
If you have to use the Math.max function and one number of the array might be undefined, you could use:
x = Math.max(undefined || 0, 5)
console.log(x) // prints 5
You will need to use the spread operator while passing array as an argument to the Math.max() method.
alert(Math.max(...data)); //this should resolve it.

unexpected "map" result (Javascript) [duplicate]

This question already has answers here:
Why does parseInt yield NaN with Array#map?
(8 answers)
Closed 3 years ago.
Can someone explain why this has the unexpected result:
["1", "2", "3"].map(parseInt);
which returns [1, NaN, NaN] instead of [1, 2, 3]?
I can make it work by forcing a single-argument function:
["1", "2", "3"].map(function(s) {return parseInt(s);});
but I don't know what exactly went wrong in the first way.
As others mentioned, the problem is due to the 2nd argument.
Comments from MDN:
// 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.
// See the blog post for more details
But, with the use of bind, you can achieve the desired result:
var _parseInt = function(radix, number) {
return parseInt(number, radix);
}
["1", "2", "3"].map(_parseInt.bind(this, 10));
parseInt takes two arguments (the second being the radix), that's why this doesn't work. See the bottom of this MDN page for more information. Cited:
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.
this article describes your issue in its last sample, exactly the same what you are trying to do. However the blog article they refer to doesn't exist anymore.
array.map() and parseInt callback
A better answer cited from Stackoverflow:
So if you call a function which actually expects two arguments, the
second argument will be the index of the element.
In this case, you ended up calling parseInt with radix 0, 1 and 2 in
turn. The first is the same as not supplying the parameter, so it
defaulted to base 10. Base 1 is an impossible number base, and 3 is
not a valid number in base 2:
parseInt('1', 0); // OK - gives 1
parseInt('2', 1); // FAIL - 1 isn't a legal radix
parseInt('3', 2); // FAIL - 3 isn't legal in base 2

Categories

Resources