如何在迭代通用列表时从通用列表中删除元素?
我正在寻找一种更好的模式来处理每个需要处理的元素列表,然后根据结果从列表中删除。
您不能在 foreach (X 中的 var element)
中使用 .Remove(element)
(因为它会导致 Collection 被修改;枚举操作可能无法执行.
例外)...您也不能使用 for (int i = 0; i < elements.Count(); i++)
和 .RemoveAt(i)
因为它会扰乱您当前在集合中相对于 i
的位置。
有没有一种优雅的方法来做到这一点?
I am looking for a better pattern for working with a list of elements which each need processed and then depending on the outcome are removed from the list.
You can't use .Remove(element)
inside a foreach (var element in X)
(because it results in Collection was modified; enumeration operation may not execute.
exception)... you also can't use for (int i = 0; i < elements.Count(); i++)
and .RemoveAt(i)
because it disrupts your current position in the collection relative to i
.
Is there an elegant way to do this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(28)
使用 for 循环反向迭代列表:
示例:
或者,您可以使用 RemoveAll方法 带有要测试的谓词:
这是一个演示的简化示例:
Iterate your list in reverse with a for loop:
Example:
Alternately, you can use the RemoveAll method with a predicate to test against:
Here's a simplified example to demonstrate:
如果您将
.ToList()
添加到列表(或 LINQ 查询的结果),则可以直接从中删除
没有可怕的item
listCollection 被修改;枚举操作可能无法执行。
错误。编译器会生成list
的副本,以便您可以安全地对数组进行删除。虽然这种模式不是非常有效,但它具有自然的感觉,并且足够灵活,适合几乎任何情况。例如,当您希望将每个
item
保存到数据库中,并仅在数据库保存成功时将其从list
中删除。If you add
.ToList()
to your list (or the results of a LINQ query), you can removeitem
directly fromlist
without the dreadedCollection was modified; enumeration operation may not execute.
error. The compiler makes a copy oflist
, so that you can safely do the remove on the array.While this pattern is not super efficient, it has a natural feel and is flexible enough for almost any situation. Such as when you want to save each
item
to a DB and remove it from thelist
only when the DB save succeeds.一个简单直接的解决方案:
使用在集合上向后运行的标准 for 循环和
RemoveAt(i)
来删除元素。A simple and straightforward solution:
Use a standard for-loop running backwards on your collection and
RemoveAt(i)
to remove elements.当您想在迭代集合时从集合中删除元素时,首先想到的应该是反向迭代。
幸运的是,有一个比编写 for 循环更优雅的解决方案,因为 for 循环涉及不必要的键入并且容易出错。
Reverse iteration should be the first thing to come to mind when you want to remove elements from a Collection while iterating over it.
Luckily, there is a more elegant solution than writing a for loop which involves needless typing and can be error prone.
选择您想要想要的元素,而不是尝试删除您不想要想要的元素。这比删除元素要容易得多(通常也更有效)。
我想将此作为评论发布,以回应下面迈克尔·狄龙留下的评论,但它太长了,而且无论如何在我的回答中可能很有用:
就我个人而言,我永远不会一一删除项目,如果你这样做的话需要删除,然后调用
RemoveAll
,它采用谓词并且仅重新排列内部数组一次,而Remove
对每个元素执行Array.Copy
操作你删除。RemoveAll
效率大大提高。当您向后迭代列表时,您已经有了要删除的元素的索引,因此调用
RemoveAt
会更有效,因为Remove
首先遍历列表以找到您要删除的元素的索引,但您已经知道该索引。总而言之,我认为没有任何理由在 for 循环中调用
Remove
。理想情况下,如果可能的话,使用上面的代码根据需要从列表中传输元素,这样就根本不需要创建第二个数据结构。Select the elements you do want rather than trying to remove the elements you don't want. This is so much easier (and generally more efficient too) than removing elements.
I wanted to post this as a comment in response to the comment left by Michael Dillon below, but it's too long and probably useful to have in my answer anyway:
Personally, I'd never remove items one-by-one, if you do need removal, then call
RemoveAll
which takes a predicate and only rearranges the internal array once, whereasRemove
does anArray.Copy
operation for every element you remove.RemoveAll
is vastly more efficient.And when you're backwards iterating over a list, you already have the index of the element you want to remove, so it would be far more efficient to call
RemoveAt
, becauseRemove
first does a traversal of the list to find the index of the element you're trying to remove, but you already know that index.So all in all, I don't see any reason to ever call
Remove
in a for-loop. And ideally, if it is at all possible, use the above code to stream elements from the list as needed so no second data structure has to be created at all.在通用列表上使用 ToArray() 允许您在通用列表上执行删除(项目):
Using the ToArray() on a generic list allows you to do a Remove(item) on your generic List:
使用 .ToList() 将复制您的列表,如本问题所述:
ToList()--它会创建一个新列表吗?
通过使用ToList(),您可以从原始列表中删除,因为您实际上是在迭代副本。
Using .ToList() will make a copy of your list, as explained in this question:
ToList()-- Does it Create a New List?
By using ToList(), you can remove from your original list, because you're actually iterating over a copy.
如果确定要删除哪些项目的函数没有副作用并且不会改变项目(它是一个纯函数),一个简单而有效(线性时间)的解决方案是:
如果有副作用,我会使用类似的东西:
假设哈希值良好,这仍然是线性时间。但由于哈希集,它增加了内存使用量。
最后,如果您的列表只是一个
IList
而不是List
我建议我的答案 我该如何做这个特殊的 foreach 迭代器?。与许多其他答案的二次运行时相比,在给定IList
的典型实现的情况下,这将具有线性运行时。If the function that determines which items to delete has no side effects and doesn't mutate the item (it's a pure function), a simple and efficient (linear time) solution is:
If there are side effects, I'd use something like:
This is still linear time, assuming the hash is good. But it has an increased memory use due to the hashset.
Finally if your list is only an
IList<T>
instead of aList<T>
I suggest my answer to How can I do this special foreach iterator?. This will have linear runtime given typical implementations ofIList<T>
, compared with quadratic runtime of many other answers.由于任何删除都是在您可以使用的条件下进行的
As any remove is taken on a condition you can use
您不能使用 foreach,但可以在删除项目时向前迭代并管理循环索引变量,如下所示:
请注意,通常所有这些技术都依赖于被迭代的集合的行为。此处显示的技术适用于标准 List(T)。 (很有可能编写自己的集合类和迭代器,确实允许在 foreach 循环期间删除项目。)
You can't use foreach, but you could iterate forwards and manage your loop index variable when you remove an item, like so:
Note that in general all of these techniques rely on the behaviour of the collection being iterated. The technique shown here will work with the standard List(T). (It is quite possible to write your own collection class and iterator that does allow item removal during a foreach loop.)
For 循环对此来说是一个糟糕的构造。
使用
while
但是,如果您绝对必须使用
for
或者,这样:
用法:
但是,LINQ 已经有
RemoveAll()
来执行此操作最后,您可能最好使用 LINQ 的
Where()
来过滤和创建新列表,而不是改变现有列表。不变性通常是好的。For loops are a bad construct for this.
Using
while
But, if you absolutely must use
for
Or, this:
Usage:
However, LINQ already has
RemoveAll()
to do thisLastly, you are probably better off using LINQ's
Where()
to filter and create a new list instead of mutating the existing list. Immutability is usually good.在迭代列表时在列表上使用
Remove
或RemoveAt
故意变得困难,因为它几乎总是错误的做法。你也许可以通过一些巧妙的技巧让它工作,但速度会非常慢。每次调用Remove
时,它都必须扫描整个列表以找到要删除的元素。每次调用RemoveAt
时,它都必须将后续元素向左移动 1 个位置。因此,任何使用Remove
或RemoveAt
的解决方案都需要二次时间,O(n²)。如果可以的话,使用
RemoveAll
。否则,以下模式将在线性时间内就地过滤列表,O(n)。Using
Remove
orRemoveAt
on a list while iterating over that list has intentionally been made difficult, because it is almost always the wrong thing to do. You might be able to get it working with some clever trick, but it would be extremely slow. Every time you callRemove
it has to scan through the entire list to find the element you want to remove. Every time you callRemoveAt
it has to move subsequent elements 1 position to the left. As such, any solution usingRemove
orRemoveAt
, would require quadratic time, O(n²).Use
RemoveAll
if you can. Otherwise, the following pattern will filter the list in-place in linear time, O(n).在迭代列表时从列表中删除项目的最佳方法是使用
RemoveAll()
。但人们主要担心的是他们必须在循环内做一些复杂的事情和/或有复杂的比较情况。解决方案是仍然使用
RemoveAll()
但使用以下表示法:The best way to remove items from a list while iterating over it is to use
RemoveAll()
. But the main concern written by people is that they have to do some complex things inside the loop and/or have complex compare cases.The solution is to still use
RemoveAll()
but use this notation:我会从 LINQ 查询中重新分配列表,该查询会过滤掉您不想保留的元素。
除非列表非常大,否则执行此操作不会出现明显的性能问题。
I would reassign the list from a LINQ query that filtered out the elements you didn't want to keep.
Unless the list is very large there should be no significant performance problems in doing this.
假设谓词是元素的布尔属性,如果它为 true,则应删除该元素:
By assuming that predicate is a Boolean property of an element, that if it is true, then the element should be removed:
在 C# 中,一种简单的方法是标记要删除的列表,然后创建一个新列表进行迭代...
或者更简单地使用 linq...
但值得考虑其他任务或线程是否可以访问相同的列表在您忙于删除的同时列出,也许可以使用 ConcurrentList 代替。
In C# one easy way is to mark the ones you wish to delete then create a new list to iterate over...
or even simpler use linq....
but it is worth considering if other tasks or threads will have access to the same list at the same time you are busy removing, and maybe use a ConcurrentList instead.
有一个选项这里没有提到。
如果您不介意在项目中的某处添加一些代码,则可以添加和扩展 List 以返回反向迭代列表的类的实例。
您可以像这样使用它:
下面是扩展的样子:
这基本上是没有分配的 list.Reverse() 。
就像一些人提到的那样,您仍然会遇到逐个删除元素的缺点,如果您的列表非常长,那么这里的一些选项会更好。但我认为有人会想要 list.Reverse() 的简单性,而不需要内存开销。
There is an option that hasn't been mentioned here.
If you don't mind adding a bit of code somewhere in your project, you can add and extension to List to return an instance of a class that does iterate through the list in reverse.
You would use it like this :
And here is what the extension looks like:
This is basically list.Reverse() without the allocation.
Like some have mentioned you still get the drawback of deleting elements one by one, and if your list is massively long some of the options here are better. But I think there is a world where someone would want the simplicity of list.Reverse(), without the memory overhead.
我希望“模式”是这样的:
这将使代码与程序员大脑中进行的过程保持一致。
I wish the "pattern" was something like this:
This would align the code with the process that goes on in the programmer's brain.
只需从第一个列表创建一个全新的列表即可。我说“简单”而不是“正确”,因为创建一个全新的列表可能比以前的方法具有更高的性能(我没有费心进行任何基准测试。)我通常更喜欢这种模式,它在克服问题方面也很有用Linq-To-Entity 限制。
这种方式使用普通的旧 For 循环向后循环列表。如果集合的大小发生变化,向前执行此操作可能会出现问题,但向后执行应该始终是安全的。
Simply create an entirely new list from the first one. I say "Easy" rather than "Right" as creating an entirely new list probably comes at a performance premium over the previous method (I haven't bothered with any benchmarking.) I generally prefer this pattern, it can also be useful in overcoming Linq-To-Entities limitations.
This way cycles through the list backwards with a plain old For loop. Doing this forwards could be problematic if the size of the collection changes, but backwards should always be safe.
只是想添加我的 2 美分,以防这对任何人有帮助,我遇到了类似的问题,但需要在迭代时从数组列表中删除多个元素。最高票数的答案在很大程度上为我做到了这一点,直到我遇到错误并意识到在某些情况下索引大于数组列表的大小,因为多个元素被删除但循环的索引没有保留的踪迹。我通过一个简单的检查修复了这个问题:
Just wanted to add my 2 cents to this in case this helps anyone, I had a similar problem but needed to remove multiple elements from an array list while it was being iterated over. the highest upvoted answer did it for me for the most part until I ran into errors and realized that the index was greater than the size of the array list in some instances because multiple elements were being removed but the index of the loop didn't keep track of that. I fixed this with a simple check:
复制您正在迭代的列表。然后从副本中取出并与原件进行交互。向后运行会令人困惑,并且在并行循环时效果不佳。
Copy the list you are iterating. Then remove from the copy and interate the original. Going backwards is confusing and doesn't work well when looping in parallel.
我想要这个
输出
I would do like this
OUTPUT
我发现自己处于类似的情况,我必须删除给定
List
中的每个第 nth 元素。I found myself in a similar situation where I had to remove every nth element in a given
List<T>
.从列表中删除一项的成本与要删除的项之后的项数成正比。在前一半项目符合删除条件的情况下,任何基于单独删除项目的方法最终都必须执行大约 N*N/4 项目复制操作,如果列表很大,这可能会变得非常昂贵。
更快的方法是扫描列表以找到第一个要删除的项目(如果有),然后从该点开始将应保留的每个项目复制到其所属的位置。完成此操作后,如果应保留 R 个项目,则列表中的前 R 个项目将是这 R 个项目,所有需要删除的项目将位于末尾。如果以相反的顺序删除这些项目,系统最终将不必复制它们中的任何一个,因此,如果列表有 N 个项目,其中 R 个项目(包括所有前 F 个项目)被保留,
需要复制 RF 项,并将列表缩小 NR 倍一项。所有线性时间。
The cost of removing an item from the list is proportional to the number of items following the one to be removed. In the case where the first half of the items qualify for removal, any approach which is based upon removing items individually will end up having to perform about N*N/4 item-copy operations, which can get very expensive if the list is large.
A faster approach is to scan through the list to find the first item to be removed (if any), and then from that point forward copy each item which should be retained to the spot where it belongs. Once this is done, if R items should be retained, the first R items in the list will be those R items, and all of the items requiring deletion will be at the end. If those items are deleted in reverse order, the system won't end up having to copy any of them, so if the list had N items of which R items, including all of the first F, were retained,
it will be necessary to copy R-F items, and shrink the list by one item N-R times. All linear time.
我的方法是首先创建一个索引列表,该列表应该被删除。然后,我循环索引并从初始列表中删除项目。这看起来像这样:
My approach is that I first create a list of indices, which should get deleted. Afterwards I loop over the indices and remove the items from the initial list. This looks like this:
用属性追踪要移除的元素,处理后将其全部移除。
Trace the elements to be removed with a property, and remove them all after process.