从Haskell中的列表中删除重复项

Bra*_*son 26 recursion haskell list

我正在尝试定义一个将从列表中删除重复项的函数.到目前为止,我有一个有效的实现:

rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs)   | x `elem` xs   = rmdups xs
                | otherwise     = x : rmdups xs
Run Code Online (Sandbox Code Playgroud)

但是我想在不使用的情况下重做这件事elem.什么是最好的方法?

我想用我自己的功能而不是nub或者这样做nubBy.

scv*_*lex 54

无论你的代码,并nub具有O(N^2)复杂性.

您可以通过排序,分组和仅采用每个组的第一个元素来提高复杂性O(N log N)并避免使用elem.

从概念上讲,

rmdups :: (Ord a) => [a] -> [a]
rmdups = map head . group . sort
Run Code Online (Sandbox Code Playgroud)

假设您从列表开始[1, 2, 1, 3, 2, 4].通过排序,你得到,[1, 1, 2, 2, 3, 4]; 通过分组,你得到,[[1, 1], [2, 2], [3], [4]]; 最后,通过取每个列表的头,你得到[1, 2, 3, 4].

上述的完整实现仅涉及扩展每个功能.

请注意,这需要对Ord列表元素具有更强的约束,并且还会在返回的列表中更改它们的顺序.

  • 非常好,但请注意,这会对列表元素设置一个"Ord"限制,而不仅仅是"Eq",并且不会保留顺序. (3认同)
  • [`ordNub`](https://github.com/nh2/haskell-ordnub)提供了@ scvalex建议使用'Ord`的复制粘贴准备,*稳定*(订单保留)变体.它还包含`\\`,`union`和`intersect`的类似物. (3认同)

The*_*net 40

更容易.

import Data.Set 
mkUniq :: Ord a => [a] -> [a]
mkUniq = toList . fromList
Run Code Online (Sandbox Code Playgroud)

O(n)时间内将集合转换为元素列表:

toList :: Set a -> [a]
Run Code Online (Sandbox Code Playgroud)

O(n log n)时间内的元素列表创建一个集合:

fromList :: Ord a => [a] -> Set a
Run Code Online (Sandbox Code Playgroud)

在python中它也不例外.

def mkUniq(x): 
   return list(set(x)))
Run Code Online (Sandbox Code Playgroud)

  • 优雅,但我不认为 Set 保留了顺序。 (2认同)

Nik*_*kov 25

与@ 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
Run Code Online (Sandbox Code Playgroud)

基准测试结果

基准结果

如您所见,基准测试结果证明此解决方案是最有效的.您可以在此处找到此基准测试的来源.


Ben*_*son 22

我不认为你没有elem(或你自己重新实施它)就能做到这一点.

但是,您的实现存在语义问题.当元素重复时,你保留最后一个元素.就个人而言,我希望它保留第一个重复的项目并删除其余项目.

*Main> rmdups "abacd"
"bacd"
Run Code Online (Sandbox Code Playgroud)

解决方案是将"看到的"元素作为状态变量进行处理.

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
Run Code Online (Sandbox Code Playgroud)

这或多或少nub是在标准库中实现的(在此处阅读源代码).实现中的小差别nub确保它是非严格的,而removeDuplicates上面是严格的(它在返回之前消耗整个列表).

如果你不担心严格,原始递归在这里实际上是过度的.removeDuplicates可以在一行中实现foldl:

removeDuplicates2 = foldl (\seen x -> if x `elem` seen
                                      then seen
                                      else seen ++ [x]) []
Run Code Online (Sandbox Code Playgroud)

  • 我认为`removeDuplicates3 = foldr(\ x见 - >如果x \`elem \`看到然后看到别的x:see)[]`运行得比removeDuplicates2快,因为`(:)`操作是常量. (7认同)