迭代列表并从中删除项目的最佳方法? [关闭]
Posted
技术标签:
【中文标题】迭代列表并从中删除项目的最佳方法? [关闭]【英文标题】:Best way to iterate over a list and remove items from it? [closed] 【发布时间】:2016-08-09 23:05:08 【问题描述】:我需要遍历 List<myObject>
并删除满足特定条件的项目。
我看到了这个答案 (https://***.com/a/1582317/5077434):
使用 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));
但我知道for
的效率不如foreach
,
所以我想到了使用后者如下:
foreach (var item in myList.ToList())
// if certain condition applies:
myList.Remove(item)
一种方法比另一种更好吗?
编辑:
我不想使用RemoveAll(...)
,因为在条件之前的循环中有大量代码。
【问题讨论】:
“更好”是什么?表现?记忆?易读性?for
的效率不低于foreach
。而不是循环,您可以使用RemoveAll
,它具有更好的性能:msdn.microsoft.com/en-us/library/wdka673a%28v=vs.110%29.aspx
你不能从你在 foreach 中使用的同一个列表中删除项目,你会得到一个异常 (System.InvalidOperationException)。
LINQ中没有RemoveAll()
这样的东西。
@poke 对于数组,这肯定不是真的,foreach
已高度优化。
【参考方案1】:
你必须循环遍历列表,for
循环是最有效的循环:
for (int i = safePendingList.Count - 1; i >= 0; --i)
if (condition)
safePendingList.RemoveAt(i);
如果要在范围中删除(不在整个列表中),只需修改for循环:
// No Enumarable.Range(1, 10) - put them into "for"
for (int i = Math.Min(11, safePendingList.Count - 1); i >= 1; --i)
if (condition)
safePendingList.RemoveAt(i);
或者如果您必须删除 前向循环中的项目:
for (int i = 0; i < safePendingList.Count;) // notice ++i abscence
if (condition)
safePendingList.RemoveAt(i);
else
i += 1; // ++i should be here
相反,safePendingList.ToList()
创建了初始safePendingList
的副本,这意味着内存 和CPU 开销 em>:
// safePendingList.ToList() - CPU and Memory overhead (copying)
foreach (var item in safePendingList.ToList())
if (condition)
myList.Remove(item); // Overhead: searching
然而,在很多情况下,最合理的计划就是让.Net为你工作:
safePendingList.RemoveAll(item => condition);
【讨论】:
要记住的一点是,使用RemoveAt()
的效率远低于RemoveAll()
,因为它会不断将索引处的所有元素打乱排列。
safePendingList.RemoveAll(item => condition);
最接近高阶 filter
y 函数
除了简短易读之外,RemoveAll
的主要优点是它需要线性时间,无论需要删除多少或哪些项目。基于RemoveAt
的解决方案可能需要二次时间,因为需要重复移动列表的大部分部分。
你把我的答案吸进了你的:-\【参考方案2】:
要删除具有特定条件的项目,您可以使用
list.RemoveAll(item => item > 5);
而不是
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);
【讨论】:
myList.Where(x => x > 5)
有什么问题?
@arrowd - 要获得相同的结果,它必须是 myList = myList.Where(x => x <= 5).ToList();
我不想使用 LINQ,因为条件很复杂,并且随着循环的进行而建立
@arrowd 制作列表的新副本省略某些项目与从现有列表中删除项目不同。
@arrowd 他说要使用RemoveAll()
,而不是RemoveAt()
【参考方案3】:
这两种方法都没有显示出显着差异。 for
和 RemoveAll
看起来要快一些(更有可能是因为使用 Remove
您正在复制列表并且它需要重新创建它......使用值类型,这也意味着所需的内存增加了一倍)。我做了一些分析:
public static void MethodA()
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);
public static void MethodB()
var list = new List<int>(Enumerable.Range(1, 10));
foreach (var item in list.ToList())
if (item > 5)
list.Remove(item);
public static void MethodC()
var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(x => x > 5);
public static void Measure(string meas, Action act)
GC.Collect();
GC.WaitForPendingFinalizers();
var st = new Stopwatch();
st.Start();
for (var i = 0; i < 10000000; i++)
act();
st.Stop();
Console.WriteLine($"meas: st.Elapsed");
static void Main(string[] args)
Measure("A", MethodA);
Measure("B", MethodB);
Measure("C", MethodC);
结果:
答:00:00:04.1179163
B: 00:00:07.7417853
C: 00:00:04.3525255
其他运行会给出更相似的结果(请注意,这是针对 10 项列表中的 1000 万次迭代[因此进行 1 亿次操作]...大约 3 秒的差异并不是我所说的“显着” ,但您的里程可能会有所不同)
所以选择你认为可以更好阅读的内容(我个人会使用RemoveAll
方法)
注意:随着列表的增加(这只是 10 项),MethodB
(与列表重复的 foreach
)会变慢并且差异会更大,因为清单的重复成本会更高。例如,列出 1,000 个项目(条件为 500 而不是 5)和 100,000 次迭代,使MethodB
为 34 秒,而其他两个项目在我的机器上运行时间都低于 2 秒。所以是的,绝对不要使用MethodB
:-)
这并不意味着foreach
的效率低于for
,它只是意味着使用foreach
从列表中删除项目,您需要复制所述列表,这很昂贵。
这个其他方法(使用foreach
)应该和for
或RemoveAll
方法一样快(在我的分析中它有点慢 - 因为它分配了一个新列表,我想 - 但很漂亮可以忽略不计):
public static void MethodD()
var originalList = new List<int>(Enumerable.Range(1, 1000));
var list = new List<int>(originalList.Count);
foreach (var item in originalList)
if (item <= 500)
list.Add(item);
list.Capacity = list.Count;
你只需要反转条件。
我不是建议这种方法,只是证明foreach
不一定比for
慢
【讨论】:
对,如果你想看看哪匹马更快……通常最好的策略是让 .Net 完成它的工作 -RemoveAll
,+1
@DmitryBychenko 我从理论上期待这个结果,但是是的,没有什么比赛马更有趣 :-)
我毫不怀疑你知道,但由于在问题,确保相反为真的最好方法是让马跑。
10 个元素的列表对于这种测试来说太小了,因为内存分配等开销会扭曲结果。尝试一百万个元素的列表(删除所有索引小于 500,000 的项目)
MethodA
和MethodB
之间的性能差异很可能是由于使用Remove
而不是RemoveAt
。 Remove
必须在整个列表中搜索有问题的项目,而使用 RemoveAt
,你要告诉它在哪里可以找到它。【参考方案4】:
问题是foreach
在修改集合时无法使用集合,这将导致InvalidOperationException
。
在foreach
中使用.ToList()
可以避免这种情况,但是这会复制整个集合,这会导致在集合中循环两次(第一次是复制所有元素,第二次是过滤它)。
for
循环是更好的选择,因为我们可以通过一次收集。除此之外,您还可以使用 LINQ
Where()
方法过滤集合,例如:
var myFilteredCollection = myList.Where(i => i <= 5).ToList();
如果您的条件很复杂,您可以将此逻辑移至单独的方法:
private bool IsItemOk(myObject item)
... your complicated logic.
return true;
并在您的 LINQ 查询中使用它:
var myFilteredCollection = myList.Where(i => IsItemOk(i)).ToList();
其他选择是反之亦然。创建新集合并根据条件添加元素。比你可以使用foreach
循环:
var newList = new List<MyObject>(myList.Count);
foreach (var item in myList)
// if certain condition does not apply:
newList.Add(item);
附言
但正如已经提到的.RemoveAll()
更好,因为这样您就不必“重新发明***”
【讨论】:
【参考方案5】:另一种方法是 foreach 列表,将项目设置为 null,然后将 linq 删除为 (item => null),如前所述,您可以选择更适合您的方式
【讨论】:
以上是关于迭代列表并从中删除项目的最佳方法? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章