reduce
使用提供的函数来组合输入元素,通常组合成单个输出值
我经常遇到一些人,他们发现 reduce 操作很难理解,甚至到了回避它的地步。然而,reduce 是一个宝贵的操作,可以用来组合集合中多个项目的价值。
首先从基础开始:reduce 操作接受一个函数,该函数接受两个参数 - 累加器和当前迭代。在每次迭代中,它将这两个参数组合成一个单一的值,然后将该值放入累加器中供下次迭代使用。考虑以下代码来连接字符串列表
["a", "b", "c"].reduce(""){|acc, s| acc + s} # => "abc"
(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 会将初始值设置为第一个元素,并从第二个元素开始迭代。
["a", "b", "c"].reduce(:+) # => "abc"
(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”。