Why filter method can't work with ternary conditional as it works with if conditionals ?
In case it works with if..else
let numbers = [-1, -2, -3, 1, 2, 3];
let negatives = [];
let positives = numbers.filter(num => {
if(num > 0) {
return num; // positives array => [1, 2, 3]
} else {
negatives.push(num); // negatives array => [-1, -2, -3]
}
})
In case it works with ?
let positives = numbers.filter(num => num > 0 ? num : negatives.push(num));
// positives array => [-1, -2, -3, 1, 2, 3]
// negatives array => [-1, -2, -3]
I tried filter method with if conditionals and it returns the result as I expect. but what is not expected for me the result when it works with ternary conditional.
The issue is that you are misunderstanding what the code you pasted does.
Let's see.. in the first code, the fitering function returns either num or nothing (after negatives.push() there is no return, so it implicitly returns undefined).
function fn(){
// no explicit return
}
const what = fn();
typeof what === "undefined"; // true
The second version, returns either num or the returned value of negatives.push() call, which is by definition:
Return value
The new length property of the object upon which the method was called.
So in the second version of the code, the filter receives for every negative number: 0, 1, 2... and so on. The first occurrence will be taken as a "falsy" (0 is a "falsy" value in JS) so it will be filtered out from the resulting array, the following negative numbers will be included in the resulting array as the filtering function will return "truthy" values for them (positive numbers are "truthy" values in JS)
Being that said...
It is a bad practice to use a filter function for other task than filtering things.
In this scenario where you need to separate positives from negatives, use forEach which is more clear and appropriate.
const positives = [];
const negatives = [];
numbers.forEach(num => {
if(num > 0) { // Note: that this comparison will send ZEROES to negatives array, which could lead to a bug.
positives.push(num);
} else {
negatives.push(num);
}
});
or simpler:
const positives = [];
const negatives = [];
numbers.forEach(num => (num > 0 ? positives : negatives).push(num));
The array method filter expects a callback which returns a boolean value. In your second example you are returning num when num is positive (which is truthy).
However, when num is negative, the method returns the result of negatives.push(num) which is also truthy.
Related
I'm trying to understand how filter() and includes() work with arrays in javascript but english isn't my native language so I would really appreciate it if someone could explain the example below to me like I was 5:
const removeFromArray = function(...num) {
let array = num[0];
return array.filter(val => !num.includes(val))
};
This function takes an array and some other arguments then removes the other arguments from that array for example removeFromArray([1, 2, 3, 4], 3) should remove 3 and return [1,2,4]
How does this part work?
return array.filter(val => !num.includes(val))
Why the exclamation mark and also how do those two methods work together?
I think the key to understanding what is going on is the parameter(s) of the function, num. The code uses a nice trick that I have not encountered before. So, num is:
[[1, 2, 3, 4], 3];
a 1D array with TWO elements: [1, 2, 3, 4] at index 0, and 3 at index 1. As a result:
num.includes([1, 2, 3, 4]) // is true
num.includes(3) // is true
num.includes(anything-else) // is false
The
Array#includes
method determines whether an array includes a certain value among its
entries, returning true or false as appropriate.
In the simplest form, whenever a boolean expression is prefixed with !, the result of the expression is negated. For example:
!num.includes(3) // becomes false
The
Array#filter
method creates a new array with all elements that pass the test
implemented by the provided function.
Pass the test simply means return true.
Now we are ready to look at num[0].filter(val => !num.includes(val)). Or:
[1, 2, 3, 4].filter(val => !num.includes(val))
Please recall that ONLY 3 and [1, 2, 3, 4] return true to:
num.includes(val)
Hence of all the elements of num[0] or [1, 2, 3, 4] only 3 returns false to the negated expression:
!num.includes(val)
1, 2, and 4 return true or !false, meaning that they pass the test and hence will be returned by the function:
[1, 2, 4];
Please note that val => !num.includes(val) is a shorthand way of writing:
function( val ) {
return !num.includes(val);
}
const removeFromArray = function(...num) {
let array = num[0];
return array.filter(val => !num.includes(val))
};
console.log( removeFromArray([1, 2, 3, 4], 3) );
Rest parameters shouldn't be used like that, it should only be used for like values. So, the array should be accepted separately and only the numbers to remove should be accepted using rest (refer to the snippet below).
The includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate.
So, we simply filter out numbers that are not present in the itemsToRemove array.
const removeFromArray = (array, ...itemsToRemove) =>
array.filter((item) => !itemsToRemove.includes(item));
removeFromArray([1, 2, 3, 4], 3, 2);
! means "not". If something is falsy (null, 0, false, an empty string), then !something returns true. This leads to a really strange looking "cheat code" where you can convert any value to a boolean (i.e. truthy to true and falsy to false) via !!value. One exclamation point converts it to a boolean value that's true if value is falsy, then the second exclamation point changes true to false (or false to true)!
array.prototype.filter requires a function to be evaluated against each element and returns an array of only the elements where the supplied function returns a truthy value.
It might be easier to think of the following code that is nearly equivalent to yours...
const removeFromArray = function(array, ...valsToRemove) {
const isValToKeep = val => array.includes(val) === false;
return array.filter(isValToKeep)
};
The only difference in this code, besides being longer, is that the first argument won't be looked for within the first argument. Consider
const a1 = [1,2,3];
a1.push(a1); // appends itself as its last element
In your version, removeFromArray(a1, 2) would return [1, 3], but mine doesn't combine the first argument as one of the elements to look for and remove from the first argument, which is probably what most people would expect and be more performant, but would definitely have a different effect in the example returning [1, 3, a1], i.e. [1, 3, [1, 2, 3, [1, 2, 3, [1, 2, 3, [1, 2, 3, [1, 2, 3, [1, 2, 3, [...]]]]]]]]
This is a simple form to explain how to use filter
function isBigEnough(value) {
return value >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// result is [12, 130, 44]
This example is from: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Lets start by rewriting this line:
Before
return array.filter(val => !num.includes(val))
after
const callback = val => {
return !num.includes(val)
}
const filteredArray = array.filter(callback);
return filteredArray
Now lets break and explain parts of this statement:
! num.includes(val)
includes method here will check if num has val in it. If num has val it will return true else it will return false. note the ! at the begining of the line ,that will change the value returned by includes to its opposite e.g if value returned is false it will change it to true if value returned is true it will change it to false.
array.filter(callback)
The filter method here will go through every element of the array and at each element it will ask whether to add that element to the filteredArray or not based on what is returned from the callback. If callback returns true(truthy) it will add it else it will not add it. The callback should only return true(truthy) or false(falsy).
example :
At index 0 of array the filter will ask did callback return true if it returned true the element at index 0 will be added to the filtered array. It will do the same for the rest of the elements.
How they work together:
array has [1,2,3,4] so array.filter(callback) will go over each element and call callback on each. Based on your function num has this [[1,2,3,4],3] so when val from array is 3 !num.includes(val) will return true but this ! will change it to false as a result callback will return false and 3 will not be included in the final array the filteredArray.
Hope a 5 year old can understand this explantion. You can now rewrite your function like this :
const removeFromArray = (array, ...numbersToRemove) => array.filter((number) => !numsToRemove.includes(number));
Disclaimer: I'm new to JS.
So, all of my test are passing but one - the last one - and I can't figure out how to fix it. Why is 0 returning the last element and not an empty array? Also, on a separate note, when I tried to use a default value it returned the wrong values hence why I decided to include a conditional statement.
const takeRight = (array, n) => {
n = -n;
//setting both default and a negative value for 'n': if there is no number, then n should be -1
const condition = !n ? -1 : n;
return array.slice(condition);
};
console.log(takeRight([1, 2, 3])); // returns [3] --> expected [3]
console.log(takeRight([1, 2, 3], 2)); //returns [2, 3] --> expected [2,3]
console.log(takeRight([1, 2, 3], 5)); //returns [1, 2, 3] --> expected [1,2,3]
console.log(takeRight([1, 2, 3], 0)); //returns [3] --> expected []
The problem is in calculating condition variable:
const condition = !n ? -1 : n;
expression !0 is true, so n is changed to -1 when has value 0.
If you want to check against not a numbers, use isNaN.:
const takeRight = (array, n) => {
n = isNaN(n) ? -1 : -n;
return array.slice(n);
};
The problem might be your expression. Check for "Not a Number"
const condition = isNaN(n) ? -1 : n;
MDN: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/isNaN
So, first of all, you shouldn't reassign arguments. But the actual issue here is using !n to test if it exists. It might be a neat trick here and there but is actually just confusing. Prefixing anything with a ! in JavaScript turns it into a boolean and then negates it, and the rules for this conversion are not obvious and also not the same in every language. But in JS, !0 gives you the same result as !undefined or !null, because the number 0 often symbolizes an "off-state", basically a false, but shorter. Anyways, to avoid all of that, just use default parameters and use a ternary operator in the return statement to check for the specific case of n = 0 like so:
const takeRight = (array, n = 1) => n === 0 ? [] : condition array.slice(-n);
You can of course make that a bit more verbose as well:
const takeRight = (array, n = 1) => {
if (n === 0) {
return [];
}
return condition array.slice(-n);
};
EDIT: Even !"" returns true by the way, which is not really "obvious" as well, so using ! to check if something is defined or exists, can really cause problems and unexpected outcomes. I'd advise against it unless you really really know what you are doing and why.
The code below is modified version of a code taken from the book Professional JavaScript for Web Developers.
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
function numElements(typedArrayConstructor, ...typedArrays) {
// Count the total elements in all arrays
return typedArrays.reduce((x,y) => (x.length || x) + y.length);
}
console.log(numElements(Int32Array, Int8Array.of(1, 2, 3), Int16Array.of(4, 5, 6), Float32Array.of(7, 8, 9)));
My question is what does the (x.length || x) do? Why do we need to perform an or operation on x.length and x?
A little more explanation to go with Pointy's answer:
The || in JavaScript isn't just a logical OR operation, it deals with "truthy/falsey" values, not just booleans.
undefined is falsey. When the first operand of || is falsey, the second operand is evaluated, and becomes the result of the expression. Thus undefined || 0 equals 0.
In your sample code, this means when x is 0, you add 0, and get a proper numeric result. If you try to add to undefined to another number, all of your calculations turn into NaN after that.
When .reduce() is invoked with only one argument, the very first iteration uses element 0 as the first callback parameter and element 1 as the second.
In your case, that means that on the first iteration, x will be one of the arrays (note that y is always an array). Thus that little expression differentiates between when it's the first iteration and when it's a subsequent iteration by taking advantage of the fact that
someNumber.length
is always undefined. (As correctly noted in another answer, it's critical to recall that (undefined || x) will always be x, whatever its value may be.) On subsequent iterations therefore x is the running total of the array lengths.
The .reduce() could have been written as follows instead:
return typedArrays.reduce((x,y) => x + y.length, 0);
By passing the second argument (0) to .reduce(), the first callback invocation will be the same as the others, and x will always be a number.
If x has any elements and x exists then use the length in the sum. Otherwise if length is undefined then return the current element x
Example 1: - happens on first iteration of reduce loop
x is array [1, 2, 3]
x.length || x -> returns the length of array or current total
// summation of code would do the following
firsthArrayLength + secondArrayLength = newTotal
Example 2: - happens on rest of iterations of reduce loop
x is integer 5
x.length || x -> returns x since length of integer is undefined
// summation of code would do the following
currentTotal + curLength = newTotal
NOTE: Keep in mind that with this example if any of the arrays is null or undefined then it will throw since we cannot access property length of undefined or null
So sad that is taken from a book. Using reduce and || like that is reckless and unprofessional -
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
const numElements = (constructor, ...arrs) =>
new constructor(arrs.reduce((r, a) => r + a.length, 0))
const result =
numElements
( Int32Array
, Int8Array.of(1, 2, 3)
, Int16Array.of(4, 5, 6)
, Float32Array.of(7, 8, 9)
)
console.log(result.constructor)
console.log(result)
// Int32Array
// { 0, 0, 0, 0, 0, 0, 0, 0, 0 }
The description of the function says it should concat the other arrays, not just initialise an empty typed array. Here's what that might look like -
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
const concatMixed = (constructor, ...arrs) =>
{ const r = new constructor(arrs.reduce((r, a) => r + a.length, 0))
let i = 0
for (const a of arrs)
for (const val of a)
r[i++] = val
return r
}
const result =
concatMixed
( Int32Array
, Int8Array.of(1, 2, 3)
, Int16Array.of(4, 5, 6)
, Float32Array.of(7.1, 8.2, 9.3)
)
console.log(result.constructor)
console.log(result)
// Int32Array
// { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
I need to check if numbers exist in my array. Using the some() function I find that zero comes back false. It's a problem because I am working with a ton of different numbers and zero being one of them.
var array = [0, 1, 3, 4, 5];
var test = function(element) {
return 0;
};
console.log(array.some(test));
// expected output: true on 0 <-- getting false
// expected output: true on 1
// expected output: false on 20
In short how can I get 0 to return true?
the test function always returns zero.
var test = function(element) {
return element == 0
};
This way the function should work properly.
The test function should return true/false.
In your case you always return 0 which is evaluated to boolean false.
For what you're trying to implement, it might make more sense to use the .includes function, which tests whether an array includes a value:
var array = [0, 1, 3, 4, 5];
console.log(array.includes(0));
console.log(array.includes(1));
console.log(array.includes(2));
Though, .includes (along with all array iteration methods) is an O(N) process - if you need to carry out a bunch of tests for the same array, you might convert the array to a Set first, so that you can then use Set.has (which is generally O(1)):
var array = [0, 1, 3, 4, 5];
const set = new Set(array);
console.log(set.has(0));
console.log(set.has(1));
console.log(set.has(2));
I will go straight to code on this one because it is very specific. How can I test if arr contains an element which is itself a specific array. Why can I not use an array literal as an arg to indexOf? I'm trying to see if an array of coordinates contains a specific coordinate pair.
var arr = [[0,0], [1,1]];
arr[0]; // [0, 0]
arr.indexOf([0, 0]); // -1
var y = arr[0];
arr.indexOf(y); // 0
var x = [0, 0];
arr.indexOf(x); // -1
As others have pointed out, [0, 0] !== [0, 0]. Instead, use findIndex with a function which checks array equality:
var match = [0, 0];
array.findIndex(function(elt) { return arrayIsEqual(elt, match); })
Now you just have to write arrayIsEqual. See How to check if two arrays are equal with JavaScript? for some ideas. If your arrays always have two elements, then it could be just elt[0] === match[0] && elt[1] === match[1].
Obligatory disclaimer: findIndex is ES6. However, it's implemented almost everywhere you would care about, except apparently IE11. If need be, use a polyfill or write your own.
"Why can I not use an array literal as an arg to indexOf?"
I beleive the problem here is that an Array is an object. You cannot create a new object
var x = [0, 0];
and expect it to match arr[0]; because it is not the same object in memory.
To demonstrate I think this would work, but I havent tested it:
var arr = [[0,0], [1,1]];
var x = [0, 0];
arr[0] = x;
arr.indexOf(x); // 0
We can use instanceOf to check the type of variable or value
if(value instanceOf Array)
{
//value is of array type
}
and if you want to compare it with some specific array try below code in if statement
var is_same = (array1.length == array2.length) && array1.every(function(element, index) {
return element === array2[index];
});
if is_same is true then array is identical
Passing objects into the array#indexOf method might not give the results you expect
The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
Hopefully, someone else can confirm this.
Sounds like the perfect use for array.prototype.some
function hasCoordinates(array, coords) {
return array.some(function(element, coords) {
return Array.isArray(element) && String(array[element]) == String(coords);
});
}
hasCoordinates([1,3,[4,6]], [4,6])
=> should return true
We can use the isArray method to determine if an object is array.
It should be noted that the some function is only available in ie9 and up
To find the exact coordinates, you can not compare arrays for equality, as they are treated as different objects.
Ex.
[0,0] == [0,0]
=> false
We need to perform type conversion first
String[0,0] == String[0,0]
=> true
This is because, now the arrays are being evaluated as strings.
You must use a comparable type to use .indexOf(). When you use a comparison operator with objects (and Arrays are objects) then JS uses reference comparison (MDN Docs). It is probably not what you want to do but you can use a reference as I will show below:
var a = [0, 0];
var b = [1, 1];
var c = [1, 1];
var e = c; // e now has the same reference as c
console.log(b == c); // false - Reference comparison is used here
console.log(c == e); // true - References are the same
var d = [a, b, c];
console.log(d.indexOf(a)); // 0
console.log(d.indexOf(b)); // 1
console.log(d.indexOf(c)); // 2
console.log(d.indexOf(e)); // 2
If you create 2 objects with the same values inside they still do not have the same reference (like b and c in the above code). As mentioned by #torazaburo you could instead use the .findIndex() function.
You can do this something like below where you pass in your array to find and it returns a function which returns true when it matches each element.
var a = [0, 0],
b = [0, 0],
c = [0, 1];
var d = [a, b, c];
function equalArrays(a) {
return function(b) {
if (a.length != b.length)
return false;
for (var i = 0, len = a.length; i < len; i++) {
if (a[i] != b[i])
return false;
}
return true;
}
}
console.log(d.findIndex(equalArrays([0, 0]))); // 0 - index of FIRST array matching [0,0]
console.log(d.findIndex(equalArrays([0, 1]))); // 2 - index of an array matching [0,1]
console.log(d.findIndex(equalArrays([1, 1]))); // -1 - No match found
To answer the last question "Why can I not use an array literal as an arg to indexOf?" the answer is due to how indexOf is defined.
Refer to the ECMAScript 2015 Array.prototype.indexOf spec: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.indexof
indexOf compares searchElement to the elements of the array, in ascending order, using the Strict Equality Comparison algorithm (7.2.13), and if found at one or more indices, returns the smallest such index; otherwise, −1 is returned.
The important part is the algorithm used; Strict Equality Comparison. In other words, triple equals.
Consider this JS:
var a = [1, 2];
var b = a === [1, 2]; // true or false?
What is the value of b? It's false. If you want to learn more about why that is the case, I recommend reading https://stackoverflow.com/a/14853974/1022333.