2015-06-23 9 views
17

considerare i due frammenti di codice che semplicemente ordinare le stringhe in C# e F# rispettivamente:ordinamento di default in C# con 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 

Questi due frammenti di codice restituiscono risultati diversi:

  • C#: Tè e caffè, telefono, TV
  • F #: TV, tè e caffè, Telefono

Nel mio caso specifico ho bisogno di correlare la logica ordinamento tra queste due lingue (uno è il codice di produzione, e uno fa parte di un'asserzione di prova). Questo pone alcune domande:

  • C'è una ragione alla base delle differenze nella logica di ordinazione?
  • Qual è il modo consigliato per superare questo "problema" nella mia situazione?
  • Questo fenomeno è specifico delle stringhe o si applica anche ad altri tipi .NET?

EDIT

In risposta ad alcuni commenti di sondaggio, eseguendo i frammenti di sotto rivela di più sulla natura esatta delle differenze di questo ordinamento:

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(); 

Dà:

  • C#: TV, TV, TV, TV, uv, UV, UV, UV
  • F #: TV, TV, UV, UV, TV, TV, UV, UV

l'ordinamento lessicografico delle stringhe è diversa a causa di una differenza nel modo di fondo di personaggi:

  • C#: "aABBCCDD ... tTuUvV ..."
  • F #: "ABC..TUV..Zabc..tuv .."
+2

sembra funzionare 'bene' se lo fai '(divertente s -> s.ToLower())' – leppie

+1

Questo è molto strano ... Mi piacerebbe davvero aspetto che questo funziona bene in F #. Se hai parole che iniziano con lettere diverse, come si presentano in F #? –

+0

Grazie a @leppie, questa è una buona soluzione per il "caso stringa". Il mio codice attuale è più generico, basandosi su implementazioni di confronto. Gestire stringhe come caso speciale sarebbe accettabile se sapessi che si tratta di un'anomalia - ma sospetto che ci sia più di quello che capisco ... – Lawrence

risposta

5

Vedere la sezione 8.15.6 di language spec.

Le stringhe, gli array e gli interi nativi dispongono di una speciale semantica di confronto, tutto il resto va a IComparable se implementato (modulo varie ottimizzazioni che producono lo stesso risultato).

In particolare, le stringhe F # usano il confronto ordinale per impostazione predefinita, a differenza della maggior parte di .NET che utilizza il confronto compatibile con la cultura per impostazione predefinita.

Questa è ovviamente un'incompatibilità di confusione fra F # e altri linguaggi .NET, tuttavia ha alcuni vantaggi:

  • OCAML compat
  • stringa e char confronti sono coerenti
    • 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

Edit:

Nota che è fuorviante (anche se non errata) di affermare che "F # usa confronto stringhe maiuscole e minuscole". F # usa il confronto ordinale, che è più rigido delle sole case-sensitive.

// 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 
+0

Grazie @latkin - questa è solo la risposta gentile che stavo cercando - davvero utile! – Lawrence

6

diverse biblioteche fare diverse scelte di l'operazione di confronto di default sulle stringhe. F # è un valore predefinito predefinito per la distinzione tra maiuscole e minuscole, mentre LINQ per gli oggetti non fa distinzione tra maiuscole e minuscole.

Entrambe List.sortWith e Array.sortWith consentono di specificare il confronto. Come fa un sovraccarico di Enumerable.OrderBy.

Tuttavia il modulonon sembra avere un equivalente (e uno non viene aggiunto in 4.6).

Per le domande specifiche:

C'è una ragione di fondo per le differenze di ordinare la logica?

Entrambi gli ordini sono validi. Nei casi inglesi l'insensibilità sembra più naturale, perché è quello a cui siamo abituati. Ma questo non lo rende più corretto.

Qual è il modo consigliato per superare questo "problema" nella mia situazione?

Sii esplicito sul tipo di confronto.

Questo fenomeno è specifico per le stringhe o si applica anche ad altri tipi di .NET?

char sarà interessato. E qualsiasi altro tipo in cui vi è più di un ordine possibile (ad esempio un tipo People: è possibile ordinare per nome o data di nascita a seconda dei requisiti specifici).

+0

Questi sono commenti utili - grazie. La mia comprensione (da [questa pagina] (https://msdn.microsoft.com/en-us/library/ee353610.aspx)) era che se un tipo implementa 'IComparable' allora questo è usato per fare l'ordinamento. Se utilizzo esplicitamente questa interfaccia per implementare 'IComparer ' per la versione C# il problema persiste. Questo suggerisce che la mia comprensione di come F # fa il confronto (anche con un 'IComparable') è difettoso. Se fosse possibile fare luce su come F # sceglie di fare il confronto sarebbe molto interessante .. – Lawrence

+0

@Lawrence I documenti per '[Seq.sortBy'] (https://msdn.microsoft.com/it us/library/ee353610.aspx) dice che è utilizza ['Operators.compare'] (https://msdn.microsoft.com/en-us/library/ee353429.aspx) ma non ci sono dettagli su quella pagina. È ora di leggere la specifica della lingua o il codice sorgente .... – Richard

+0

> Tuttavia il modulo Seq non sembra avere un equivalente (e uno non viene aggiunto in 4.6). < Questo non è corretto (o forse solo un refuso, o forse entrambi?). 'Seq.sortWith' è stato effettivamente aggiunto in F # 4.0. – latkin

2

Grazie a @pluto e his answers per me indica la direzione per ottenere un po 'più avanti nella comprensione di questo problema

I miei problemi sembrano essere stati radicati nel non comprendere appieno le conseguenze del vincolo comparison in F #. Ecco la firma di Seq.sortBy

Seq.sortBy : ('T -> 'Key) -> seq<'T> -> seq<'T> (requires comparison) 

La mia ipotesi era che se il tipo 'T implementato IComparable allora questo sarebbe stato utilizzato nella selezione. Avrei dovuto consultare prima questa domanda: F# comparison vs C# IComparable, che contiene alcuni riferimenti utili, ma che richiedono un'ulteriore lettura attenta per apprezzare appieno cosa sta succedendo.

Così, per tentare di rispondere alle mie domande:

C'è una ragione di fondo per le differenze di ordinare la logica?

Sì. La versione C# sembra utilizzare l'implementazione della stringa IComparable, mentre la versione F # no.

Qual è il modo consigliato per superare questo "problema" nella mia situazione?

Anche se non posso commentare se questo è "raccomandato", la funzione F # order seguito userà un'implementazione di IComparable se ce n'è uno del tipo in questione:

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 

È questo il fenomeno specifico per le stringhe o si applica ad altre.Anche i tipi NET?

ci sono chiaramente alcune sottigliezze coinvolti con il rapporto tra il vincolo comparison e l'interfaccia IComparable. Per sicurezza, seguirò il consiglio di Richard e sarò sempre esplicito sul tipo di confronto, probabilmente usando la funzione sopra per "dare la priorità" usando IComparable nell'ordinamento.

+0

Nel tuo codice stai ordinando due volte: prima 'Seq.sortBy', quindi' Array.sortWith'. –

+0

@FyodorSoikin - D'oh!Un punto eccellente - grazie. – Lawrence

2

Questo non ha nulla a che fare con C# vs F #, o anche IComparable, ma è solo a causa delle diverse implementazioni di ordinamento nelle librerie.

TL; DR; versione è che le stringhe di selezione possono dare risultati diversi:

"tv" < "TV" // false 
"tv".CompareTo("TV") // -1 => implies "tv" *is* smaller than "TV" 

O anche più chiaro:

"a" < "A" // false 
"a".CompareTo("A") // -1 => implies "a" is smaller than "A" 

Questo perché CompareTo utilizza la lingua corrente (see MSDN).

Possiamo vedere come si svolge in pratica con alcuni esempi diversi.

Se usiamo la F # sorta di serie otteniamo il risultato maiuscolo-prima:

let strings = [ "UV"; "Uv"; "uV"; "uv"; "Tv"; "TV"; "tv"; "tV" ] 

strings |> List.sort 
// ["TV"; "Tv"; "UV"; "Uv"; "tV"; "tv"; "uV"; "uv"] 

Anche se abbiamo gettato al IComparable otteniamo lo stesso risultato:

strings |> Seq.cast<IComparable> |> Seq.sort |> Seq.toList 
// ["TV"; "Tv"; "UV"; "Uv"; "tV"; "tv"; "uV"; "uv"] 

D'altra parte, se usiamo Linq da F #, otteniamo lo stesso risultato del codice C#:

open System.Linq 
strings.OrderBy(fun s -> s).ToArray() 
// [|"tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"|] 

Accordin g a MSDN, il metodo OrderBy "confronta le chiavi utilizzando il confronto predefinito predefinito."

Le # librerie F non utilizzano Comparer di default, ma possiamo usare sortWith:

open System.Collections.Generic 
let comparer = Comparer<string>.Default 

Ora, quando facciamo questo tipo, si ottiene lo stesso risultato come LINQ OrderBy:

strings |> List.sortWith (fun x y -> comparer.Compare(x,y)) 
// ["tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"] 

In alternativa, possiamo usare la funzione integrata CompareTo, che dà lo stesso risultato:

strings |> List.sortWith (fun x y -> x.CompareTo(y)) 
// ["tv"; "tV"; "Tv"; "TV"; "uv"; "uV"; "Uv"; "UV"] 

Morale del racconto: se ti interessa l'ordinamento, specifica sempre il confronto specifico da usare!