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.

rob-gordon
  • 1,419
  • 3
  • 20
  • 38
  • Have you checked this [http://stackoverflow.com/questions/9882284/looping-through-array-and-removing-items-without-breaking-for-loop] answer? – Rui Marques Jun 26 '13 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);` –  Jun 26 '13 at 12:16

2 Answers2

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--) {
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • +1 I asked a similar question in an interview today morning ;-) The best part is even I didn't think of this solution – Atif Jun 26 '13 at 12:09
  • This is definitely recommended way to iterate through a collection that will have items removed. I do this in any language. – Jeff LaFay Jun 26 '13 at 12:11
  • @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). – Denys Séguret Jun 26 '13 at 12:14
  • 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. – Jeff LaFay Jun 26 '13 at 12:20
  • @dystroy I'm curious why i-- > 0 doesn't miss the element at index 0 because it seems like 0 > 0 would return false? – rob-gordon Jun 26 '13 at 12:20
  • the result of the i-- expression is the value of i before the decrement. – Denys Séguret Jun 26 '13 at 12:21
  • @jlafay no, it works. Try this in your console : `for(var i=5; i-->0;) console.log(i)`. You'll see it goes from 4 to 0. This is a frequent and very useful pattern, so it's as simple to just remember it. – Denys Séguret Jun 26 '13 at 12:22
  • @dystroy so it actually checks if i > 0, and then decrements it, weird, thanks again! – rob-gordon Jun 26 '13 at 12:23
  • @jlafay You might prefer the alternate, more readable, version, see edit. – Denys Séguret Jun 26 '13 at 12:27
  • @dystroy, thanks for further clarifying. I tend to prefer code that's more straight forward because it's more maintainable when others work on the code or if you come back to code after weeks or more away from it. – Jeff LaFay Jun 26 '13 at 17:56
  • I like your first approach - it will only fail when `i === 0` which is a falsey value; but it will decrement after `i` has been evaluated, so will reach 0 anyway. – keldar Sep 17 '14 at 11:50
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

Xotic750
  • 22,914
  • 8
  • 57
  • 79