Skip to main content

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 })
}
Open in Kotlin Playground

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]
}
Open in Kotlin Playground

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)))
}
Open in Kotlin Playground

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}
}
Open in Kotlin Playground

Interview questions​

  1. How do runningFold and fold differ in output size?
  2. Why does foldRight reverse lambda argument roles?
  3. What does average() return for empty numeric input?
  4. When is groupingBy().fold() better than groupBy().mapValues()?