Beh, prima di tutto, possiamo riscrivere merge per essere un po 'più elegante utilizzando pattern matching
merge [] ys = ys
merge xs [] = xs
merge [email protected](x:xs1) [email protected](y:ys1)
| x <= y = x : merge xs1 ys
| otherwise = y : merge xs ys1
In generale si dovrebbe evitare di utilizzare head
e tail
poiché sono un po 'non sicuri (generano un errore per la lista vuota) e usano la corrispondenza del modello quando possibile.
L'implementazione di msort
è praticamente azzeccata, tranne che possiamo dividere l'elenco in un modo più efficiente. Questo perché length xs
- richiede O (N) per completare. Il compilatore potrebbe salvarti e memorizzare nella cache il risultato della chiamata length
in modo che la seconda chiamata a length
non attraversi nuovamente l'elenco. Ma lo take
e lo drop
causeranno praticamente altri due attraversamenti, dividendo così la lista con 3 attraversamenti che possono rivelarsi costosi. Siamo in grado di fare meglio suddividendo la lista in due liste - la prima contenente gli elementi sulle posizioni dispari e il secondo elenco con gli elementi posti su posizione pari, in questo modo:
msort [] = []
msort [x] = [x]
msort xs = merge (msort first) (msort second)
where
(first, second) = splitInHalves xs
splitInHalves [] = ([], [])
splitInHalves [x] = ([x], [])
splitInHalves (x:y:xs) =
let (xs1, ys1) = splitInHalves xs
in (x:xs1, y:ys1)
In questo modo si ottiene la stessa Unisci Ordina in tempo O (NlogN). Sembra diverso perché probabilmente lo implementerai sul posto (modificando la lista originale) in un linguaggio imperativo come C. Questa versione è leggermente più costosa sulla memoria, ma ha i suoi vantaggi - è più facile ragionare su , quindi è più manutenibile, ed è anche molto semplice per parallelize senza preoccuparsi di nient'altro che dell'algoritmo stesso - che è esattamente ciò che un buon linguaggio di programmazione dovrebbe fornire agli sviluppatori che lo usano.
EDIT 1:
Se la sintassi è un po 'troppo, ecco alcune risorse:
- Pattern Matching - il bit con il simbolo
@
è chiamato un come-modello. Lo troverai lì
let
è una parola chiave utilizzata per dichiarare una variabile da utilizzare nell'espressione che la segue (mentre where
associa una variabile nell'espressione che lo precede).Altro su sintassi Haskell, tra cui guardie (le cose con | condition = value
) può essere trovato qui, in questo capitolo della Learn You a Haskell
EDIT 2:
@ is7s hanno proposto una versione molto più concisa della splitInHalves
utilizzando il foldr
function:
splitInHalves = foldr (\x (l,r) -> (x:r,l)) ([],[])
EDIT 3:
012.351.
Ecco un'altra risposta che fornisce un'implementazione alternativa di merge sort, che ha anche la proprietà di essere stable:
Lazy Evaluation and Time Complexity
Spero che questo aiuti e benvenuti nel meraviglioso mondo della programmazione funzionale!
nelle due righe, 'sorted =' e 'left =', è necessario utilizzare lo stesso confronto; o '<' o '<=', ma entrambi devono essere uguali (e la terza riga, 'right =', dovrà usare l'altra variante di conseguenza). –
Oh scusa, ho appena visto quel commento ora. Credo che tu abbia ragione. Se capisco bene, questo piccolo errore riguarda solo l'attributo stable/unstable dell'algoritmo giusto? Come in realtà non importa se prendo testa (xs) o testa (ys) se sono uguali. – Nocta
No, dei due equals ne lascerai uno completamente e avrai due volte in output: 'unisci [(1,1), (2,1)] [(1,2), (2,2)] = (1,2): unione [(2,1)] [(1,2), (2,2)] 'per un tipo di dati simile a una coppia che confronta solo il primo sottoelemento. –