Skip to main content

Kotlin Collection Aggregation

When working with collections in Kotlin, you'll often need to process multiple elements to produce a single result. This is where collection aggregation operations come in handy. These operations allow you to calculate sums, averages, find minimum and maximum values, or apply custom aggregation logic.

What is Collection Aggregation?

Collection aggregation refers to the process of taking multiple values from a collection and combining them into a single result value. Kotlin's standard library provides numerous functions to perform common aggregation operations efficiently and with minimal code.

Basic Aggregation Functions

Count

The count() function returns the number of elements in a collection or the number of elements matching a given predicate.

kotlin
val numbers = listOf(1, 2, 3, 4, 5, 6)
val totalCount = numbers.count()
val evenCount = numbers.count { it % 2 == 0 }

println("Total count: $totalCount") // Output: Total count: 6
println("Even numbers count: $evenCount") // Output: Even numbers count: 3

Sum

For collections of numeric types, Kotlin provides sum() and sumOf() functions:

kotlin
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.sum() // 15

// Using sumOf with a selector function
val squares = numbers.sumOf { it * it } // 1 + 4 + 9 + 16 + 25 = 55

println("Sum: $sum") // Output: Sum: 15
println("Sum of squares: $squares") // Output: Sum of squares: 55

Average

The average() function calculates the arithmetic mean of numeric collections:

kotlin
val numbers = listOf(1, 2, 3, 4, 5)
val average = numbers.average()

println("Average: $average") // Output: Average: 3.0

Min and Max

Find the minimum or maximum values in a collection:

kotlin
val numbers = listOf(5, 2, 10, 4)

val min = numbers.minOrNull()
val max = numbers.maxOrNull()

println("Minimum: $min") // Output: Minimum: 2
println("Maximum: $max") // Output: Maximum: 10

You can also use minByOrNull and maxByOrNull with custom selectors:

kotlin
data class Product(val name: String, val price: Double)

val products = listOf(
Product("Laptop", 999.99),
Product("Phone", 699.99),
Product("Tablet", 399.99)
)

val cheapestProduct = products.minByOrNull { it.price }
val mostExpensiveProduct = products.maxByOrNull { it.price }

println("Cheapest product: ${cheapestProduct?.name}") // Output: Cheapest product: Tablet
println("Most expensive product: ${mostExpensiveProduct?.name}") // Output: Most expensive product: Laptop

Advanced Aggregation Functions

Fold and Reduce

Fold and reduce operations are powerful tools for aggregating collections with custom logic.

Fold

fold() takes an initial value and a combining function. It then accumulates value starting with the initial value and applying the combining function to the current accumulator value and each element.

kotlin
val numbers = listOf(1, 2, 3, 4, 5)

val sum = numbers.fold(0) { acc, number -> acc + number }
// Same as sum = 0 + 1 + 2 + 3 + 4 + 5

println("Sum using fold: $sum") // Output: Sum using fold: 15

// Creating a concatenated string with a separator
val names = listOf("Alice", "Bob", "Charlie")
val nameString = names.fold("Names: ") { acc, name -> "$acc$name, " }.dropLast(2)

println(nameString) // Output: Names: Alice, Bob, Charlie

There's also foldRight() which iterates from right to left:

kotlin
val chars = listOf('a', 'b', 'c', 'd')
val reverseString = chars.foldRight("") { char, acc -> acc + char }

println("Reversed: $reverseString") // Output: Reversed: dcba

Reduce

reduce() is similar to fold() but uses the first element as the initial accumulator value:

kotlin
val numbers = listOf(1, 2, 3, 4, 5)

val product = numbers.reduce { acc, number -> acc * number }
// Same as product = 1 * 2 * 3 * 4 * 5

println("Product using reduce: $product") // Output: Product using reduce: 120

Like with fold, there's also reduceRight() for right-to-left iteration:

kotlin
val numbers = listOf(1, 2, 3, 4, 5)
val resultRightToLeft = numbers.reduceRight { number, acc -> number - acc }

println("Result of right-to-left reduction: $resultRightToLeft")
// Calculates: 1 - (2 - (3 - (4 - 5)))
// = 1 - (2 - (3 - (-1)))
// = 1 - (2 - 4)
// = 1 - (-2)
// = 3

Note: Be careful with reduce() on empty collections as it will throw an exception. Use foldOrNull() for potentially empty collections.

Aggregating to Collections

Sometimes you want to aggregate values but still maintain a collection structure.

GroupBy

The groupBy() function can be used to categorize elements into groups:

kotlin
data class Student(val name: String, val grade: Char)

val students = listOf(
Student("Alice", 'A'),
Student("Bob", 'B'),
Student("Carol", 'A'),
Student("Dave", 'C'),
Student("Eve", 'B')
)

val studentsByGrade = students.groupBy { it.grade }

println("Students with grade A: ${studentsByGrade['A']?.map { it.name }}")
println("Students with grade B: ${studentsByGrade['B']?.map { it.name }}")
println("Students with grade C: ${studentsByGrade['C']?.map { it.name }}")

