关于javascript:遍历数组并删除项目,而不会中断for循环

Looping through array and removing items, without breaking for loop

我有以下for循环,当我使用splice()删除项目时,我得到的是"秒"是不确定的。 我可以检查它是否未定义,但我觉得可能有一种更优雅的方法。 我们的愿望是简单地删除项目并继续进行。

1
2
3
4
5
6
7
for (i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    Auction.auctions[i]['seconds'] --;
    if (auction.seconds < 0) {
        Auction.auctions.splice(i, 1);
    }          
}


当您执行.splice()时,该数组将被重新索引,这意味着删除索引时将跳过索引,并且缓存的.length已过时。

要修复此问题,您需要在.splice()之后递减i,或者只是反向迭代...

1
2
3
4
5
6
7
var i = Auction.auctions.length
while (i--) {
    ...
    if (...) {
        Auction.auctions.splice(i, 1);
    }
}

这样,重新索引不会影响迭代中的下一个项目,因为索引仅影响从当前点到数组末尾的项目,并且迭代中的下一个项目低于当前点。


这是一个很常见的问题。解决方案是向后循环:

1
2
3
4
5
6
for (var i = Auction.auctions.length - 1; i >= 0; i--) {
    Auction.auctions[i].seconds--;
    if (Auction.auctions[i].seconds < 0) {
        Auction.auctions.splice(i, 1);
    }
}

是否从头开始弹出都没关系,因为索引将在您向后移动时保留。


每次循环时都要重新计算长度,而不是一开始就重新计算长度,例如:

1
2
3
4
5
6
7
8
for (i = 0; i < Auction.auctions.length; i++) {
      auction = Auction.auctions[i];
      Auction.auctions[i]['seconds'] --;
      if (auction.seconds < 0) {
          Auction.auctions.splice(i, 1);
          i--; //decrement
      }
}

这样,您就不会超出范围。

编辑:在if语句中增加了一个减量。


尽管您的问题是关于从要迭代的数组中删除元素,而不是有效地删除元素(除了某些其他处理),但我认为如果处于类似情况,则应该重新考虑它。

这种方法的算法复杂度为O(n^2),因为剪接函数和for循环都在数组上迭代(在最坏的情况下,剪接函数会移动数组的所有元素)。相反,您可以将所需的元素推到新数组中,然后将该数组分配给所需的变量(刚刚对其进行迭代)。

1
2
3
4
5
6
7
8
9
var newArray = [];
for (var i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    auction.seconds--;
    if (!auction.seconds < 0) {
        newArray.push(auction);
    }
}
Auction.auctions = newArray;

从ES2015开始,我们可以使用Array.prototype.filter将所有内容合而为一:

1
Auction.auctions = Auction.auctions.filter(auction => --auction.seconds >= 0);

1
2
3
Auction.auctions = Auction.auctions.filter(function(el) {
  return --el["seconds"] > 0;
});

如果您正在使用ES6 +-为什么不只使用Array.filter方法?

1
2
3
4
Auction.auctions = Auction.auctions.filter((auction) => {
  auction['seconds'] --;
  return (auction.seconds > 0)
})

请注意,在过滤器迭代期间修改数组元素仅适用于对象,而不适用于原始值数组。


一次消化数组元素的另一种简单解决方案:

1
2
3
4
5
6
7
8
while(Auction.auctions.length){
    // From first to last...
    var auction = Auction.auctions.shift();
    // From last to first...
    var auction = Auction.auctions.pop();

    // Do stuff with auction
}

这是正确使用接头的另一个示例。本示例将要删除"数组"中的"属性"。

1
2
3
4
5
for (var i = array.length; i--;) {
    if (array[i] === 'attribute') {
        array.splice(i, 1);
    }
}

对于在发布此问题的七年内,使用循环运行splice()且运行时间为O(n2)的代码回答了这个非常基本的问题的每个人,或者对这样的回答提出了最高评价的人: 羞愧。

这是此简单线性时间问题的简单线性时间解决方案。

当我运行此代码段(n = 1百万)时,每次调用filterInPlace()都需要0.013到.016秒。 二次解(例如可接受的答案)将花费一百万倍左右。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Remove from array every item such that !condition(item).
function filterInPlace(array, condition) {
   var iOut = 0;
   for (var i = 0; i < array.length; i++)
     if (condition(array[i]))
       array[iOut++] = array[i];
   array.length = iOut;
}

