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

Looping through array and removing items, without breaking for loop

提问人:dzm 提问时间:3/27/2012 最后编辑:JJJdzm 更新时间:8/4/2023 访问量:408777

问:

我有以下 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);
    }           
}
JavaScript 循环

评论

14赞 RobG 3/27/2012
除了向后迭代和调整长度外,您还可以将所需的成员放入新数组中。
3赞 Don Hatch 6/30/2019
你为什么说而不是?Auction.auctions[i]['seconds']--auction.seconds--
0赞 Rakushoe 5/10/2020
您可能想查看预定义的函数 .shift();

答:

60赞 Marc 3/27/2012 #1

每次通过循环重新计算长度,而不仅仅是在开始时,例如:

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 语句中添加了递减。

1070赞 4 revs, 3 users 76%user1106925 #2

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

要修复它,您要么需要在 之后递减,要么只是反向迭代......i.splice()

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

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

评论

1赞 lukas_o 4/22/2021
想知道是否会陷入无限循环,我尝试了这个解决方案,并且(当然它确实有效),因为它首先评估 的值,然后递减。然而,(和)的行为是如此奇怪,以至于像 swift 这样的现代语言不再支持它们。我认为它们是不好的做法(至少在这样的背景下)。length === 0i--++
9赞 aggregate1166877 1/4/2022
@lukas_o 如果您只是理解它的含义,就不会有奇怪或意想不到的功能。 均值计算值,然后递增该值。 表示递增值,然后计算它。JS永远不会做任何事情。这真的很容易理解,并且保证每次都以完全相同的方式工作,即使您使用不同的 JS 引擎也是如此。i++++i
57赞 0xc0de 9/6/2013 #3

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

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

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);
28赞 Aesthete 9/6/2013 #4
Auction.auctions = Auction.auctions.filter(function(el) {
  return --el["seconds"] > 0;
});
252赞 frattaro 1/24/2015 #5

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

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

是否将它们从末尾弹出并不重要,因为当您向后移动时,索引将被保留。

0赞 Zon 1/28/2016 #6

循环时尝试将数组中继到 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;
11赞 daniel.szaniszlo 2/26/2016 #7

这是正确使用拼接的另一个例子。此示例即将从“array”中删除“attribute”。

for (var i = array.length; i--;) {
    if (array[i] === 'attribute') {
        array.splice(i, 1);
    }
}
14赞 Pablo 11/18/2016 #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
}
-8赞 user8533067 8/29/2017 #9

你可以浏览并使用 shift()

评论

7赞 Ivan 8/29/2017
请使用此方法添加一个示例。
24赞 Rubinsh 11/29/2017 #10

如果你使用的是ES6+,为什么不直接使用方法呢?Array.filter

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

请注意,在筛选器迭代期间修改数组元素仅适用于对象,不适用于基元值数组。

-1赞 bp4D 11/7/2018 #11

这个线程上已经有很多精彩的答案。但是,当我尝试在 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 个元素 在迭代时。

这基本上只剩下一个数组方法来执行第 n 个元素的删除(您也可以使用这些方法执行其他操作,但在这个问题的上下文中,我专注于删除元素):Array.splice

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

话虽如此,我们仍然可以使用删除第 n 个元素,如下所示。但是,它需要更多的代码(因此效率低下)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("Array after removal of ith element: ");
    arr = arr.slice(0, i).concat(arr.slice(i + 1));
    console.log(arr);
  }

}

该方法对于实现极为重要 函数式编程中的不可变性 à la reduxArray.slice

评论

0赞 kano 4/2/2020
请注意,更多的代码不应该是代码效率的衡量标准。
0赞 bp4D 12/13/2022
@kano - 请阅读上面的内容。它实际上说,更多的代码是低效的。对于开发人员来说,编写它对开发人员时间的低效利用。
17赞 Don Hatch 6/30/2019 #12

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

当我运行此代码片段时,n = 100 万,每次调用 filterInPlace() 需要 0.013 到 0.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)

请注意,这会就地修改原始数组,而不是创建新数组;像这样就地这样做可能是有利的,例如,在数组是程序的单个内存瓶颈的情况下;在这种情况下,您不想创建另一个相同大小的数组,即使是暂时的。

0赞 Fred 12/14/2019 #13

两个有效的示例:

示例一

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

示例二

// 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赞 Rick 5/30/2020 #14

试一试

RemoveItems.forEach((i, j) => {
    OriginalItems.splice((i - j), 1);
});
0赞 Aayush Bhattacharya 2/12/2021 #15

删除参数

        oldJson=[{firstName:'s1',lastName:'v1'},
                 {firstName:'s2',lastName:'v2'},
                 {firstName:'s3',lastName:'v3'}]
        
        newJson = oldJson.map(({...ele}) => {
          delete ele.firstName;
          return ele;
          })

它删除并创建新的数组,并且由于我们在每个对象上使用扩展运算符,因此原始数组对象也不受伤害

1赞 Chris Huang 4/2/2021 #16

为什么要在 .splice 上浪费 CPU 周期?该操作必须一次又一次地遍历整个循环才能删除数组中的元素。

为什么不在一个循环中使用传统的 2 个标志呢?

const elements = [1, 5, 5, 3, 5, 2, 4];
const remove = 5

i = 0

for(let j = 0; j < elements.length; j++){
  if (elements[j] !== remove) {
    elements[i] = elements[j]
    i++
  }
}
elements.length = i

评论

0赞 Iluvatar 4/2/2021
此代码有效,但对于一长串列表,移动所有元素(如气泡)会很乏味
1赞 lucaswxp 4/24/2021
我不明白这一点,你能解释一下吗?
15赞 Accountant م 4/23/2021 #17

正常的for循环对我来说更熟悉,我只需要在每次从数组中删除一个项目时递减索引

//5 trues , 5 falses
var arr1 = [false, false, true, true, false, true, false, true, true, false];

//remove falses from array
for (var i = 0; i < arr1.length; i++){
    if (arr1[i] === false){
        arr1.splice(i, 1);
        i--;// decrement index if item is removed
    }
}
console.log(arr1);// should be 5 trues

评论

2赞 PotatoFarmer 5/5/2022
我发现这种方法(拼接和递减)考虑到熟悉度,当想要从 for 循环中剪切出小的边缘情况而不将循环重写为过滤器/减少/平面图等或向后循环时,这种方法(拼接和递减)是最便携/可理解的(6 个月后可能不会注意到)。其他解决方案更好/更智能,但有时只需要修补小的环形边缘情况。
0赞 rbrandino 8/4/2023 #18
    const arr = [...]
    const indexes = [1,2,8,9,34,67]
    
    const result = arr.filter((_, index) => !indexes.includes(index));
    
    console.log(result);

评论

1赞 moken 8/4/2023
答案需要支持信息 您的答案可以通过其他支持信息来改进。请编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何写出好答案的更多信息。