2009-08-01 13 views
18

Sono nuovo di Haskell e sto cercando di implementare alcuni algoritmi noti al suo interno.Unisci ordina in Haskell

Ho implementato il merge sort su stringhe. Sono un po 'deluso dalle prestazioni della mia implementazione Haskell rispetto alle implementazioni C e Java. Sulla mia macchina (Ubuntu Linux, 1,8 GHz), C (gcc 4.3.3) ordina 1 000 000 stringhe in 1,85 s, Java (Java SE 1.6.0_14) in 3.68 s, Haskell (GHC 6.8.2) in 25.89 S. Con un input più grande (10.000.000 di stringhe), C ha 21.81 s, Java richiede 59.68 s, Haskell inizia lo scambio e ho preferito interrompere il programma dopo alcuni minuti.

Poiché sono nuovo di Haskell, sarei interessato a sapere se la mia implementazione può rendere più efficiente in termini di tempo/spazio.

Grazie in anticipo per qualsiasi suggerimento Giorgio

mia realizzazione:

merge :: [String] -> [String] -> [String] 
merge [] ys = ys 
merge xs [] = xs 
merge (x:xs) (y:ys) = if x < y 
         then x : (merge xs (y:ys)) 
         else y : (merge (x:xs) ys) 

mergeSort :: [String] -> [String] 
mergeSort xs = if (l < 2) 
       then xs 
       else merge h t 
       where l = length xs 
        n = l `div` 2 
        s = splitAt n xs 
        h = mergeSort (fst s) 
        t = mergeSort (snd s) 
+0

btw, quali flag di compilazione hai usato con GHC? – yairchu

+1

Questa non è esattamente una implementazione ideale. Stai continuamente attraversando ogni sottolista per trovare la sua lunghezza, e questo è solo inutile. Vedi la versione Hynek -Pichi- Vychodil sotto per la versione più pigra e probabilmente più veloce. – Axman6

+0

@ Axman6 - È possibile fornire un collegamento a questo algoritmo? O una citazione? – rtperson

risposta

5

Non sono sicuro se questa è la causa del problema, ma ricordo che le liste sono una struttura di dati sequenziali. In particolare, sia length xs sia splitAt n xs impiegheranno una quantità di tempo proporzionale alla lunghezza dell'elenco (O(n)).

In C e Java, si sta probabilmente utilizzando matrici, che richiedono tempo costante per entrambe le operazioni (O(1)).

Modifica: rispondendo alla domanda su come renderlo più efficiente, è possibile utilizzare anche gli array in Haskell.

+1

Ma probabilmente non vuoi rappresentare le stringhe. Norman Ramsey ha ragione: questo è il significato di 'Data.ByteString'. –

+1

@alexey_r: Intendevo gli array per la sostituzione di [String], non per la sostituzione di String aka [Char]. Sostituire String è un'ottimizzazione separata. – CesarB

14

In Haskell, una stringa è un elenco pigro di caratteri e ha lo stesso sovraccarico di qualsiasi altro elenco. Se ricordo proprio da un discorso che ho ascoltato Simon Peyton Jones nel 2004, il costo dello spazio in GHC è di 40 byte per personaggio. Per un confronto tra mele e mele probabilmente dovresti scegliere lo Data.ByteString, progettato per offrire prestazioni paragonabili ad altre lingue.

+1

Grazie per il suggerimento. Non sono sicuro che ByteString sia uguale a String. Per quanto ne so Stringa :: [Char] dove Char è un charactoer unicode. D'altra parte, BytyString contiene una stringa di Word8, cioè di byte. Quindi dovrei assicurarmi che il mio input sia in una codifica dei caratteri da un byte per , ad es. Latin1. Altrimenti, in che modo ByteString gestisce i caratteri multibyte quando valuta l'ordinamento lessicografico? –

+1

http://hackage.haskell.org/package/utf8-string-0.3.5: "Il pacchetto utf8-string fornisce operazioni per la codifica di stringhe UTF8 a liste di Word8 e viceversa, e per la lettura e la scrittura di UTF8 senza troncamento." –

9

modo migliore per dividere l'elenco per evitare il problema CesarB sottolinea:

split []    = ([], []) 
split [x]   = ([x], []) 
split (x : y : rest) = (x : xs, y : ys) 
         where (xs, ys) = split rest 

mergeSort [] = [] 
mergeSort [x] = [x] 
mergeSort xs = merge (mergesort ys) (mergesort zs) 
       where (ys, zs) = split xs 

EDIT: Fisso.

+1

@alexey_r: hai due errori nel codice. uno è che il modello "x: y: xs" deve venire tra parentesi. l'altra è che le precedenze di ":" e "$" fanno si che tu dia "split xs" alla funzione "x: fst" – yairchu

+0

@alexey_r: Penso che il tuo codice calcoli attualmente "split xs" due volte. uso migliore (ys, zs) = split xs. o splitxs = split xs. quindi c'è solo una chiamata di divisione – yairchu

+0

Hai ragione, ovviamente. Fisso. –

18

Prova questa versione:

mergesort :: [String] -> [String] 
mergesort = mergesort' . map wrap 

mergesort' :: [[String]] -> [String] 
mergesort' [] = [] 
mergesort' [xs] = xs 
mergesort' xss = mergesort' (merge_pairs xss) 

merge_pairs :: [[String]] -> [[String]] 
merge_pairs [] = [] 
merge_pairs [xs] = [xs] 
merge_pairs (xs:ys:xss) = merge xs ys : merge_pairs xss 

merge :: [String] -> [String] -> [String] 
merge [] ys = ys 
merge xs [] = xs 
merge (x:xs) (y:ys) 
= if x > y 
     then y : merge (x:xs) ys 
     else x : merge xs (y:ys) 

wrap :: String -> [String] 
wrap x = [x] 
  1. Bad idea è la lista splitting prima. Invece di fare solo lista di un elenco di membri. Haskell è pigro, sarà fatto nel momento giusto.
  2. Quindi unire coppie di elenchi finché non si dispone di una sola lista.

Edit: Qualcuno che down-voto questa risposta: sopra merge sort implementazione è stesso algoritmo utilizzato in ghc Data.List.sort se non con la funzione cmp rimosso. Gli autori di well ghc potrebbero essere errati: -/

+0

Una versione "stabile" +1 –

+0

Questo alloca molta memoria rispetto a un quicksort quindi dubito che sia ancora usato come una funzione di libreria standard. – egdmitry

+0

@egdmitry: Sì è stato sostituito il 24/12/2009 da migliore, ma ancora uniamo implementazione di ordinamento. Quindi era vero quando inizialmente ho risposto alla domanda. Ad ogni modo se hai qualche prova che quicksort assegna meno memoria o si comporta meglio, per favore, elabora. E perché non guardi al codice sorgente ma indovina? –