【问题标题】:Change List inside a for loop在 for 循环中更改列表
【发布时间】:2016-02-01 12:17:11
【问题描述】:

我正在尝试修改 for 值中的列表

for (int i = 0; i < theList.Count; i++) {
    if(someCircunstances)
        theList.remove(component);
    else
        theList.add(component);
}

我用这种方法得到一个ArgumentOutOfRangeException。 有什么方法可以实现吗?

【问题讨论】:

  • 您可能没有对component 对象的正确引用。你是如何看待这个参考的?
  • 我做的,当第五次迭代进来时它崩溃了,我调试了看到崩溃与删除和添加函数无关
  • 需要反转for循环从Count -1到0开始
  • 你的意思是for(int i = theList.Count - 1; i &lt; 0; i++)
  • 你在迭代过程中添加的项目呢?你也想遍历它们吗?

标签: c# list for-loop


【解决方案1】:

可以通过向后迭代并使用索引而不是项目来解决:

for (int i = list.Count - 1; i > 0; i--)
{
    if(condition)
        list.RemoveAt(i);
    else
        list.Add(component);
}

一些解释:当您迭代集合时,您不应该更改范围内的项目。迭代器会检测到并抛出(如果是foreach,您必须使用列表的副本)。但是在使用索引(RemoveAt() 方法)的情况下,当向后迭代时,下一次迭代是安全的,范围不包括已删除的项目。 Add() 添加到末尾,因此新项目永远不在范围内。


我会添加更多解决方案,哪个更好自己决定:

经典的 foreach 先复制:

foreach(var item in list.ToArray()) // imho `ToArray` is better than `ToList`
    if(condition)
        list.Remove(item);
    else
        list.Add(component);

作为结果的新列表:

var result = new List<...>();
foreach(var item in list)
    result.Add(condition ? component : item); // not sure here, but should give you idea
list = result;

