J'essaie de le faire à partir de zéro, sans utiliser de bibliothèque en dehors de la bibliothèque standard. Heres mon code:
permutations :: [a] -> [[a]]
permutations (x:xs) = [x] : permutations' xs
where permutations' (x:xs) = (:) <$> [x] <*> split xs
split l = [[x] | x <- l]
Le problème est que cela ne produit qu'une fourchette du calcul non déterministe. Idéalement je voudrais
(:) <$> [x] <*> ((:) <$> [x] <*> ((:) <$> [x] <*> ((:) <$> [x] <*> xs)))
Mais je ne peux pas trouver un moyen de le faire proprement. Le résultat souhaité est quelque chose comme ceci:
permutations "abc" -> ["abc", "acb", "bac", "bca", "cab", "cba"]
Comment puis-je faire cela?
Peut-être devriez-vous utiliser le code existant:
import Data.List
permutations [1,2,3,4]
Pour une implémentation simple sans considérer les doublons dans l'entrée
permutations :: Eq a => [a] -> [[a]]
permutations [] = [[]]
permutations as = do a <- as
let l = delete a as
ls <- permutations l
return $ a : ls
Tester:
λ> permutations [1,2,3]
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
λ> permutations "abc"
["abc","acb","bac","bca","cab","cba"]
λ>
Je pense que cette variante est plus courte et plus élégante pour ce que d’autres suggèrent:
permutate :: (Eq a) => [a] -> [[a]]
permutate [] = [[]]
permutate l = [a:x | a <- l, x <- (permutate $ filter (\x -> x /= a) l)]
Je le ferais comme ça:
select :: [a] -> [(a,[a])]
select = select' id where
select' _ [] = []
select' acc (a:r) = (a, acc r) : select' (acc . (a:)) r
permutations [] = [[]]
permutations l = do
(a,r1) <- select l
r2 <- permutations r1
return (a: r2)
Je suis relativement nouveau chez Haskell mais j’avais développé un algorithme de permutation très efficace pour JS . Cela bat presque l’algorithme des tas, mais en JS, la rotation d’un tableau coûte plus cher que la paresseuse fonction Haskell iterate
sur les listes. Donc, celle-ci, contrairement à toutes les réponses fournies ci-dessus, semble être beaucoup plus efficace.
Le Data.List.permutations
intégré est toujours 2 fois plus rapide que celui-ci à ce jour, car je ne connais pas du tout les contraintes de performance de Haskell. Peut-être que quelqu'un ici pourrait m'aider à pousser ce code un peu en avant.
J'ai donc une fonction d'assistance qui renvoie une liste de toutes les rotations de la liste fournie. Tel que
rotations [1,2,3]
donnerait [[1,2,3],[2,3,1],[3,1,2]]
en conséquence, la fonction perms est;
rotations :: [a] -> [[a]]
rotations xs = take (length xs) (iterate (\(y:ys) -> ys ++ [y]) xs)
perms :: [a] -> [[a]]
perms [] = [[]]
perms (x:xs) = concatMap (rotations.(x:)) (perms xs)
Edit: J'ai donc réfléchi à la manière de rendre le code ci-dessus plus efficace. OK, les listes dans Haskell sont des listes chaînées et contrairement à JavaScript, la longueur n'est pas une propriété accessible en O(1) time mais en O (n). C'est une fonction qui parcourt toute la liste, en comptant essentiellement tous les éléments de la liste. Par conséquent, très coûteux si utilisé à plusieurs reprises. C'est ce que nous faisons exactement par l'instruction take (length xs)
dans chaque invocation de la fonction de rotation. Nous l'invoquons littéralement des millions de fois si votre liste d'entrées contient 10 à 11 éléments ou plus. Le couper donnerait des économies énormes. Ne faisons pas ensuite en sorte que la longueur des listes de la même longueur soit calculée, mais fournissons-la simplement comme;
rotations :: Int -> [a] -> [[a]]
rotations len xs = take len (iterate (\(y:ys) -> ys ++ [y]) xs)
Belle. Eh bien, maintenant nous devons modifier légèrement notre fonction perms
en conséquence, comme;
perms :: [a] -> [[a]]
perms [] = [[]]
perms il@(x:xs) = concatMap ((rotations len).(x:)) (perms xs)
where len = length il
si évidemment il
est maintenant assigné au i nput l ist et len
met en cache sa longueur. Maintenant, c'est beau et assez intéressant, il utiliseplus rapideque le Data.List.permutations
par défaut.
Jusqu'ici tout va bien ... Mais maintenant, il est temps de tricher un peu et de le rendre encore plus rapide. Une chose que je sais et comme je viens de le prouver, dans CS, le meilleur carburant pour un algorithme est la mise en cache. Alors, qu'allons-nous aller plus loin la cache ici ..? Nous pouvons facilement mettre en cache les valeurs de retour pour des entrées telles que [x]
, [x,y]
et [x,y,z]
et enregistrer certains calculs. Vous voulez plus de vitesse ..? Puis cachez sans vergogne [w,x,y,z]
aussi. Voyons le nouveau code dans son ensemble.
rotations :: Int -> [a] -> [[a]]
rotations len xs = take len (iterate (\(y:ys) -> ys ++ [y]) xs)
perms :: [a] -> [[a]]
perms [] = [[]]
perms [x] = [[x]]
perms [x,y] = [[x,y],[y,x]]
perms [x,y,z] = [[x,y,z],[y,z,x],[z,x,y],[x,z,y],[z,y,x],[y,x,z]]
perms il@(x:xs) = concatMap ((rotations len).(x:)) (perms xs)
where len = length il
Toutes les idées sont les bienvenues ...
C'est déjà dans la bibliothèque standard base , donc pas besoin de se débattre. Si vous voulez vraiment savoir comment faire, vous pouvez regarder la source de cette bibliothèque.
Tout va mieux avec les monades:
perm :: [a] -> [[a]]
perm [] = return []
perm (x:xs) = (perm xs) >>= (ins x)
where
ins :: a -> [a] -> [[a]]
ins x [] = [[x]]
ins x (y:ys) = [x:y:ys] ++ ( map (y:) (ins x ys) )
Donc: vous avez une fonction qui insère une lettre dans un mot, mais elle produit plus d’un mot, alors comment l’appliquer de manière récursive? >>=
aide!