J'essaie de définir une fonction qui supprimera les doublons d'une liste. Jusqu'à présent, j'ai une implémentation qui fonctionne:
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) | x `elem` xs = rmdups xs
| otherwise = x : rmdups xs
Cependant, j'aimerais retravailler ceci sans utiliser elem
. Quelle serait la meilleure méthode pour cela?
J'aimerais faire cela en utilisant ma propre fonction et non pas nub
ou nubBy
.
Je ne pense pas que vous serez capable de le faire sans elem
(ou votre propre ré-implémentation de celle-ci).
Cependant, votre implémentation pose un problème sémantique. Lorsque des éléments sont dupliqués, vous conservez celui last. Personnellement, je m'attendrais à ce qu'il garde le premier élément en double et laisse tomber le reste.
*Main> rmdups "abacd"
"bacd"
La solution consiste à insérer les éléments "vus" dans une variable d'état.
removeDuplicates :: Eq a => [a] -> [a]
removeDuplicates = rdHelper []
where rdHelper seen [] = seen
rdHelper seen (x:xs)
| x `elem` seen = rdHelper seen xs
| otherwise = rdHelper (seen ++ [x]) xs
C’est plus ou moins la façon dont nub
est implémenté dans la bibliothèque standard (lisez le source ici ). La petite différence dans l'implémentation de nub
garantit qu'elle est non-strict , alors que removeDuplicates
ci-dessus est strict (il consomme la liste complète avant de revenir).
La récursivité primitive est en fait excessive ici, si vous n'êtes pas inquiet de la rigueur. removeDuplicates
peut être implémenté sur une seule ligne avec foldl
:
removeDuplicates2 = foldl (\seen x -> if x `elem` seen
then seen
else seen ++ [x]) []
Votre code et nub
ont O(N^2)
complexité.
Vous pouvez améliorer la complexité de O(N log N)
et éviter d'utiliser elem
en triant, en regroupant et en ne prenant que le premier élément de chaque groupe.
Conceptuellement,
rmdups :: (Ord a) => [a] -> [a]
rmdups = map head . group . sort
Supposons que vous commenciez par la liste [1, 2, 1, 3, 2, 4]
. En le triant, vous obtenez, [1, 1, 2, 2, 3, 4]
; en regroupant cela, vous obtenez, [[1, 1], [2, 2], [3], [4]]
; enfin, en prenant la tête de chaque liste, vous obtenez [1, 2, 3, 4]
.
La mise en œuvre complète de ce qui précède implique simplement d’élargir chaque fonction.
Notez que cela nécessite la contrainte Ord
plus forte sur les éléments de la liste et modifie également leur ordre dans la liste renvoyée.
Encore plus facile.
import Data.Set
mkUniq :: Ord a => [a] -> [a]
mkUniq = toList . fromList
Convertissez l'ensemble en une liste d'éléments dans O(n) time:
toList :: Set a -> [a]
Créez un ensemble à partir d'une liste d'éléments dans O (n log n) time:
fromList :: Ord a => [a] -> Set a
En python, ce ne serait pas différent.
def mkUniq(x):
return list(set(x)))
Identique à la solution de @ scvalex, les éléments suivants ont une complexité O(n * log n)
et une dépendance Ord
. Contrairement à cela, il conserve l'ordre, en gardant les premières occurrences d'éléments.
import qualified Data.Set as Set
rmdups :: Ord a => [a] -> [a]
rmdups = rmdups' Set.empty where
rmdups' _ [] = []
rmdups' a (b : c) = if Set.member b a
then rmdups' a c
else b : rmdups' (Set.insert b a) c
Comme vous pouvez le constater, les résultats de l’indice de référence prouvent que cette solution est la plus efficace ... Vous pouvez trouver la source de cet indicateur de référence ici .
Utiliser récursion-schémas :
import Data.Functor.Foldable
dedup :: (Eq a) => [a] -> [a]
dedup = para pseudoalgebra
where pseudoalgebra Nil = []
pseudoalgebra (Cons x (past, xs)) = if x `elem` past then xs else x:xs
Bien que cela soit certainement plus avancé, je pense que c'est assez élégant et montre certains paradigmes de programmation fonctionnels intéressants.
Graham Hutton a une fonction rmdups
sur p. 86 de Programmer en Haskell . Cela préserve l'ordre. C'est comme suit.
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : filter (/= x) (rmdups xs)
rmdups "maximum-minimum"
"maxiu-n"
Cela me dérangeait jusqu'à ce que je voie la fonction de Hutton. Ensuite, j'ai essayé, encore. Il existe deux versions, la première conserve la dernière copie, la seconde conserve la première.
rmdups ls = [d|(z,d)<- Zip [0..] ls, notElem d $ take z ls]
rmdups "maximum-minimum"
"maxiu-n"
Si vous voulez utiliser le premier et non le dernier élément en double de la liste, changez simplement take
en drop
dans la fonction et remplacez l'énumération Zip [0..]
en Zip [1..]
.
Il est trop tard pour répondre à cette question, mais je souhaite partager ma solution qui est originale sans utiliser elem
et ne présume pas Ord
.
rmdups' :: (Eq a) => [a] -> [a]
rmdups' [] = []
rmdups' [x] = [x]
rmdups' (x:xs) = x : [ k | k <- rmdups'(xs), k /=x ]
Cette solution supprime les doublons à la fin de l’entrée, tandis que l’implémentation de la question supprime au début. Par exemple,
rmdups "maximum-minimum"
-- "ax-nium"
rmdups' "maximum-minimum"
-- ""maxiu-n"
De plus, cette complexité de code est O (N * K), où N est la longueur de la chaîne et K le nombre de caractères uniques dans la chaîne. N> = K donc, il s'agira de O (N ^ 2) dans le pire des cas, mais cela signifie qu'il n'y a pas de répétition dans la chaîne et que ce n'est pas comme si vous essayez de supprimer les doublons dans la chaîne.
Vous pouvez également utiliser cette fonction de compression.
cmprs ::Eq a=>[a] -> [a]
--cmprs [] = [] --not necessary
cmprs (a:as)
|length as == 1 = as
|a == (head as) = cmprs as
|otherwise = [a]++cmprs as