【问题标题】:How can this function be written using foldr?如何使用 foldr 编写此函数?
【发布时间】:2012-10-14 22:54:11
【问题描述】:

我有一个简单的函数,它返回一个列表中相邻元素的对列表。

adjacents :: [a] -> [(a,a)]
adjacents (x:y:xs) = [(x,y)] ++ adjacents (y:xs)
adjacents (x:xs) = []

我在尝试使用 foldr 写入相邻时遇到问题。我已经做了一些研究,但似乎没有任何提示。怎么办?

【问题讨论】:

  • 用zip写,或者看看zip是怎么实现的。
  • (x,y) : adjacents (y:xs)[(x,y)] ++ adjacents (y:xs) 好,Marcin 说的对,zip 好:zip xs (tail xs) 清晰干净。
  • 折叠(以及mapfilter)是基本的递归方案——它们在每个步骤中只查看一个元素。正如 AndrewC 所示,您必须将输入“加倍”才能一次看到两个元素。有一个递归方案可以用来避免输入加倍——paramorphism。 Para 像折叠一样遍历输入,但也让您在递归步骤中查看“输入的其余部分”。不幸的是,para 不在 Prelude 或 Data.List 中。

标签: haskell recursion functional-programming fold higher-order-functions


【解决方案1】:

这种棘手的折叠通常可以通过让折叠构建一个函数而不是尝试直接构建结果来解决。

adjacents :: [a] -> [(a, a)]
adjacents xs = foldr f (const []) xs Nothing
  where f curr g (Just prev) = (prev, curr) : g (Just curr)
        f curr g Nothing     = g (Just curr)

这里的想法是让结果成为Maybe a -> [(a, a)] 类型的函数,其中Maybe 包含前一个元素,如果我们位于列表的开头,则为Nothing

让我们仔细看看这里发生了什么:

  1. 如果我们同时有前一个元素和一个当前元素,我们创建一对并将当前元素传递给递归的结果,递归的结果是生成列表尾部的函数。

    f curr g (Just prev) = (prev, curr) : g (Just curr)
    
  2. 如果没有前一个元素,我们只是将当前元素传递到下一步。

    f curr g Nothing     = g (Just curr)
    
  3. 列表末尾的基本情况const [] 只是忽略了前一个元素。

通过这种方式,结果和你原来的定义一样懒惰:

> adjacents (1 : 2 : 3 : 4 : undefined)
[(1,2),(2,3),(3,4)*** Exception: Prelude.undefined

【讨论】:

  • 这确实很好,但我仍然认为zip xs (tail xs) 要好得多。 zip 很懒惰,可以在无限列表上工作,所以放弃它并没有真正的好处。
  • @AndrewC:毫无疑问。但是,看看如何根据foldr 编写这些函数仍然具有指导意义。例如,zip 可以使用相同的技术实现。
  • 这让我意识到foldl 的标准实现技术在foldr 方面不仅仅是一个巧妙的技巧,而且实际上是一个有用的概念。谢谢!
【解决方案2】:

我认为您的函数不适合折叠,因为它查看两个元素而不是一个。

我认为解决问题的最佳方法是

adjacents [] = []
adjacents xs = zip xs (tail xs)

但是,如果您愿意,我们可以将它硬塞进一个滑稽的折叠中。首先是辅助功能。

prependPair :: a -> [(a,a)] -> [(a,a)]
prependPair x [] = [(x,b)]             where b = error "I don't need this value."
prependPair x ((y,z):ys) = ((x,y):(y,z):ys)

adjacents' xs = init $ foldr prependPair [] xs

我觉得我通过制造和投掷有点作弊 用错误值去掉最后一个元素,但是嘿嗬,我已经说过我不认为 foldr 是这样做的好方法,所以我想这个 hack 是它不是折叠的一个例子。

【讨论】:

  • 这种方法的问题在于,通过对prependPair 中的第二个参数进行模式匹配,这需要在生成任何结果之前一直对折叠进行评估,这也意味着它不再适用于无限列表。
  • @hammar 是的,这是一个糟糕的解决方案,我已经说过了! zip 是正确的解决方案。 foldr 是一个很好的函数,可以在适当的时候编写好的代码。这不是。
【解决方案3】:

您也可以尝试用unfoldr 代替foldr

import Data.List
adjacents xs = unfoldr f xs where
  f (x:rest@(y:_)) = Just ((x,y), rest)
  f _ = Nothing

【讨论】:

    猜你喜欢
    • 2011-09-04
    • 1970-01-01
    • 2021-08-13
    • 1970-01-01
    • 1970-01-01
    • 2019-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多