web-dev-qa-db-ja.com

elemなしでHaskellのリストから重複を削除する

リストから重複を削除する関数を定義しようとしています。これまでのところ、実用的な実装があります:

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

ただし、elemを使用せずにこれをやり直したいと思います。これに最適な方法は何でしょうか?

nubnubByではなく、独自の関数を使用してこれを行いたいです。

26
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が標準ライブラリに実装される方法です(ソースを読む here )。 nubの実装のわずかな違いにより、 non-strict になりますが、上記のremoveDuplicatesは厳密です(戻る前にリスト全体を消費します)。

厳密性について心配していないのであれば、ここではプリミティブな再帰は実際には過剰です。 removeDuplicatesは、foldlを使用して1行で実装できます。

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

コードと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制約が必要であり、返されるリスト内の順序も変更することに注意してください。

53
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

pythonでは違いはありません。

def mkUniq(x): 
   return list(set(x)))
39
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

ご覧のように、ベンチマーク結果はこのソリューションが最も効果的であることを証明しています。このベンチマークのソースを見つけることができます こちら

25
Nikita Volkov

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

これは確かに高度ですが、非常にエレガントであり、価値のある関数型プログラミングのパラダイムをいくつか示していると思います。

1
user8174234

この質問に答えるのは遅すぎますが、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)になりますが、これは文字列に繰り返しがないことを意味し、文字列の重複を削除しようとするため、これは異なります。

この圧縮機能も使用できます。

cmprs ::Eq a=>[a] -> [a]
--cmprs [] = [] --not necessary
cmprs (a:as) 
    |length as == 1 = as
    |a == (head as) = cmprs as
    |otherwise = [a]++cmprs as
1
mrkanet

Graham Huttonにはpにrmdups関数があります。 86のHaskellでのプログラミング。順序を保持します。以下の通りです。

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

「maxiu-n」

ハットンの機能を見るまで、これは私を悩ませていました。それから、もう一度試しました。 2つのバージョンがあります。最初のバージョンは最後の複製を保持し、2番目のバージョンは最初の複製を保持します。

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

「maxiu-n」

あなたがしようとしているように、リストの最後の重複要素ではなく最初の要素を取得したい場合は、関数のtakedropに変更し、列挙体Zip [0..]からZip [1..]

0
fp_mora