C# 与 F# 中的默认排序
本文关键字:默认 排序 | 更新日期: 2023-09-27 17:59:12
考虑两个代码片段,它们分别按C#
和F#
对字符串进行排序:
C#:
var strings = new[] { "Tea and Coffee", "Telephone", "TV" };
var orderedStrings = strings.OrderBy(s => s).ToArray();
F#:
let strings = [| "Tea and Coffee"; "Telephone"; "TV" |]
let orderedStrings =
strings
|> Seq.sortBy (fun s -> s)
|> Seq.toArray
这两个代码片段返回不同的结果:
- C#: 茶和咖啡, 电话, 电视
- F#:电视、茶和咖啡、电话
在我的特定情况下,我需要关联这两种语言之间的排序逻辑(一种是生产代码,另一种是测试断言的一部分(。这就提出了几个问题:
- 排序逻辑的差异是否存在根本原因?
- 在我的情况下,克服这个"问题"的推荐方法是什么?
- 这种现象是特定于字符串,还是也适用于其他 .NET 类型?
编辑
作为对几个探索性评论的回应,运行下面的片段揭示了更多关于这种排序差异的确切性质:
F#:
let strings = [| "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" |]
let orderedStrings =
strings
|> Seq.sortBy (fun s -> s)
|> Seq.toArray
C#:
var strings = new[] { "UV", "Uv", "uv", "uV", "TV", "tV", "Tv", "tv" };
var orderedStrings = strings.OrderBy(s => s).ToArray();
给:
- C#: 电视, 电视,
- 电视, 电视, 紫外线, uV, 紫外线, 紫外线 F#: 电视, 电视, 紫外线, 紫外线,
- 电视, uV, 紫外线
由于字符的基本顺序不同,字符串的字典顺序不同:
- C#: "aAbBcCdD...tTuUvV...">
- F#:"ABC..图..扎布克..图夫..">
请参阅语言规范的第 8.15.6 节。
字符串、数组和本机整数具有特殊的比较语义,其他一切都会IComparable
是否实现(模化产生相同结果的各种优化(。
特别是,F# 字符串默认使用序号比较,而大多数 .NET 默认使用区域性感知比较。
这显然是 F# 和其他 .NET 语言之间令人困惑的不兼容性,但它确实有一些好处:
- OCAML 兼容
- 字符串和字符比较是一致的
- C#
Comparer<string>.Default.Compare("a", "A") // -1
- C#
Comparer<char>.Default.Compare('a', 'A') // 32
- F#
compare "a" "A" // 1
- F#
compare 'a' 'A' // 32
- C#
编辑:
请注意,声明"F# 使用区分大小写的字符串比较"具有误导性(尽管并非不正确(。 F# 使用序号比较,这比区分大小写更严格。
// case-sensitive comparison
StringComparer.InvariantCulture.Compare("[", "A") // -1
StringComparer.InvariantCulture.Compare("[", "a") // -1
// ordinal comparison
// (recall, '[' lands between upper- and lower-case chars in the ASCII table)
compare "[" "A" // 26
compare "[" "a" // -6
不同的库对字符串的默认比较操作做出不同的选择。F# 严格默认区分大小写,而 LINQ to Objects 不区分大小写。
List.sortWith
和Array.sortWith
都允许指定比较。Enumerable.OrderBy
的超载也是如此.
但是,Seq
模块似乎没有等效的模块(并且在 4.6 中没有添加(。
对于具体问题:
排序逻辑的差异是否存在根本原因?
两种排序均有效。在英国的情况下,不敏感似乎更自然,因为这是我们习惯的。但这并不能使它更正确。
在我的情况下,克服这个"问题"的推荐方法是什么?
明确比较的类型。
这种现象是特定于字符串,还是也适用于其他 .NET 类型?
char
也将受到影响。以及有多个可能排序的任何其他类型(例如People
类型:您可以根据具体要求按姓名或出生日期排序(。
C#与F#无关,甚至与IComparable
无关,而只是由于库中不同的排序实现。
TL;DR; 版本是排序字符串可以给出不同的结果:
"tv" < "TV" // false
"tv".CompareTo("TV") // -1 => implies "tv" *is* smaller than "TV"
甚至更清楚:
"a" < "A" // false
"a".CompareTo("A") // -1 => implies "a" is smaller than "A"
这是因为CompareTo
使用当前区域性(请参阅 MSDN(。
我们可以通过一些不同的例子看到这在实践中是如何发挥作用的。
如果我们使用标准的 F# 排序,我们会得到大写优先的结果:
let strings = [ "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" ]
strings |> List.sort
// ["TV"; "Tv"; "UV"; "Uv"; "tV"; "tv"; "uV"; "uv"]
即使我们投射到IComparable
,我们也会得到相同的结果:
strings |> Seq.cast<IComparable> |> Seq.sort |> Seq.toList
// ["TV"; "Tv"; "UV"; "Uv"; "tV"; "tv"; "uV"; "uv"]
另一方面,如果我们使用 F# 中的 Linq,我们会得到与 C# 代码相同的结果:
open System.Linq
strings.OrderBy(fun s -> s).ToArray()
// [|"tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"|]
根据MSDN,OrderBy
方法"使用默认比较器默认值比较键"。
默认情况下,F# 库不使用Comparer
,但我们可以使用sortWith
:
open System.Collections.Generic
let comparer = Comparer<string>.Default
现在,当我们执行此排序时,我们得到的结果与 LINQ OrderBy
相同:
strings |> List.sortWith (fun x y -> comparer.Compare(x,y))
// ["tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"]
或者,我们可以使用内置的 CompareTo
函数,它给出相同的结果:
strings |> List.sortWith (fun x y -> x.CompareTo(y))
// ["tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"]
这个故事的寓意:如果您关心排序,请始终指定要使用的特定比较!
感谢@Richard和他的回答为我指明了理解这个问题的方向
我的问题似乎源于没有完全理解 F# 中comparison
约束的后果。 这是Seq.sortBy
的签名
Seq.sortBy : ('T -> 'Key) -> seq<'T> -> seq<'T> (requires comparison)
我的假设是,如果'T
实现的类型IComparable
那么这将用于排序。 我应该先咨询这个问题:F#比较与C#IComparable,其中包含一些有用的参考,但需要进一步仔细阅读才能充分理解正在发生的事情。
所以,尝试回答我自己的问题:
排序逻辑的差异是否存在根本原因?
是的。C#版本似乎使用字符串的IComparable
实现,而F#版本则没有。
在我的情况下,克服这个"问题"的推荐方法是什么?
虽然我无法评论这是否"推荐",但下面order
的 F# 函数将使用 IComparable
的实现(如果相关类型上有(:
let strings = [| "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" |]
let order<'a when 'a : comparison> (sequence: seq<'a>) =
sequence
|> Seq.toArray
|> Array.sortWith (fun t1 t2 ->
match box t1 with
| :? System.IComparable as c1 -> c1.CompareTo(t2)
| _ ->
match box t2 with
| :? System.IComparable as c2 -> c2.CompareTo(t1)
| _ -> compare t1 t2)
let orderedValues = strings |> order
这种现象是特定于字符串,还是也适用于其他 .NET 类型?
显然,comparison
约束和IComparable
接口之间的关系涉及一些微妙之处。 为了安全起见,我将遵循@Richard的建议,并始终明确比较的类型 - 可能使用上面的函数在排序中使用IComparable
"优先级"。