Kotlin 中的聚合操作

在之前,我们已经学会了对集合执行一些操作:过滤、排序,以及使用聚合函数获取信息。

然而,有时候我们需要对集合的元素顺序地执行更复杂的操作,并返回累积的结果。Kotlin 提供了一些方法,使这种操作变得非常容易处理。
FoldReduce

使用 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 及其变体方法,对集合元素执行各种基于值或索引的累积操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值