haskell 列表中的唯一元素

Posted

技术标签:

【中文标题】haskell 列表中的唯一元素【英文标题】:unique elements in a haskell list 【发布时间】:2011-03-07 02:50:02 【问题描述】:

好的,这可能会在前奏中出现,但是:是否有标准库函数用于查找列表中的唯一元素?为了澄清,我的(重新)实现是:

has :: (Eq a) => [a] -> a -> Bool
has [] _ = False
has (x:xs) a
  | x == a    = True
  | otherwise = has xs a

unique :: (Eq a) => [a] -> [a]
unique [] = []
unique (x:xs)
  | has xs x  = unique xs
  | otherwise = x : unique xs

【问题讨论】:

你的has也是标准的;只是flip elem 甚至has xs = (`elem` xs) @yatima2975 你为什么使用elem 作为中缀? @dopatraman 因为elem 的类型为Eq a => a -> [a] -> Bool,所以将其用作中缀操作部分会使xs 成为第二个参数。 (`elem` xs) 被取消为 (\x -> elem x xs) 这就是我们想要的! 【参考方案1】:

我认为这样就可以了。

unique [] = []
unique (x:xs) = x:unique (filter ((/=) x) xs)

【讨论】:

【参考方案2】:

Data.List 中的 nub 函数(不,它实际上不在 Prelude 中)确实可以满足您的需求,但它与您的 unique 函数并不完全相同。它们都保留了元素的原始顺序,但unique 保留了最后一个 每个元素的出现次数,而nub 保留第一次出现。

你可以这样做让nub 的行为与unique 完全一样,如果这很重要的话(虽然我觉得它不重要):

unique = reverse . nub . reverse

另外,nub 仅适用于小型列表。 它的复杂性是二次方的,所以如果您的列表可以包含数百个元素,它就会开始变慢。

如果您将类型限制为具有 Ord 实例的类型,则可以使其扩展得更好。 nub 的这种变体仍然保留了列表元素的顺序,但其复杂性是 O(n * log n)

import qualified Data.Set as Set

nubOrd :: Ord a => [a] -> [a] 
nubOrd xs = go Set.empty xs where
  go s (x:xs)
   | x `Set.member` s = go s xs
   | otherwise        = x : go (Set.insert x s) xs
  go _ _              = []

其实一直是proposednubOrdData.Set

【讨论】:

可以说最好将它作为一个集合而不是首先使用列表 老实说:nub 不适合任何列表。即使在有 2 个元素的列表中 nubOrd 也是 faster。 这有点像“地图筛”,类似于不纯的“哈希筛”。 函数类型签名中有错字。应该是Ord a。此外,我发现 nubOrd 出人意料地(或没有)在我的情况下并不比 nub 更好。它甚至更慢。可能是因为没有太多重复值(尽管引入了 nub 将运行时间减少了一半,但 nub ord 比仅 nub 慢了大约 20%)。 @alternative,通过保持项目的顺序,列表上的函数比集合上的等效函数更普遍有用。这可能非常重要。【参考方案3】:

Haskell 中创建唯一列表的算法:

data Foo = Foo  id_ :: Int
               , name_ :: String
                deriving (Show)

alldata = [ Foo 1 "Name"
          , Foo 2 "Name"
          , Foo 3 "Karl"
          , Foo 4 "Karl"
          , Foo 5 "Karl"
          , Foo 7 "Tim"
          , Foo 8 "Tim"
          , Foo 9 "Gaby"
          , Foo 9 "Name"
          ]

isolate :: [Foo] -> [Foo]
isolate [] = []
isolate (x:xs) = (fst f) : isolate (snd f)
  where
    f = foldl helper (x,[]) xs
    helper (a,b) y = if name_ x == name_ y
                     then if id_ x >= id_ y
                          then (x,b)
                          else (y,b)
                     else (a,y:b)

main :: IO ()
main = mapM_ (putStrLn . show) (isolate alldata)

输出:

Foo id_ = 9, name_ = "Name"
Foo id_ = 9, name_ = "Gaby"
Foo id_ = 5, name_ = "Karl"
Foo id_ = 8, name_ = "Tim"

【讨论】:

【参考方案4】:

另一种去除重复的方法:

unique :: [Int] -> [Int]
unique xs = [x | (x,y) <- zip xs [0..], x `notElem` (take y xs)]

【讨论】:

【参考方案5】:
import Data.Set (toList, fromList)
uniquify lst = toList $ fromList lst

【讨论】:

这会改变元素的顺序。【参考方案6】:

我认为 unique 应该返回在原始列表中只出现一次的元素列表;也就是说,原始列表中多次出现的任何元素都不应包含在结果中。

我可以建议一个替代定义,unique_alt:

    unique_alt :: [Int] -> [Int]
    unique_alt [] = []
    unique_alt (x:xs)
        | elem x ( unique_alt xs ) = [ y | y <- ( unique_alt xs ), y /= x ]
        | otherwise                = x : ( unique_alt xs )

以下是一些突出 unique_alt 和 unqiue 之间区别的示例:

    unique     [1,2,1]          = [2,1]
    unique_alt [1,2,1]          = [2]

    unique     [1,2,1,2]        = [1,2]
    unique_alt [1,2,1,2]        = []

    unique     [4,2,1,3,2,3]    = [4,1,2,3]
    unique_alt [4,2,1,3,2,3]    = [4,1]

【讨论】:

这实际上是 Data.List.Unique (唯一)的定义,尽管我个人从未遇到过该用例,而“仅包含一个重复项的壁球列表”功能是许多操作的基础。【参考方案7】:

我在Hoogle 上搜索了(Eq a) =&gt; [a] -&gt; [a]

第一个结果是nub(从列表中删除重复元素)。

Hoogle 很棒。

【讨论】:

另外,你可以像这样提供你自己的相等函数:nubBy :: (a -> a -> Bool) -> [a] -> [a] 如果 Bart 有时间,我们可能会看到一个 nubOrd,这在性能方面会更合理。 值得一提的是nub函数来自Data.List包。 @Thomas:Data.List.Unique 有 sortUniq,这是您请求的“nubOrd”。我宁愿有一个 (Eq a, Hashable a) => [a] -> [a] 这会更合理,性能方面...... 直到现在,我一直在努力理解如何有效地使用 Hoogle,从来没有想过要搜索我正在寻找的类型签名

以上是关于haskell 列表中的唯一元素的主要内容,如果未能解决你的问题,请参考以下文章

替换Haskell中的各个列表元素?

Haskell,树中的列表列表

Python 等效于 Haskell 的 [1..](索引列表)

Haskell 列表生成器

更新列表的第'x'个元素 - Haskell [重复]

随机化一个Haskell列表