c#在遍历列表时删除元素-向后迭代或使用i--或使用linq同时迭代和删除?

Posted

技术标签:

【中文标题】c#在遍历列表时删除元素-向后迭代或使用i--或使用linq同时迭代和删除?【英文标题】:c# Removing element while traversing list- iterate backwards or use i-- or using linq to iterate and remove at the same time? 【发布时间】:2016-04-16 19:36:53 【问题描述】:

我想根据条件从列表中删除一个元素。问题是我无法在向前迭代时删除它,因为它的实现类似于数组,这会改变列表结构和计数。

所以我读到了这个答案,我确信这应该是解决方案:how-to-remove-elements-from-a-generic-list-while-iterating-over-it

但是有人给我反馈说这不太容易理解,因为我们正在向后迭代它,而是我们应该向前迭代并执行 i-- 每当满足移除条件时。

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = 0; i < list.Count; i++)

    if (list[i] > 5)
    
        list.RemoveAt(i);
        i--;
    

list.ForEach(i => Console.WriteLine(i));

我对这种方法进行了单元测试,结果符合预期,但我仍然担心所有可能的情况都是一样的,因为我在任何论坛都没有得到这个答案,而且每个人都建议向后迭代。

谁能告诉我这两种解决方案的行为是否相同或不同?

【问题讨论】:

你为什么不喜欢List&lt;T&gt;.RemoveAll 【参考方案1】:

就地从列表中删除项目的首选方法是使用这种代码反向执行:

int index = list.Count - 1;
while (index >= 0)

    if (expression identifying item to remove)
        list.RemoveAt(index);
    index--;

现在,请记住,这仅适用于 就地 列表中的删除,这意味着您不想构建要保留的项目的新列表。如果你能做到这一点,建立一个包含要保留的项目的新列表,LINQ 表达式可能会更好。

那么,为什么首选上述方法?

好吧,考虑一下。无论如何,“从列表中删除项目”是什么意思? C# 中List&lt;T&gt; 的底层数据结构是一个数组。从数组中删除一个项目实际上是无法完成的,但您可以做的是移动数组中的值。要从数组中“删除”一个项目,您可以将其后的所有项目上移一个索引。

这是一个相对于它后面的项目数量而言需要时间的操作。

因此,让我们看看在“前向方法”中执行此操作。在这种方法中,您将从索引 0 开始,并在每次找到要保留的项目时向上移动,但您也可以通过将其后的每个项目向下移动一个元素来移除该项目。

让我们举个简单的例子,你有一个从 1 到 10 的每个数字的列表,并且你想删除每个偶数元素值(2、4、6、8 等)。

所以你有这个:

1 2 3 4 5 6 7 8 9 10

要删除的第一个项目是2,这将不得不移动它后面的每个数字,从 3 到 10,向下一个索引。即移动了 8 个数字。

下一项是4,它必须向下移动 5 到 10,即 6 个数字。现在我们总共移动了 14 个数字。

接下来是 6,从 7 到 10 向下移动,即 4 个数字,现在我们最多移动 18 个数字。

接下来是 8,移动 9 和 10,2 个数字,我们最多移动了 20 个数字。

由于我们要删除的最后一个数字 10 后面没有数字,因此我们总共移动了 20 个数字。

现在让我们反过来做。

首先我们看 10,我们要删除它,后面没有数字,所以不要移动。

接下来我们要删除的是 8,它后面只有一个数字,所以我们将 9 下移一位。 10 已被删除,因此不再存在。

接下来我们要移除的是 6。它后面有两个数字,7 和 9,所以将它们向下移动,总共移动了 3 个数字。

去掉4,后面是5、7、9,所以现在一共移动了6个数字。

删除 2 - 3、5、7 和 9 跟随它,所以 现在我们总共移动了 10 个数字。

因此,在处理列表时,这完全取决于您的意思是“行为方式相同”。

当我们只考虑最终列表中剩下哪些数字时,最终结果会相同吗?

是的。

总运行时间是否相同?

没有。

【讨论】:

抱歉评论一个旧帖子并回答,但我们不应该每次都做“索引--”吗?我认为在删除列表末尾的项目(我们检查的第一个项目)时保持相同的索引会导致 IndexOutOfRangeException。因此,我们可以做一个 for 而不是一段时间。 @Rifu 重读代码,你说的似乎是对的,让我测试一下。不敢相信这个答案中出现了这么长时间的错误。 @Rifu 不会有例外,因为在每个索引处,最多会删除一个项目。代码是偶然运行的,因为第一次遇到匹配的新索引时,它将删除该项目,而不是减少索引,然后循环将再次迭代,这次它不会匹配(因为 items "到右边”会更早被删除),所以它会减少索引。我将删除 else 关键字并使其变得更好。感谢您的关注! 没问题,乐于助人!【参考方案2】:

我建议不要只使用索引从迭代中的列表中删除项目。简单地使用带有 where 条件的 Linq Select。这实际上会为您生成一个新列表。

var list = new List<int>(Enumerable.Range(1, 10));
var newList= list.Select(number=>number<=5).ToList();

现在 newList 将只包含满足您在 lambda 表达式中提供的条件的项目。

【讨论】:

但它没有从原始列表中删除选定的元素。我需要删除它们并在其他地方使用带有减法元素的列表。 好的,如果我正确理解您的问题,您将根据某些条件删除一些项目后使用该列表。所以就像我上面的代码一样。它不会从原始列表中删除任何内容。在使用您的条件从原始结果中过滤项目后,它只会为您创建一个新列表。您可以在其他地方使用新列表。

以上是关于c#在遍历列表时删除元素-向后迭代或使用i--或使用linq同时迭代和删除?的主要内容,如果未能解决你的问题,请参考以下文章

Python:如何在迭代列表时从列表中删除元素而不跳过未来的迭代

可以通过 foreach 向后迭代吗?

比较两个迭代器并检查哪些元素被添加,删除或两者之间相同

C#遍历List并删除某个元素的方法

c# arraylist遍历删除

Java集合框架顶层接口collectiion接口