在之前,我们已经学会了对集合执行一些操作:过滤、排序,以及使用聚合函数获取信息。
然而,有时候我们需要对集合的元素顺序地执行更复杂的操作,并返回累积的结果。Kotlin 提供了一些方法,使这种操作变得非常容易处理。
Fold 和 Reduce
使用 fold() 和 reduce(),我们可以将集合中的元素作为一个序列来操作,并返回一个累积的结果。但这两个方法之间有一些细微的区别。
-
reduce()会将集合转换为一个单一的结果。它接受一个 lambda 表达式作为操作符,该操作符用于将两个元素组合成一个累计值。然后它会遍历整个集合,逐步将累计值与下一个元素组合。 -
fold()接受一个初始值,并将其作为累计值的起始点,然后从左到右依次将当前的累计值与集合中的每个元素组合。
fun main() {
val list = listOf(1, 2, 3, 4, 5)
// fold 使用传入的初始值作为累加器的起始值
var sum = list.fold(0) { acc, i ->
println("acc: $acc, i: $i, acc + i: ${acc + i}")
acc + i
}
println(sum) // 15
var product = list.fold(1) { acc, i ->
println("acc: $acc, i: $i, acc * i: ${acc * i}")
acc * i
}
println(product) // 120
// reduce 使用第一个元素作为累加器的初始值
sum = list.reduce { acc, i ->
println("acc: $acc, i: $i, acc + i: ${acc + i}")
acc + i
}
println(sum) // 15
product = list.reduce { acc, i ->
println("acc: $acc, i: $i, acc * i: ${acc * i}")
acc * i
}
println(product) // 120
}
fold 分析
fold 会对集合中每个元素执行操作,因此它的操作次数等于集合元素的个数:
acc: 0, i: 1, acc + i: 1
acc: 1, i: 2, acc + i: 3
acc: 3, i: 3, acc + i: 6
acc: 6, i: 4, acc + i: 10
acc: 10, i: 5, acc + i: 15
结果:15
acc: 1, i: 1, acc * i: 1
acc: 1, i: 2, acc * i: 2
acc: 2, i: 3, acc * i: 6
acc: 6, i: 4, acc * i: 24
acc: 24, i: 5, acc * i: 120
结果:120
当集合为空时,fold 会直接返回初始值,并不会抛出异常。此外,fold 还能改变累加器的类型:
val emptyList = listOf<Int>()
val sum = emptyList.fold(0) { acc, i ->
println("acc: $acc, i: $i, acc + i: ${acc + i}")
acc + i
}
println(sum) // 0
val list2 = listOf("a", "b", "c", "d", "e")
val string = list2.fold(StringBuilder()) { acc, s ->
acc.append(s)
}
println(string) // abcde
reduce 分析
reduce 的初始值是集合的第一个元素,因此它的操作次数比元素个数少一次:
acc: 1, i: 2, acc + i: 3
acc: 3, i: 3, acc + i: 6
acc: 6, i: 4, acc + i: 10
acc: 10, i: 5, acc + i: 15
结果:15
acc: 1, i: 2, acc * i: 2
acc: 2, i: 3, acc * i: 6
acc: 6, i: 4, acc * i: 24
acc: 24, i: 5, acc * i: 120
结果:120
如果集合为空,reduce 会抛出异常,而且不能更改返回类型:
val emptyList = listOf<Int>()
// 抛出异常:Empty collection can't be reduced
val sum = emptyList.reduce { acc, i -> acc + i }
val string2 = list2.reduce(StringBuilder()) { acc, s ->
acc.append(s)
} // 编译错误,不能改变累加器类型
反向处理:foldRight() 和 reduceRight()
如果需要从右向左处理元素,可以使用 foldRight() 和 reduceRight()。注意,这时操作参数顺序为:元素在前,累加器在后。
val list = listOf(1, 2, 3, 4, 5)
val sum = list.foldRight(0) { i, acc -> acc + i }
println(sum) // 15
val product = list.foldRight(1) { i, acc -> acc * i }
println(product) // 120
val sum2 = list.reduceRight { i, acc -> acc + i }
println(sum2) // 15
val product2 = list.reduceRight { i, acc -> acc * i }
println(product2) // 120
带索引的累加操作
有时我们需要使用元素的索引参与计算,可以使用如下方法:
-
foldIndexed() -
foldRightIndexed() -
reduceIndexed() -
reduceRightIndexed()
val list = listOf(1, 2, 3, 4, 5)
// 索引为偶数的元素求和
val sum = list.foldIndexed(0) { index, acc, i ->
if (index % 2 == 0) acc + i else acc
}
println(sum) // 9
// 从右向左,索引为奇数的元素相乘
val product = list.foldRightIndexed(1) { index, i, acc ->
if (index % 2 == 1) acc * i else acc
}
println(product) // 8
// reduceIndexed 示例
val sum2 = list.reduceIndexed { index, acc, i ->
if (index % 2 == 0) acc + i else acc
}
println(sum2) // 9
val product2 = list.reduceRightIndexed { index, i, acc ->
if (index % 2 == 1) acc * i else acc
}
println(product2) // 40
空集合时的处理
所有 reduce 相关方法在空集合上都会抛异常,如果想要空集合时返回 null,可以使用 xxxOrNull() 版本:
val sum = emptyList<Int>().fold(0) { acc, i -> acc + i }
println(sum) // 0
val sum2 = emptyList<Int>().reduceOrNull { acc, i -> acc + i } ?: 0
println(sum2) // 0
获取中间结果
有时我们希望保留中间的累加结果,可以使用:
-
runningFold() -
runningReduce() -
runningFoldIndexed() -
runningReduceIndexed()
val list = listOf(1, 2, 3, 4, 5)
println("runningFold 和 runningReduce 示例")
val runningSum = list.runningFold(0) { acc, i -> acc + i }
println(runningSum) // [0, 1, 3, 6, 10, 15]
val runningProduct = list.runningReduce { acc, i -> acc * i }
println(runningProduct) // [1, 2, 6, 24, 120]
val runningSumWithIndex = list.runningFoldIndexed(0) { index, acc, i ->
if (index % 2 == 0) acc + i else acc
}
println(runningSumWithIndex) // [0, 1, 1, 4, 4, 9]
val runningProductWithIndex = list.runningReduceIndexed { index, acc, i ->
if (index % 2 == 1) acc * i else acc
}
println(runningProductWithIndex) // [1, 2, 2, 8, 8]
总结
在本节中,我们学习了如何使用 fold 和 reduce 及其变体方法,对集合元素执行各种基于值或索引的累积操作。
553

被折叠的 条评论
为什么被折叠?



