繁体   English   中英

C#与F#的默认排序

[英]Default ordering in C# vs. F#

考虑只分别在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#:tv,tV,Tv,TV,uv,uV,Uv,UV
  • F#:电视,电视,紫外线,紫外线,电视,电视,紫外线,紫外线

字符串的字典顺序不同,因为字符的基本顺序不同:

  • C#:“aAbBcCdD ... tTuUvV ......”
  • F#:“ABC..TUV..Zabc..tuv ..”

不同的库对字符串进行默认比较操作的不同选择。 F#严格默认为区分大小写,而LINQ to Objects不区分大小写。

List.sortWithArray.sortWith允许指定比较。 Enumerable.OrderBy的重载一样。

然而, Seq模块似乎没有等效物(并且在4.6中没有添加一个)。

针对具体问题:

订购逻辑存在差异的根本原因是什么?

两个订单都有效。 在英语中,不敏感似乎更自然,因为这就是我们习惯的。 但这并没有使它更正确。

在我的情况下,克服这个“问题”的推荐方法是什么?

明确这种比较。

这种现象是否特定于字符串,还是也适用于其他.NET类型?

char也会受到影响。 以及有多种可能排序的任何其他类型(例如, People类型:您可以根据具体要求按名称或出生日期订购)。

请参阅语言规范的第8.15.6节。

字符串,数组和本机整数具有特殊的比较语义,如果实现的话,其他所有内容都会转到IComparable (以各种优化方式产生相同的结果)。

特别是,F#字符串默认使用序数比较,而大多数.NET默认使用文化感知比较。

这显然是F#和其他.NET语言之间令人困惑的不兼容性,但它确实有一些好处:

  • OCAML compat
  • 字符串和字符比较是一致的
    • 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#使用序数比较,这比仅区分大小写更严格。

// 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

这与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"|]

根据MSDNOrderBy方法“使用默认比较器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"] 

故事的道德:如果您关心排序,请始终指定要使用的具体比较!

感谢@Richard和他的回答 ,指出我在进一步了解这个问题上的方向

我的问题似乎根植于不完全理解F#中comparison约束的后果。 这是Seq.sortBy的签名

Seq.sortBy : ('T -> 'Key) -> seq<'T> -> seq<'T> (requires comparison)

我的假设是,如果类型'T实现IComparable那么这将用于排序。 我应该首先咨询这个问题: F#比较与C#IComparable ,它包含一些有用的参考,但需要进一步仔细阅读才能完全理解正在发生的事情。

所以,试图回答我自己的问题:

订购逻辑存在差异的根本原因是什么?

是。 C#版本似乎使用字符串的IComparable实现,而F#版本则没有。

在我的情况下,克服这个“问题”的推荐方法是什么?

虽然我无法评论这是否是“推荐”,但如果相关类型中有一个,则下面的F#函数order将使用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 “优先”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM