it-swarm.com.ru

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

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

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

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

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

25
BradStevenson

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

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

*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 гарантирует, что он не является строгим , в то время как removeDuplicates является строгим (он потребляет весь список перед возвратом).

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

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

И ваш код, и 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 для элементов списка, а также изменяется их порядок в возвращаемом списке.

51
scvalex

Еще проще.

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

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

toList :: Set a -> [a]

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

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

В питоне это было бы не иначе.

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

Как и в случае решения @ 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

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

24
Nikita Volkov

Использование рекурсивных схем :

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

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

1
user8174234

Грэм Хаттон имеет функцию 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..].

0
fp_mora

Слишком поздно ответить на этот вопрос, но я хочу поделиться своим оригинальным решением без использования 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), но это означает, что в строке нет повторений, и это не похоже на то, что вы пытаетесь удалить дубликаты в строке.

0
Muhammed Hasan Celik

Вы также можете использовать эту функцию сжатия. 

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