为什么使用Linq-to-Objects进行订购会将项目与自己进行比较?

时间:2017-08-29 07:39:07

标签: c# algorithm linq sorting base-class-library

使用LINQ OrderByThenBy考虑以下简单代码:

static void Main()
{
  var arr1 = new[] { "Alpha", "Bravo", "Charlie", };

  var coStr = Comparer<string>.Create((x, y) =>
  {
    Console.WriteLine($"Strings: {x} versus {y}");
    return string.CompareOrdinal(x, y);
  });

  arr1.OrderBy(x => x, coStr).ToList();

  Console.WriteLine("--");

  var arr2 = new[]
  {
    new { P = "Alpha", Q = 7, },
    new { P = "Bravo", Q = 9, },
    new { P = "Charlie", Q = 13, },
  };

  var coInt = Comparer<int>.Create((x, y) =>
  {
    Console.WriteLine($"Ints: {x} versus {y}");
    return x.CompareTo(y);
  });

  arr2.OrderBy(x => x.P, coStr).ThenBy(x => x.Q, coInt).ToList();
}

这只是使用一些比较器向控制台写出他们比较的内容。

在我的硬件和Framework版本(.NET 4.6.2)上,这是输出:

Strings: Bravo versus Alpha
Strings: Bravo versus Bravo
Strings: Bravo versus Charlie
Strings: Bravo versus Bravo
--
Strings: Bravo versus Alpha
Strings: Bravo versus Bravo
Ints: 9 versus 9
Strings: Bravo versus Charlie
Strings: Bravo versus Bravo
Ints: 9 versus 9

我的问题是:为什么他们会将查询中的项目与自身进行比较?

在第一种情况下,在--分隔符之前,它们进行四次比较。其中两个比较了一个条目(“Strings:Bravo vs. Bravo”)。为什么呢?

在第二种情况下,不应该求助于比较Q属性(整数);因为P值中没有重复项(wrt。序数比较),所以不应该需要ThenBy的平局。我们仍然看到“Ints:9对9”两次。为什么使用具有相同参数的ThenBy比较器?

注意:任何比较器在比较某些东西时都必须返回0。因此,除非算法只是想检查我们是否正确实现了比较器(无论如何它都无法完全执行),发生了什么?

请注意:我的示例中的查询所产生的元素中没有重复项。

我在另一个示例中看到了同样的问题,从查询中产生了更多条目。上面我举一个小例子。这种情况发生在甚至数量的元素产生的情况下。

4 个答案:

答案 0 :(得分:2)

QuickSort使用的OrderBy方法的reference source中,您可以看到以下两行:

while (i < map.Length && CompareKeys(x, map[i]) > 0) i++;
while (j >= 0 && CompareKeys(x, map[j]) < 0) j--;

这些while循环一直运行,直到找到一个不再比x指向的“更大”(分别为“更少”)的元素。因此,当比较相同的元素时,它们会中断。

我不能证明它是数学的,但我想避免比较相同的元素会使算法更复杂,并且引入的开销会影响性能,而不是单一的比较。
(请注意,您的比较器应该足够聪明,以便快速返回0相同的元素)

答案 1 :(得分:0)

  

在第一种情况下,在 - 分隔符之前,它们进行四次比较。其中两个比较了一个条目(&#34; Strings:Bravo vs. Bravo&#34;)。为什么呢?

效率。当然可以首先检查对象本身是否有问题,但这意味着在每次比较时都要进行额外的操作,以避免出现相对较少的情况,并且在大多数情况下这种情况相当便宜(大多数比较器都是)。这将是净亏损。

(顺便说一句,我确实尝试过对算法进行这样的改变,并且在测量时,通过普通比较(例如默认的int比较器)实际上是效率损失。

  

在第二种情况下,不应该需要求助于比较Q属性(整数);因为在P值中没有重复(wrt。序数比较),因此不需要从ThenBy开始打破平局。我们仍然看到&#34; Ints:9对9&#34;两次。为什么使用具有相同参数的ThenBy比较器?

谁能说没有重复?内部比较给出了两件事(不一定是参考类型,因此参考标识的短路并不总是一个选项)并且有两个规则要遵循。第一条规则需要打破平局,以便完成抢七。

该代码旨在适用于第一次比较可能具有等效值的情况。

如果知道OrderBy的价值不相同,那么对于知道不使用不必要的ThenBy的人来说,因为他们是可能知道这一点的人。

答案 2 :(得分:-1)

好的,让我们看看这里的可能性:

  1. T是值类型

    为了检查它是否将项目与自身进行比较,首先需要检查两个项目是否相同。你会怎么做?

    如果商品不相同,您可以先调用Equals然后调用CompareTo。你真的想这么做吗? Equals的成本与比较大致相同,所以你实际上要将订购成本加倍,究竟是什么? OrderBy只是比较所有项目,句号。

  2. T是参考类型

    c#不允许您仅使用泛型约束进行重载,因此如果T是引用类型,则需要在运行时检查,然后调用将改变上述行为的特定实现。在每种情况下,您是否想要支付这笔费用?当然不是。

    如果比较费用昂贵,那么在比较逻辑中实施参考优化,以避免在将项目与自身进行比较时产生愚蠢的成本,但该选择必须属于您自己。我很确定string.CompareTo正是这样做的。

  3. 我希望这会让我的回答更清楚,对于之前的简短回答感到抱歉,我的推理并不是那么明显。

答案 3 :(得分:-1)

简单来说,案例1

var coStr = Comparer<string>.Create((x, y) =>
{
    Console.WriteLine($"Strings: {x} versus {y}");
    return string.CompareOrdinal(x, y);
});

我们只是比较元素,如果结果为0则没有条件可以忽略。因此Console.WriteLine条件与比较输出无关。如果您更改下面的代码

var coStr = Comparer<string>.Create((x, y) =>
{
   if (x != y)
      Console.WriteLine($"Strings: {x} versus {y}");
   return string.CompareOrdinal(x, y);
});

您的输出将是

Strings: Bravo versus Alpha
Strings: Bravo versus Charlie

对于第二个语句,我们检查两者的输出是一样的,所以对于字符串比较将返回0然后它将进行比较,因此它将采用那个并输出所需的。希望它能清除你的怀疑:))