// Try it out.  A quadratic solution would take a very long time.
var n = 1*1000*1000;
console.log("constructing array...");
var Auction = {auctions: []};
for (var i = 0; i < n; ++i) {
  Auction.auctions.push({seconds:1});
  Auction.auctions.push({seconds:2});
  Auction.auctions.push({seconds:0});
}
console.log("array length should be"+(3*n)+":", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be"+(2*n)+":", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be"+n+":", Auction.auctions.length)
filterInPlace(Auction.auctions, function(auction) {return --auction.seconds >= 0; })
console.log("array length should be 0:", Auction.auctions.length)

注意,这将修改原始数组,而不是创建一个新数组。 像这样在适当的位置进行操作可能会很有利 如果数组是程序的单个内存瓶颈; 在这种情况下,您甚至不想临时创建另一个相同大小的数组。


在此线程上已经有很多精彩的答案。但是,当我尝试解决ES5上下文中的"从数组中删除第n个元素"时,我想分享自己的经验。

JavaScript数组具有不同的方法来从开始或结束添加/删除元素。这些是:

1
2
3
4
arr.push(ele) - To add element(s) at the end of the array
arr.unshift(ele) - To add element(s) at the beginning of the array
arr.pop() - To remove last element from the array
arr.shift() - To remove first element from the array

基本上,上述任何方法都不能直接用于从数组中删除第n个元素。

A fact worth noting is that this is in contrast with java iterator's
using which it is possible to remove nth element for a collection
while iterating.

基本上,这只给我们留下了一个数组方法Array.splice来执行第n个元素的删除(您也可以使用这些方法执行其他操作,但是在这个问题的上下文中,我将重点放在删除元素上):

1
Array.splice(index,1) - removes the element at the index

这是从原始答案中复制的代码(带有注释):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var arr = ["one","two","three","four"];
var i = arr.length; //initialize counter to array length

while (i--) //decrement counter else it would run into IndexOutBounds exception
{
  if (arr[i] ==="four" || arr[i] ==="two") {
    //splice modifies the original array
    arr.splice(i, 1); //never runs into IndexOutBounds exception
    console.log("Element removed. arr:");

  } else {
    console.log("Element not removed. arr:");
  }
  console.log(arr);
}

另一个值得注意的方法是Array.slice。但是,此方法的返回类型是移除的元素。同样,这不会修改原始数组。修改后的代码段如下:

1
2
3
4
5
6
7
8
9
10
11
12
var arr = ["one","two","three","four"];
var i = arr.length; //initialize counter to array length

while (i--) //decrement counter
{
  if (arr[i] ==="four" || arr[i] ==="two") {
    console.log("Element removed. arr:");
    console.log(arr.slice(i, i + 1));
    console.log("Original array:");
    console.log(arr);
  }
}

话虽如此,我们仍然可以使用Array.slice删除第n个元素,如下所示。但是,它的代码更多(因此效率低下)

1
2
3
4
5
6
7
8
9
10
11
12
var arr = ["one","two","three","four"];
var i = arr.length; //initialize counter to array length

while (i--) //decrement counter
{
  if (arr[i] ==="four" || arr[i] ==="two") {
    console.log("Array after removal of ith element:");
    arr = arr.slice(0, i).concat(arr.slice(i + 1));
    console.log(arr);
  }

}

The Array.slice method is extremely important to achieve
immutability in functional programming à la redux


循环时尝试将数组中继到newArray中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var auctions = Auction.auctions;
var auctionIndex;
var auction;
var newAuctions = [];

for (
  auctionIndex = 0;
  auctionIndex < Auction.auctions.length;
  auctionIndex++) {

  auction = auctions[auctionIndex];

  if (auction.seconds >= 0) {
    newAuctions.push(
      auction);
  }    
}

Auction.auctions = newAuctions;


您可以浏览并使用shift()


1
2
3
4
5
6
7
8
9
for (i = 0, len = Auction.auctions.length; i < len; i++) {
    auction = Auction.auctions[i];
    Auction.auctions[i]['seconds'] --;
    if (auction.seconds < 0) {
        Auction.auctions.splice(i, 1);
        i--;
        len--;
    }
}