it-swarm.dev

Duplikate aus einer Liste in Haskell entfernen

Ich versuche, eine Funktion zu definieren, die Duplikate aus einer Liste entfernt. Bisher habe ich eine funktionierende Implementierung:

rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs)   | x `elem` xs   = rmdups xs
                | otherwise     = x : rmdups xs

Ich möchte dies jedoch ohne elem überarbeiten. Was wäre die beste Methode dafür?

Ich möchte dies mit meiner eigenen Funktion tun und nicht nub oder nubBy.

25
BradStevenson

Ich glaube nicht, dass Sie dies ohne elem (oder Ihre eigene Implementierung) tun können.

Es gibt jedoch ein semantisches Problem bei Ihrer Implementierung. Wenn Elemente dupliziert werden, behalten Sie das last. Ich persönlich würde davon ausgehen, dass es das erste Duplikat behält und den Rest fallen lässt.

*Main> rmdups "abacd"
"bacd"

Die Lösung besteht darin, die gesehenen Elemente als Zustandsvariable durchzuziehen.

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

Dies ist mehr oder weniger so, wie nub in der Standardbibliothek implementiert ist (lesen Sie die Quelle hier ). Der kleine Unterschied in nubs Implementierung gewährleistet, dass es nicht-streng ist, während removeDuplicates oben streng ist (es verbraucht die gesamte Liste, bevor es zurückgegeben wird).

Primitive Rekursion ist hier eigentlich übertrieben, wenn Sie sich keine Sorgen um die Strenge machen. removeDuplicates kann mit foldl in einer Zeile implementiert werden:

removeDuplicates2 = foldl (\seen x -> if x `elem` seen
                                      then seen
                                      else seen ++ [x]) []
21

Sowohl Ihr Code als auch nub haben O(N^2) Komplexität.

Sie können die Komplexität von O(N log N) verbessern und die Verwendung von elem vermeiden.

Konzeptionell

rmdups :: (Ord a) => [a] -> [a]
rmdups = map head . group . sort

Angenommen, Sie beginnen mit der Liste [1, 2, 1, 3, 2, 4]. Wenn Sie es sortieren, erhalten Sie [1, 1, 2, 2, 3, 4]; Wenn Sie das gruppieren, erhalten Sie [[1, 1], [2, 2], [3], [4]]; Wenn Sie den Kopf jeder Liste nehmen, erhalten Sie schließlich [1, 2, 3, 4].

Die vollständige Implementierung des Vorstehenden beinhaltet lediglich die Erweiterung jeder Funktion.

Beachten Sie, dass dies die stärkere Ord-Einschränkung für die Elemente der Liste erfordert und auch deren Reihenfolge in der zurückgegebenen Liste ändert.

51
scvalex

Sogar einfacher.

import Data.Set 
mkUniq :: Ord a => [a] -> [a]
mkUniq = toList . fromList

Konvertieren Sie den Satz in eine Liste von Elementen in O(n) time:

toList :: Set a -> [a]

Erstellen Sie einen Satz aus einer Liste von Elementen in O (n log n) time:

fromList :: Ord a => [a] -> Set a

In Python wäre es nicht anders.

def mkUniq(x): 
   return list(set(x)))
37
The Internet

Wie bei der Lösung von @ scvalex hat das Folgende eine O(n * log n)-Komplexität und eine Ord-Abhängigkeit. Im Unterschied dazu behält es die Reihenfolge bei und bewahrt die ersten Vorkommen von Gegenständen auf.

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

Benchmark-Ergebnisse

benchmark results

Wie Sie sehen, beweisen die Benchmark-Ergebnisse, dass diese Lösung die effektivste ist. Sie finden die Quelle dieses Benchmarks hier .

24
Nikita Volkov

Verwenden von Rekursionsschemata :

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

Obwohl dies sicherlich fortgeschrittener ist, denke ich, dass es ziemlich elegant ist und einige sinnvolle Funktionsprogrammierungsparadigmen zeigt.

1
user8174234

Graham Hutton hat eine rmdups-Funktion auf p. 86 von Programmierung in Haskell . Es bewahrt die Ordnung. Es ist wie folgt.

rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : filter (/= x) (rmdups xs)
rmdups "maximum-minimum"

"maxiu-n"

Das störte mich, bis ich Huttons Funktion sah. Dann versuchte ich es erneut. Es gibt zwei Versionen: Die erste enthält das letzte Duplikat, die zweite die erste.

rmdups ls = [d|(z,d)<- Zip [0..] ls, notElem d $ take z ls]
rmdups "maximum-minimum"

"maxiu-n"

Wenn Sie die ersten und nicht die letzten Elemente der Liste übernehmen möchten, ändern Sie einfach take in drop in der Funktion und ändern Sie die Aufzählung Zip [0..] in Zip [1..].

0
fp_mora

Es ist zu spät, um diese Frage zu beantworten, aber ich möchte meine ursprüngliche Lösung ohne elem teilen und nicht Ord annehmen.

rmdups' :: (Eq a) => [a] -> [a]
rmdups' [] = []
rmdups' [x] = [x]
rmdups' (x:xs) = x : [ k  | k <- rmdups'(xs), k /=x ]

Diese Lösung entfernt Duplikate am Ende der Eingabe, während die Implementierung von Fragen am Anfang gelöscht wird. Zum Beispiel,

rmdups "maximum-minimum"
-- "ax-nium"

rmdups' "maximum-minimum"
-- ""maxiu-n"

Diese Code-Komplexität ist auch O (N * K), wobei N die Länge der Zeichenfolge und K die Anzahl der eindeutigen Zeichen in der Zeichenfolge ist. N> = K, im ungünstigsten Fall ist dies also O (N ^ 2). Dies bedeutet jedoch, dass der String keine Wiederholung ist.

Sie können diese Komprimierungsfunktion auch verwenden. 

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