Заказ по умолчанию в С# против F #
Рассмотрим два фрагмента кода, которые просто упорядочивают строки в C#
и F#
соответственно:
С#:
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 #: телевизор, чай и кофе, телефон.
В моем конкретном случае мне нужно сопоставить логику упорядочения между этими двумя языками (один - это производственный код, а один - часть тестового утверждения). Это вызывает несколько вопросов:
- Есть ли основная причина различий в логике заказа?
- Каков рекомендуемый способ преодоления этой "проблемы" в моей ситуации?
- Является ли это явление специфичным для строк, или оно относится к другим типам .NET тоже?
ИЗМЕНИТЬ
В ответ на несколько комментариев зондирования, запуск фрагментов ниже показывает больше о точной природе различий этого упорядочения:
F #:
let strings = [| "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" |]
let orderedStrings =
strings
|> Seq.sortBy (fun s -> s)
|> Seq.toArray
С#:
var strings = new[] { "UV", "Uv", "uv", "uV", "TV", "tV", "Tv", "tv" };
var orderedStrings = strings.OrderBy(s => s).ToArray();
дает:
- С#: tv, tV, Tv, TV, uv, uV, Uv, UV
- F #: TV, Tv, UV, Uv, tV, tv, uV, uv
Лексикографическое упорядочение строк отличается из-за разницы в базовом порядке символов:
- С#: "aAbBcCdD... tTuUvV..."
- F #: "ABC..TUV..Zabc..tuv.."
Ответы
Ответ 1
См. раздел 8.15.6 описания .
Строки, массивы и собственные целые числа имеют специальную семантику сравнения, все остальное просто переходит к IComparable
, если это реализовано (по модулю различных оптимизаций, которые дают тот же результат).
В частности, строки F # используют по умолчанию сравнение по порядку, в отличие от большинства .NET, которые по умолчанию используют сопоставления, сопоставляемые культурам.
Это, очевидно, запутанная несовместимость между F # и другими языками .NET, однако она имеет некоторые преимущества:
- Совместимость с OCAML
- Сравнение строк и char согласовано
- С#
Comparer<string>.Default.Compare("a", "A") // -1
- С#
Comparer<char>.Default.Compare('a', 'A') // 32
- F #
compare "a" "A" // 1
- F #
compare 'a' 'A' // 32
Edit:
Обратите внимание, что он вводит в заблуждение (хотя и неверно), что "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
Различные библиотеки делают разные варианты сравнения по умолчанию для строк. F # строгий дефолт по чувствительности к регистру, тогда как LINQ to Objects нечувствителен к регистру.
Оба List.sortWith
и Array.sortWith
позволяют сравнивать сравнение. Как и перегрузка Enumerable.OrderBy
.
Однако модуль Seq
не имеет эквивалента (и один не добавляется в 4.6).
По конкретным вопросам:
Есть ли основная причина различий в логике заказа?
Оба порядка действительны. В английском случае нечувствительность кажется более естественной, потому что это то, к чему мы привыкли. Но это не делает его более правильным.
Каков рекомендуемый способ преодоления этой "проблемы" в моей ситуации?
Будьте ясны относительно такого сравнения.
Является ли это явление специфичным для строк, или оно относится к другим типам .NET тоже?
char
также будет затронуто. И любой другой тип, где имеется более одного возможного заказа (например, тип People
: вы можете заказать по имени или дате рождения в зависимости от конкретных требований).
Ответ 3
Спасибо @Richard и его ответы за то, что указали мне в направлении дальнейшего изучения этой проблемы.
Мои проблемы, похоже, укоренены в том, что они не полностью понимают последствия ограничения comparison
в F #. Вот подпись Seq.sortBy
Seq.sortBy : ('T -> 'Key) -> seq<'T> -> seq<'T> (requires comparison)
Мое предположение заключалось в том, что если тип 'T
реализован IComparable
, то это будет использоваться при сортировке. Я должен был сначала посоветоваться с этим вопросом: сравнение F # против С# IComparable, в котором содержатся некоторые полезные ссылки, но которые требуют некоторого дальнейшего тщательного чтения, чтобы в полной мере оценить, что происходит.
Итак, чтобы попытаться ответить на мои собственные вопросы:
Есть ли основная причина различий в логике заказа?
Да. Версия С#, по-видимому, использует строчную реализацию 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
в сортировке.
Ответ 4
Это не имеет ничего общего с С# vs 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"]
С другой стороны, если мы используем Linq из F #, получаем тот же результат, что и код С#:
open System.Linq
strings.OrderBy(fun s -> s).ToArray()
// [|"tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"|]
Согласно MSDN,
метод OrderBy
"сравнивает ключи с помощью стандартного сравнения по умолчанию."
Библиотеки 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"]
Мораль сказки: если вы заботитесь о сортировке, всегда указывайте конкретное сравнение!