【讨论】:

    【解决方案2】:

    在迭代列表时改变列表也是一种不好的做法。

    这是另一种选择:

    theList.RemoveAll(someCircunstances);
    

    【讨论】:

    • 我需要从列表中删除“坏”节点,我不能全部删除。
    • Tamas 是对的,只要写一些类似 list.RemoveAll(item => someCircunstancesMatch(item));如果您想手动执行此操作,请使用临时列表将项目分配给情况(原文如此)匹配的时间。然后从第二个列表中的第一个列表中删除所有项目。
    • 这个答案是正确的。请注意,RemoveAll() 的参数是一个谓词。该方法可以更详细地命名为RemoveAllItemsThatMatchThisPredicate()。你可以这样称呼它:theList.RemoveAll(item =&gt; item.SomeProperty == someTarget);。但是,这并没有解决在 OP 中添加项目
    【解决方案3】:

    由于索引从 0 开始,您会遇到超出范围的异常。

    如上所述,一种解决方案是从 theList.count 中删除 1,另一种解决方案是将 i 初始化为 1 而不是 0。

    想一想:如果您的列表中有 1 个元素,则该元素的索引为 0,如果您有 100 个元素,则您的第 10 个元素的索引为 99。

    您正在考虑这样的列表:[1][2][3],而实际上它是 [0][1][2]

    【讨论】:

      【解决方案4】:

      这里的问题是您要从列表中删除值,然后使用已删除的索引再次遍历它 -> ArgumentOutOfRangeException

      所以为了解决这个问题,我建议你将它分成两个 for 循环:

      for (int i = theList.Count - 1; i >= 0; i--) {
          if(someCircunstances)
              theList.remove(component);
      }
      
      for (int i = 0; i < theList.Count; i++) {
          if(someCircunstances)
              theList.add(component);
      }
      

      【讨论】:

      • 崩溃发生在任何项目被删除之前。
      【解决方案5】:

      我同意 Tamas 的观点,即在迭代时不要改变列表,还有另一种方法可以实现您的观点

       List<someType> ToRemove=new List<someType>() ; //some type is same type as theList is  
       List<someType> ToAdd=new List<someType>();
      
      for (int i = 0; i < theList.Count; i++) {
       if(someCircunstances)
           ToRemove.add(component);
       else
           ToAdd.add(component);
      }
      
       theList=((theList.Except(ToRemove)).Concat(ToAdd)).ToList();
      

      【讨论】:

      • 我需要在 for 循环中使用添加的值,以便它们可以用于查找新节点。
      • 新列表将始终与您在 for 循环中使用,ToAdd 作为新添加的元素。
      【解决方案6】:

      基于 cmets,您需要能够为新创建的项目应用相同的逻辑。

      你需要做这样的事情:

      public void DoIt(List<MyObject> theList)
      {
          List<MyObject> items_to_remove = new List<MyObject>();
      
          List<MyObject> items_to_add = new List<MyObject>();
      
          for (int i = 0; i < theList.Count; i++)
          {
              if (someCircunstances)
                  items_to_remove.Add(....); //Remove some existing item
              else
                  items_to_add.Add(....); //Add a new item
          }
      
          if(items_to_remove.Count > 0)
              items_to_remove.ForEach(x => theList.Remove(x));
      
          if (items_to_add.Count > 0)
          {
              DoIt(items_to_add); //Recursively process new objects 
      
              theList.AddRange(items_to_add);
          }
      }
      

      这个想法是您将要添加的项目和要删除的项目插入到它们自己的列表中。

      然后在迭代之后,你删除需要删除的项目。

      之后,您需要添加要添加的项目。但是,在这样做之前,您需要对它们运行相同的逻辑,这就是递归调用的解释。

      请注意,我使用的是MyObject,因为我不知道您的列表类型。使用您正在使用的任何类型。

      【讨论】:

        【解决方案7】:

        如果您可以使用循环的当前索引从 lst 中删除项目,您可以像这样轻松地做到这一点:

        using System;
        using System.Linq;
        
        namespace ConsoleApplication1
        {
            class Program
            {
                static void Main()
                {
                    var numbers = Enumerable.Range(1, 20).ToList();
                    var rng = new Random();
        
                    for (int i = 0; i < numbers.Count; ++i)
                    {
                        if (rng.NextDouble() >= 0.5) // If "someCircumstances"
                        {
                            numbers.Add(numbers[i]*2);
                        }
                        else
                        {
                            // Assume here you have some way to determine the 
                            // index of the item to remove. 
                            // For demo purposes, I'll just calculate a random index.
        
                            int index = rng.Next(0, numbers.Count);
        
                            if (index >= i)
                                --i;
        
                            numbers.RemoveAt(index);
                        }
                    }
        
                    Console.WriteLine(string.Join("\n", numbers));
                }
            }
        }
        

        这还将循环所有添加到列表末尾的数字。 numbers.Count 的值在每次迭代时都会重新计算,所以当它发生变化时,循环会适当地扩展。

        (Offtopic) 额外问题:在上面的代码中,循环退出时列表的平均大小是多少?最大尺寸是多少?

        【讨论】:

        • 由于是 A* 寻路算法,列表的最大大小为 15^15。这永远不会发生,因为它会更早地找到方法。完成循环的唯一方法是完成一些要求。
        • @Patxiku 我修改了代码来展示当你可以获得要删除的项目的索引时如何做。
        • 删除索引会影响列表结构吗?它会像一个数组并且在该索引内有一个空值吗?
        • @Patxiku 不,当您调用RemoveAt() 时,它会从列表中删除该元素,并将所有剩余元素随机排列。然后,该列表将比您调用 RemoveAt() 之前的元素短一个。
        • @Patxiku 不,将检查所有值。有两种情况:(1)被删除的项目在当前索引之后。在这种情况下,不会跳过任何项目。或者 (2) 被移除的项目在当前索引处或之前。在这种情况下,我们需要减少当前索引以避免跳过一个项目。这就是--i 在我上面的示例代码中所做的。
        猜你喜欢
        • 1970-01-01
        • 2021-10-11
        • 1970-01-01
        • 2019-07-25
        • 2023-01-25
        • 1970-01-01
        • 2020-08-28
        • 2018-07-06
        • 2018-12-01
        相关资源
        最近更新 更多