Map and splice gives empty instead of nothing - javascript

I have an algorithm :
let someArray = [1, 2, 3, 4, 5]
let mapped = someArray.map(number => {
let index = someArray.indexOf(5)
if (index !== -1) {
someArray.splice(index, 1)
}
console.log(typeof number)
return number
})
console.log(mapped)
console.log(mapped.length)
console.log(Object.keys(mapped).length)
So what I expected to have was mapped=[1,2,3,4] and mapped.length=4
But instead I have mapped=[1,2,3,4,empty] and mapped.length=5.
So what I thought is : in the beginning, map is going for 5 iterations so it does it no matter what. That's why I added console.log(typeof number).
But it's executed only 4 times.
I know to have my expected result, filter is way better. I'm just wondering, what is happening here ?

See MDN documentation:
map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
If existing elements of the array are changed, their value as passed to callback will be the value at the time map visits them. Elements that are deleted after the call to map begins and before being visited are not visited.
You're mutating the array as you're iterating over it, which means that once index [4] is reached, that element (whose value used to be 5) doesn't exist anymore, which means the function does not get called on that iteration, resulting in <empty>. The resulting array is created with 5 elements, but the callback is never called on the last element.
Use filter instead.

Related

forEach loop quirky behavior with undefined values?

Was writing a script in JS to make some dummy data for testing my API and ran into an interesting quirk with the forEach loop in JS.
const dictionary = {};
const undefinedArray = Array(3); // [undefined, undefined, undefined]
undefinedArray.forEach((_, index) => {
console.log('Logging at index: ', index)
const someObject = { id: index };
if (!dictionary[someObject.id]) {
dictionary[someObject.id] = someObject
}
});
console.log(dictionary);
After checking the output of this snippet, you'll see that nothing inside the forEach loop is logged and the dictionary is still an empty object. I was talking with my coworker about this behaviour and he said he ran into this particular issue before and offered this as a solution.
const dictionary = {};
const undefinedArray = [...Array(3)]; // [undefined, undefined, undefined]
undefinedArray.forEach((_, index) => {
console.log('Logging at index: ', index)
const someObject = { id: index };
if (!dictionary[someObject.id]) {
dictionary[someObject.id] = someObject
}
});
console.log(dictionary);
By wrapping the Array constructor in square brackets and utilizing the spread operator, now the array is looped through and the object is built correctly. This fascinated me, so I went to the documentation for the Array object and found this:
arrayLength
If the only argument passed to the Array constructor is an integer between 0 and 2^32 - 1 (inclusive), this returns a new JavaScript array with its length property set to that number (Note: this implies an array of arrayLength empty slots, not slots with actual undefined values). If the argument is any other number, a RangeError exception is thrown.
So apparently it is not assigning each value undefined, but only setting its length property to whatever is passed in the constructor. This is not apparent when you log Array(n) to the console because it shows an array with n undefined values. I assume the toString method for the Array object is based on its length property and uses a normal for or for of loop to construct the string.
It does begin to make a little bit more sense, however, when you explicitly set an index of the newly defined array. In the snippet below, the same array is initialized, but the zero index is explicitly assigned undefined as a value. Since this is an "actual undefined value" in Mozilla's words, the forEach loop exectues at index zero.
const dictionary = {};
const undefinedArray = Array(3); // [undefined, undefined, undefined]
undefinedArray[0] = undefined
undefinedArray.forEach((_, index) => {
console.log('Logging at index: ', index)
const someObject = { id: index };
if (!dictionary[someObject.id]) {
dictionary[someObject.id] = someObject
}
});
console.log(dictionary);
Array.map() behaves the same way. So I guess my main question would be, are there other ways to execute forEach and map without filling the array or by using the quirky hack I mentioned earlier?
To recap: these are the two work arounds I've found for this particular use case:
[...Array(n)] OR Array(n).fill(). Both of these mutations to the array will allow a forEach loop to iterate over all values in the array.
So apparently it is not assigning each value undefined, but only setting its length property to whatever is passed in the constructor.
Correct. (Provided you pass only a single argument and it's a number. If you pass a non-number, or pass more than one argument, they're used as elements for the array. So Array("3") results in ["3"]; Array(3, 4) results in [3, 4].)
This is not apparent when you log Array(n) to the console because it shows an array with n undefined values.
It depends on what console you use. The devtools in Chromium browsers show (3) [empty x 3] for exactly that reason, to differentiate between empty array slots and ones containing the value undefined.
So I guess my main question would be, are there other ways to execute forEach and map without filling the array or by using the quirky hack I mentioned earlier?
If you want forEach and map to visit elements of the array, they have to actually exist. Those methods (and several others) are defined such that they don't call your callback for empty slots in sparse arrays. If by "quirky hack" you mean [...Array(3)], that's also filling the array (and is fully-specified behavior: [...x] uses the iterator x provides, and the array iterator is defined that it yields undefined for empty slots rather than skipping them as forEach, map, and similar do). Doing that (spreading the sparse array) is one way to create an array filled with undefined (not empty) elements. Array.fill is another. Here's a third: Array.from({length: 3})
const a = Array.from({length: 3});
a.forEach(value => {
console.log(`value = ${value}`);
});
Which you use is up to you. Array.from is very simple and direct. Similarly Array(3).fill(). I probably wouldn't use the spread version (just because I think it's fairly unclear to people who don't have a deep knowledge of how the array iterator works), but it's a matter of style.

