Kotlin library provides extension functions for grouping collection items. In this tutorial, I will show you some examples to use Kotlin groupBy()
, groupByTo()
, groupingBy()
methods.
Related Posts:
– Kotlin sum(), sumBy(), sumByDouble() and BigDecimal in List, Map example
– Kotlin Fold Example: fold(), foldRight(), foldIndexed(), foldRightIndexed()
Contents
Kotlin groupBy
groupBy() takes a selector
and returns a Map
.
inline fun <T, K> Iterable<T>.groupBy(
keySelector: (T) -> K
): Map<K, List<T>>
In the Map
, each key is the keySelector
result and the corresponding value is the List of items on which this selector is returned.
To understand, please look at this example, we have a List of words, and what we need is to group them by their length (number of characters):
val words = listOf("bezkoder", "zkoder", "kotlin", "programmingz", "bezcoder", "professional", "zcoder")
val byLength = words.groupBy { it.length }
println(byLength)
// {8=[bezkoder, bezcoder], 6=[zkoder, kotlin, zcoder], 12=[programmingz, professional]}
println(byLength.keys)
// [8, 6, 12]
println(byLength.values)
// [[bezkoder, bezcoder], [zkoder, kotlin, zcoder], [programmingz, professional]]
Or by first letter:
val byFirstLetter = words.groupBy { it.first() }
println(byFirstLetter)
// {b=[bezkoder, bezcoder], z=[zkoder, zcoder], k=[kotlin], p=[programmingz, professional]}
In addition to select which keys you want to group, you can also change them directly.
In the following example, I will group by the last letter and capitalize it.
val byLastLetter = words.groupBy { it.last().toUpperCase() }
println(byLastLetter)
// {R=[bezkoder, zkoder, bezcoder, zcoder], N=[kotlin], Z=[programmingz], L=[professional]}
How about the values? Yes, we can transform each item of the original collection with the valueTransform
function passed to groupBy()
method.
inline fun <T, K, V> Iterable<T>.groupBy(
keySelector: (T) -> K,
valueTransform: (T) -> V
): Map<K, List<V>>
For example, we group the words by last letter, then instead of getting list of corresponding items, we get length of them.
val byLastLetter_Length = words.groupBy({ it.last() }, { it.length })
println(byLastLetter_Length)
// {r=[8, 6, 8, 6], n=[6], z=[12], l=[12]}
Kotlin groupBy multiple keys
Think about the case you want to use groupBy()
for two or more fields, for instance, group by first character and length (number of characters). How to do this?
You need to create a Key
data class with these properties first:
data class Key(val letter: Char, val length: Int)
Then create an extension method that helps to transform item of the collection into Key
:
fun String.toKey() = Key(this.first(), this.length)
Now we only need to group by the Key
.
val words = listOf("bezkoder", "zkoder", "kotlin", "programmingz", "bezcoder", "professional", "zcoder")
val byLastLetterandLength = words.groupBy({ it.toKey() })
println(byLastLetterandLength)
// {Key(letter=b, length=8)=[bezkoder, bezcoder], Key(letter=z, length=6)=[zkoder, zcoder], Key(letter=k, length=6)=[kotlin], Key(letter=p, length=12)=[programmingz, professional]}
Kotlin groupBy Class/Type
Assume that we have two classes like this:
data class Employer(val name: String, val age: Int)
data class Employee(val name: String, val age: Int, val salaray: Double)
And there is a List of objects that contains items which are instances of the classes above:
val people = listOf(
Employer("Mark", 42),
Employer("Helen", 38),
Employee("Tracy", 27, 3500.0),
Employee("Katherin", 29, 3600.0),
Employee("John", 32, 4100.0)
)
This is how to use groupBy()
with Class or Type:
val byType: Map<KClass<*>, List<Any>> = people.groupBy { it.javaClass.kotlin }
println("Employers: ${byType[Employer::class]}")
// Employers: [Employer(name=Mark, age=42), Employer(name=Helen, age=38)]
println("Employees: ${byType[Employee::class]}")
// Employees: [Employee(name=Tracy, age=27, salaray=3500.0), Employee(name=Katherin, age=29, salaray=3600.0), Employee(name=John, age=32, salaray=4100.0)]
Kotlin groupByTo
groupByTo() is just like groupBy()
, but it also puts the result to the destination
map.
inline fun <T, K, M : MutableMap<in K, MutableList<T>>> Iterable<T>.groupByTo(
destination: M,
keySelector: (T) -> K,
valueTransform: (T) -> V
): M
For example, we have a List like this:
val phoneToYear = listOf(
"Nexus One" to 2010,
"Pixel 2" to 2017,
"Pixel 4a" to 2020,
"iPhone 4" to 2010,
"iPhone X" to 2017,
"Galaxy Note 8" to 2017,
"Galaxy S11" to 2020
)
If you use groupBy()
, you cannot modify the result:
val phonesByYear = phoneToYear.groupBy({ it.second }, { it.first })
println(phonesByYear)
// {2010=[Nexus One, iPhone 4], 2017=[Pixel 2, iPhone X, Galaxy Note 8], 2020=[Pixel 4a, Galaxy S11]}
phonesByYear[2020]?.add("iPhone 12") // compile error
We need a mutable Map as result, so we use groupByTo()
as the following code:
val phonesByYearMutable = LinkedHashMap<Int, MutableList<String>>();
phoneToYear.groupByTo(phonesByYearMutable, { it.second }, { it.first })
println(phonesByYearMutable)
// {2010=[Nexus One, iPhone 4], 2017=[Pixel 2, iPhone X, Galaxy Note 8], 2020=[Pixel 4a, Galaxy S11]}
phonesByYearMutable[2020]?.add("iPhone 12")
println(phonesByYearMutable[2020])
// [Pixel 4a, Galaxy S11, iPhone 12]
You can also use groupByTo()
by this way:
val phonesByYearMutable = phoneToYear.groupByTo(LinkedHashMap(), { it.second }, { it.first })
phonesByYearMutable[2020]?.add("iPhone 12")
println(phonesByYearMutable[2020])
// [Pixel 4a, Galaxy S11, iPhone 12]
Kotlin groupingBy
groupingBy() groups items and apply an operation to all groups at one time, then returns a Grouping
object.
inline fun <T, K> Iterable<T>.groupingBy(
crossinline keySelector: (T) -> K
): Grouping<T, K>
For example, we use groupingBy()
to get Grouping
object, then:
– Grouping.eachCount()
to count number of items corresponding to each group.
– Grouping.fold()
to calculate total number of characters in each group.
val words = listOf("bezkoder", "zkoder", "kotlin", "programmingz", "bezcoder", "professional", "zcoder")
println(words.groupBy { it.first() })
// {b=[bezkoder, bezcoder], z=[zkoder, zcoder], k=[kotlin], p=[programmingz, professional]}
println(words.groupingBy { it.first() }.eachCount())
// {b=2, z=2, k=1, p=2}
println(words.groupingBy { it.first() }.fold(0) { total, word -> total + word.length })
// {b=16, z=12, k=6, p=24}
Kotlin groupBy vs groupingBy
Both groupBy
& groupingBy
help to group Collections and use selector
as input parameter.
Here are difference between groupBy
and groupingBy
method:
– groupBy:
- returns a
Map
object - changes the values by
valueTransform
- allows to pass a
destination
object to get mutable Map as result
– groupingBy:
- returns a
Grouping
object - doesn’t have
valueTransform
, so it changes the values byGrouping
method such aseachCount()
,fold()
,reduce()
,aggregate()
… - doesn’t provide simple way to return a mutable Map
Conclusion
Today we’ve looked at many ways to use Kotlin groupBy
, groupByTo
, groupingBy
method.
Finally, we compared groupBy
and groupingBy
to understand them deeply and know when to use them.
Happy Learning! See you again.
val empList = listOf(Employee(“Aaron”, listOf(“Cricket”, “Football”)),
Employee(“David”, listOf(“Cricket”)),
Employee(“Steve”, listOf(“Football”)))
how to get this output:
[Cricket = [Aaron, David], Football = [Aaron, Steve]]
empList.map { emp->
emp.sports.map { sport ->
Pair(emp.name, sport)
}
}.flatten()
.groupBy( { it.second }, { it.first } )
I’m new to Kotlin but your posts are great in explaining the language. Cool article, thanks
Thanks!
Thank you for the tutorial!
Comprehensive tutorial. Thanks!