reduce 与聚合
组合元素的其他方式。
reduce 与聚合 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。
从 Fold 到 Reduce
有时,您希望组合元素,而不另外提供一个初始值。
reduce 使用第一个元素作为初始累加器,并将其余元素逐一组合进去。
当元素类型和结果类型相同时,它是 fold 更精简的替代方案。
reduce 基础
reduce 接收一个形如 (a, b) 的二元函数,并在整个集合上执行折叠,不需要显式的初始值。
对于求和操作,它只需将所有元素相加。
val xs = List(1, 2, 3, 4)
val total = xs.reduce((a, b) => a + b)
@main def run(): Unit =
println(total) // 10空集合陷阱
由于 reduce 没有初始值,对空集合调用它会抛出 UnsupportedOperationException。
fold 和 foldLeft 在空集合上是安全的,因为它们会使用所提供的初始值作为后备。
只有在确定集合非空时,才应使用 reduce。
// List.empty[Int].reduce(_ + _)
// throws UnsupportedOperationException
List.empty[Int].foldLeft(0)(_ + _) // safe, returns 0使用 reduceOption 确保安全
reduceOption 返回一个 Option,对于空集合返回 None,而不会抛出错误。
当集合可能为空时,这是安全的 reduce 方式。
val xs = List(5, 9, 2)
val maybeMax = xs.reduceOption(_ max _)
@main def run(): Unit =
println(maybeMax) // Some(9)reduceLeft 与 reduceRight
和 fold 一样,reduce 也有带方向的变体。
reduceLeft 从左到右组合,reduceRight 从右到左组合。对于并行集合,普通 reduce 不保证顺序,因此当方向很重要时,请使用明确的变体。
val xs = List(1, 2, 3)
val l = xs.reduceLeft(_ - _) // (1-2)-3 = -4
val r = xs.reduceRight(_ - _) // 1-(2-3) = 2
@main def run(): Unit =
println((l, r)) // (-4, 2)为什么存在 aggregate
fold 和 reduce 要求累加器类型与元素类型以较简单的方式相关。
aggregate 是最通用的 fold:它允许累加器使用不同的类型,并告诉 Scala 如何合并部分累加器,这对于并行处理很重要。
aggregate 的签名
aggregate 接受一个初始值、一个将元素折叠进累加器的 seqop,以及一个合并两个累加器的 combop。
对于顺序集合,combop 很少使用;对于并行集合,它会合并每个数据块的结果。
val xs = List(1, 2, 3, 4)
val sum = xs.aggregate(0)(
(acc, x) => acc + x, // seqop
(a, b) => a + b // combop
)
@main def run(): Unit = println(sum) // 10使用不同类型的 aggregate
这里的 aggregate 将单词列表折叠为一个表示总长度的 Int。
seqop 会累加每个单词的长度,而 combop 会将两个部分总数相加。
累加器类型(Int)与元素类型(String)不同。
val words = List("hi", "there", "you")
val chars = words.aggregate(0)(
(acc, w) => acc + w.length,
(a, b) => a + b
)
@main def run(): Unit = println(chars) // 10并行聚合
aggregate 的真正威力体现在并行集合中。
每个线程使用 seqop 折叠自己的数据块,然后使用 combop 合并各个数据块。这两个函数让 Scala 能够安全地拆分并重新合并工作。
// Conceptual: par splits the work
// val n = data.par.aggregate(0)(_ + _.length, _ + _)
// seqop runs per chunk, combop merges chunk results选择合适的工具
当结果类型与元素类型相同,且组合操作满足结合律时,请使用 reduce 或 reduceOption。
当您需要初始值,或需要在顺序处理中使用不同的结果类型时,请使用 foldLeft。
当累加器类型与元素类型不同时,并且希望以适合并行处理的方式合并结果时,请使用 aggregate。
// reduce: same type, non-empty, associative
// foldLeft: seed + different type, sequential
// aggregate: different type + parallel merge一次遍历完成组合
aggregate 可以通过将结果累加到元组中,同时计算多个结果。
这里我们在一次遍历中同时得到总和与计数,并在 combop 中合并元组。
val xs = List(2, 4, 6, 8)
val (s, c) = xs.aggregate((0, 0))(
(acc, x) => (acc._1 + x, acc._2 + 1),
(a, b) => (a._1 + b._1, a._2 + b._2)
)
@main def run(): Unit = println((s, c)) // (20, 4)快速检查
请判断对于可能为空的列表,哪种操作最安全。
回顾
reduce 不使用初始值,而是将第一个元素作为起点来组合元素;它会在空集合上抛出错误,而 reduceOption 则返回 None。
aggregate 是最通用的 fold:它使用一个初始值、一个用于折叠元素的 seqop,以及一个用于并行处理时合并部分累加器的 combop。
同类型且满足结合律的组合请使用 reduce;顺序的带初始值折叠请使用 foldLeft;类型不同时或需要并行处理时请使用 aggregate。
常见问题解答
「reduce 与聚合」课时是免费的吗?
是的 — 「reduce 与聚合」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。
「reduce 与聚合」这节课中我会学到什么?
组合元素的其他方式。 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「reduce 与聚合」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?
能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 递归思维
- 累加器模式
- foldLeft 与 foldRight
- reduce 与聚合