遍历数组并删除项目,而不会中断循环


462

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

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);
    }           
}

11
除了向后迭代和调整长度之外,您还可以将所需的成员放入新数组中。
RobG 2012年

2
为什么这么说Auction.auctions[i]['seconds']--来代替auction.seconds--
唐·哈奇

您可能想研究预定义的函数.shift();。

Answers:


856

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

要解决此问题,您可能需要在i后面减一.splice(),或者只是简单地反向迭代...

var i = Auction.auctions.length
while (i--) {
    ...
    if (...) { 
        Auction.auctions.splice(i, 1);
    } 
}

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


151

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

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);
    }
}

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


47

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

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语句中增加了一个减量。


32

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

这种方法的算法复杂性在于O(n^2),剪接函数和for循环都在数组上迭代(在最坏的情况下,剪接函数会移动数组的所有元素)。取而代之的是,您可以仅将所需的元素压入新数组,然后将该数组分配给所需的变量(刚刚对其进行迭代)。

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将所有内容合而为一:

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

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

10

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

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

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


9

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

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
}

8

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

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

7

为了谁回答了具有码拼接()在一个循环中,其运行时间为O(n这个很基本的问题,每个人2,因为这个问题被张贴,或谁已经upvoted这样的答案,在七年):你应该感到as愧

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

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

// 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)

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


我从未意识到您可以分配一个数组的长度!
迈克尔

我不知道Array.splice(i,1)每次都会创建一个新的数组实例。我感到很惭愧。
dehart

2
@dehart Ha,好:-)实际上,它不会每次都创建一个新的数组实例;但它确实必须将索引大于i的所有项向下颠倒,平均每个n / 2颠簸。
唐·哈奇

1

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

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

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个元素。

值得注意的事实是,这与java迭代器的使用相反,后者可以在迭代时删除集合的第n个元素。

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

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

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

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。但是,此方法的返回类型是移除的元素。同样,这不会修改原始数组。修改后的代码段如下:

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个元素,如下所示。但是,它的代码更多(因此效率低下)

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);
  }

}

Array.slice方法对于实现功能编程中的不变性极为重要


请注意,更多代码不应衡量代码的效率。
卡诺

0

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

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;

0

两个有效的示例:

(Example ONE)
// Remove from Listing the Items Checked in Checkbox for Delete
let temp_products_images = store.state.c_products.products_images
if (temp_products_images != null) {
    for (var l = temp_products_images.length; l--;) {
        // 'mark' is the checkbox field
        if (temp_products_images[l].mark == true) {
            store.state.c_products.products_images.splice(l,1);         // THIS WORKS
            // this.$delete(store.state.c_products.products_images,l);  // THIS ALSO WORKS
        }
    }
}

(Example TWO)
// Remove from Listing the Items Checked in Checkbox for Delete
let temp_products_images = store.state.c_products.products_images
if (temp_products_images != null) {
    let l = temp_products_images.length
    while (l--)
    {
        // 'mark' is the checkbox field
        if (temp_products_images[l].mark == true) {
            store.state.c_products.products_images.splice(l,1);         // THIS WORKS
            // this.$delete(store.state.c_products.products_images,l);  // THIS ALSO WORKS
        }
    }
}

0

试试这个

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});

-2
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--;
    }
}

7
一个好的答案总是可以解释这样做的原因以及为什么这样做,不仅对于OP,而且对于将来的SO访问者。
B001ᛦ16年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.