LINQ中的“RemoveAll”如何比迭代快得多?

时间:2015-09-02 11:08:07

标签: c# performance linq

以下代码:

List<Interval> intervals = new List<Interval>();
List<int> points = new List<int>();

//Initialization of the two lists
// [...]

foreach (var point in points)
{
    intervals.RemoveAll (x => x.Intersects (point));
}
当列表大小为~10000时,

比此快至少100倍:

List<Interval> intervals = new List<Interval>();
List<int> points = new List<int>();

//Initialization of the two lists
// [...]

foreach (var point in points)
{
    for (int i = 0; i < intervals.Count;)
    {
        if (intervals[i].Intersects(point))
        {
            intervals.Remove(intervals[i]);
        }
        else
        {
            i++;
        }
    }
}

怎么可能?使用“RemoveAll”在引擎盖下执行什么操作?根据{{​​3}},“RemoveAll”执行线性搜索,因此在O(n)中。所以我希望两者都有类似的表现。

当用“RemoveAt”替换“Remove”时,迭代速度要快得多,与“RemoveAll”相当。但两者“删除”和“删除”都有O(n)复杂性,那么为什么它们之间的性能差异如此之大?难道只是因为“删除(项目)”将列表元素与“项目”进行比较,“RemoveAt”不执行任何比较?

4 个答案:

答案 0 :(得分:25)

如果您从List<T>中移除某个项目,则其后的所有项目都将移回一个位置。因此,如果删除n个项目,很多项目将被移动n次 RemoveAll只会移动一次,您可以在List<T>的来源中看到:source

另一件事是Remove(T item)将在整个列表中搜索该项目,因此这是另外n次操作。

与你的问题无关的东西,但无论如何我想指出:
如果你使用for循环从List中删除项目,那么从最后开始就容易得多:

for (int i = intervals.Count - 1; i >= 0; i--)
{
    if (intervals[i].Intersects(point))
    {
        intervals.RemoveAt(i);
    }
}

这样,你不需要那个丑陋的else子句

答案 1 :(得分:9)

通过检查RemoveAll元素的条件并移动最多O(n)元素,可以在n中完成

n

您的循环为O(n^2),因为每个Remove都需要检查n个元素。即使您将其更改为RemoveAt,它仍然需要升级到n元素。

这可能是最快的解决方案: intervals.RemoveAll(x => points.Any(x.Intersects));

答案 2 :(得分:3)

List是一个数组,从数组中删除一个元素需要将所有元素移到您要删除的元素之后,因此a[i]会移到a[i-1]

重复执行此操作需要多次移动,即使有更多元素符合删除条件。 RemoveAll可以通过在遍历列表时一次移动元素超过1个索引并找到更多符合删除条件的元素来优化此项。

答案 3 :(得分:0)

区别在于Remove本身是一个O(n),所以得到O(n ^ 2)。

for替换为新的集合和作业。

items = items.Where(i => ...).ToList();

此方法与RemoveAll具有相同的算法时间复杂度,但使用额外的O(n)内存。