【问题标题】:Remove consecutive duplicates from an infinite list via folding?通过折叠从无限列表中删除连续重复项?
【发布时间】:2019-05-10 03:17:50
【问题描述】:

考虑从列表中删除连续重复项的函数的以下实现之一:

uniq :: Eq a => [a] -> [a]
uniq [] = []
uniq (x:xs) = x:uniq (dropWhile (x ==) xs)
uniq :: Eq a => [a] -> [a]
uniq (x:xs@(y:_))
 | x == y    = x:tail (uniq xs)
 | otherwise = x:uniq xs
uniq l = l

它们在有限和无限列表上都按预期工作。更具体地说,对于无限列表,只要在返回 n 值之前没有无限长的重复值序列,我希望 take n $ uniq l 终止。

现在考虑这个函数的尝试,使用foldr

uniq :: (Foldable t, Eq a) => t a -> [a]
uniq = foldr uniqHelper []
 where uniqHelper x [] = [x]
       uniqHelper x acc@(y:_)
        | x == y    =   acc
        | otherwise = x:acc

这在有限列表上正常工作,但永远不会在无限列表中终止,因为uniqHelper 总是需要评估它的第二个参数。这是可以通过更聪明的uniqHelper 来解决的问题,还是本来就不可能使用折叠来完成这项任务?

【问题讨论】:

  • “按预期工作”是指uniq (let ones = 1:ones in ones) 永远不会终止吗?您的第一个只有在找到一对不同的相邻元素时才会终止。
  • @chepner 很好。我希望head $ uniq $ repeat 1 确实返回1。不过,接受的答案可以正确处理。
  • @chepner 我的非折叠示例现在适用于这种情况。
  • foldl 应该更好,但你也可以像map head . group 一样使用无限列表。
  • 不像foldl'foldl是懒惰的,所以我想终止应该没有问题。

标签: haskell fold infinite-sequence


【解决方案1】:

您的第一个 sn-p 产生了许多中的第一个 x,但第三个 sn-p 产生了最后一个 x,这就是差异的原因。

为了忠实地将第一个 sn-p 渲染为正确的折叠,我们折叠成函数,以便我们可以传递一个状态参数,偶尔更新为列表的新唯一元素:

uniq [] = []
uniq (x:xs) = x : foldr g (const []) xs x
  where
  g y r x | y==x  =     r x
      | otherwise = y : r y

这实际上跳过了重复项,而不是像其他两个答案一样一遍又一遍地忽略它们,这实际上是彼此等效的:dropWhile 只能跳过不超过一个元素,然后会被以下调用 its dropWhile (==x) 的 reducer 跳过。

我总是使用r 作为reducer 的第二个参数的名称,作为“recursive result”的助记符。这里在g 的定义中r 之后的另一个参数的存在意味着r 是一个函数,我们可以传递一个更新的值作为状态。

此技术允许使用 foldr 对有状态的计算进行编码,例如 takedropdropWhiletakeWhileetc.

> head $ uniq $ repeat 1
1

> take 10 $ uniq [n | n <- [1..], n <- replicate n n]
[1,2,3,4,5,6,7,8,9,10]

> uniq [1..10]
[1,2,3,4,5,6,7,8,9,10]

【讨论】:

    【解决方案2】:

    您可以组合您的实现:

    uniq :: Eq a => [a] -> [a]
    uniq = foldr uniqHelper []
      where uniqHelper x acc = x : dropWhile (==x) acc
    

    在无限列表上获得所需的行为:

    Prelude> take 5 $ uniq [1..]
    [1,2,3,4,5]
    

    【讨论】:

    • uniq = foldr uniq' [] where uniq' x acc = x : dropWhile (== x) acc
    【解决方案3】:

    你可以将一个元素的移除转移到尾部,所以不管值如何,我们先“yieldx,然后我们使用一个函数(这里是tl)来评估列表的尾部:

    uniq :: (Foldable t, Eq a) => t a -> [a]
    uniq = foldr uniqHelper []
     where uniqHelper x acc = x : tl acc
               where tl acc@(x2:xs) | x == x2 = xs
                                    | otherwise = acc
                     tl [] = []

    因此我们首先产生x,然后再考虑下一个元素。如果第二个参数(列表尾部的折叠列表)包含相同的元素,则我们“删除”它,否则,我们保留整个列表。

    上面也可以产生repeat的第一个元素,例如:

    Prelude> head (uniq (repeat 1))
    1
    Prelude> take 5 $ uniq [1..]
    [1,2,3,4,5]
    Prelude> uniq [1,1,1,1,2,2,2,1,1,1,1,1,1,3,3,3,3,1,1]
    [1,2,1,3,1]
    

    当然,如果有无限数量的0s 后跟1,则永远不会发出1

    Prelude> uniq (repeat 0 ++ [1])
    [0
    

    【讨论】:

      猜你喜欢
      • 2014-04-19
      • 2015-02-13
      • 2021-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-23
      • 2016-01-12
      • 1970-01-01
      相关资源
      最近更新 更多