リストから重複を削除する関数を定義しようとしています。これまでのところ、実用的な実装があります:
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) | x `elem` xs = rmdups xs
| otherwise = x : rmdups xs
ただし、elem
を使用せずにこれをやり直したいと思います。これに最適な方法は何でしょうか?
nub
やnubBy
ではなく、独自の関数を使用してこれを行いたいです。
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]) []
コードと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
制約が必要であり、返されるリスト内の順序も変更することに注意してください。
さらに簡単に。
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)))
@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
ご覧のように、ベンチマーク結果はこのソリューションが最も効果的であることを証明しています。このベンチマークのソースを見つけることができます こちら 。
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
これは確かに高度ですが、非常にエレガントであり、価値のある関数型プログラミングのパラダイムをいくつか示していると思います。
この質問に答えるのは遅すぎますが、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
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」
あなたがしようとしているように、リストの最後の重複要素ではなく最初の要素を取得したい場合は、関数のtake
をdrop
に変更し、列挙体Zip [0..]
からZip [1..]
。