【问题标题】:Efficiently deleting item from within 'foreach'从“foreach”中有效地删除项目
【发布时间】:2012-01-09 16:18:57
【问题描述】:

目前,我能想到的最好的是:

bool oneMoreTime = true;
while (oneMoreTime)
{
    ItemType toDelete=null;
    oneMoreTime=false;
    foreach (ItemType item in collection)
    {
        if (ShouldBeDeleted(item))
        {
            toDelete=item;
            break;
        }
    }
    if (toDelete!=null)
    {
        collection.Remove(toDelete);
        oneMoreTime=true;
    }
}

我知道我在这里至少有一个额外的变量,但我将它包括在内是为了提高算法的可读性。

【问题讨论】:

标签: c# .net collections


【解决方案1】:

“RemoveAll”方法是最好的。

另一种常见的技术是:

var itemsToBeDeleted = collection.Where(i=>ShouldBeDeleted(i)).ToList();
foreach(var itemToBeDeleted in itemsToBeDeleted)
    collection.Remove(itemToBeDeleted);

另一种常见的技术是使用“for”循环,但请确保您向后

for (int i = collection.Count - 1; i >= 0; --i)
    if (ShouldBeDeleted(collection[i]))
        collection.RemoveAt(i);

另一种常见的技术是将删除的项目添加到新集合中:

var newCollection = new List<whatever>();
foreach(var item in collection.Where(i=>!ShouldBeDeleted(i))
    newCollection.Add(item);

现在您有两个集合。如果你想得到两个集合,我特别喜欢的一种技术是使用不可变数据结构。对于不可变的数据结构,“删除”一个项目不会改变数据结构;它为您提供了一个新的数据结构(如果可能,它会重新使用旧数据结构中的位),其中没有您删除的项目。使用不可变的数据结构,您不会修改您正在迭代的东西,所以没有问题:

var newCollection = oldCollection;
foreach(var item in oldCollection.Where(i=>ShouldBeDeleted(i))
    newCollection = newCollection.Remove(item);

var newCollection = ImmutableCollection<whatever>.Empty;
foreach(var item in oldCollection.Where(i=>!ShouldBeDeleted(i))
    newCollection = newCollection.Add(item);

当你完成后,你有两个集合。新的去掉了物品,旧的和以前一样。

【讨论】:

  • 你有没有使用过使用 foreach 的 Reverse 扩展 - 我刚刚在这里遇到了 stackoverflow.com/a/10541025/706363 ?为什么它不是一个更广泛使用的选项?是否存在某种严重的性能影响或发生了什么?
  • @ppumkin:尝试为未实现IListICollection 等的IEnumerable 编写Reverse 的实现。您的实现的内存和时间性能如何?
  • 我的实现不是时间或资源关键。我在列表上的 Linq 中使用 IEnumerable.Reverse&lt;T&gt; 扩展名,它似乎在 foreach 中工作正常 - 这就是我问的原因,为什么不更频繁地使用这个例子,而不是所有这些(我0)相反,就像你的答案一样。使用反向扩展是一个有效的选项吗?您可以将其添加到您的答案中,还是将 Linq 的反向扩展与 foreach 结合使用是否有问题?只是想,由于您的经验,您的意见将最重要。
  • @ppumkin:由于反转 IEnumerable 需要创建整个 IEnumerable 的副本(因此您可以在 foreach 循环中修改原始集合),因此没有什么意义;您也可以只复制集合而不反转它,可能通过调用ToList。为破坏他的课程向 Eric 道歉。
  • @BKSpureon:确实如此!所有比被移除元素高于的元素都需要向下移动一个位置。如果您需要有效地执行此操作,那么不要使用列表。使用无序类型,如哈希集,或有效支持此操作的有序类型,如双向链表、可连接双端队列等。
【解决方案2】:

就在我完成打字时,我记得有 lambda 方法可以做到这一点。

collection.RemoveAll(i=>ShouldBeDeleted(i));

更好的方法?

【讨论】:

  • 仅供参考:这可以转换为方法组。 collection.RemoveAll(ShouldBeDeleted).
【解决方案3】:

后向 for 循环的前向变体:

for (int i = 0; i < collection.Count; )
    if (ShouldBeDeleted(collection[i]))
        collection.RemoveAt(i)
    else
        i++;

【讨论】:

    【解决方案4】:

    您不能从 foreach 循环内的集合中删除(除非它是具有特殊枚举数的非常特殊的集合)。如果在枚举时修改了集合,BCL 集合将引发异常。

    您可以使用for 循环来删除单个元素并相应地调整索引。但是,这样做很容易出错。根据底层集合的实现,删除单个元素也可能很昂贵。例如,删除 List&lt;T&gt; 的第一个元素将复制列表中的所有剩余元素。

    最好的解决方案通常是在旧集合的基础上创建一个新集合:

    var newCollection = collection.Where(item => !ShouldBeDeleted(item)).ToList();
    

    使用ToList()ToArray() 创建新集合或从Where() 子句返回的IEnumerable 初始化您的特定集合类型。

    【讨论】:

      【解决方案5】:

      lambda 方式很好。您也可以使用常规的 for 循环,您可以迭代 for 循环在循环本身中使用的列表,这与 foreach 循环不同。

      for (int i = collection.Count-1; i >= 0; i--)
      {
          if(ShouldBeDeleted(collection[i])
              collection.RemoveAt(i);
      }
      

      这里我假设collection是一个arraylist,如果你使用不同的数据结构,代码可能会有点不同。

      【讨论】:

        猜你喜欢
        • 2023-03-08
        • 2019-10-26
        • 1970-01-01
        • 2020-02-11
        • 2010-11-02
        • 2021-10-13
        • 2015-01-06
        • 1970-01-01
        • 2021-10-13
        相关资源
        最近更新 更多