【问题标题】:Haskell function to keep the repeating elements of a listHaskell 函数保留列表的重复元素
【发布时间】:2019-05-03 08:42:48
【问题描述】:

这是预期的输入/输出:

重复“密西西比”==“ips”

重复 [1,2,3,4,2,5,6,7,1] == [1,2]

重复“”==“”

到目前为止,这是我的代码:

repeated :: String -> String
repeated "" = "" 
repeated x = group $ sort x 

我知道代码的最后一部分不起作用。我正在考虑对列表进行排序然后对其进行分组,然后我想对大于 1 的列表列表进行过滤,或者类似的东西。

【问题讨论】:

  • 到目前为止听起来不错。你有什么问题吗?
  • 不知道如何过滤list的列表,总是报错。我正在考虑使用这个:filter (\x -> length x > 1) 或类似的东西。
  • 这是第 1 步。之后您还需要做一些事情(如果您从 repeated 中删除(顺便说一句不正确的)类型签名,您可以看到到目前为止您所拥有的东西) .

标签: list sorting haskell filter


【解决方案1】:

您的代码已经完成了一半的工作

> group $ sort "Mississippi"
["M","iiii","pp","ssss"]

您说过要过滤掉非重复项。让我们定义一个谓词来标识至少包含两个元素的列表:

atLeastTwo :: [a] -> Bool
atLeastTwo (_:_:_) = True
atLeastTwo _       = False

使用这个:

> filter atLeastTwo . group $ sort "Mississippi"
["iiii","pp","ssss"]

很好。现在,我们只需要从这些列表中取出第一个元素。由于列表非空,我们可以安全地使用head

> map head . filter atLeastTwo . group $ sort "Mississippi"
"ips"

或者,我们可以将过滤器替换为filter (\xs -> length xs >= 2),但这会降低效率。

另一个选择是使用列表推导

> [ x | (x:_y:_) <- group $ sort "Mississippi" ]
"ips"

此模式匹配以x 开头并至少具有另一个元素_y 的列表,将过滤器与获取头部相结合。

【讨论】:

  • 很好的答案,但对group 来说似乎有点过分了:q 只关心元素是否重复,而不关心重复的次数。然后,sort 整个列表似乎也有点矫枉过正:它可以在获得想要的元素后进行排序(而且会少得多)。类似于nub 的内容是否需要更少的列表遍历?
  • 虽然排序确实是 O(n lg n),但对未排序列表的线性遍历将需要一些其他数据结构来跟踪所有可能的重复项,并且每个中的查找将添加到至少空间复杂度。虽然可以使用精心挑选的数据结构来维持总体 O(n) 运行时间,但该常数可能会使算法在实践中比 O(n lg n) 解决方案更慢,同时大大复杂化实现。
  • @chepner 我不明白:map head . filter p . group 对于任何p 都应该始终是安全的,因为group 从不生成空列表。 (不)存在重复项不应影响此代码中map head 的安全性。我是否忽略了一些案例?
  • 不,我只是将空列表与包含空列表的列表混淆了。
【解决方案2】:

好的,好的开始。一个直接的问题是规范要求该函数处理数字列表,但您为字符串定义它。列表必须排序,因此其元素必须具有类型类Ord。因此,让我们修复类型签名:

repeated :: Ord a => [a] -> [a]

调用sortgroup 后,您将获得一个列表列表[[a]]。让我们看看你使用filter 的想法。这样可行。正如您所说,您的谓词应该检查列表中每个列表的 length,然后将 length 与 1 进行比较。

过滤列表列表会为您提供一个子集,它是另一个列表列表,类型为[[a]]。您需要展平此列表。您想要做的是map 列表列表中的每个条目到其元素之一。比如第一个。 Prelude 中有一个函数可以做到这一点。

所以,你可以填写以下骨架:

module Repeated (repeated) where

import Data.List (group, sort)

repeated :: Ord a => [a] -> [a]
repeated = map _
         . filter (\x -> _)
         . group
         . sort 

