Fold
(又名reduce
)被认为是一个非常重要的高阶函数。 Map
可以用fold
(see here)表示。但这对我来说听起来比实际更具有学术性。典型的用途可能是获得总和,产品或数字的最大值,但这些函数通常接受任意数量的参数。那么为什么在(fold + 0 '(2 3 5))
工作正常时写(+ 2 3 5)
。我的问题是,在什么情况下使用fold
最简单或最自然?
答案 0 :(得分:13)
fold
的要点是它更抽象。并不是说你可以做你以前做过的事情,而是你可以更容易地做到这些。
使用fold
,您可以概括在两个元素上定义的任何函数,以应用于任意数量的元素。这是一个胜利,因为编写,测试,维护和修改应用两个参数而不是列表的单个函数通常要容易得多。而且编写,测试,维护等一个简单的功能总是比较容易,而不是两个具有相似但不完全功能的功能。
由于fold
(就此而言,map
,filter
和朋友)具有明确定义的行为,因此使用这些函数来理解代码通常比显式递归更容易。
基本上,一旦你拥有一个版本,就可以“免费”获得另一个版本。最终,你最终会做更少的工作来获得相同的结果。
答案 1 :(得分:8)
以下是reduce
非常适用的一些简单示例。
查找每个子列表的最大值之和
Clojure的:
user=> (def x '((1 2 3) (4 5) (0 9 1)))
#'user/x
user=> (reduce #(+ %1 (apply max %2)) 0 x)
17
球拍:
> (define x '((1 2 3) (4 5) (0 9 1)))
> (foldl (lambda (a b) (+ b (apply max a))) 0 x)
17
从列表构建地图
Clojure的:
user=> (def y '(("dog" "bark") ("cat" "meow") ("pig" "oink")))
#'user/y
user=> (def z (reduce #(assoc %1 (first %2) (second %2)) {} y))
#'user/z
user=> (z "pig")
"oink"
对于包含reduce
的更复杂的clojure示例,请查看my solution项目Euler问题18& 67
另请参阅:reduce vs. apply
答案 2 :(得分:5)
在Common Lisp函数中,不接受任意数量的参数。
每个Common Lisp实现CALL-ARGUMENTS-LIMIT中都定义了一个常量,必须为50或更大。
这意味着任何这种可移植编写的函数都应该接受至少50个参数。但它可能只有50个。
此限制的存在是为了允许编译器可能使用优化的调用方案,并且不提供一般情况,其中可以传递无限数量的参数。
因此,要在可移植Common Lisp代码中真正处理大型(大于50个元素)列表或向量,必须使用迭代构造,reduce,map等。因此,还有必要不使用(apply '+ large-list)
,而是使用(reduce '+ large-list)
。
答案 3 :(得分:4)
您的示例(+ 2 3 4
)仅适用,因为您事先知道参数的数量。折叠工作在大小可能不同的列表上。
fold
/ reduce
是“cdr-down a list”模式的通用版本。每个关于按顺序处理序列的每个元素并从中计算一些返回值的算法都可以用它表示。它基本上是foreach循环的功能版本。
答案 4 :(得分:3)
以下是其他人没有提及的例子。
通过使用具有小型,定义良好的接口(如“fold”)的函数,您可以替换该实现而不会破坏使用它的程序。例如,您可以生成distributed version that runs on thousands of PCs,因此使用它的排序算法将变为distributed sort,依此类推。您的计划成为more robust, simpler, and faster。
你的例子很简单:+
已经接受了任意数量的参数,在很少的内存中快速运行,并且已经由编写编译器的人编写和调试。这些属性通常不适用于我需要运行的算法。
答案 5 :(得分:3)
使用折叠的代码通常难以阅读。这就是为什么人们更喜欢地图,过滤,存在,求和等等。这些天我主要编写编译器和口译员;这是我使用折叠的一些方法:
所有这些用途的共同之处在于,它们将序列的信息累积到某种集或字典中。 非常实用。