【问题标题】:How to use foldr to add variables to each other in a list?如何使用 foldr 在列表中相互添加变量?
【发布时间】:2020-07-12 06:26:33
【问题描述】:

当给定一个列表[x0, x1, x2, . . . , xn−1]时,函数 应该返回列表[y0, y1, y2, . . . , yn−1] 其中y0 = x0, y1 = x0 + x1, ...

所以如果你有 [1,2,3] 作为输入,你会得到 [1,3,6] 作为输出

我不完全理解foldr,所以如果我能得到一些帮助来尝试弄清楚如何更改最后一行以获得正确的答案。

scan :: [Integer] -> [Integer]
scan [] = []
scan [x] = [x]

scan (x:xs) = x : foldr (/y -> y (+) x) 0 (scan xs)

我的初始解决方案(有效)使用map 函数。

scan :: [Integer] -> [Integer]
scan [] = []
scan [x] = [x]

scan (x:xs) = x : map (+x) (scan xs)

【问题讨论】:

  • 为什么要使用foldr..?此功能已经存在。 Data.List.scanl1 是你的朋友。

标签: list function haskell fold map-function


【解决方案1】:

编辑,我添加了第一部分以更好地解决您的两个实现。

首先,使用foldr 解决您的实施问题,这里有几点说明:

  1. 在 Haskell 中,Lambda 以反斜杠开头,而不是斜杠。这是因为反斜杠有点像 lambda 希腊字母 (λ)。

  2. 仅使用特殊字符命名的函数,如+,默认情况下是中缀。如果你在它们周围使用括号,它会将它们变成前缀函数:

$> (+) 1 5
$> 6
  1. 传递给foldr 的函数需要两个参数,而您只在您的lambda 中提供一个。如果您真的想忽略第二个,可以使用 _ 而不是将其绑定到变量 (\x _ -> x)。

我认为你会在这个实现中陷入困境。请参阅下面的讨论,了解我对解决此问题的正确方法的看法。

注意:可以使用foldr (source) 来实现map,这是您可以在工作(第二个)实现中使用foldr 的一种方式。


使用foldr 实现这一点并不是最优的,因为顾名思义,它是从右侧折叠的:

foldr1 (+) [1..5]
--is equivalent to:
(1+(2+(3+(4+5))))

如您所见,求和操作是从列表的尾部开始完成的,这不是您要查找的。要完成这项工作,您必须“作弊”,并将列表翻转两次,一次是在折叠之前,一次是在折叠之后:

scan = tail . reverse . foldr step [0] . reverse where
  step e acc@(a:_) = (e + a) : acc

您可以使用从左侧折叠的左折叠来改善这一点:

foldl1 (+) [1..5]
--is equivalent to:
((((1+2)+3)+4)+5)

然而,这仍然不理想,因为要保持累加器中元素的顺序相同,您必须使用 ++ 函数,这相当于此类函数的二次时间复杂度。一个折衷方案是使用: 函数,但是你仍然需要在折叠后反转你的累加器列表,这只是线性复杂度:

scan' :: [Integer] -> [Integer]
scan' = tail . reverse . foldl step [0] where
  step acc@(a:_) e = (e + a) : acc

这仍然不是很好,因为reverse 增加了额外的计算。因此,理想的解决方案是使用scanl1,作为奖励,它不需要您提供起始值(上述示例中的[0]):

scan'' :: [Integer] -> [Integer]
scan'' = scanl1 (+)

scanl1是按照scanl来实现的,大致是这样定义的:

scanl f init list = init : (case list of
                      []   -> []
                      x:xs -> scanl f (f init x) xs)

因此,您可以这样做:

$> scanl1 (+) [1..3]
$> [1,3,6]

最后一点,您的 scan 函数不必要地专用于 Integer,因为它只需要一个 Num 约束:

scan :: Num a => [a] -> [a]

这甚至可能会提高性能,但这就是我的能力结束的地方,所以我不会再进一步​​了:)

【讨论】:

  • 如果你没见过这种东西,1.scanl f a xs = ys where { ys = a : zipWith f ys xs = a : map (uncurry f) (zip ys xs) }。 2.scanl f a xs = foldr g z xs a where { z a = [a] ; g x r a = a : r (f a x) }。 :)
  • @WillNess 嗯.. 我敢肯定那些 sn-ps 非常聪明,但它们不会编译...
  • 非常感谢您花时间提供如此详细的答案!你说的完全有道理。我知道我没有提到它,但我一直在寻找一种专门使用递归的解决方案,因为这就是我试图回答的问题(对于 Haskell 来说非常新,所以只是想学习所有的基础知识)。另外,我尝试使用 foldr/foldl 的原因是为了了解这些功能在实际场景中是如何工作的。关于我需要在哪里实际使用它们的任何建议?
  • @Biggeez foldrfoldlscanl 都使用递归。由于递归是函数式编程中非常常见的模式,它通常被抽象为高阶函数。如果你想使用显式递归,你基本上是在重新实现这些函数,所以看看它们是如何工作的是个好主意。您可以找到大量使用折叠的示例,只需在 SO 上搜索 [haskell] fold,或查看 Haskell Wiki 上的 Foldr Foldl Foldl'
  • zipWith 重写为map ... zip ... 是为了表明zipWith 只是一个二进制map。事实上,例如Scheme map 接受任意数量的参数,只要它们的数量与组合函数的数量匹配。
猜你喜欢
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 2019-10-11
  • 1970-01-01
  • 1970-01-01
  • 2021-11-28
  • 2021-08-21
  • 1970-01-01
相关资源
最近更新 更多