firstly here is my code :
const removeFromArray = function (...args) {
const array = args[0];
const newArray = [];
array.forEach((item) => {
if (!args.includes(item)) {
newArray.push(item);
}
});
return newArray;
The problem is this !args.includes(item), the NOT operator, it really makes no sense in my mind in this case... I tried to reformulate, rewrite but it doesn't help.
The function is supposed to push every element into the array unless it is included in the function arguments (for example with arguments like this : removeFromArray([1, 2, 3], 2, 4)) but how the 'unless' work with this if condition ?
Can you light up my lantern please ?
It seems you kind of interpreting the "unless" as literal here and I see how this might be a little bit confusing at first.
Perhaps if it was rephrased, it might be less confusing:
don't push element into array unless it's NOT including in the arguments.
Or more literal to actual code:
if item is NOT in the arguments, add it to the array
.includes() returns true or false and ! negates the result. The loop will only execute if .includes() in your example returns false.
To me it seems as if you want to filter the array, so maybe you should use the .filter() method:
const newArray = array.filter((el)=>!(testArray.includes(el)))
Related
Why do I need to wrap animals.includes in an anonymous function for the following ES6 js code to work as expected?
const animals = ["ape", "dog", "pig"]
const nouns = ["car", "planet", "apple", "dog"]
const hasPulse = nouns.some((n) => animals.includes(n))
console.log(`Heartbeat ${hasPulse ? '' : 'not '}detected!`)
If I unwrap animals.includes it throws a type error.
// this fails with TypeError: can't convert undefined to object
const hasPulse = nouns.some(animals.includes)
It's because of the incompatibility between how Array.prototype.some() calls it's callback and how Array.prototype.includes() expect arguments.
The full definition of [].includes() is:
[].includes(thingToSearch, startSearchFrom);
For example, if you do:
[1,2,3,4,5].includes(1,3); // false
It would return false even though the array clearly includes 1. This is because you told it to start searching from element 3 so .includes() only looked at 4 and 5 and could not find 1.
The full definition of [].some() is:
[].some(function (item, index, theArray) {}, this);
So the .some() will pass the index of the current item to the callback you pass to it. For example if you do:
[10,20,30,40,50].some((x,y) => y == 2); // 30
It would return 30 because he first time .some() loops over the array it passes 10 to x and 0 to y. The second time it passes 20 to x and 1 to y. And the third time y will be 2 so y == 2 is true so it returns the value where it is true which is 30.
Now you can see that what .some() passes as the second argument and what .includes() expects as the second argument have different meanings.
The .some() method will pass the loop index while the .includes() method expect the second argument to tell it where to start searching.
So if you do:
const animals = ["ape", "dog", "pig"];
const nouns = ["car", "planet", "apple", "dog"];
nouns.some(animals.includes);
.. it would search for car in ape,dog,pig,
.. then it would search for planet in dog,pig (remember index is now 1 so you are telling .includes() to ignore ape)
.. then it would search for apple in pig (index is now 2 so .includes() ignores ape and dog)
.. then it would search for dog in an empty array which of course it would not find.
Sure, there may be some algorithm where you want to do this. But I don't think this is what you expect to happen.
You could not take
const hasPulse = nouns.some(Array.prototype.includes, animals);
with thisArgs, but unfortunately Array#includes features a second parameter fromIndex, which is handed over by the index and destroys the wanted result.
I believe this is a context issue, you should bind the includes function to the nouns array for it to work the way you want it to. The reason why the code you have is not working as you expect it is because when you pass the includes function to the some, it is not executed on the array (this is either undefined or is set to the window object in the browser). Here is the working example
const animals = ["ape", "dog", "pig"]
const nouns = ["car", "planet", "apple", "dog"]
const hasPulse = nouns.some(animals.includes.bind(nouns))
console.log(`Heartbeat ${hasPulse ? '' : 'not '}detected!`)
It has to do with how a Javascript method "knows" which object it was attached to when called: its this reference.
includes might be implemented something like this (hypothetical example):
Array.prototype.includes = function includes(couldBeMember) {
for (const member of this) {
if (member === couldBeMember) {
return true;
}
}
return false;
}
animals.includes(argument) passes argument to Array.prototype.includes, but also sets its this to animals during the call. Just referencing animals.includes and passing that elsewhere, is no different than passing Array.prototype.includes; if it is ultimately called (inside some) without reference to animals, it won't get animals set as its this.
To create a function includes that "remembers" its this is animals, no matter where the function by itself is passed, you have to bind it to animals: animals.includes.bind(animals) (or Array.prototype.includes.bind(animals) or [].includes.bind(animals)).
const animals = ["ape", "dog", "pig"]
const nouns = ["car", "planet", "apple", "dog"]
const hasPulse = nouns.some([].includes.bind(animals))
This isn't a whole lot cleaner than just using an arrow function (or "lambda"), but hopefully answers your question.
If you like the idea but want to clean it up, you could create a freestanding bind that does something like so:
function bind(self, method, ...args) {
return self[method].bind(self, ...args);
}
const hasPulse = nouns.some(bind(animals, "includes"))
(There's also at least one proposal out there to create an operator that performs the equivalent binding, something like animals::includes, but I don't believe that's finalized yet last I checked so the syntax might change or it might not end up supported.)
The some() method tests if one element or more in the array passes the test implemented in the provided function and returns a Boolean value indicating the result.
const hasPulse = nouns.some((n) => animals.includes(n))
The second alternative won't work.
Array.includes uses a second parameter (fromIndex) which is received from the calling function as index and omits values to be checked by that function.
Is the difference just that while some() returns true if there's at least element that passes the test in the callback function, findIndex() returns index of the first element that succeeds the test?
Or is one more optimal or is there more to it than I understand, because, if that's the only difference then I could simply get the index from findIndex() and check if it's -1 or not.
let foo = [
{
'animal':'dog'
},
{
'animal':'cat'
},
{
'animal':'cow'
},
]
foo.find( obj => obj.animal === 'cat' )
{animal: "cat"}
foo.some( obj => obj.animal === 'cat' )
true
findIndex and some are indeed very similar and you can easily use findIndex instead of some. Also, find has a similar specification. But you cannot easily use it to replace some as the return value of find is the found element or undefined which you cannot tell apart from an actual element.
There is, however, a small difference between findIndex/find and some. See the specification of some:
callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
See this example:
new Array(1).findIndex(() => console.log('called')); // prints 'called' once
new Array(1).some(() => console.log('called')); // does not print anything
That should not actually be relevant in most applications, though.
The reason why those functions exist and you should use them how they were specified is readability. You should always strive to write readable/maintanable code. Of course you can write [].findIndex(...) !== -1 instead of [].some(...) but the latter is clearly more readable as it has better semantics of what it does.
I can't help but notice that it's impossible to do something like
["cat", "dog"].map(String.prototype.toUpperCase);
which throws
Uncaught TypeError: String.prototype.toUpperCase called on null or undefined
The following code works (in es6 arrow notation for my typing convenience), but seems weirdly indirect:
["cat", "dog"].map(s=>s.toUpperCase())
Weirdly indirect because it creates an extra anonymous function just to wrap a perfectly good function that already exists. And maybe this is something that one must live with, but it doesn't taste right.
So I have two questions:
Is there a direct way to map a string method over an array of strings without wrapping it in an anonymous function?
What's the technical explanation for why the code I tried first doesn't work? I have two guesses, but don't know which is right.
Guess (a): this is because of boxing, i.e., string literals aren't the same as string objects, and for some reason mapping the method over them doesn't do the same kind of quiet coercion that just calling the method on the string does.
Guess (b): this is because while functions are first class objects, methods aren't in the same way, and can't ever be mapped?
Guess (c): there's some other syntax I should be using?! ("".prototype.toUpperCase??) Because of JavaScript gremlins? Because null both is and is not an object? Fixed in ES2025? Just use lodash and all will be cured?
Thanks!
1. Is there a direct way to map a string method over an array of strings without wrapping it in an anonymous function?
No (assuming that by "without wrapping it in an anonymous function" you more generally mean "without creating an additional function").
2. What's the technical explanation for why the code I tried first doesn't work? I have two guesses, but don't know which is right.
The "problem" is that toUpperCase is essentially an instance method. It expects this to refer to the value that should be changed. .map however will pass the value as argument to the function. Your first example is basically doing
String.prototype.toUpperCase("dog")
and that's simply not how that function works. The following would work
String.prototype.toUpperCase.call("dog")
but that in turn is not how .map works.
No to guess a and b. Regarding c, the syntax you should be using is your second solution. Of course there are other ways. You could write a helper function:
function toUpper(s) {
return s.toUpperCase();
}
but that's not much different from using the arrow function.
Since toUpperCase doesn't accept arguments, you could even go crazy and to something like
["cat", "dog"].map(String.prototype.toUpperCase.call.bind(String.prototype.toUpperCase));
but that
also creates a new function
is probably less understandable
is not generally applicable
Object methods (and dealing with this) can be a real pain in the neck when you're trying to write elegant functional programs. No worries tho, just abstract the problematic syntax away into its own function
const send = (method, ...args) => obj =>
obj[method](...args)
let x = ["cat", "dog"].map(send('toUpperCase'))
console.log(x) // ['CAT', 'DOG']
let y = ['cat', 'dog'].map(send('substr', 0, 1))
console.log(y) // ['c', 'd']
Here's another way that might be nice to write it
const call = (f, ...args) => obj =>
f.apply(obj, args)
let x = ["cat", "dog"].map(call(String.prototype.toUpperCase))
console.log(x) // ['CAT', 'DOG']
let y = ['cat', 'dog'].map(call(String.prototype.substr, 0, 1))
console.log(y) // ['c', 'd']
And another way by defining your own functions
const map = f => xs => xs.map(x => f(x))
const toUpperCase = x => x.toUpperCase()
const substr = (x,y) => z => z.substr(x,y)
let x = map (toUpperCase) (["cat", "dog"])
console.log(x) // ['CAT', 'DOG']
let y = map (substr(0,1)) (['cat', 'dog'])
console.log(y) // ['c', 'd']
To answer both your questions, No you can't just map a string method to an array of strings without using a callback function.
And the first code you wrote:
["cat", "dog"].map(String.prototype.toUpperCase);
Doesn't work because .map() method expects a callback function that process every iterated element and return it, but you were just trying to call String.prototype.toUpperCase function on undefined because you haven't binded it to the current element.
Solution:
In order to get it to work and to be equivalent to ES6 provided code:
["cat", "dog"].map(s=>s.toUpperCase())
You need to amend your code to accept a callback function that calls String.prototype.toUpperCase on the iterated element which is passed by .map() method:
["cat", "dog"].map(function(s){return s.toUpperCase()});
var a = ["cat", "dog"];
var res = a.map(String.prototype.toUpperCase.call, String.prototype.toUpperCase);
console.log(res);
I've found something interesting and I don't know why it's happening.
If I try in google chrome developer tools, the following two staments
(Array([1,2,3])).filter(function (item, index, array) {
return item === 1;
}); ==> []
and
([1,2,3]).filter(function (item, index, array) {
return item === 1;
}); ==> [1]
The results are an empty array for the first statement and array with a single value (1) for the second
Inspecting the parameters for the callback function, i found that in the first statement the arguments are (array, index, value) and for the second statemente are(value, index, array).
Inspecting with typeof and constructor of both objects the result are the expected, and the same "object", and Array.
Why is this happening?
Thanks
Because that's not how you define an array with Array().
Should be without the square brackets, otherwise it's an array of a single element, which is also an array ([1,2,3]).
Array(1,2,3)
That inner array never equals to 1 (basically you check [1,2,3] == 1), so the result is an empty array.
If you define an array by using Array([1,2,3]) this code, then the following array will be created,
[[1,2,3]]
Since you are pushing an array into another one. And if you really want the Array function to create an array by reading an array then you have to write something like this,
Array.apply([], [1,2,3])
But the above one is completely pointless. Again I am telling it is completely pointless since we are having an array that we require in our hand. But just for a knowledge you can know about it.
Array([1,2,3]) create array of arrays [[1, 2, 3]] so .map()function will iterate one time only.
If you want to create array with Array constructor use next syntax:
Array(1,2,3)
the shorter is the better :
[1,2,3].filter(item => item === 1);
The following code works. But is it the right way to modify an array value within the functional-programming styled methods.
a = [1, 2, 3];
a.every(function(val,index, arr) {
if (val === 2) {
arr[index] += 1;
}
});
No. Though your code works, conceptually you should use the forEach method instead, see here.
(Also, for readability, drop your arr argument and use this.)
But is it the right way to modify an array value within the functional-programming styled methods.
No. With the functional programming paradigm you should not modify any objects (regardless of what method you use). Better:
var a = [1, 2, 3];
var b = a.map(function(val) {
return (val === 2) ? 3 : val;
});
Notice that using every like you did makes no sense - you are not interested in testing a predicate on all elements. When you really want to carry out side-effects from your callback, you should use forEach (which is not very functional, but at least makes your intention clear).