9

I'm wondering if and what is a reliable and/or standard way of iterating an array whose length is changing inside the loop. I ask because I end up choosing a different method to do this each time I want to do it, e.g.

for ( var i = 0; i < myarray.length; i++ ) {
 if (myarray[i] === 'something') {
 myarray.splice(i, 1);
 // *to avoid jumping over an element whose index was just shifted back to the current i
 i--;
 }
}

or

var i = 0;
while (myarray[i]) {
 if (myarray[i] === 'something') {
 myarray.splice(i, 1);
 } else {
 i++;
 }
}

These are the ways I find myself doing this, but I'm curious if there is a standard approach.

asked Jun 26, 2013 at 12:04
2
  • Have you checked this [stackoverflow.com/questions/9882284/… answer? Commented Jun 26, 2013 at 12:07
  • 1
    You could keep your forward iteration in the first example and put your post decrementing i directly in the .splice() call: myarray.splice(i--, 1); Commented Jun 26, 2013 at 12:16

2 Answers 2

23

I find simpler to iterate in the other direction :

for (var i=myarray.length; i--; ) {
 if (myarray[i] === 'something') myarray.splice(i, 1);
}

This way you don't have to change the increment when removing.

Many developers, especially the ones who didn't deal with C-like languages before JavaScript, find it confusing to deal with the subtleties of the decrement operator. The loop I wrote can also be written as

for (var i=myarray.length-1; i>=0; i--) {
answered Jun 26, 2013 at 12:05
Sign up to request clarification or add additional context in comments.

11 Comments

+1 I asked a similar question in an interview today morning ;-) The best part is even I didn't think of this solution
This is definitely recommended way to iterate through a collection that will have items removed. I do this in any language.
@jlafay I've canceled your edit because for (var i = myarray.length; i > 0; i--) doesn't do the same (it starts outside of the array and miss the element at index 0).
You're right on both points but that also means that your code that sets i to myarray.length is also wrong. That should be var i = myarray.length - 1 instead. IMO decrementing the index in the for conditional is a little dirty and that's why I changed that. I should have checked i >= 0 instead.
@dystroy I'm curious why i-- > 0 doesn't miss the element at index 0 because it seems like 0 > 0 would return false?
|
0

However you choose to do it, starting in reverse and counting down is simplest. It also depends on whether your array is sparse and if you wish for it to remain sparse. Easiest is to create yourself a reusable function and your own library. You could do this. If you set compress to true then your array will become a continuous rather than sparse array. This function will remove all matched occurrences of the value and will return an array of the removed elements.

Javascript

function is(x, y) {
 if (x === y) {
 if (x === 0) {
 return 1 / x === 1 / y;
 }
 return true;
 }
 var x1 = x,
 y1 = y;
 return x !== x1 && y !== y1;
}
function removeMatching(array, value /*, compress (default = false)*/ ) {
 var removed = [],
 compress = arguments[2],
 index,
 temp,
 length;
 if (typeof compress !== "boolean") {
 compress = false;
 }
 if (compress) {
 temp = [];
 length = array.length;
 index = 0;
 while (index < length) {
 if (array.hasOwnProperty(index)) {
 temp.push(array[index]);
 }
 index += 1;
 }
 } else {
 temp = array;
 }
 index = 0;
 length = temp.length;
 while (index < length) {
 if (temp.hasOwnProperty(index) && is(temp[index], value)) {
 if (compress) {
 removed.push(temp.splice(index, 1)[0]);
 } else {
 removed.push(temp[index]);
 delete temp[index];
 }
 }
 index += 1;
 }
 if (compress) {
 array.length = 0;
 index = 0;
 length = temp.length;
 while (index < length) {
 if (temp.hasOwnProperty(index)) {
 array.push(temp[index]);
 }
 index += 1;
 }
 }
 return removed;
}
var test = [];
test[1] = 1;
test[50] = 2;
test[100] = NaN;
test[101] = NaN;
test[102] = NaN;
test[200] = null;
test[300] = undefined;
test[400] = Infinity;
test[450] = NaN;
test[500] = -Infinity;
test[1000] = 3;
console.log(test);
console.log(removeMatching(test, NaN));
console.log(test);
console.log(removeMatching(test, Infinity, true));
console.log(test);

Output

[1: 1, 50: 2, 100: NaN, 101: NaN, 102: NaN, 200: null, 300: undefined, 400: Infinity, 450: NaN, 500: -Infinity, 1000: 3]
[NaN, NaN, NaN, NaN]
[1: 1, 50: 2, 200: null, 300: undefined, 400: Infinity, 500: -Infinity, 1000: 3]
[Infinity]
[1, 2, null, undefined, -Infinity, 3] 

On jsfiddle

answered Jun 26, 2013 at 13:01

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.