Подтвердить что ты не робот

Заказ по умолчанию в С# против 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.."
4b9b3361

Ответ 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"] 

Мораль сказки: если вы заботитесь о сортировке, всегда указывайте конкретное сравнение!