Inv*_*ion 418 c# generics loops list key-value
我正在寻找一个更好的模式来处理每个需要处理的元素列表,然后根据结果从列表中删除.
你不能.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
.
有一种优雅的方式来做到这一点?
Ahm*_*eed 682
使用for循环反向迭代列表:
for (int i = safePendingList.Count - 1; i >= 0; i--)
{
// some code
// safePendingList.RemoveAt(i);
}
Run Code Online (Sandbox Code Playgroud)
例:
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));
Run Code Online (Sandbox Code Playgroud)
或者,您可以将RemoveAll方法与谓词一起使用来测试:
safePendingList.RemoveAll(item => item.Value == someValue);
Run Code Online (Sandbox Code Playgroud)
这是一个简化的例子来演示:
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));
Run Code Online (Sandbox Code Playgroud)
Jan*_*Jan 83
一个简单直接的解决方案:
使用标准for循环向后运行集合并RemoveAt(i)
删除元素.
jed*_*sah 66
当您想要在迭代时从Collection中删除元素时,首先应该想到反向迭代.
幸运的是,有一种比写一个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);
}
}
Run Code Online (Sandbox Code Playgroud)
Gre*_*tle 56
foreach (var item in list.ToList()) {
list.Remove(item);
}
Run Code Online (Sandbox Code Playgroud)
如果向列表中添加".ToList()"(或LINQ查询的结果),则可以直接从"列表"中删除"item",而不会删除可疑的 " Collection被修改;枚举操作可能无法执行".错误.编译器会复制"list",以便您可以安全地对阵列执行删除操作.
虽然这种模式不是非常有效,但它具有自然的感觉,并且对于几乎任何情况都足够灵活.例如,当您想要将每个"项目"保存到数据库并仅在数据库保存成功时将其从列表中删除.
小智 21
在通用列表上使用ToArray()允许您在通用列表上执行删除(项目):
List<String> strings = new List<string>() { "a", "b", "c", "d" };
foreach (string s in strings.ToArray())
{
if (s == "b")
strings.Remove(s);
}
Run Code Online (Sandbox Code Playgroud)
Jul*_*anR 21
选择您的元素都想要,而不是试图消除你的元素不想要的.这比删除元素更容易(并且通常也更有效).
var newSequence = (from el in list
where el.Something || el.AnotherThing < 0
select el);
Run Code Online (Sandbox Code Playgroud)
我想发布这篇评论作为评论,以回应迈克尔·狄龙在下面留下的评论,但它太长了,无论如何都可能在我的答案中有用:
就个人而言,我永远不会一个一个地删除项目,如果你确实需要删除,那么调用RemoveAll
哪个获取谓词并且只重新排列内部数组一次,而Remove
对Array.Copy
你删除的每个元素执行操作.RemoveAll
效率更高.
当你向后迭代列表时,你已经有了要删除的元素的索引,所以调用它会更有效率RemoveAt
,因为Remove
首先遍历列表以找到元素的索引你我试图删除,但你已经知道该索引.
总而言之,我认为没有任何理由要求Remove
for-loop.理想情况下,如果可能的话,使用上面的代码根据需要从列表中流式传输元素,因此根本不需要创建第二个数据结构.
Stu*_*rtQ 18
使用.ToList()将创建列表的副本,如此问题中所述: ToList() - 它是否创建新列表?
通过使用ToList(),您可以从原始列表中删除,因为您实际上正在迭代副本.
foreach (var item in listTracked.ToList()) {
if (DetermineIfRequiresRemoval(item)) {
listTracked.Remove(item)
}
}
Run Code Online (Sandbox Code Playgroud)
Cod*_*aos 12
如果确定要删除哪些项目的函数没有副作用且不改变项目(它是纯函数),则简单有效(线性时间)解决方案是:
list.RemoveAll(condition);
Run Code Online (Sandbox Code Playgroud)
如果有副作用,我会使用类似的东西:
var toRemove = new HashSet<T>();
foreach(var item in items)
{
...
if(condition)
toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);
Run Code Online (Sandbox Code Playgroud)
假设哈希是好的,这仍然是线性时间.但由于hashset,它的内存使用量增加了.
最后,如果你的列表只是一个IList<T>
而不是List<T>
我建议我的答案我怎么能做这个特殊的foreach迭代器?.IList<T>
与许多其他答案的二次运行时相比,这将具有给定典型实现的线性运行时.
Ahm*_*mad 11
任何删除都是在您可以使用的条件下进行的
list.RemoveAll(item => item.Value == someValue);
Run Code Online (Sandbox Code Playgroud)
List<T> TheList = new List<T>();
TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));
Run Code Online (Sandbox Code Playgroud)
For 循环对此来说是一个糟糕的构造。
while
var numbers = new List<int>(Enumerable.Range(1, 3));
while (numbers.Count > 0)
{
numbers.RemoveAt(0);
}
Run Code Online (Sandbox Code Playgroud)
但是,如果你绝对必须使用for
var numbers = new List<int>(Enumerable.Range(1, 3));
for (; numbers.Count > 0;)
{
numbers.RemoveAt(0);
}
Run Code Online (Sandbox Code Playgroud)
或这个:
public static class Extensions
{
public static IList<T> Remove<T>(
this IList<T> numbers,
Func<T, bool> predicate)
{
numbers.ForEachBackwards(predicate, (n, index) => numbers.RemoveAt(index));
return numbers;
}
public static void ForEachBackwards<T>(
this IList<T> numbers,
Func<T, bool> predicate,
Action<T, int> action)
{
for (var i = numbers.Count - 1; i >= 0; i--)
{
if (predicate(numbers[i]))
{
action(numbers[i], i);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
var numbers = new List<int>(Enumerable.Range(1, 10)).Remove((n) => n > 5);
Run Code Online (Sandbox Code Playgroud)
然而,LINQ 已经必须RemoveAll()
这样做
var numbers = new List<int>(Enumerable.Range(1, 3));
while (numbers.Count > 0)
{
numbers.RemoveAt(0);
}
Run Code Online (Sandbox Code Playgroud)
最后,您可能最好使用 LINQWhere()
来过滤和创建新列表,而不是改变现有列表。不变性通常是好的。
var numbers = new List<int>(Enumerable.Range(1, 10))
.Where((n) => n <= 5)
.ToList();
Run Code Online (Sandbox Code Playgroud)
您不能使用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--);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,通常所有这些技术都依赖于迭代的集合的行为.此处显示的技术将与标准List(T)一起使用.(很有可能编写自己的集合类和迭代器,它允许在foreach循环期间删除项目.)
在迭代列表时从列表中删除项目的最佳方法是使用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;
});
Run Code Online (Sandbox Code Playgroud)
我会从 LINQ 查询中重新分配列表,该查询会过滤掉您不想保留的元素。
list = list.Where(item => ...).ToList();
Run Code Online (Sandbox Code Playgroud)
除非列表非常大,否则执行此操作不会出现明显的性能问题。
有意地遍历该列表时,在列表上使用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);
Run Code Online (Sandbox Code Playgroud)