web-dev-qa-db-fra.com

Supprimer les doublons d'une liste dans Haskell

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.

25
BradStevenson

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]) []
21
Benjamin Hodgson

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.

51
scvalex

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)))
37
The Internet

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

Résultats de référence

benchmark results

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 .

24
Nikita Volkov

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.

1
user8174234

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..].

0
fp_mora

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.

0

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
0
mrkanet