I was working with splice within a nested for loop and I came across a behaviour I could not understand.
var a = [0, 1, 2, 3, 4];
for (b in a) {
console.log(a);
for (c in a) {
console.log(c + '==' + a[c]);
if (c === "1")
a.splice(c, 1);
}
}
console.log(a);
Its output is strange
[0, 1, 2, 3, 4]
"0==0"
"1==1"
"2==3" // why is index 2 referring to value 3 , whereas it should refer to 2
"3==4"
[0, 2, 3, 4]
"0==0"
"1==2"
"2==4" // index 2 referring to value 4 , whereas it should refer to 3
[0, 3, 4]
"0==0"
"1==3"
[0, 4]
I am splicing index 1 and it is skipping the next element .
Why this Behaviour ...
Here checkout : http://jsbin.com/isahoj/3/edit
EDIT:
ok , I understand that it shifts the index after splicing , but I am calling splice after doing console.log()... so how is it spliced earlier?
why is index 2 referring to value 3 , whereas it should refer to 2
Because on the previous iteration, you removed an entry via splice:
if (c==="1")
a.splice(c, 1);
...so everything shifted up (that's what splice does). Then the next loop continues with c being "2", and the entry (now) at index 2 is 3.
Re your comment:
ok , I understand that it shifts the index after splicing , but I am calling splice after doing console.log()... so how is it spliced earlier?
Okay, let's step back:
Before the inner loop starts, a looks like this:
[0, 1, 2, 3, 4]
On the first pass of your inner loop, c is "0" and the entry at index 0 is 0, so your log statement shows "0==0". splice is not called, and a is not changed, so it still looks like this:
[0, 1, 2, 3, 4]
On the next pass of your inner loop, c is "1", and the entry at index 1 is 1, so your log statement shows "1==1". Then splice is called, changing a so it looks like this:
[0, 2, 3, 4]
On the next pass of your inner loop, c is "2", and the entry at index 2 is 3 (not 2, because a has changed), so your log statement shows "2==3".
Later, when your outer loop runs again, c is again "1" at one point, so another entry gets removed and you see the same effect.
What you should do is actually watch the code run live, single-stepping through with the debugger, and watching how a changes in real time. All modern browsers have capable debuggers built in, you don't have to litter your code with console.log statements, you can actually watch what happens.
It's not the reason for your problem, but note that for-in isn't for looping over array indexes, it's for enumerating the property names of an object (which is why your c==="1" comparison works, property names are always strings). More: Myths and realities of for..in
After you remove element 1, then "a" looks like:
[0, 2, 3, 4]
The new element 1 is 2. The next iteration of the loop, then "c" is 2, and a[2] is indeed 3.
You should not use for…in-enumerations to loop arrays.
That said, your problem is that the .splice method modifies your array, removing an item and adjusting indizes after it. Yet, you do not adjust your iteration variable, so it will skip the index - the old index "1" was visited already, next will be "2" not the new "1" again.
To solve this, you can either loop backwards or decrease the iteration counter by the number or removed items. However, when revisiting the index your condition would always be fulfilled, and you are successively removing all second elements from the arrays until there is no second element left - not sure whether this is your goal.
Related
i was traying to do the odin project exercises of javascript,
when I execute this line in the console
2 in [2,3,4,5]
it gives
true
but when I execute 3 in [3,4,5] in the console it gives back false I think it should also be true!!!
any explanation please
thank you in advance
The in operator checks if a property key exists on an object. You can think of this array as having 4 keys.
const arr = [2, 3, 4, 5];
// arr has the keys `0`, `1`, `2`, and `3`.
console.log(arr[0], arr[1], arr[2], arr[3]);
So when you have the expression 2 in arr, you are checking if the array has a key 2, not if the value 2 exists in the array.
If you do want to check if an array contains an item, the includes() method would achieve what you want.
const arr = [2, 3, 4, 5];
arr.includes(2); // true
arr.includes(0); // false
It behaves like this because the in operator returns true if the specified property (not value) is in the specified object.
So in the first case the array has a length of 4 with indices 0,1,2,3 so the element with the index 2 exists.
In the second case there are 3 elements with indices 0,1,2, so there is no element with an index 3 that is why it returns false.
So im learning about the different array iterating methods and ran into something odd:
[1,2,3].forEach(function(element,index,arr){
console.log(element,index);
console.log(arr);
arr.shift();
})
So you would think this would produce:
1,0
[1,2,3]
2,1
[2,3]
3,2
[3]
however you get this:
1 0
[1, 2, 3]
3 1
[2, 3]
Im printing off 3 despite have only shifted off at the end? Is this because the arr callback happens at the beginning of the next item or something? Since it should be doing shift() on the array AFTER? I realize this is a "bad case" of using this method, im just curious why this happens.
The .length of the array is changed. The element adjacent to element at index 0 becomes index 0 following .shift() call.
The answer to your question is described in the documentation of shift method.
As per MDN from here
The shift method removes the element at the zeroeth index and shifts
the values at consecutive indexes down, then returns the removed
value. If the length property is 0, undefined is returned.
So, once the shift operation is done the Original array is changed from
[1,2,3] to [2,3] and the value of index increments from 0 to 1. Next, iteration the third iteration of the loop is Not required as the length of the array (2) has been traversed.
I've been studying recursive functions and I'm starting to understand them more or less. I was working on a free code camp challenge when I came across this and I do not understand it. A recursive function inside of a for loop:
function steamroller(arr) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
//If (i)th element is an array
if (Array.isArray(arr[i])) {
newArr = newArr.concat(steamroller(arr[i]));
console.log(newArr);
} else {
newArr.push(arr[i]);
}
}
return newArr;
}
steamroller([1, [2],[3, [[4]]]]);
//returns [1, 2, 3, 4]
The line I'm having a hard time understanding is:
newArr = newArr.concat(steamroller(arr[i]));
On that line, newArr is concatenated to what? The function is called again inside of the .concat method, right? But what happens to that for loop? Does the function call inside of the concat method force the loop to exit?
Here is a JSFiddle, I have each newArr logged to console, but I can't even follow it. The array is built up like so:
[1, 2]
[4]
[3, 4]
[1, 2, 3, 4] //Final
Thanks.
The steamroller function needs to loop over the indices in the array provided as the function parameter in order to ensure each index of the array is seen.
However, the original array has a number of indices, which in turn may contain multiple indices themselves, all of which need to be looped over in turn.
The call to concat is done only on the current index of the loop, meaning that the outcome is the "steamrollered" representation of the current index.
Step by step
The original array is passed in to the function: [1, [2],[3, [[4]]]]
The loop begins with the first index: 1, which is not an array, so it is pushed to the resulting array.
The next loop iteration's index is [2], which is an array, so is recursed.
The first recursive call to the function receives [2] and iterates over it.
The first iteration of this recursive call finds the index to be 2, which is not an array, so is pushed to the resulting array.
... continue ...
What we see is that as the nested arrays are iterated over using the recursive function, we always end up obtaining the internal integer values, no matter how nested.
It sounds like your mental muddling is mostly to do with the way "the stack" works.
A basic function call pauses execution at the point that it was called, and steps through each of the lines inside the function, then resumes outside of the function. And, you can have functions run inside of other functions; that much you may have already known.
The important thing to understand here is everything that the program is keeping track of. For starters, the program keeps track of what position it was being called from so it can "pop up one" in the stack, and continue. eg, at the midpoint of execution the stack may look like this (knowing which function/line it's at):
steamroller:10
steamroller:8
steamroller:8
main:20
Then its current loop hits "return"...
steamroller:8
steamroller:8
main:20
But that's not all - it also preserves each instance of the variables declared in that run of the function. You can, in fact, have about 5 or 6 or 5 million newArrs because those variables are declared on the stack, not in one singular spot.
And none of that information - the line number, or instance variables, are destroyed when it enters a function - it just saves its spot in the current function, and steps through the inner one.
Make sense?
First enter:
steamroller([1, [2],[3, [[4]]]])
First element isn't an Array, so newArr receives '1'.
Second element is an Array, so it calls steamroller again:
steamroller([1, [2],[3, [[4]]]])->steamroller(2)
It's not an array, so it will concat the element to a newArr, so it will simply concat 2 to an empty array. The function will return it, and concat the [2] to the newArr that contains [1], and now you have [1,2], and the console pops out the first line you saw on your fiddle.
I think you got it to what happens latter, but comment you still need explanation for what happens with 3 and 4.
With recursive functions is that the return of the console will be a mess because is different than a normal loop it need to finish the last position of the recursive tree before return all the values in the console, so if you put in one of the positions of the array with a more deeper position like [[[[3]]]] it will go to the end of the position and then it will return all the values, so if you put some kind of console to see how is working your recursive function you will receive some kind of tree, not like a normal loop, that's the danger with the recursive functions, and they can be pretty heavy to the memory speaking about CPU usage in a production server. so if you can perform this operation with doing any recursive function it will be better.
So if you change you code to
steamroller([1, [2],[3, [4]]]);
//the return will be
[1, 2]
[3, 4]
[1, 2, 3, 4]
Speaking about levels in the recursive tree
So if you put the [[3]] as the [[4]] the return will be in the same level of the tree.
steamroller([1, [2],[[3], [4]]]);
//the return will be
[1, 2]
[3]
[4]
[3, 4]
[1, 2, 3, 4]
Hi hope that can get you a better idea about the recursive functions
If we inspect the run without the recursion then:
i = 0 => arr[i] = 1 => newArr.push(arr[i]) => newArr = [1]
i = 1 => arr[i] = [2] => steamroller(arr[i]) = [2] => newArr = [1, 2]
i = 2 => arr[i] = [3, [[4]]] => steamroller(arr[i]) = [3, 4] => newArr = [1, 2, 3, 4]
While implementing an internal EventEmitter for a project I was working on, I came across a strange quirk when using Array.prototype.splice inside a for... in loop. The function does not successfully remove the indeces from the array within the loop:
var array = [1, 2, 3, 4, 5], index;
for (index in array) {
if (index === 2) {
array.splice(index, 1);
}
console.log(array[index]);
}
console.log(array);
Running on Google Chrome version 43, this outputs
1
2
3
4
5
[1, 2, 3, 4, 5]
when I'm expecting something like
1
2
4
5
undefined†
[1, 2, 4, 5]
Is this by design or a bug? I cannot find any documented reference to this behavior.
† Possibly, if length is not calculated during each iteration of for... in implementation
Great question. :)
In Javascript, Arrays are Objects, which means that Array indices are Object keys. And in Javascript, Object keys are strings.
So your condition index === 2 is always going to be false, because the number 2 is not the same as the string '2'.
One solution would be to continue to use the identity operator (recommended by most) and compare index to the string value '2'
index === '2'
Or, alternatively, you can use the equality operator which will typecast the comparison (although this will likely get you into trouble at some point)...
index == 2
but works just fine in your case.
I've just found a slip in my code while copying an array. I did array.splice(0), rather than using slice. But strangely it still works. This usage isn't compliant with the Array.prototype.splice spec on MDN which states that the deleteCount (second argument) is mandatory and that it returns
"An array containing the deleted elements. If only one element is removed, an array of one element is returned. If no elements are removed, an empty array is returned. "
I've run some tests in Chrome and this is what I got
var array = [0, 1, 2, 3];
var spliced = array.splice(0); // [0, 1, 2, 3]
But when I do
var array = [0, 1, 2, 3];
var spliced = array.splice(0, 0); // [] as per the spec
Is this an interesting Chrome feature or is the spec out of date?
According to ECMAScript 5, one could argue arr.splice(0) and arr.splice(0, 0) should return the same result, because in step 7, ToInteger(undefined) is 0.
Let actualDeleteCount be min(max(ToInteger(deleteCount),0), len – actualStart).
However, it's not clear if it is even valid to call .splice with a single argument.
ECMAScript 6 has a special case for single arguments (and one for no arguments):
If the number of actual arguments is 0, then
Let insertCount be 0.
Let actualDeleteCount be 0.
Else if the number of actual arguments is 1, then
Let insertCount be 0.
Let actualDeleteCount be len - actualStart
len is the length of the array and actualStart is derived from the first argument. So arr.splice(0) becomes equivalent to arr.splice(0, arr. length), which means Chrome seems to comply to the new spec (intentionally or unintentionally).
On the other hand, arr.splice() is equivalent to arr.splice(0, 0) and Chrome returns an empty array as expected.