While on the nodejs REPL I was trying to clean up an array defined as const array = [...]
and just found out that using array.forEach(() => /pop|shift/())
would not work. After such expression the array will still hold values in it.
I'm well aware of better methods to clean the array, like array.splice(0)
, but I'm really curious about this behavior as seems counter-intuitive, at least for me.
const a = [1, 2, 3]
a.forEach(() => {
a.shift()
})
console.log(a) // [ 3 ]
const b = [1, 2, 3]
b.forEach(() => {
b.pop()
})
console.log(b) // prints [ 1 ]
At first I was using arr.forEach(() => arr.pop())
, so I though that one of the values was short-circuiting the forEach
but wrapping the lambda in a body-block { .. }
will also produce the same results.
The results are consistent across different node versions and browsers .. so it seems like it's well-defined behavior.
The quantity of leftover values, those still in the result array, change depending on the length of the input array, and seems to be Math.floor(array.length / 2)
The leftover values are always ordered accordingly to the /pop|shift/
method used, so some of the calls are actually changing the input array.
It also returns the same results by calling Array.prototype.forEach(array, fn)
The forEach() method calls a function for each element in an array. The forEach() method is not executed for empty elements.
The pop() method removes the last element from an array and returns that element.
Input : Array = [2, 3, 5, 4, 1] Output : Steps Taken: 3 Explanation: Step 1: Remove 5 and elements to its right so, Array becomes [2, 3] Step 2: Remove 3 as it is the maximum and right most already so, Array becomes [2] Step 3: Remove 2 and the array becomes EMPTY Hence, at the end of step 3 the array stands exhausted.
Check out this quote from here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
If the values of existing elements of the array are changed, the value passed to callback will be the value at the time forEach() visits them; elements that are deleted before being visited are not visited.
You're iterating from the beginning and removing the last element each iteration. This means you're moving forward 1, and reducing the length by 1, each iteration. Hence why you're ending up with floor(initialLength / 2)
iterations. You're modifying the same array that you're forEach
ing, which as stated above means that you will not have the callback invoked for those pop
'd elements.
Modifying an array while iterating over it is generally a bad idea. In fact, in Java, trying to do so would cause an exception to be thrown. But let's convert the forEach
into an old-school for loop, and maybe you'll see the issue.
for (let i = 0; i < a.length; ++i) {
a.pop();
}
Is it clearer now what's going on? Each iteration you're shortening the length of the array by 1 when you pop the last element off. So the loop will end after iterating over half the elements -- because by then, it will have REMOVED half the elements, too, causing the value of i
to be more than the current length of the array.
The same thing is happening when you use forEach
: you're shortening the array with each iteration when you pop, causing the loop to terminate after only half the elements have been iterated. In other words, the iterator variable will move forward past the end of the array as the array shrinks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With