我正在尝试定义一个从列表中删除重复项的函数。到目前为止,我有一个有效的实现:
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) | x `elem` xs = rmdups xs
| otherwise = x : rmdups xs
但是我想在不使用elem
. 最好的方法是什么?
我想使用我自己的函数而不是nub
or来做到这一点nubBy
。
您的代码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)时间内将集合转换为元素列表:
toList :: Set a -> [a]
在O(n log n)时间内从元素列表创建一个集合:
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
如您所见,基准测试结果证明此解决方案是最有效的。您可以在此处找到此基准测试的来源。
我认为没有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
确保它是非严格的,而removeDuplicates
上面是严格的(它在返回之前消耗整个列表)。
如果您不担心严格性,这里的原始递归实际上是多余的。removeDuplicates
可以在一行中实现foldl
:
removeDuplicates2 = foldl (\seen x -> if x `elem` seen
then seen
else seen ++ [x]) []
现在回答这个问题为时已晚,但我想分享我的原创解决方案,不使用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) 但这意味着字符串中没有重复,这与您尝试删除字符串中的重复项不同。
Graham Huttonrmdups
在 p 上有一个函数。86 的Haskell 编程。它保持秩序。如下。
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : filter (/= x) (rmdups xs)
rmdups "maximum-minimum"
“maxiu-n”
这一直困扰着我,直到我看到赫顿的功能。然后,我又试了一次。有两个版本,第一个保留最后一个副本,第二个保留第一个。
rmdups ls = [d|(z,d)<- zip [0..] ls, notElem d $ take z ls]
rmdups "maximum-minimum"
“maxiu-n”
如果您想获取列表的第一个而不是最后一个重复元素,就像您尝试做的那样,只需在函数中更改take
为drop
并将枚举更改zip [0..]
为zip [1..]
.
我想在@fp_mora 的回答中补充说,在 Haskell 编程的第 136 页上,还有另一个略有不同的实现:
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x : xs) = x : rmdups (filter (/= x) xs)
我更容易把头绕在这个上面。
您也可以使用此压缩功能。
cmprs ::Eq a=>[a] -> [a]
--cmprs [] = [] --not necessary
cmprs (a:as)
|length as == 1 = as
|a == (head as) = cmprs as
|otherwise = [a]++cmprs as
使用递归方案:
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
虽然这肯定更先进,但我认为它非常优雅,并展示了一些有价值的函数式编程范例。
使用 dropWhile 也可以,但请记住在使用它之前对列表进行排序
rmdups :: (Eq a) => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : (rmdups $ dropWhile (\y -> y == x) xs)
remove_duplicates (x:xs)
| xs == [] = [x]
| x == head (xs) = remove_duplicates xs
| otherwise = x : remove_duplicates xs
你可以尝试这样做。我只是用我自己的实现替换了“elem”。这个对我有用。
...或通过使用来自 Data.List 的函数 union 应用于自身:
import Data.List
unique x = union x x