// Output:
// Students with grade A: [Alice, Carol]
// Students with grade B: [Bob, Eve]
// Students with grade C: [Dave]

Partition

The partition() function divides a collection into two parts based on a predicate:

kotlin
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val (evens, odds) = numbers.partition { it % 2 == 0 }

println("Even numbers: $evens") // Output: Even numbers: [2, 4, 6, 8, 10]
println("Odd numbers: $odds") // Output: Odd numbers: [1, 3, 5, 7, 9]

Real-World Examples

Example 1: Sales Analytics

Let's create a simple sales analytics application:

kotlin
data class SalesTransaction(
val productId: String,
val amount: Double,
val category: String,
val date: String // Format: YYYY-MM-DD for simplicity
)

val transactions = listOf(
SalesTransaction("P001", 125.99, "Electronics", "2023-01-15"),
SalesTransaction("P002", 89.50, "Clothing", "2023-01-20"),
SalesTransaction("P003", 210.75, "Electronics", "2023-01-25"),
SalesTransaction("P004", 45.25, "Groceries", "2023-02-01"),
SalesTransaction("P005", 199.99, "Electronics", "2023-02-05"),
SalesTransaction("P006", 65.00, "Clothing", "2023-02-10")
)

// Total revenue
val totalRevenue = transactions.sumOf { it.amount }

// Revenue by category
val revenueByCategory = transactions.groupBy { it.category }
.mapValues { (_, transactions) -> transactions.sumOf { it.amount } }

// Monthly revenue
val monthlyRevenue = transactions.groupBy { it.date.substring(0, 7) }
.mapValues { (_, transactions) -> transactions.sumOf { it.amount } }

// Average transaction value
val averageTransaction = transactions.map { it.amount }.average()

println("Total Revenue: $${String.format("%.2f", totalRevenue)}")
println("Revenue by Category:")
revenueByCategory.forEach { (category, revenue) ->
println(" - $category: $${String.format("%.2f", revenue)}")
}
println("Monthly Revenue:")
monthlyRevenue.forEach { (month, revenue) ->
println(" - $month: $${String.format("%.2f", revenue)}")
}
println("Average Transaction Value: $${String.format("%.2f", averageTransaction)}")

// Output:
// Total Revenue: $736.48
// Revenue by Category:
// - Electronics: $536.73
// - Clothing: $154.50
// - Groceries: $45.25
// Monthly Revenue:
// - 2023-01: $426.24
// - 2023-02: $310.24
// Average Transaction Value: $122.75

Example 2: Student Grade Calculator

Here's how you might calculate grades for a class:

kotlin
data class Student(val name: String, val scores: List<Int>)

val students = listOf(
Student("Alice", listOf(95, 87, 92, 88, 90)),
Student("Bob", listOf(72, 65, 81, 77, 70)),
Student("Charlie", listOf(86, 88, 84, 90, 85)),
Student("Diana", listOf(92, 95, 88, 97, 91))
)

// Calculate average score for each student
val studentAverages = students.map { student ->
val average = student.scores.average()
"${student.name}: ${String.format("%.1f", average)}"
}

// Find the highest scoring student
val topStudent = students.maxByOrNull { it.scores.average() }

// Calculate class average
val allScores = students.flatMap { it.scores }
val classAverage = allScores.average()

// Grade distribution (A: 90-100, B: 80-89, C: 70-79, D: 60-69, F: below 60)
val scoreToGrade = { score: Int ->
when(score) {
in 90..100 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
in 60..69 -> "D"
else -> "F"
}
}

val gradeDistribution = allScores.groupBy(scoreToGrade)
.mapValues { it.value.size }

println("Student Averages:")
studentAverages.forEach { println(" $it") }
println("Top Student: ${topStudent?.name}")
println("Class Average: ${String.format("%.1f", classAverage)}")
println("Grade Distribution:")
gradeDistribution.forEach { (grade, count) ->
println(" $grade: $count")
}

// Output:
// Student Averages:
// Alice: 90.4
// Bob: 73.0
// Charlie: 86.6
// Diana: 92.6
// Top Student: Diana
// Class Average: 85.7
// Grade Distribution:
// A: 8
// B: 7
// C: 5
// D: 0
// F: 0

Summary

Kotlin's collection aggregation functions offer powerful ways to process and analyze data in collections:

  • Basic aggregation: count(), sum(), average(), min(), and max()
  • Advanced aggregation: fold() and reduce() for custom aggregation logic
  • Collection aggregation: groupBy() and partition() to create new collections based on criteria

These functions enable you to write concise, readable code that focuses on what you want to achieve rather than how to implement the logic.

Exercises

To reinforce your understanding, try these exercises:

  1. Create a list of integers and calculate the sum of all even numbers squared.
  2. Given a list of words, find the longest word.
  3. Create a function that takes a list of integers and returns the product of all elements.
  4. Given a list of people with names and ages, calculate the average age by decade (20s, 30s, etc.).
  5. Create a shopping cart system that can calculate the total cost, apply discounts, and group items by category.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)