How to iterate over an array and remove elements in JavaScript [duplicate]
Asked Answered
S

6

87

I have an array of elements and need to remove certain ones from it. The problem is that JavaScript doesn't seem to have a for each loop and if I use a for loop I run into problems with it basically trying to check elements beyond the bounds of the array, or missing elements in the array because the indexes change. Let me show you what I mean:

var elements = [1, 5, 5, 3, 5, 2, 4];
for(var i = 0; i < elements.length; i++){
    if(elements[i] == 5){
        elements.splice(i, 1);
    }
}

The problem is that when elements[1] is removed, elements[2] becomes elements[1]. So first problem is that some elements are never examined. The other problem is that .length changes and if I hard code the bounds, then I might be trying to examine elements beyond the bounds of the array. So what's the best way to do this incredibly simple thing?

Subheading answered 3/5, 2013 at 5:57 Comment(3)
elements.splice(i--, 1);Pettis
I don't like "--" or "++" syntax but a useful tip when starting from the beginning of the array +1Derinna
See also: Looping through array and removing items, without breaking for loopKay
J
203

Start from the top!

var elements = [1, 5, 5, 3, 5, 2, 4];
for(var i = elements.length - 1; i >= 0; i--){
    if(elements[i] == 5){
        elements.splice(i, 1);
    }
}
Joellejoellen answered 3/5, 2013 at 5:58 Comment(10)
+1 for starting from the end to avoid skipping elements due to the changing length that the OP has mentioned.Derinna
for (var i = elements.length; i--;) is probably the more common way to write it.Pettis
@Dagg Nabbit, one thing to note with that shorthand is that it will throw errors in jslint, if you should use such a tool for static code analysis (and of course the "--").Derinna
@Derinna yeah, jslint throws silly errors. =/Pettis
See also https://mcmap.net/q/47400/-looping-through-array-and-removing-items-without-breaking-for-loopWellfixed
can this be done in a for of looP?Edema
Seriously? An O(n^2) answer, with 147 upvotes and no downvotes? Here's a downvote for you.Ursel
Now if you want to put the elements in a different array, you have to unshift instead of push to retain the correct order. not cool.Impoverish
Isn't this just O(n)? It only loops through the entire array once.Dowdell
@Tasik What is O() complexity of splice? It seems to be O(N), so if code would remove say 1/4 of array, in front - it would need to make (1/4*N) * (3/4*N) = 3/16N^2 operations, giving N^2 But see https://mcmap.net/q/83021/-what-39-s-the-time-complexity-of-array-splice-in-google-chrome so...Subsume
E
37

You could use the filter method here:

var elements = [1, 5, 5, 3, 5, 2, 4].filter(function(a){return a !== 5;});
//=> elements now [1,3,2,4]

Or if you don't want to touch elements:

var elementsfiltered
   ,elements = [1, 5, 5, 3, 5, 2, 4]
                .filter( function(a){if (a!==5) this.push(a); return true;},
                         elementsfiltered = [] );
   //=> elementsfiltered = [1,3,2,4], elements = [1, 5, 5, 3, 5, 2, 4]

See MDN documentation for filter

Alternatively you can extend the Array.prototype

Array.prototype.remove = Array.prototype.remove || function(val){
    var i = this.length;
    while(i--){
        if (this[i] === val){
            this.splice(i,1);
        }
    }
};
var elements = [1, 5, 5, 3, 5, 2, 4];
elements.remove(5);
//=> elements now [1,3,2,4]
Effieeffigy answered 3/5, 2013 at 6:2 Comment(3)
While creating a new array, which filter does, is not a bad suggestion as a solution, the OP does actually ask about removing elements inline and it would seem best to give an example of that.Derinna
Seems like an unnecessary departure from the original code. OP could keep his existing code by decrementing i after the splice.Pettis
personally I feel filter is safer as decrementing i may lead to edge cases as if zero elements are there etcSpar
A
10

