如果有顺序的话。并且您只需要有序序列的第一个元素。Orderby 是否足够聪明,不会对完整序列进行排序?
IEnumerable<MyClass> myItems = ...
MyClass maxItem = myItems.OrderBy(item => item.Id).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)
因此,如果询问第一个元素,则仅将具有最小值的项目排序为序列的第一个元素。当询问下一个元素时,对剩余序列中具有最小值的项目进行排序,等等。
或者如果您只想要第一个元素,则完整序列是否完全有序?
添加
显然这个问题不清楚。让我们举个例子。
Sort 函数可以执行以下操作:
代码:
public static IEnumerable<TSource> Sort<TSource, TKey>(
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
IComparer<TKey> comparer = Comparer<TKey>.Default;
// create a linkedList with keyValuePairs of TKey and TSource
var keyValuePairs = source
.Select(source => new KeyValuePair<TKey, TSource>(keySelector(source), source);
var itemsToSort = new LinkedList<KeyValuePair<Tkey, TSource>>(keyValuePairs);
while (itemsToSort.Any())
{ // there are still items in the list
// select the first element as the smallest one
var smallest = itemsToSort.First();
// scan the rest of the linkedList to find the smallest one
foreach (var element in itemsToSort.Skip(1))
{
if (comparer.Compare(element.Key, smallest.Key) < 1)
{ // element.Key is smaller than smallest.Key: element becomes the smallest:
smallest = element;
}
}
// remove the smallest element from the linked list and return the value:
itemsToSort.Remove(smallestElement);
yield return smallestElement.Value;
}
Run Code Online (Sandbox Code Playgroud)
假设我有一个整数序列。
Suppose I have the following sequence of integers:
{4, 8, 3, 1, 7}
Run Code Online (Sandbox Code Playgroud)
在第一次迭代时,迭代器在内部创建一个键/值对的链接列表,并将列表的第一个元素分配为最小的元素
Linked List = 4 - 8 - 3 - 1 - 7
Smallest = 4
Run Code Online (Sandbox Code Playgroud)
链表被扫描一次,看是否有更小的链表。
Linked List = 4 - 8 - 3 - 1 - 7
Smallest = 1
Run Code Online (Sandbox Code Playgroud)
最小的被从链表中删除并返回:
Linked List = 4 - 8 - 3 - 7
return 1
Run Code Online (Sandbox Code Playgroud)
第二次迭代使用较短的链表进行相同的操作
Linked List = 4 - 8 - 3 - 7
smallest = 4
Run Code Online (Sandbox Code Playgroud)
再次扫描链表一次以找到最小的链表
Linked List = 4 - 8 - 3 - 7
smallest = 3
Run Code Online (Sandbox Code Playgroud)
从链表中取出最小的并返回最小的
Linked List = 4 - 8 - 7
return 3
Run Code Online (Sandbox Code Playgroud)
很容易看出,如果您只要求排序列表中的第一个元素,则列表只会被扫描一次。每次迭代,要扫描的列表都会变小。
回到我原来的问题:
据我所知,如果您只想要第一个元素,则必须至少扫描列表一次。如果您不要求第二个元素,则列表的其余部分不会排序。
Enumerable.OrderBy 使用的排序是否如此智能,如果您只要求第一个订购的项目,则不会对列表的其余部分进行排序?
这取决于版本。
\n\n在框架版本(4.0、4.5 等)中:
\n\nFirstOrDefault来根据此映射获取第一项。它要么找到一个,要么(如果由于源为空而缓冲区为空)返回。MoveNextCurrentdefault(TSource)在 .NET Core 中,则:
\n\nFirstOrDefault进行扫描。IOrderedEnumerable如果没有元素,则返回,default(TSource)否则它将保留找到的第一个元素和密钥生成器生成的密钥,并将其与所有后续元素进行比较,如果下一个找到的比较值低于当前值。这意味着在Framework版本中myItems.OrderBy(item => item.Id).FirstOrDefault()是O(n log n)时间复杂度(最坏情况O(n\xc2\xb2))和O(n)空间复杂度,但在.NET Core版本中是O(n)时间复杂度和O(1)空间复杂度。
这里的主要区别在于,.NET CoreFirstOrDefault()知道结果OrderBy(ThenBy等)与其他可能来源的结果有何不同,并具有处理它的代码*,而在框架版本中则不然。
两者都扫描整个序列(你无法知道最后一个元素myItems第一个元素),但在那之后它们的机制和效率有所不同。
\n\n\n当询问下一个元素时,对剩余序列中具有最小值的项目进行排序,等等。
\n
如果询问下一个元素,那么不仅会再次进行任何排序,而且还必须按照myItems进行排序,因为同时
如果你试图使用它来获取它,myItems.OrderBy(item => item.Id).ElementAtOrDefault(i)那么框架版本将通过首先进行排序(O(n log n))然后扫描(O(n)相对于i)来找到元素,而.NET Core版本将通过快速选择来找到它(O(n)尽管常数因子更大)比 forFirstOrDefault()并且可以O(n\xc2\xb2)与排序相同的情况下一样高,所以它比排序慢(由于这个原因O(n)它足够聪明)。两个版本也使用空间复杂度(除非.NET Core可以把它变成ElementAtOrDefault(0)FirstOrDefault()O(n)FirstOrDefault())。
如果您找到前几个值,那么myItems.OrderBy(item => item.Id).Take(k)框架版本将再次执行排序(O(n log n))并对结果的后续枚举进行限制,以便在k获得后停止返回元素。.NET Core 版本会进行部分排序,而不是费心对元素进行排序,因为它意识到这些元素总是出现在所取部分之后,这就是O(n + k log k)时间复杂度。.NET Core 还会对Take和的组合进行单一部分排序Skip进一步减少必要的排序量。
理论上, just 的排序OrderBy(cmp)可能会更懒惰,如下所示:
yield一旦发现它们是下一个要枚举的元素。这将缩短获得第一个结果的时间(获得第一个结果的时间短通常是其他 Linq 操作的一个很好的功能),并且特别有利于那些可能中途停止处理结果的消费者。然而,它给排序操作增加了额外的常量成本,并且要么阻止以减少递归量的方式选择要处理的下一个分区(基于分区的排序的重要优化),要么通常实际上不会产生任何结果,直到无论如何都快结束了(使得练习毫无意义)。这也会使排序变得更加复杂。虽然我尝试了这种方法,但某些情况下的回报并不能证明其他情况下的成本是合理的,特别是因为它似乎可能伤害的人多于受益的人。
\n\n*严格来说,几个 linq 操作的结果知道如何以针对每个操作优化的方式查找第一个元素,并且FirstOrDefault()知道如何检测任何这些情况。
| 归档时间: |
|
| 查看次数: |
656 次 |
| 最近记录: |