2009-08-01 6 views
18

Je suis nouveau à Haskell et j'essaye d'implémenter quelques algorithmes connus dedans.Fusionner trier dans Haskell

J'ai implémenté le tri par fusion sur les chaînes. Je suis un peu déçu des performances de mon implémentation Haskell par rapport aux implémentations C et Java. Sur ma machine (Ubuntu Linux, 1,8 GHz), C (gcc 4.3.3) trie 1 000 000 chaînes en 1,85 s, Java (Java SE 1.6.0_14) en 3,68 s, Haskell (GHC 6.8.2) en 25,89 s. Avec une plus grande entrée (10 000 000 chaînes), C prend 21,81 s, Java prend 59,68 s, Haskell commence à échanger et j'ai préféré arrêter le programme après plusieurs minutes.

Depuis que je suis nouveau à Haskell, je serais intéressé de savoir si ma mise en œuvre peut être rendue plus de temps/espace efficace.

Merci à l'avance pour tout soupçon Giorgio

Ma mise en œuvre:

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, quels drapeaux de compilation avez-vous utilisé avec GHC? – yairchu

+1

Ce n'est pas exactement une implémentation idéale. Vous parcourez continuellement chaque sous-liste pour trouver sa longueur, ce qui n'est pas nécessaire. Voir la version de Hynek -Pichi- Vychodil ci-dessous pour la version la plus paresseuse et probablement plus rapide. – Axman6

+0

@ Axman6 - Pouvez-vous fournir un lien vers cet algorithme? Ou une citation? – rtperson

Répondre

5

Je ne sais pas si cela est la cause de votre problème, mais rappelez-vous que les listes sont une structure de données séquentielle. En particulier, length xs et splitAt n xs prendront une durée proportionnelle à la longueur de la liste (O(n)). En C et Java, vous utilisez probablement des tableaux, qui prennent un temps constant pour les deux opérations (O(1)). Edit: répondant à votre question sur la façon de le rendre plus efficace, vous pouvez également utiliser des tableaux dans Haskell.

+1

Mais vous ne voulez probablement pas représenter les chaînes. Norman Ramsey a raison: c'est ce à quoi sert 'Data.ByteString'. –

+1

@alexey_r: Je voulais dire des tableaux pour remplacer [String], pas pour remplacer String aka [Char] lui-même. Remplacer la chaîne est une optimisation distincte. – CesarB

14

Dans Haskell, une chaîne est une liste de caractères paresseux et a le même préfixe que toute autre liste. Si je me souviens bien d'une conversation que j'ai entendue en 2004 avec Simon Peyton Jones, le coût de l'espace dans GHC est de 40 octets par personnage. Pour une comparaison pommes-pommes, vous devriez probablement trier Data.ByteString, qui est conçu pour donner des performances comparables aux autres langues.

+1

Merci pour l'indice. Je ne suis pas sûr que ByteString soit identique à String. Pour autant que je sache Chaîne :: [Char] où Char est un caractère unicode. D'autre part, BytyString contient une chaîne de Word8, c'est-à-dire de octets. Ensuite, je devrais m'assurer que mon entrée est dans un codage de caractères de 1 octet par , par ex. Latin1. Sinon, comment un ByteString gère-t-il les caractères multi-octets lorsque évalue l'ordre lexicographique? –

+1

http://hackage.haskell.org/package/utf8-string-0.3.5: "Le paquetage utf8-string fournit des opérations d'encodage des chaînes UTF8 vers les listes Word8 et vice-versa, et de lecture et d'écriture de UTF8 sans troncature." –

9

meilleure façon de diviser la liste pour éviter les CesarB d'émission souligne:

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: fixe.

+1

@alexey_r: vous avez deux erreurs dans votre code. l'une est que le motif "x: y: xs" doit être entre parenthèses. l'autre est que les précédences de ":" et "$" font que vous donnez "split xs" à la fonction "x: fst" – yairchu

+0

@alexey_r: Je pense que votre code calcule actuellement "split xs" deux fois. meilleure utilisation (ys, zs) = split xs. ou splitxs = split xs. donc il n'y a qu'une seule invocation de split – yairchu

+0

Vous avez raison, bien sûr. Fixé. –

18

Essayez cette version:

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. idée Bad est la liste fractionnement en premier. Au lieu de cela, il suffit de faire la liste des listes d'un membre. Haskell est paresseux, ça se fera au bon moment.
  2. Fusionnez ensuite les paires de listes jusqu'à ce que vous ayez une seule liste.

Modifier: Quelqu'un qui down-vote cette réponse: ci-dessus tri par fusion est mise en œuvre même algorithme que celui utilisé dans GHC Data.List.sort sauf fonction cmp supprimée. Eh bien les auteurs de ghc peuvent se tromper: -/

+0

Une version qui est "stable" +1 –

+0

Cela alloue beaucoup de mémoire comparé à un quicksort donc je doute qu'il soit toujours utilisé comme une fonction de bibliothèque standard. – egdmitry

+0

@egdmitry: Oui, il a été remplacé le 24/12/2009 par une meilleure implémentation de tri mais toujours de fusion. Donc c'était vrai quand j'ai d'abord répondu à la question. Quoi qu'il en soit, si vous avez une preuve que quicksort alloue moins de mémoire ou se comporte mieux, veuillez élaborer. Et pourquoi ne pas regarder le code source mais deviner? –