2015-08-23 1 views
3

Je commence juste avec Haskell et ai martelé cet algo récursif simple pour trouver le LCM pour chaque nombre dans une liste. Cela fonctionne, mais c'est en désordre et j'espérais un examen par les pairs sur la façon de le rendre plus élégant, lisible et Haskell-y.Existe-t-il un moyen plus propre et plus élégant pour écrire cette fonction LCM?

lcms list 
    | length list > 1 = lcms (lcm (head list) (head (tail list)):(tail (tail list))) 
    | otherwise = list 

Alors qui prend une liste et fait LCM des deux premiers éléments, prepends ensuite à la liste moins ces deux éléments. Fondamentalement, le psudocode que je vais pour est comme ceci:

lcms [a,b,c] = lcm (a, (lcm (b, c)) 

Des suggestions, quelqu'un? Je suis impatient de m'améliorer chez Haskell et d'écrire des choses que les gens peuvent réellement lire. Les conseils d'efficacité sont les bienvenus aussi!

Merci à tous!

Répondre

9

Il est un pli:

import Data.List 

lcms :: [Int] -> Int 
lcms xs = foldl' lcm 1 xs 

lcm calculer le PPCM de seulement deux numéros:

lcm :: Int -> Int -> Int 
+0

Oh cool, parfait, merci! Je dois étudier les plis et m'habituer à les utiliser davantage. En tant que question de suivi: y a-t-il une raison d'utiliser le pli paresseux du prélude sur le pli plus ardent de Data.List? Est-ce que c'est plus rapide? Je sais que le foldl paresseux peut créer des débordements de pile sur de grandes listes, mais ne sait pas quel avantage il offre ou de sa paresse est plus rapide pour les listes courtes. – clb

+1

Vous pouvez également utiliser 'foldr', comme dans' lcms = foldr lcm 1' – amar47shah

+2

@clball 'foldl' n'est presque jamais ce que vous voulez. –

3

Vous pouvez écrire avec presque la syntaxe que vous suggérez, ainsi:

lcms (a:b:c) = lcms (lcm a b:c) 
lcms list = list 

Je trouve la deuxième clause un peu étrange, mais pas terrible: elle gère gracieusement les listes vides, Bien que renvoyer une liste quand vous savez que vous retournerez au plus un élément pourrait être vu par certains hasochistes comme étant un peu imprécis avec vos types. Vous pouvez également envisager d'utiliser Maybe, le type d'élément canonique 0 ou 1:

lcms (a:b:c) = lcms (lcm a b:c) 
lcms [answer] = Just answer 
lcms []  = Nothing 

Un autre bon choix serait d'identifier un cas de base sain d'esprit. Pour les opérations binaires, l'unité de l'opération est généralement un bon choix, donc pour lcm, je choisirais 1, ainsi:

lcms (a:b:c) = lcms (lcm a b:c) 
lcms [answer] = answer 
lcms []  = 1 

En général, on essaie d'éviter la récursivité explicite lorsque cela est possible; l'autre réponse montre comment faire ce pas. Dans ce cas, il y a une transformation intermédiaire qui rend les cas de base légèrement plus esthétiques: au lieu de garder votre accumulateur en tête de liste - ce qui ne fonctionne qu'incidemment parce que votre accumulateur est du même type que les éléments de la liste - peut rendre l'accumulation plus explicite ou moins. Ainsi, l'un de ces:

lcms (x:xs) = lcm x (lcm xs) 
lcms []  = 1 

-- OR 

lcms = go 1 where 
    go acc (x:xs) = go (lcm acc x) xs 
    go acc []  = acc 

Ces deux implémentations correspondent à choisir foldr ou foldl pour éliminer la récursion explicite; foldl' serait similaire à la seconde, mais avec un seq supplémentaire:

lcms = go 1 where 
    go acc (x:xs) = let acc' = lcm acc x in acc' `seq` go acc' xs 
    go acc []  = acc 
+0

Merci pour une explication très détaillée! Pourquoi la récurrence explicite est-elle généralement évitée? Juste généralement pas le meilleur moyen de faire quelque chose? Aussi c'est vraiment cool que vous ayez pu implémenter une solution qui était presque exactement le psudocode. C'est plutôt cool. – clb

+4

@clball Généralement, l'utilisation de fonctions telles que map, filter, et similaires (et dans une certaine mesure 'foldr') est préférable à la récursion générale car il y a moins de façons d'insérer des bogues et parce qu'il est plus facile en un coup d'oeil. Si je vois la récursivité, je suis toujours à la recherche de cas spéciaux qui ne correspondent pas aux modèles standards. –

+1

@clball Notez comment cette solution évite 'head' et' tail'. Ces fonctions sont dangereuses car elles bloqueront votre programme si vous les appelez sur une liste vide. Vous avez alors le fardeau d'insérer soigneusement des gardes comme votre «liste de longueur> 1». Au lieu de cela, l'utilisation de la correspondance de modèle comme ci-dessus ne plantera pas le programme, _unless_ vous ne couvre pas tous les cas possibles. Heureusement, '-Wall' vous avertira si une définition n'est pas exhaustive. Je trouve ce _very_ utile. En outre, la correspondance de modèle peut rendre le code plus lisible. – chi