簡體   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