我用过滤谓词作为 lambda 表达式以无点风格编写此代码,但许多其他编写方式也同样出色。找一个你喜欢的! (例如,您还可以将filter 谓词写成无点样式,作为两个函数的组合:比较length 的结果。)

当您尝试编译它时,编译器会告诉您有两个类型化的孔,即等号右侧的_ 条目。它还会告诉您孔的类型。第一个洞需要一个函数,它接受一个列表并返回一个元素。第二个洞需要使用x 的布尔表达式。正确填写这些内容,您的程序就可以运行了。

【讨论】:

    【解决方案3】:

    这里有一些其他方法,用于评估 @chepner 对使用 group $ sort 的解决方案的评论。 (这些解决方案看起来更简单,因为一些复杂性隐藏在库例程中。)

    虽然排序确实是 O(n lg n), ...

    不仅仅是排序,尤其是group: 使用span,它们都构建和销毁临时列表。 IE。他们这样做:

    未排序列表的线性遍历将需要一些其他数据结构来跟踪所有可能的重复项,并且每个中的查找至少会增加空间复杂度。虽然精心选择的数据结构可用于维持整体 O(n) 运行时间,但该常数可能会使算法在实践中比 O(n lg n) 解决方案慢,...

    group/span 大大增加了这种复杂性,因此 O(n lg n) 不是正确的度量。

    同时极大地复杂化了实现。

    以下都只遍历输入列表一次。是的,他们建立了辅助列表。 (可能Set 会提供更好的性能/更快的查找。)它们可能看起来更复杂,但要将苹果与苹果进行比较,还请查看group/span 的代码。

    repeated2, repeated3, repeated4 :: Ord a => [a] -> [a]
    
    • repeated2/inserter2 构建一个对 [(a, Bool)] 的辅助列表,其中 BoolTrue 如果 a 出现多次,False 如果到目前为止只出现一次。

      repeated2 xs = sort $ map fst $ filter snd $ foldr inserter2 [] xs
      
      inserter2 :: Ord a => a -> [(a, Bool)] -> [(a, Bool)]
      inserter2 x [] = [(x, False)]
      inserter2 x (xb@(x', _): xs)
          | x == x'   = (x', True): xs
          | otherwise = xb: inserter2 x xs
      
    • repeated3/inserter3 构建了一个对[(a, Int)] 的辅助列表,其中Int 计算出现了多少a。无论如何,辅助列表都是排序的,只是为了它。

      repeated3 xs = map fst $ filter ((> 1).snd) $ foldr inserter3 [] xs
      
      inserter3 :: Ord a => a -> [(a, Int)] -> [(a, Int)]
      inserter3 x [] = [(x, 1)]
      inserter3 x xss@(xc@(x', c): xs) = case x `compare` x' of
          { LT -> ((x, 1): xss)
          ; EQ -> ((x', c+1): xs)
          ; GT -> (xc: inserter3 x xs)
          }
      
    • repeated4/go4 构建一个已知重复元素的输出列表。它在遍历输入列表时维护了一个中间元素列表(到目前为止)。如果遇到重复:它将该元素添加到输出列表中;从中间列表中删除它;从输入列表的尾部过滤掉该元素。

      repeated4 xs = sort $ go4 [] [] xs
      
      go4 :: Ord a => [a] -> [a] -> [a] -> [a]
      go4 repeats _ [] = repeats
      go4 repeats onces (x: xs) = case findUpd x onces of
          { (True, oncesU) -> go4 (x: repeats) oncesU (filter (/= x) xs)
          ; (False, oncesU) -> go4 repeats oncesU xs
          }
      
      findUpd :: Ord a => a -> [a] -> (Bool, [a])
      findUpd x [] = (False, [x])
      findUpd x (x': os) | x == x' = (True, os)      -- i.e. x' removed
                         | otherwise =
                           let (b, os') = findUpd x os in (b, x': os')
      

      findUpd 中的最后一点列表摆弄与span 非常相似。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-21
      • 2021-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多