Considere los dos fragmentos de código que simplemente ordenan cadenas en C# y F# respectivamente:
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.toArrayEstos dos fragmentos de código devuelven resultados diferentes:
En mi caso específico, necesito correlacionar la lógica de orden entre estos dos idiomas (uno es el código de producción y el otro es parte de una afirmación de prueba). Esto plantea algunas preguntas:
EDITAR
En respuesta a varios comentarios de sondeo, la ejecución de los fragmentos a continuación revela más sobre la naturaleza exacta de las diferencias de este orden:
F#:
let strings = [| "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" |] let orderedStrings = strings |> Seq.sortBy (fun s -> s) |> Seq.toArrayC#:
var strings = new[] { "UV", "Uv", "uv", "uV", "TV", "tV", "Tv", "tv" }; var orderedStrings = strings.OrderBy(s => s).ToArray();Da:
El orden lexicográfico de las cadenas difiere debido a una diferencia en el orden subyacente de los caracteres:
Diferentes bibliotecas hacen diferentes elecciones de la operación de comparación predeterminada en cadenas. F# tiene un valor predeterminado estricto para distinguir entre mayúsculas y minúsculas, mientras que LINQ to Objects no distingue entre mayúsculas y minúsculas.
Tanto List.sortWith como Array.sortWith permiten especificar la comparación. Al igual que una sobrecarga de Enumerable.OrderBy .
Sin embargo, el módulo Seq no parece tener un equivalente (y no se agrega uno en 4.6).
Para las preguntas específicas:
¿Existe una razón subyacente para las diferencias en la lógica de ordenación?
Ambos pedidos son válidos. En los casos ingleses la insensibilidad parece más natural, porque es a lo que estamos acostumbrados. Pero esto no lo hace más correcto.
¿Cuál es la forma recomendada de superar este "problema" en mi situación?
Sea explícito sobre el tipo de comparación.
¿Este fenómeno es específico de las cadenas o también se aplica a otros tipos de .NET?
char también se verá afectado. Y cualquier otro tipo donde haya más de un ordenamiento posible (ej. un tipo People : podrías ordenar por nombre o fecha de nacimiento dependiendo de los requerimientos específicos).
Consulte la sección 8.15.6 de las especificaciones de idioma .
Las cadenas, las matrices y los enteros nativos tienen una semántica de comparación especial, todo lo demás simplemente va a IComparable si se implementa (modulo varias optimizaciones que producen el mismo resultado).
En particular, las cadenas de F# usan la comparación ordinal de forma predeterminada, a diferencia de la mayoría de .NET, que usa la comparación con reconocimiento cultural de forma predeterminada.
Obviamente, esta es una incompatibilidad confusa entre F # y otros lenguajes .NET, sin embargo, tiene algunos beneficios:
Comparer<string>.Default.Compare("a", "A") // -1Comparer<char>.Default.Compare('a', 'A') // 32compare "a" "A" // 1compare 'a' 'A' // 32Editar:
Tenga en cuenta que es engañoso (aunque no incorrecto) afirmar que "F# usa una comparación de cadenas que distingue entre mayúsculas y minúsculas ". F# usa la comparación ordinal , que es más estricta que solo distingue entre mayúsculas y minúsculas.
// 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" // -6Esto no tiene nada que ver con C# vs F#, o incluso con IComparable , sino que solo se debe a las diferentes implementaciones de clasificación en las bibliotecas.
El TL;DR; versión es que ordenar cadenas puede dar diferentes resultados:
"tv" < "TV" // false "tv".CompareTo("TV") // -1 => implies "tv" *is* smaller than "TV"O aún más claro:
"a" < "A" // false "a".CompareTo("A") // -1 => implies "a" is smaller than "A" Esto se debe a que CompareTo usa la referencia cultural actual (consulte MSDN) .
Podemos ver cómo se desarrolla esto en la práctica con algunos ejemplos diferentes.
Si usamos la ordenación estándar de F#, obtenemos el resultado de mayúsculas primero:
let strings = [ "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" ] strings |> List.sort // ["TV"; "Tv"; "UV"; "Uv"; "tV"; "tv"; "uV"; "uv"] Incluso si lanzamos a IComparable obtenemos el mismo resultado:
strings |> Seq.cast<IComparable> |> Seq.sort |> Seq.toList // ["TV"; "Tv"; "UV"; "Uv"; "tV"; "tv"; "uV"; "uv"]Por otro lado, si usamos Linq de F#, obtenemos el mismo resultado que el código de C#:
open System.Linq strings.OrderBy(fun s -> s).ToArray() // [|"tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"|] Según MSDN , el método OrderBy "compara claves usando el comparador predeterminado Default".
Las bibliotecas de F# no usan Comparer de forma predeterminada, pero podemos usar sortWith :
open System.Collections.Generic let comparer = Comparer<string>.Default Ahora, cuando hacemos este tipo, obtenemos el mismo resultado que LINQ OrderBy :
strings |> List.sortWith (fun xy -> comparer.Compare(x,y)) // ["tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"] Alternativamente, podemos usar la función incorporada CompareTo , que da el mismo resultado:
strings |> List.sortWith (fun xy -> x.CompareTo(y)) // ["tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"]Moraleja del cuento: si le importa clasificar, ¡siempre especifique la comparación específica que usará!