通过折叠从无限列表中删除连续的重复项?

时间:2018-12-08 17:27:24

标签: haskell fold infinite-sequence

考虑以下这些功能的实现之一,以从列表中删除连续的重复项:

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

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

现在考虑使用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来解决的问题,还是天生就不可能使用折叠来完成此任务?

3 个答案:

答案 0 :(得分:3)

您可以将元素的删除转移到尾部,因此无论值如何,我们都首先使用“ yield x,然后使用函数(此处{{1 }})评估列表的结尾:

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] 后跟0,则不会发出1

1

答案 1 :(得分:1)

您的第一个代码段产生了第一个x,而第三个代码段产生了最后一个x,这就是差异的原因。

为了如实地将第一个代码片段正确地呈现,我们将其折叠成函数,以便传递状态参数,偶尔将其更新为列表的新唯一元素:

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

这实际上跳过了重复项,而不是重新进行重复,然后在其他两个答案相同的情况下一遍又一遍地忽略了它们,它们实际上是等效的:dropWhile只能跳过不超过一个元素,随后的以下reducer调用 its dropWhile (==x)将跳过该元素。

我始终将r用作化简器的第二个参数的名称,作为“ r 递归 r esult”的助记符。在这里,r的定义中g之后还有另一个参数表示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 :(得分:1)

您可以仅合并您的实现:

sc = SparkContext.getOrCreate()

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

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