[英]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
這兩個代碼片段返回不同的結果:
在我的具體情況下,我需要關聯這兩種語言之間的排序邏輯(一種是生產代碼,一種是測試斷言的一部分)。 這提出了幾個問題:
編輯
在回答幾個探測性評論時,運行下面的片段可以更多地了解這種排序差異的確切性質:
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();
得到:
字符串的字典順序不同,因為字符的基本順序不同:
不同的庫對字符串進行默認比較操作的不同選擇。 F#嚴格默認為區分大小寫,而LINQ to Objects不區分大小寫。
List.sortWith
和Array.sortWith
允許指定比較。 和Enumerable.OrderBy
的重載一樣。
然而, Seq
模塊似乎沒有等效物(並且在4.6中沒有添加一個)。
針對具體問題:
訂購邏輯存在差異的根本原因是什么?
兩個訂單都有效。 在英語中,不敏感似乎更自然,因為這就是我們習慣的。 但這並沒有使它更正確。
在我的情況下,克服這個“問題”的推薦方法是什么?
明確這種比較。
這種現象是否特定於字符串,還是也適用於其他.NET類型?
char
也會受到影響。 以及有多種可能排序的任何其他類型(例如, People
類型:您可以根據具體要求按名稱或出生日期訂購)。
請參閱語言規范的第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
這與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
方法“使用默認比較器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.