C# 与 F# 中的默认排序
Posted
技术标签:
【中文标题】C# 与 F# 中的默认排序【英文标题】:Default ordering in C# vs. F# 【发布时间】:2015-09-09 00:43:20 【问题描述】:考虑分别对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 类型?编辑
为了响应几个探测 cmets,运行下面的片段可以更多地了解这种排序差异的确切性质:
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#:电视,电视,电视,电视,紫外线,紫外线,紫外线,紫外线 F#:电视、电视、紫外线、紫外线、电视、电视、紫外线、紫外线字符串的字典顺序不同,因为字符的基本顺序不同:
C#:“aAbBcCdD...tTuUvV...” F#:“ABC..TUV..Zabc..tuv..”【问题讨论】:
如果你这样做,似乎工作“很好”(fun s -> s.ToLower())
这很奇怪......我真的希望这在 F# 中可以正常工作。如果你有以不同字母开头的单词,它们如何在 F# 中显示?
感谢@leppie - 这是“字符串大小写”的一个很好的解决方案。我的实际代码更通用 - 依赖于比较实现。如果我知道这是异常情况,将字符串作为特殊情况处理是可以接受的 - 但我怀疑发生的事情比我理解的要多......
可以用 [| 《茶与咖啡》; “电话”; "Tv" |] |> Array.sort 必须是大写
根据this questionF#的sortBy
方法没有使用Linq的OrderBy()
。
【参考方案1】:
不同的库对字符串的默认比较操作做出不同的选择。 F# 严格默认区分大小写,而 LINQ to Objects 不区分大小写。
List.sortWith
和 Array.sortWith
都允许指定比较。 Enumerable.OrderBy
的重载也是如此。
但是Seq
模块似乎没有等效项(并且没有在 4.6 中添加)。
具体问题:
排序逻辑上的差异是否存在根本原因?
两个命令都有效。在英语的情况下,不敏感似乎更自然,因为这是我们习惯的。但这并不能使它更正确。
在我的情况下,克服这个“问题”的推荐方法是什么?
明确说明比较的类型。
这种现象是字符串特有的,还是也适用于其他 .NET 类型?
char
也会受到影响。以及有多个可能订购的任何其他类型(例如 People
类型:您可以根据具体要求按姓名或出生日期订购)。
【讨论】:
这些是有用的 cmets - 谢谢。我的理解(来自this page)是,如果一个类型实现了IComparable
,那么它用于进行排序。如果我明确使用此接口为 C# 版本实现IComparer<string>
,问题仍然存在。这表明我对 F# 如何进行比较(即使是 IComparable
)的理解是错误的。如果有可能阐明 F# 如何选择进行比较,那将非常有趣..
@Lawrence [Seq.sortBy
](msdn.microsoft.com/en-us/library/ee353610.aspx) 的文档说它使用了Operators.compare
,但该页面上没有详细信息。是时候阅读语言规范或源代码了……
> 但是 Seq 模块似乎没有等效项(并且在 4.6 中没有添加)。Seq.sortWith 确实已添加到 F# 4.0 中。
请注意,F# 默认使用 ordinal 比较,这比仅 不区分大小写 更严格。 char
与所有大型 .NET 语言的序数比较进行比较,因此与 string
不同,F# 用户至少不会感到“惊讶”。
@latkin 我没有检查Seq
是否有未来版本的 F#。 WRT 序数:.NET 还支持序数不区分大小写的比较,因此序数与区分大小写有些正交,并迅速进入所需的大量背景细节。【参考方案2】:
感谢@Richard 和his answers 为我指明了进一步理解这个问题的方向
我的问题似乎源于没有完全理解 F# 中 comparison
约束的后果。这是Seq.sortBy
的签名
Seq.sortBy : ('T -> 'Key) -> seq<'T> -> seq<'T> (requires comparison)
我的假设是,如果'T
类型实现了IComparable
,那么它将用于排序。我应该先咨询这个问题:F# comparison vs C# IComparable,其中包含一些有用的参考资料,但需要进一步仔细阅读才能充分了解正在发生的事情。
所以,尝试回答我自己的问题:
排序逻辑上的差异是否存在根本原因?
是的。 C#版本似乎使用了IComparable
的字符串实现,而F#版本没有。
在我的情况下,克服这个“问题”的推荐方法是什么?
虽然我无法评论这是否是“推荐的”,但如果相关类型上有一个 IComparable
的实现,下面的 F# 函数 order
将使用:
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
“优先化”。
【讨论】:
在您的代码中,您要排序两次:首先是Seq.sortBy
,然后是Array.sortWith
。【参考方案3】:
这与 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
使用当前文化(see 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
方法“使用默认比较器 Default 比较键。”
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"]
故事的寓意:如果您关心排序,请始终指定要使用的具体比较!
【讨论】:
【参考方案4】:请参阅language spec 的第 8.15.6 节。
字符串、数组和本机整数具有特殊的比较语义,如果实现了其他所有内容,则只需转到 IComparable
(对产生相同结果的各种优化取模)。
特别是,F# 字符串默认使用 ordinal 比较,而大多数 .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
编辑:
请注意,声明“F# 使用 区分大小写 字符串比较”是一种误导(尽管并非不正确)。 F# 使用 ordinal 比较,比区分大小写更严格。
// 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
【讨论】:
谢谢@latkin - 这正是我一直在寻找的好答案 - 真的很有帮助!以上是关于C# 与 F# 中的默认排序的主要内容,如果未能解决你的问题,请参考以下文章