【问题标题】:unique elements in a haskell listhaskell 列表中的唯一元素
【发布时间】:2023-03-31 12:39:01
【问题描述】:

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

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) 这就是我们想要的!

标签: list haskell


【解决方案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,从来没有想过要搜索我正在寻找的类型签名
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-04-12
        • 2012-01-09
        • 2011-05-23
        • 1970-01-01
        • 2022-12-03
        • 1970-01-01
        相关资源
        最近更新 更多