为什么没有IOrderedEnumerable重新实现.Contains()来获得性能

时间:2016-01-25 17:09:13

标签: c# ienumerable contains iorderedenumerable

如果你到这里:The IOrderedEnumerableDocs然后点击.Contains()方法然后它会带你到这里:the generalised Enumerable.Contains() docs

我认为这意味着它只是使用底层的IEnumerable实现?

考虑到你知道你有一个可以与你的元素进行比较的排序列表(例如,进行二元搜索以确认元素是否存在,而不是枚举整体),这看起来有可能进行更高效的搜索设置?

我错过了什么吗?

3 个答案:

答案 0 :(得分:4)

从一开始就值得注意的是,给定方法仅记录为在IEnumerable<T>上运行的事实并不意味着它没有针对给定的实现或派生接口进行优化。事实上,Enumerable中的许多方法为不同的派生接口和/或具体实现采用不同的路径。这里的经典示例是Count()如果在实现IEnumerable<T>ICollection<T>上调用ICollection,则采用不同的路径。在完整的框架中还有其他几个例子,在.NET Core中甚至更多,包括一些为调用IOrderedEnumerable<T>得到的OrderBy()实现的优化路径。

其中一些是我的工作,因为我的爱好这些天对.NET Core有贡献,特别是对Linq,特别是性能提升(尽管很明显,如果我正在攻击某些东西,我需要增加对I'的测试触摸,当这样做时会出现小错误,他们会优先考虑性能改进)。

说到IOrderedEnumerable,我已经完成了从O(n log n)时间更改.OrderBy(someLambda).Skip(j).Take(k)(常见的分页习惯用法)到计算和O(j + k)枚举的时间到O(n + k log k)计算时间和O(k)枚举时间,O {n)空间.OrderBy(someLambda).First()和O(n log n)时间到O(1)空间和O( n)时间,等等。

我可能会考虑改进其他方法,当然如果我不这样做,很可能别人会这样做。

如果我这样做,我会按照你的建议去做。

首先,为IOrderedEnumerable<T>设置一个单独的重载需要向公共API添加一个方法,但只涵盖一些情况(也许我们作为IEnumerable<T>给出的实际上是{{} 1}})。更好的是只有IOrderedEnumerable<T>的重载并检测IEnumerable<T>情况。

其次要使用二进制搜索,我们必须知道IOrderedEnumerable<T>的排序方式。通过IOrderedEnumerable调用创建的OrderedEnumerable<TElement, TKey>可以实现这一点,但不是更普遍。

第三,它不会是最大的收益。

OrderBy的当前费用如下:

  1. 缓冲区source.OrderBy(someLambda).Contains(someItem):O(n)空间,O(n)时间。
  2. 对缓冲区进行排序:O(n log n)时间(平均值,O(n²)更差)。
  3. 找到与source匹配的项目,或确认不存在:O(n)时间。
  4. 如果someItem被优化使用二进制搜索,它将变为:

    1. 缓冲区Contains():O(n)空间,O(n)时间。
    2. 对缓冲区进行排序:O(n log n)时间(平均值,O(n²)更差)。
    3. 找到与source匹配的项目,或确认不存在:O(log n)时间(平均值,O(n)更差,因为精确匹配可能与所有元素排序在同一级别并具有与所有人进行比较)。
    4. 但是,这完全是浪费。如果我们想要优化someItem(以及许多其他聚合方法),最佳策略是:

      1. 致电Contains()并返回结果。这将更糟糕的是O(n)时间和O(1)空间,但如果source.Contains(someItem)例如是source,则可能是O(log n)或O(1)时间(一个案例) HashSet<T>已经优化了)。在理论和实践中,它最终都会比上面的缓冲步骤更快。
      2. 实施这一改变将大大减少工作量,并获得更大的收益。

        我考虑过这个问题,可能确实会提交这样一个公关,但我还不确定它是否值得(因此,如果其他人提交这样的公关,我的意见是什么),因为它几乎总是如此调用者可以更轻松地将Contains()转换为….OrderBy(foo).Contains(bar)本身,并且针对此类案例进行优化所需的检查会很便宜,但并非完全免费。

答案 1 :(得分:3)

为了能够使用二进制搜索,您需要某种排序数据结构。也许是排序数组或SortedList。但是你只有IOrderedEnumerable实施;查询尚未实现。

使用Iterator块或一些惰性查询(通常是生成它的方式)可以简单地创建IOrderedEnumerable。那里没有真正的数据结构。你无法获得IOrderedEnumerableIEnumerable中的所有元素而不枚举它们是O(n)。

所以你无法实现二进制搜索或类似的东西。

答案 2 :(得分:0)

所以基于@Sriram的答案,但充实了具体问题:

这里的根本问题是因为你只有一个生成规则,而不是一个实例化的数据集,那么为了做二进制搜索的任何变化,你首先必须生成所有元素直到你的上限,所以你已经超越了你的目标元素。那么最好抓住它。

如果您的对象真的很难比较但很容易生成,那么您可能会获得更好的性能(即有效地实例化整个集合然后进行二分查找,因此比依次比较每个元素进行更少的比较)。但是你这样做是以更常见的情况为代价的。无论如何,你可以通过调用.ToArray()并将THAT的结果传递给你的二进制搜索算法来实现。