Подтвердить что ты не робот

Удаление дубликатов из списка в Haskell

Я пытаюсь определить функцию, которая удалит дубликаты из списка. Пока у меня есть рабочая реализация:

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

Однако я хотел бы переработать это без использования elem. Какой был бы лучший способ для этого?

Я хотел бы сделать это, используя мою собственную функцию, а не nub или nubBy.

4b9b3361

Ответ 1

Я не думаю, что вы сможете сделать это без elem (или вашей собственной повторной реализации).

Однако с вашей реализацией существует семантическая проблема. Когда элементы дублируются, вы сохраняете последний. Лично я ожидаю, что он сохранит первый дубликат и оставит остальные.

*Main> rmdups "abacd"
"bacd"

Решение состоит в том, чтобы нарезать "видимые" элементы через переменную состояния.

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

Это больше или меньше, чем nub реализовано в стандартной библиотеке (читайте источник здесь). Небольшая разница в реализации nub гарантирует, что она non-strict, а removeDuplicates выше строго (она возвращает весь список перед возвратом).

Примитивная рекурсия на самом деле переполнена здесь, если вас не беспокоит строгость. removeDuplicates может быть реализована в одной строке с помощью foldl:

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

Ответ 2

Оба кода и nub имеют сложность O(N^2).

Вы можете улучшить сложность O(N log N) и не использовать elem путем сортировки, группировки и принятия только первого элемента каждой группы.

Концептуально,

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

Предположим, вы начинаете со списка [1, 2, 1, 3, 2, 4]. Сортируя его, вы получите [1, 1, 2, 2, 3, 4]; сгруппировав это, вы получите [[1, 1], [2, 2], [3], [4]]; наконец, взяв главу каждого списка, вы получите [1, 2, 3, 4].

Полная реализация вышеуказанного просто включает в себя расширение каждой функции.

Обратите внимание, что для этого требуется более сильное ограничение Ord для элементов списка, а также изменение их порядка в возвращенном списке.

Ответ 3

Еще проще.

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

Преобразование набора в список элементов в O (n) времени:

toList :: Set a -> [a]

Создайте набор из списка элементов в O (n log n) времени:

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

В python это ничем не отличается.

def mkUniq(x): 
   return list(set(x)))

Ответ 4

То же, что и решение @scvalex, имеет сложность O(n * log n) и зависимость Ord. В отличии от него он сохраняет порядок, сохраняя первые вхождения элементов.

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 results

Как вы можете видеть, результаты тестов доказывают, что это решение является наиболее эффективным. Здесь вы можете найти источник этого эталона .

Ответ 5

Использование recursion-schemes:

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

Хотя это, безусловно, более продвинутый, я думаю, что он довольно изящный и демонстрирует некоторые полезные парадигмы функционального программирования.

Ответ 6

... или используя объединение функций из Data.List, применяемое к самому себе:

import Data.List

unique x = union x x

Ответ 7

Слишком поздно ответить на этот вопрос, но я хочу поделиться своим оригинальным решением без использования elem и не предполагать Ord.

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

Это решение удаляет дубликаты в конце ввода, в то время как реализация вопроса удаляется в начале. Например,

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

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

Кроме того, эта сложность кода - O (N * K), где N - длина строки, а K - количество уникальных символов в строке. N >= K, таким образом, это будет O (N ^ 2) в худшем случае, но это означает, что в строке нет повторения, и это не похоже на то, что вы пытаетесь удалить дубликаты в строке.

Ответ 8

Грэм Хаттон имеет функцию rmdups на p. 86 программирования в Haskell. Он сохраняет порядок. Это так.

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

"Maxiu-п"

Это беспокоило меня, пока я не увидел функцию Хаттона. Затем я снова попытался. Есть две версии: первая хранит последний дубликат, второй - первый.

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

"Maxiu-п"

Если вы хотите взять первый и не последний дублирующие элементы списка, как вы пытаетесь сделать, просто изменить take на drop в функции и изменить нумерацию zip [0..] на zip [1..].