Advanced aggregation and running results
Aggregation turns multiple elements into a smaller result. Important distinctions are empty-input behavior, left-versus-right order, and whether intermediate accumulator values are retained.
Common numeric and selector aggregates
data class Build(val version: String, val durationSeconds: Int)
fun main() {
val builds = listOf(Build("1.0", 60), Build("1.1", 45), Build("1.2", 90))
println(builds.sumOf { it.durationSeconds })
println(builds.map { it.durationSeconds }.average())
println(builds.minByOrNull { it.durationSeconds })
println(builds.maxOfOrNull { it.durationSeconds })
}
The ...OrNull selector functions return null for empty input. Numeric average() returns NaN for an empty numeric collection, so validate whether empty input has a meaningful domain result.
Running accumulations
runningFold includes the initial value and then every intermediate accumulator. runningReduce starts from the first element.
fun main() {
val transactions = listOf(100, -30, 50)
println(transactions.runningFold(0) { balance, amount -> balance + amount })
// [0, 100, 70, 120]
println(transactions.runningReduce { balance, amount -> balance + amount })
// [100, 70, 120]
}
Running operations allocate results for every step. Use ordinary fold when only the final accumulator is needed.
Left and right folds are different for non-associative operations
fun main() {
val words = listOf("A", "B", "C")
val left = words.fold("start") { acc, item -> "($acc+$item)" }
val right = words.foldRight("end") { item, acc -> "($item+$acc)" }
println(left) // (((start+A)+B)+C)
println(right) // (A+(B+(C+end)))
}
Notice the lambda argument order changes for right folds: element first, accumulator second.
Per-group aggregation
data class Sale(val team: String, val amount: Int)
fun main() {
val sales = listOf(Sale("Android", 10), Sale("iOS", 20), Sale("Android", 30))
val totals = sales.groupingBy { it.team }
.fold(0) { total, sale -> total + sale.amount }
println(totals) // {Android=40, iOS=20}
}
Interview questions
- How do
runningFoldandfolddiffer in output size? - Why does
foldRightreverse lambda argument roles? - What does
average()return for empty numeric input? - When is
groupingBy().fold()better thangroupBy().mapValues()?