如何从一个泛型列表在遍历它删除元素?
题
我寻找更好的 图案 作为与每个需要处理的元素的列表的工作,然后根据结果从列表中被删除。
您不能使用.Remove(element)
一个foreach (var element in X)
内(因为它导致Collection was modified; enumeration operation may not execute.
除外)......你还因为它破坏了相对于for (int i = 0; i < elements.Count(); i++)
收集您的当前位置不能使用.RemoveAt(i)
和i
。
有一种优雅的方式来做到这一点?
解决方案
迭代列表在反向与一个for循环:
for (int i = safePendingList.Count - 1; i >= 0; i--)
{
// some code
// safePendingList.RemoveAt(i);
}
示例:
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方法与谓词针对测试:
safePendingList.RemoveAll(item => item.Value == someValue);
下面是一个简化的例子来说明:
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循环运行的后退在收集和RemoveAt(i)
删除元素。
反向迭代应该是浮现在脑海中,当你想在遍历它从一个集合中删除元素的第一件事。
幸运的是,有一个更好的解决方案比编写一个for循环涉及不必要打字并且可以是容易出错。
ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
foreach (int myInt in test.Reverse<int>())
{
if (myInt % 2 == 0)
{
test.Remove(myInt);
}
}
foreach (var item in list.ToList()) {
list.Remove(item);
}
如果您的添加“.ToList()” 以您的列表(或LINQ查询的结果),你可以直接从‘名单’中删除‘项’不可怕“的集合已修改;枚举操作可能不执行“。错误。编译器可以“名单”的副本,让您可以放心地做阵列上的删除。
虽然的这种模式强>不超有效,它有一个自然的感觉,并且是几乎任何情况强>的足够灵活。这样,当你想每一个“项目”保存到数据库,并从只有当数据库保存成功列表中删除它。
泛型列表上使用ToArray的(),您可以在您的泛型列表做了删除(项目):
List<String> strings = new List<string>() { "a", "b", "c", "d" };
foreach (string s in strings.ToArray())
{
if (s == "b")
strings.Remove(s);
}
选择的元素,你的做想,而不是试图删除您的不想要的元素。这是如此比移除元素容易得多(通常更有效的太)。
var newSequence = (from el in list
where el.Something || el.AnotherThing < 0
select el);
我想张贴此为响应由下面的迈克尔·狄龙留下评论评论,但它太长时间,可能有用的在我的答案呢:
就个人而言,我从来没有一个接一个删除项目,如果你确实需要拆除,然后调用RemoveAll
这需要一个谓语,只是重新安排内部数组一次,而Remove
做的Array.Copy
操作每次你删除元素。 RemoveAll
是大大更有效率。
,当你向后遍历列表,你已经有了要删除,所以这将是更为有效的调用RemoveAt
元素的索引,因为Remove
首先做列表的遍历查找元素的索引你想删除,但你已经知道了指数。
所以,一切的一切,我没有看到一个for循环任何理由永远呼叫Remove
。理想地,如果它是在所有可能的,使用上面的代码根据需要所以没有第二数据结构具有在所有被创建以从列表流元素。
使用.ToList()会让你的列表的副本,因为在这一问题解释说: ToList() - 它创建一个新的列表 ? p>
通过使用ToList(),您可以从您最初的名单中删除,因为你实际上是迭代一个副本。
foreach (var item in listTracked.ToList()) {
if (DetermineIfRequiresRemoval(item)) {
listTracked.Remove(item)
}
}
如果确定哪些项目删除的功能没有副作用,并且不发生变异的项目(它是一个纯函数)的简单和有效的(线性时间)的解决方案是:
list.RemoveAll(condition);
如果有副作用,我会使用这样的:
var toRemove = new HashSet<T>();
foreach(var item in items)
{
...
if(condition)
toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);
这仍然是线性的时间,假定哈希是好的。但它具有增加的存储器使用由于HashSet的。
最后,如果您的列表只是一个IList<T>
代替List<T>
我建议我的回答我如何能做到这一点特别的foreach迭代器?。这将具有给定IList<T>
的典型实现线性运行时,与许多其他的答案二次运行时进行比较。
如任何删除被取上的条件可以使用
list.RemoveAll(item => item.Value == someValue);
List<T> TheList = new List<T>();
TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));
您不能使用的foreach,但你可以向前遍历和管理您的循环变量,当你删除一个项目,像这样:
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循环中删除物品。)
在列表上使用Remove
或RemoveAt
在遍历列表中故意犯了难,因为它几乎总是错误的事情。你也许可以得到它与一些聪明的把戏工作,但它会非常慢。每次调用Remove
有时间来扫描通过整个列表,找到要删除的元素。每次调用RemoveAt
一次有移动后续元素1个位置向左。这样,在采用Remove
或RemoveAt
任何溶液,将需要二次时间, O(N²)
使用RemoveAll
如果你能。否则,以下图案将过滤列表的就地在线性时间, O(n)的
// 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);
我的希望的的 “模式” 是这样的:
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);
}
此将对准代码与在编程器的大脑上前进的过程。
我就从一个LINQ查询过滤掉你不想保留的元素重新分配名单。
list = list.Where(item => ...).ToList();
除非列出的是非常大的应该有在做这个没有显著的性能问题。
而遍历它从列表中删除项的最好的方法是使用 RemoveAll()
即可。但人写的主要问题是他们必须做环内的一些复杂的事情和/或有比较复杂的情况。
的解决方案是仍然使用 RemoveAll()
强>但使用此表示法:
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;
});
通过假设谓词是一个元素的布尔属性,即,如果它是真实的,则该元件应被删除:
int i = 0;
while (i < list.Count())
{
if (list[i].predicate == true)
{
list.RemoveAt(i);
continue;
}
i++;
}
foreach(var item in list.ToList())
{
if(item.Delete) list.Remove(item);
}
简单地创建从第一个完全新的列表。我说“易”,而不是“右”作为创建一个全新的列表可能来自超过以前的方法性能溢价(我没有打扰任何基准测试。)我一般喜欢这种模式,它也可以是在克服有用LINQ到实体限制。
for(i = list.Count()-1;i>=0;i--)
{
item=list[i];
if (item.Delete) list.Remove(item);
}
此方式,通过与一个普通的旧For循环列表向后循环。这样做前锋可能是,如果集合的大小变化问题,但反向应该永远是安全的。
我发现自己在一个类似的情况下,我不得不移除每n 个在给定List<T>
元件。
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次列表。所有的线性时间。
我的做法是,我首先创建的索引列表,它应该被删除。后来在我的索引循环并从初始列表中的项目。这看起来像这样:
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);
}
}
复制列表中你迭代。然后从副本中删除,并interate原。倒退是混淆和并联循环时不能很好地工作。
var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();
Parallel.ForEach(iterableIds, id =>
{
ids.Remove(id);
});
在C#一个简单的方法是选取您要删除,则那些创建一个新的列表来遍历...
foreach(var item in list.ToList()){if(item.Delete) list.Remove(item);}
或者也可以简单使用LINQ ....
list.RemoveAll(p=>p.Delete);
但它是值得考虑的,如果其他任务或线程将在同一时间,你都在忙着卸下访问相同的列表,也许使用ConcurrentList代替。
痕量元素,和处理后除去它们。
using System.Linq;
List<MyProperty> _Group = new List<MyProperty>();
// ... add elements
bool cond = true;
foreach (MyProperty currObj in _Group)
{
if (cond)
{
// SET - element can be deleted
currObj.REMOVE_ME = true;
}
}
// RESET
_Group.RemoveAll(r => r.REMOVE_ME);
只是想我的2美分到这个情况下这会有所帮助的人,我也有类似的问题,但同时它被遍历从数组列表中删除多个元素需要。最高upvoted答案为我做的大部分,直到我遇到了错误,并意识到,因为多个元素被移除的指数比在某些情况下,数组列表的规模更大,但环的指数没有跟上跟踪这一点。我固定这用一个简单的检查:
ArrayList place_holder = new ArrayList();
place_holder.Add("1");
place_holder.Add("2");
place_holder.Add("3");
place_holder.Add("4");
for(int i = place_holder.Count-1; i>= 0; i--){
if(i>= place_holder.Count){
i = place_holder.Count-1;
}
// some method that removes multiple elements here
}
myList.RemoveAt(i--);
simples;