使用LINQ OrderBy
和ThenBy
考虑以下简单代码:
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
。因此,除非算法只是想检查我们是否正确实现了比较器(无论如何它都无法完全执行),发生了什么?
请注意:我的示例中的查询所产生的元素中没有重复项。
我在另一个示例中看到了同样的问题,从查询中产生了更多条目。上面我举一个小例子。这种情况发生在甚至数量的元素产生的情况下。
答案 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)
好的,让我们看看这里的可能性:
T
是值类型
为了检查它是否将项目与自身进行比较,首先需要检查两个项目是否相同。你会怎么做?
如果商品不相同,您可以先调用Equals
然后调用CompareTo
。你真的想这么做吗? Equals
的成本与比较大致相同,所以你实际上要将订购成本加倍,究竟是什么? OrderBy
只是比较所有项目,句号。
T
是参考类型
c#不允许您仅使用泛型约束进行重载,因此如果T
是引用类型,则需要在运行时检查,然后调用将改变上述行为的特定实现。在每种情况下,您是否想要支付这笔费用?当然不是。
如果比较费用昂贵,那么在比较逻辑中实施参考优化,以避免在将项目与自身进行比较时产生愚蠢的成本,但该选择必须属于您自己。我很确定string.CompareTo
正是这样做的。
我希望这会让我的回答更清楚,对于之前的简短回答感到抱歉,我的推理并不是那么明显。
答案 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然后它将进行比较,因此它将采用那个并输出所需的。希望它能清除你的怀疑:))