var elements = [1, 5, 5, 3, 5, 2, 4];    
var i = elements.length;
while (i--) {
    if (elements[i] == 5) {
        elements.splice(i, 1);
    }
}
console.log(elements);
Ascension answered 27/2, 2017 at 21:58 Comment(0)
C
3

You could simply decrement i whenever you remove an item.

var elements = [1, 5, 5, 3, 5, 2, 4];

var l = elements.length;
for(var i = 0; i < l; i++){
    if(elements[i] == 5){
        elements.splice(i, 1);
        i--;
    }
}

console.log(elements);
Cristophercristy answered 18/10, 2016 at 14:42 Comment(0)
T
1

Using Array.shift():

var array = [1, 2, 3, 'a', 'b', 'c'];
while (array.length > 0) {
  console.log(array.shift());
}

Edit: Probably does not suit the specs. I misread the question (only remove certain elements) and was too eager instead to add a method that was not mentioned yet...

Tollgate answered 23/2, 2015 at 15:9 Comment(1)
As you say in your edit, this doesn't actually answer the question, however it does answer the question I was trying to search for. Thank you.Peristome
D
0

This is an example of using Array.indexOf, while and Array.splice to remove elements inline.

var elements = [1, 5, 5, 3, 5, 2, 4];
var remove = 5;
var index = elements.indexOf(remove);

while (index !== -1) {
    elements.splice(index, 1);
    index = elements.indexOf(remove);
}

console.log(elements);

On jsfiddle

Derinna answered 3/5, 2013 at 6:12 Comment(15)
downvoted for inefficient O(n^2) algorithmTime
I never made any claims on efficiency, and the question didn't concern that, but how you can deal with a changing array length when performing inline element removal. Here is a jsPerf so that you can give your efficient version as an answer and compare it with other answers posted. jsperf.com/soq-iterate-over-an-array-and-removeDerinna
the canonical method of dealing with a changing array length is to start from the end of the array and work backwards. Alternatively it's possible to work forwards from the current position and not increment if you've removed the current element. Instead, your method starts over from the zeroth element every time a match is found, thereby repeatedly going over and over the same elements. It's a very poor algorithm and shouldn't ever be used.Time
And yet this ECMA5 method is super fast on the most current version of Chrome, and other browsers are bound to catch up, faster than the accepted answer on the small sample provided.Derinna
If you had used the fromIndex parameter to .indexOf() I would have no complaint - that would make it O(n) instead of O(n^2). At the moment it only wins (on short arrays) because it's using a compiled built-in to scan the array instead of a hand-written loop. In fact if you had done this you'd actually have the best answer here.Time
OK, that's weird - I tried that and (on this small sample set) it was substantially slower. I don't know why yet. jsperf.com/soq-iterate-over-an-array-and-remove/2Time
Personally, I would usually use the method shown in the accepted answer, and that is why I also voted for it. But interesting results.Derinna
I changed the test case slightly - now the fromIndex case runs very slightly faster... jsperf.com/soq-iterate-over-an-array-and-remove/3Time
I guess (not tested) that it will make more of a difference on a larger set of data, and it is still only fastest on Chrome (with this small data set) and so using the reverse loop gives better cross browser performance. Feel free to improve my answer, if you feel like it. :)Derinna
Downvoted for "I never made any claims on efficiency, and the question didn't concern that".Ursel
@Alnitak, no, using the fromIndex parameter will not fix this algorithm's quadratic behavior.Ursel
@DonHatch Are you alluding to the removal operation itself being something other than O(1) ? The point of using fromIndex is that it prevents the scan from considering the entire array from the start each time.Time
@Alnitak, right, the removal isn't O(1).Ursel
@DonHatch well, it might be, but that's implementation dependent. If the removal is itself O(n) then the original algorithm here would be O(n^3)Time
@Alnitak, no, it's not implementation dependent; think about how many items the splice() has to move, in order to make them look-up-able in O(1) time afterwards. And no, that doesn't make the overall algorithm O(n^3); there are O(n) splices taking O(n) time, contributing O(n)*O(n) = O(n^2) to the overall time.Ursel

© 2022 - 2024 — McMap. All rights reserved.