【问题标题】:Looping through array and removing items, without breaking for loop循环遍历数组并删除项目,而不会中断 for 循环
【发布时间】:2021-12-25 04:18:39
【问题描述】:

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

【问题讨论】:

  • 除了向后迭代和调整长度之外,你还可以将你想要的成员放入一个新的数组中。
  • 你为什么说Auction.auctions[i]['seconds']--而不是auction.seconds--
  • 您可能想查看预定义函数 .shift();

标签: javascript loops


【解决方案1】:

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

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

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

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

【讨论】:

  • 想知道length === 0 是否会陷入无限循环,我已经尝试过这个解决方案并且(当然它确实有效),因为它首先评估i 的值然后递减。然而,--(和++)的行为非常奇怪,以至于像 swift 这样的现代语言不再支持它们。我认为它们是不好的做法(至少在这样的情况下)。
  • @lukas_o 如果您只是理解它的含义,就没有奇怪或意外的功能。 i++ 表示评估该值,然后将其递增。 ++i 表示增加值,然后评估它。 JS 永远不会做任何事情。这真的很容易理解,并且保证每次都以完全相同的方式工作,即使您使用不同的 JS 引擎。
【解决方案2】:

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

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

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

【讨论】:

  • 这个反向循环的想法拯救了我的一天。谢谢你
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    这种方法的算法复杂度是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);
    

    【讨论】:

      【解决方案5】:
      Auction.auctions = Auction.auctions.filter(function(el) {
        return --el["seconds"] > 0;
      });
      

      【讨论】:

        【解决方案6】:

        如果你使用 ES6+ - 为什么不直接使用 Array.filter 方法?

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

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

        【讨论】:

          【解决方案7】:

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

          当我在 n = 100 万的情况下运行这个 sn-p 时,每次调用 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)

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

          【讨论】:

            【解决方案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
            }
            

            【讨论】:

              【解决方案9】:

              这是正确使用拼接的另一个示例。此示例即将从“数组”中删除“属性”。

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

              【讨论】:

                【解决方案10】:

                普通的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

                【讨论】:

                  【解决方案11】:

                  为什么要在 .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

                  【讨论】:

                  • 此代码有效,但对于一个长列表,移动所有元素(如气泡)会很乏味
                  • 这个我看不懂,能解释一下吗?
                  【解决方案12】:

                  尝试在循环时将数组中继到 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;
                  

                  【讨论】:

                    【解决方案13】:

                    这个帖子已经有很多精彩的答案了。但是,当我尝试在 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 
                    

                    这是从原始答案复制的代码(使用 cmets):

                    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。但是,此方法的返回类型是已删除的元素。这也不会修改原始数组。修改代码sn-p如下:

                    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 方法是极其重要的实现 函数式编程中的不变性 à la redux

                    【讨论】:

                    • 请注意,更多的代码不应该是衡量代码效率的标准。
                    【解决方案14】:

                    两个有效的例子:

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

                    【讨论】:

                      【解决方案15】:

                      试试这个

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

                      【讨论】:

                        【解决方案16】:

                        删除参数

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

                        它删除并创建新数组,因为我们在每个对象上使用扩展运算符,所以原始数组对象也不会受到伤害

                        【讨论】:

                          【解决方案17】:
                          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--;
                              }
                          }
                          

                          【讨论】:

                          • 一个好的答案将始终解释所做的事情以及为什么以这种方式完成,不仅是为了 OP,而且是为了未来的 SO 访问者。
                          【解决方案18】:

                          您可以浏览并使用shift()

                          【讨论】:

                          • 请使用此方法添加示例。
                          猜你喜欢
                          • 1970-01-01
                          相关资源
                          最近更新 更多