Why is this JavaScript map NOT an Infinite Loop?

I'm learning JavaScript. I wrote this code to learn the map function. But then I got confused as to why is this not mapping over it continuously as with each map sequence a new element is pushed to the array. Shouldn't it continue to push new elements as it is mapping over ? Why does the map function only run for the original three elements and not for the new pushed ones?
I tried to debug it in the node environment and the arr variable goes in a closure. I know what a closure but I'm not able to understand what is going on here.
let array = [1, 2, 3];
array.map((element) => {
array.push(10);
console.log(element);
});
I expect that the output should be 1,2,3,10,10,10,10,10,10,10,10......10
But the actual output is only 1,2,3.
To quote from MDN:
The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback. If existing elements of the array are changed, their value as passed to callback will be the value at the time map visits them.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Description
So, it behaves like that because that is how it is designed. And it is designed that way, amongst other reasons, to prevent infinite loops!
map is a function from the Functional Programming world, where immutability is an important principle. According to this principle, if you call map on an input (and other variables don't change) you will always get exactly the same result. Allowing modification of the input breaks immutability.
Because it does not mutate the array (see Array​.prototype​.map()).
Instead, it returns a new array with the results of calling a provided function on every element in the calling array.
In the following snippet, what mutates the array is the call to array.push(10); three times (once per element in the original array) and not the map function itself.
let newArray = array.map((element) => {
array.push(10); // <-- here you mutate the array
console.log(element);
});
An important quote from the mentioned documentation (and key point here is):
The range of elements processed by map is set before the first
invocation of callback. Elements which are appended to the array
after the call to map begins will not be visited by callback.
In the following snippet you can see an example of how to properly use the map function:
let array = [1,2,3];
let newArray = array.map(element => element + 10); // sum 10 to every element
console.log('original: ', array); // original: [1,2,3]
console.log('new one: ', newArray) // new one: [11,12,13]
One last thought (from the docs too), taking as reference the code you posted:
Since map builds a new array, using it when you aren't using the returned array is an anti-pattern; use forEach or for-of instead.
Signs you shouldn't be using map:
A) You're not using the array it returns, and/or
B) You're not returning a value from the callback.
Why is exactly as seen on MDN:
var new_array = arr.map(function callback(currentValue[, index[, array]]){}
The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback.
(Thank you to Joe's post above from MDN for this quote.)
Once map is called it then takes the array at that moment as it's parameter; once it's been passed then any changes are irrelevant to the previous variable itself.
See below:
let array = [1, 2, 3];
array.map((element) => {
array.push(10);
console.log(element);
});
console.log(array)

What is the JavaScript variable 'empty'

Doing Array.apply(null,[1]) gets me this [empty] but Array(null,[1,2]) gets me [1,2]
the array with 'empty' has a length of one but index zero is undefined.
Array.apply(null,[1]).length
1
Array.apply(null,[1])[0]
undefined
Array.apply(null,[1])
[empty]
console.log(Array.apply([1]));
console.log(Array.apply([1]).length);
There is nothing like empty variable in Javascript, they are empty slots. You are calling Array([1,2]) instead Array.apply([1,2]).
Both Array.apply([1]) and Array.apply([1,2]) will give you same result which will be [].
But when you call Array.apply(this,[1]) it will result in [empty] because internally it is equal to Array(1). And when you call Array.apply(this,[1,2]) it is equal to Array(1,2) which will be [1,2]. When only one number passed to Array constructor it returns an array of empty slots with its length property set to that number.
MDN Docs
The reason you are getting two different results is that in the first case of doing:
Array.apply(null,[4])
You are simply invoking the JavaScript Array constructor with a list of one argument. This single argument to the constructor is used to create an array of size n where n is the number you have in the list. It creates this list without anything in it.
For me in a node REPL, this is the result of the above:
> Array.apply(null, [4])
[ <4 empty items> ]
In the second case where you did:
Array.apply(null, [1, 2])
This is another constructor overload used to specify the contents of the array. So in this case, you are telling the array constructor to create an array containing the elements 1, 2.
> Array.apply(null, [1, 2])
[ 1, 2 ]
See the MDN documentation for details.

Do array elements have names by default in JavaScript? [duplicate]

This question already has answers here:
Understanding Javascript callback parameters
(3 answers)
Closed 5 years ago.
var animals = ["cat","dog","fish"];
var lengths = animals.map(function(c) {
return c.length;
});
console.log(lengths);//[3, 3, 4]
Here is the code. I don't understand where this 'c' argument comes from.
I tried to change this argument to another one (any word, actually, in both places), and the console.log result is always the same!
But this 'c' is not defined anywhere! Where does 'the engine' get the value of this 'c'?
You've asked two slightly different questions. First to the question body:
I don't understand where this 'c' argument comes from. I tried to change this argument to another (any word, actually, in both places), and the console.log result is always the same!
But this 'c' is not defined anywhere!
Where does 'the engine' gets the value of this 'c'?
You define the parameter name (as you've noticed, you can choose any name for it you like). The value comes from the array, because map calls your callback and determines what argument to pass for that parameter.
Here's a conceptual implementaton of Array.prototype.map, which make make this clearer:
// CONCEPTUAL ONLY, NOT AN ACTUAL VERSION OF IT
function maplike(array, callback) {
var result = [];
for (var i = 0; i < array.length; ++i) {
result[i] = callback(array[i]);
// ^^^^^^^^--- where 'c' comes from
}
return result;
}
var animals = ["cat","dog","fish"];
var lengths = maplike(animals, function(c) {
return c.length;
});
console.log(lengths);//[3, 3, 4]
Do array elements have names by default in JavaScript?
Sort of, but not in the way you're thinking. The name of the element is its index, 0, 1, etc. In fact, JavaScript arrays aren't really arrays at all* and those indexes are converted to string property names (in theory; in practice, JavaScript engines optimize it).
* (disclosure: that's a post on my anemic little blog)
You're telling the interpreter how the parameter is called, here:
function(c) {
^
Array.prototype.map() requires a callback that accepts up to 3 parameters. The first parameter is always the "current item", which you happen to have named c.
For a more in-depth explanation, have a look at T.J. Crowders answer, as well.
In javascript, functions are first class object, which means they can be assigned to variables, passed as function parameters and returned from values. The Array.prototype.map function takes a function with it's first parameter denoting an item of the array. When invoked, the map function executes the given function for each of the items and creates a new array from the outputs of the given function.
In your case, you are defining the input function on the fly, inside the map function.
You can actually define the function outside and pass the function by reference inside map like below.
function getLength(item) {
return item.length;
}
var animals = ["cat","dog","fish"];
var lengths = animals.map(getLength);
console.log(lengths);//[3, 3, 4]
Here, you can see it outputs the same result.
The code does not know what is the parameter named. You map an array. map function creates a new array in the lengths variable (variable being assigned to). How? It provides to the function parameter inside it, each element in the current array one-by-one by value.
Here the value is actual string name ("cat" or "dog" or "fish").
In javascript, parameters can be optional. This map function can take three parameters, currentValue, index, array. In your case, c provides currentvalue.
If you would add one more parameter c,idx. Map function will get currentvalue and index inside it.
var animals = ["cat","dog","fish"];
var lengths = animals.map(function(c, idx, arr, test) {
console.log(c); // currentvalue being processed in the array.
console.log(idx); // index of currentvalue in the array
console.log(arr); // original array being operated on.
console.log(test); // undefined always. not available in map.
return c.length;
});
console.log(lengths);//[3, 3, 4]

Overwrite the this value in array.prototype

I've found that the native js sort function screws up from time to time so I wanted to implement my own. Lets say i have the following :
Array.prototype.customSort = function(sortFunction, updated) {...}
var array = [5, 2, 3, 6]
array.customSort(function(a,b) {return a - b})
console.log(array)
Array should be [2, 3, 5, 6]
Updated is the array that has been sorted.
No matter what I return in customSort, the order of array is still in the original order. How do I overwrite the 'this' value / get it to point to the array with the correct order?
If you consider the actual code you gave above, you have to make sure that your customSort function updates this.
One case is that customSort only uses this as "read-only" input, that is - only puts the sorted array in updated, rather than changing this.
In such case, considering the code above (which you might have performed tests with), no updated parameter is sent to the function, to receive the sorted values.
Another case is that customSort returns the sorted array, in which case you have to collect it:
array = array.customSort(function(a,b) {return a - b});
console.log(array);
I just ended up iterating over the updated array and replacing each value in this with the value in updated. In code, that looks like...
function customSort(cb) {
...//updated is the sorted array that has been built
var that = this;
_.each(updated, function (ele, index) {
that[index] = ele;
})
}
I wanted the function to operate in the exact same way the native array.sort function does - it overwrites the array provided instead of returning a new, sorted array.
I find it odd that this works... you cannot overwrite the entire this value in one clean sweep but you can in steps. I couldn't do this for in the customSort function :
this = updated;

Categories