How to remove elements from a generic list while iterating over it?
我正在寻找一个更好的模式来处理每个需要处理的元素列表,然后根据结果从列表中删除。
在
有优雅的方法吗?
用for循环反向迭代列表:
1 2 3 4 5 | for (int i = safePendingList.Count - 1; i >= 0; i--) { // some code // safePendingList.RemoveAt(i); } |
例子:
1 2 3 4 5 6 7 | var list = new List<int>(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); } list.ForEach(i => Console.WriteLine(i)); |
或者,可以使用带有谓词的removeall方法来测试:
1 | safePendingList.RemoveAll(item => item.Value == someValue); |
下面是一个简单的例子来演示:
1 2 3 4 5 6 | var list = new List<int>(Enumerable.Range(1, 10)); Console.WriteLine("Before:"); list.ForEach(i => Console.WriteLine(i)); list.RemoveAll(i => i > 5); Console.WriteLine("After:"); list.ForEach(i => Console.WriteLine(i)); |
简单明了的解决方案:
使用在集合上向后运行的标准for循环和
当您希望在迭代过程中从集合中移除元素时,应该首先想到反向迭代。
幸运的是,有一个比编写for循环更优雅的解决方案,它涉及到不必要的输入,并且容易出错。
1 2 3 4 5 6 7 8 9 |
1 2 3 | foreach (var item in list.ToList()) { list.Remove(item); } |
如果将".to list()"添加到列表(或Linq查询的结果),则可以直接从"list"中删除"item",而不必修改可怕的"collection";枚举操作可能不会执行。"error"。编译器生成"list"的副本,这样就可以安全地在数组上执行删除操作。
虽然这种模式不是超高效的,但它有一种自然的感觉,并且对几乎任何情况都足够灵活。例如,当您希望将每个"项"保存到数据库中,并且仅当数据库保存成功时才将其从列表中删除。
在通用列表上使用toarray()可以在通用列表上执行移除(项):
1 2 3 4 5 6 | List<String> strings = new List<string>() {"a","b","c","d" }; foreach (string s in strings.ToArray()) { if (s =="b") strings.Remove(s); } |
选择您想要的元素,而不是尝试删除您不想要的元素。这比移除元素容易得多(而且通常也更有效)。
1 2 3 | var newSequence = (from el in list where el.Something || el.AnotherThing < 0 select el); |
我想把这篇文章作为对迈克尔·迪伦评论的回应发表在下面,但是这篇文章太长了,而且可能对我的回答很有用:
就我个人而言,我从不逐个删除项目,如果您确实需要删除,那么调用
当你反向迭代一个列表时,你已经有了你想要删除的元素的索引,所以调用
总之,我看不出有任何理由在for循环中调用
使用.tolist()将复制列表,如本问题中所述:tolist()--它是否创建一个新列表?
通过使用tolist(),您可以从原始列表中删除,因为您实际上正在迭代一个副本。
1 2 3 4 5 6 7 | foreach (var item in listTracked.ToList()) { if (DetermineIfRequiresRemoval(item)) { listTracked.Remove(item) } } |
如果确定要删除的项的函数没有副作用并且不会改变项(它是一个纯函数),那么一个简单有效的(线性时间)解决方案是:
1 | list.RemoveAll(condition); |
如果有副作用,我会用如下方法:
1 2 3 4 5 6 7 8 | var toRemove = new HashSet<T>(); foreach(var item in items) { ... if(condition) toRemove.Add(item); } items.RemoveAll(toRemove.Contains); |
这仍然是线性时间,假设散列是好的。但由于哈希集的存在,它的内存使用量增加了。
最后,如果您的列表只是一个
因为任何移除都是在您可以使用的条件下进行的
1 | list.RemoveAll(item => item.Value == someValue); |
1 2 3 | List<T> TheList = new List<T>(); TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element)); |
不能使用foreach,但可以在删除项时向前迭代并管理循环索引变量,如:
1 2 3 4 5 6 7 8 | for (int i = 0; i < elements.Count; i++) { if (<condition>) { // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation. elements.RemoveAt(i--); } } |
注意,一般来说,所有这些技术都依赖于被迭代的集合的行为。这里显示的技术将与标准列表(t)一起工作。(很可能编写自己的集合类和迭代器,它允许在foreach循环期间删除项。)
在列表上使用
如果可以,使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Create a list to be filtered IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); // Filter the list int kept = 0; for (int i = 0; i < elements.Count; i++) { // Test whether this is an element that we want to keep. if (elements[i] % 3 > 0) { // Add it to the list of kept elements. elements[kept] = elements[i]; kept++; } } // Unfortunately IList has no Resize method. So instead we // remove the last element of the list until: elements.Count == kept. while (kept < elements.Count) elements.RemoveAt(elements.Count-1); |
我将从一个筛选出您不希望保留的元素的Linq查询中重新分配列表。
1 | list = list.Where(item => ...).ToList(); |
除非列表非常大,否则在执行此操作时不应该存在显著的性能问题。
我希望"模式"是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | foreach( thing in thingpile ) { if( /* condition#1 */ ) { foreach.markfordeleting( thing ); } elseif( /* condition#2 */ ) { foreach.markforkeeping( thing ); } } foreachcompleted { // then the programmer's choices would be: // delete everything that was marked for deleting foreach.deletenow(thingpile); // ...or... keep only things that were marked for keeping foreach.keepnow(thingpile); // ...or even... make a new list of the unmarked items others = foreach.unmarked(thingpile); } |
这将使代码与程序员大脑中进行的过程保持一致。
1 2 3 4 5 6 7 | foreach(var item in list.ToList()) { if(item.Delete) list.Remove(item); } |
只需从第一个列表创建一个全新的列表。我说"简单"而不是"正确",因为创建一个全新的列表可能比以前的方法要高性能(我没有考虑任何基准测试)。我通常更喜欢这种模式,它还可以帮助克服LINQ对实体的限制。
1 2 3 4 5 6 7 8 9 | for(i = list.Count()-1;i>=0;i--) { item=list[i]; if (item.Delete) list.Remove(item); } |
这种方法使用一个普通的旧for循环向后循环列表。如果集合的大小改变了,那么向前做可能会有问题,但是向后做应该总是安全的。
在遍历列表时从列表中删除项的最佳方法是使用
解决方案是仍然使用
1 2 3 4 5 6 7 8 9 | var list = new List<int>(Enumerable.Range(1, 10)); list.RemoveAll(item => { // Do some complex operations here // Or even some operations on the items SomeFunction(item); // In the end return true if the item is to be removed. False otherwise return item > 5; }); |
假设谓词是元素的布尔属性,如果为真,则应删除该元素:
1 2 3 4 5 6 7 8 9 10 | int i = 0; while (i < list.Count()) { if (list[i].predicate == true) { list.RemoveAt(i); continue; } i++; } |
我发现自己也处于同样的情况,我必须删除给定
1 2 3 4 5 6 7 8 9 10 | for (int i = 0, j = 0, n = 3; i < list.Count; i++) { if ((j + 1) % n == 0) //Check current iteration is at the nth interval { list.RemoveAt(i); j++; //This extra addition is necessary. Without it j will wrap //down to zero, which will throw off our index. } j++; //This will always advance the j counter } |
从列表中删除项目的成本与要删除的项目后面的项目数成比例。如果前半个项目符合删除条件,则基于单独删除项目的任何方法最终都必须执行大约n*n/4个项目复制操作,如果列表很大,则可能会非常昂贵。
一种更快的方法是扫描列表以找到要删除的第一个项目(如果有的话),然后从这一点向前复制每个项目,这些项目应该保留到它所属的位置。完成后,如果要保留R项,列表中的第一个R项将是那些R项,所有需要删除的项都将在末尾。如果按相反的顺序删除这些项目,系统将不必复制它们中的任何一个,因此,如果列表中有n个项目保留了R项,包括所有的第一个F项,有必要复制R-F项目,并将列表缩小一个项目n-r次。所有线性时间。
我的方法是首先创建一个索引列表,它应该被删除。然后,我循环遍历索引,并从初始列表中删除这些项。如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | var messageList = ...; // Restrict your list to certain criteria var customMessageList = messageList.FindAll(m => m.UserId == someId); if (customMessageList != null && customMessageList.Count > 0) { // Create list with positions in origin list List<int> positionList = new List<int>(); foreach (var message in customMessageList) { var position = messageList.FindIndex(m => m.MessageId == message.MessageId); if (position != -1) positionList.Add(position); } // To be able to remove the items in the origin list, we do it backwards // so that the order of indices stays the same positionList = positionList.OrderByDescending(p => p).ToList(); foreach (var position in positionList) { messageList.RemoveAt(position); } } |
复制正在迭代的列表。然后从副本中删除并与原始副本交互。向后走会让人困惑,并且在并行循环时不能很好地工作。
1 2 3 4 5 6 7 | var ids = new List<int> { 1, 2, 3, 4 }; var iterableIds = ids.ToList(); Parallel.ForEach(iterableIds, id => { ids.Remove(id); }); |
1 2 3 | myList.RemoveAt(i--); simples; |