foldinject

此页面描述了集合管道模式中的一个操作。有关更多上下文,请阅读

reduce

使用提供的函数来组合输入元素,通常组合成单个输出值

我经常遇到一些人,他们发现 reduce 操作很难理解,甚至到了回避它的地步。然而,reduce 是一个宝贵的操作,可以用来组合集合中多个项目的价值。

首先从基础开始:reduce 操作接受一个函数,该函数接受两个参数 - 累加器和当前迭代。在每次迭代中,它将这两个参数组合成一个单一的值,然后将该值放入累加器中供下次迭代使用。考虑以下代码来连接字符串列表

ruby…
["a", "b", "c"].reduce(""){|acc, s| acc + s}
# => "abc"
clojure…
(reduce str "" ["a" "b" "c"])
;; => "abc"

为了理解发生了什么,这里有一个表格,列出了每次迭代的步骤。

索引 acc s 表达式 结果
0 ”” “a” ”” + “a” “a”
1 “a” “b” “a” + “b” “ab”
2 “ab” “c” “ab” + “c” “abc”

对于第一个示例,我包含了一个初始值,但通常情况下,它不是必需的。如果省略初始值,则 reduce 会将初始值设置为第一个元素,并从第二个元素开始迭代。

ruby…
["a", "b", "c"].reduce(:+)
# => "abc"
clojure…
(reduce str ["a" "b" "c"])
;; => "abc"

尽管我在 ruby 中习惯使用 lambda 来进行集合管道操作,但我更喜欢为 reduce 命名函数。

到目前为止,我展示的 reduce 操作都是左 reduce,因为我们从左侧开始迭代列表。一些语言还提供了从右到左的右 reduce。从语义上讲,这与反转列表然后进行左 reduce 相同。根据实现方式,右 reduce 操作可能会对内存(特别是堆栈)消耗和惰性产生不同的影响。如果语言使用“fold”术语,则左 fold 通常写为“foldl”,右 fold 写为“foldr”。

带有累加器的左 fold 和右 fold 的概念是思考 reduce 的最常见方式,但它有一个缺陷 - 它本身不是可并行的。一些方法试图通过将 reduce 视为一个固有的并行操作来解决这个问题,前提是组合函数是关联的(例如 clojure 的 reduce 库)。

Smalltalk 中的 reduce 术语是 inject:into,使用方法如下

aList inject: '' into: [:acc, :each| acc , each]

Smalltalk 的字符串连接运算符是“ , ”。

Ruby 使用“inject”作为“reduce”的别名,但如果没有 inject:into: 的两个关键字参数,它读起来就不那么好了。我仍然出于熟悉的原因使用它,但现在更喜欢使用“reduce”。