考虑两个代码片段,它们只分别在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
这两段代码会返回不同的结果:
在我的具体情况下,我需要关联这两种语言之间的排序逻辑(一种是生产代码,一种是测试断言的一部分)。这提出了几个问题:
修改
在回答几条探测性评论时,运行下面的片段可以更多地了解这种排序差异的确切性质:
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();
给出:
字符串的字典顺序不同,因为字符的基本顺序不同:
答案 0 :(得分:6)
不同的库对字符串进行默认比较操作的不同选择。 F#严格默认为区分大小写,而LINQ to Objects不区分大小写。
List.sortWith
和Array.sortWith
都允许指定比较。和Enumerable.OrderBy
的重载一样。
然而,Seq
模块似乎没有等效物(并且在4.6中没有添加一个)。
针对具体问题:
订购逻辑存在差异的潜在原因吗?
两种排序均有效。在英语中,不敏感似乎更自然,因为这就是我们习惯的。但这并没有使它更正确。
在我的情况下,克服这个“问题”的推荐方法是什么?
明确比较的类型。
这种现象是否特定于字符串,还是也适用于其他.NET类型?
char
也会受到影响。以及有多种可能排序的任何其他类型(例如People
类型:您可以根据具体要求按名称或出生日期订购。)
答案 1 :(得分:6)
参见language spec的第8.15.6节。
字符串,数组和本机整数具有特殊的比较语义,如果实现的话,其他所有内容都会转到IComparable
(模数会产生相同结果的各种优化)。
特别是,默认情况下,F#字符串使用序数比较,而大多数.NET默认使用文化感知比较。
这显然是F#与其他.NET语言之间令人困惑的不兼容性,但它确实有一些好处:
Comparer<string>.Default.Compare("a", "A") // -1
Comparer<char>.Default.Compare('a', 'A') // 32
compare "a" "A" // 1
compare 'a' 'A' // 32
修改强>
请注意,说明“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
答案 2 :(得分:4)
这与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
方法&#34;使用默认比较器Default来校正密钥。&#34;
默认情况下,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"]
故事的道德:如果您关心排序,请始终指定要使用的具体比较!
答案 3 :(得分: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#版本则没有。
建议的方法是什么来解决这个问题&#34;在我的情况下?
虽然我无法评论是否&#34;推荐&#34;,如果相关类型中有一个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
的实现:
comparison
这种现象是否特定于字符串,还是也适用于其他.NET类型?
IComparable
约束与IComparable
接口之间的关系显然存在一些细微之处。为了安全起见,我将遵循@ Richard的建议并始终明确比较的类型 - 可能使用上述功能来优先考虑&#34;&#34;在排序中使用gapi.auth.authorize
。