Kotlin groupBy, groupByTo, groupingBy example

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()


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 by Grouping method such as eachCount(), 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.

Further Reading

6 thoughts to “Kotlin groupBy, groupByTo, groupingBy example”

  1. 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]]

    1. empList.map { emp->
      emp.sports.map { sport ->
      Pair(emp.name, sport)
      }
      }.flatten()
      .groupBy( { it.second }, { it.first } )

  2. I’m new to Kotlin but your posts are great in explaining the language. Cool article, thanks

Comments are closed to reduce spam. If you have any question, please send me an email.