Linq Orderby 与自身进行比较。为啥?

Posted

技术标签:

【中文标题】Linq Orderby 与自身进行比较。为啥?【英文标题】:Linq Orderby compares with itself. Why?Linq Orderby 与自身进行比较。为什么? 【发布时间】:2021-08-07 14:35:13 【问题描述】:

总结:在将 Linq OrderBy 与比较器一起使用时,我看到 OrderBy 将项目与自身进行比较 Compare (x, x),并且我看到它多次比较相同的项目 Comparer (x, y)

为什么 OrderBy Compare (x, x)? 为什么 OrderBy 会多次比较同一商品?

问题描述

如果你有一个(可能是空的)项目序列,并且你想要最大的一个,你可以使用OrderBy(...).FirstOrDefault()

我想,如果您只使用最大的一件,订购数千件商品会浪费处理能力。您可以尝试通过创建某种Max 方法在一个枚举中找到这个最大的元素。

同样,如果您搜索最大的几个元素:为什么要对所有项目进行排序?

我听到有人说,如果你使用 OrderBy 并且只取第一个元素,那么就不是完整的序列。

所以我想创建一个测试程序,在其中使用客户比较器订购客户。要查看哪些客户是比较者,客户比较者会将客户的 Id 写入控制台。

class Customer

    public int Id get; set;
    ...


class CustomerComparer : Comparer<Customer>

    public override int Compare(Customer x, Customer y)
    
        int result = Comparer<int>.Default(x.Id, y.Id);
        Console.WriteLine("Compare 0 - 1 => 2", x.Id, y.Id, result);
        return result;
    

控制台程序

static void Main(string[] args)

    var customers = new[]
    
        new Customer Id = 2,
        new Customer Id = 9,
        new Customer Id = 6,
        new Customer Id = 1,
        new Customer Id = 4,
        new Customer Id = 7,
        new Customer Id = 3,
        new Customer Id = 8,
        new Customer Id = 5,
    ;

    IComparer<Customer> comparer = new CustomerComparer;
    var result = customers.OrderBy(customer => customer, customerComparer).FirstOrDefault();

如果我运行程序,我会得到以下输出:

Compare 4 - 2 => 1
Compare 4 - 9 => -1
Compare 4 - 5 => -1
Compare 4 - 8 => -1
Compare 4 - 3 => 1
Compare 4 - 6 => -1
Compare 4 - 7 => -1
Compare 4 - 4 => 0
Compare 4 - 1 => 1
Compare 4 - 6 => -1
Compare 4 - 1 => 1
Compare 3 - 2 => 1
Compare 3 - 3 => 0
Compare 3 - 1 => 1
Compare 3 - 4 => -1
Compare 3 - 4 => -1
Compare 3 - 1 => 1
Compare 2 - 2 => 0
Compare 2 - 1 => 1
Compare 4 - 4 => 0
Compare 4 - 3 => 1
Compare 9 - 6 => 1
Compare 9 - 7 => 1
Compare 9 - 9 => 0
Compare 9 - 5 => 1
Compare 9 - 8 => 1
Compare 9 - 9 => 0
Compare 9 - 8 => 1
Compare 7 - 6 => 1
Compare 7 - 7 => 0
Compare 7 - 8 => -1
Compare 7 - 5 => 1
Compare 6 - 6 => 0
Compare 6 - 5 => 1
Compare 7 - 7 => 0
Compare 7 - 8 => -1
Compare 7 - 7 => 0

我看到了一些奇怪的东西:

客户 [4] 与其自身进行了多次比较。这也适用于客户 [7] 和 [6],但不适用于客户 [8] 和 [1] 将客户 [4] 与客户 [6] 进行比较,经过几次比较后,再次将客户 [4] 与客户 [6] 进行比较。 客户 [3] 和 [4] 被比较两次,中间没有任何其他比较。 双重比较也适用于客户 [4] 和 [1],稍后适用于 [4] 和 [3],但不适用于其他客户

为什么这是一种高效的排序算法?

【问题讨论】:

对于基于比较的排序算法,比较通常被认为是廉价的(与重新排列元素相反)。当然可以清除冗余比较或缓存它们(以空间换时间),但这通常会降低性能。很少有排序算法能保证最小必要的比较次数,而不是一个数量级。 【参考方案1】:

正如 Jeroen Mostert 所提到的,它可能将元素与自身进行比较以使算法更简单,并且在某些情况下,简单性可以提高性能。我希望排序算法得到相当好的优化,所以我不会担心一些额外的比较。另请注意,Orderby 保证是稳定的,这可能会对算法施加额外的限制。

为了解决返回最大值的问题,我建议创建您自己的实现来迭代集合并返回最小/最大。这是相当微不足道的。或者使用类似MoreLinq MaxBy / MinBy

我听说有人说,如果你使用 OrderBy 并且只取第一个元素,那么就不是完整的序列。

OrderBy 的内部运作没有记录。理论上,运行时可以检查整个 linq 调用序列并生成最佳代码。 编辑:

在 .Net core 3.x 及更高版本中似乎确实将其优化为 O(n)(感谢 Matthew Watson 指出这一点)。 在 .Net 框架中,它看起来会创建一个 EnumerableSorter,最终对整个事情进行快速排序,大概是一个稳定的变体。即O(n log n) 在实体框架中,查询应转换为 SQL 并通过查询优化器运行,可能会导致 O(n)(或更好的)运行时。

【讨论】:

.Net Core 3.x 及更高版本肯定会进行优化,如果您只采用第一项,则它是 O(N) 而不是 O(Log(N))(但仅适用于 Linq-to-objects) .但正如你所说,这没有记录,所以你不能依赖它! @Matthew Watson,感谢您提供的信息,这对我来说是个新闻。

以上是关于Linq Orderby 与自身进行比较。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

使用二级排序对 linq 查询进行排序 [重复]

如何构建与通用对象进行比较的 Linq 表达式树?

mvc为啥倒序不了啊?

使用 LINQ 在 OrderBy 中自定义排序逻辑

OrderBy 将 Dynamic Linq 与 Relation 结合使用

如何通过 orderBy 对嵌套的 Linq 对象进行排序