Kotlin maps: counting and indexing

Count words with groupingBy and build a lookup map with getOrPut.

Kotlin
fun main() {
    val text = "the cat and the hat and the bat"
    val freq = text.split(" ").groupingBy { it }.eachCount()
    println(freq.entries.sortedWith(compareBy({ -it.value }, { it.key })).joinToString(" ") { "${it.key}=${it.value}" })

    val index = mutableMapOf<Char, MutableList<String>>()
    for (word in listOf("apple", "avocado", "banana", "blueberry", "cherry")) {
        index.getOrPut(word[0]) { mutableListOf() }.add(word)
    }
    println(index)
    println(index['b']?.size ?: 0)
    println(index['z']?.size ?: 0)
}
Output
the=3 and=2 bat=1 cat=1 hat=1
{a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
2
0

Explanation

Kotlin has read-only Map and MutableMap types. mapOf and mutableMapOf keep the order of insertion when printed. groupingBy { key }.eachCount() counts how many elements share each key in one pass. getOrPut(key) { default } returns the existing value or creates and stores a new one, which is the clean way to build a map of lists.

The word-frequency map is sorted by count, highest first, with ties broken by the word, using compareBy with negative counts. The index map groups words by their first letter. Both run in O(n) apart from the sorting, because map lookups take O(1) on average.

Explore the dry run
Guided walkthroughReady
1fun main() {
2 val text = "the cat and the hat and the bat"
3 val freq = text.split(" ").groupingBy { it }.eachCount()
4 println(freq.entries.sortedWith(compareBy({ -it.value }, { it.key })).joinToString(" ") { "${it.key}=${it.value}" })
5
6 val index = mutableMapOf<Char, MutableList<String>>()
7 for (word in listOf("apple", "avocado", "banana", "blueberry", "cherry")) {
8 index.getOrPut(word[0]) { mutableListOf() }.add(word)
9 }
10 println(index)
11 println(index['b']?.size ?: 0)
12 println(index['z']?.size ?: 0)
13}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Count each distinct word in a single pass.—
—
Highest counts first, ties alphabetical.—
the=3 and=2 bat=1 cat=1 hat=1
Create the list for a letter the first time and then add to it.—
the=3 and=2 bat=1 cat=1 hat=1
A mutable map prints its entries in insertion order.—
the=3 and=2 bat=1 cat=1 hat=1
{a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
Two words start with b.—
the=3 and=2 bat=1 cat=1 hat=1
{a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
2
A missing key gives null, and ?: turns it into 0.—
the=3 and=2 bat=1 cat=1 hat=1
{a=[apple, avocado], b=[banana, blueberry], c=[cherry]}
2
0

Cost and common mistakes

Counting and indexing are O(n); the sort is O(k log k) for k distinct words.

  • map[key] returns null for a missing key. Use getValue if a missing key is a bug.
  • A read-only Map is only a read-only view. If someone holds the mutable original, the contents can still change.
  • Using a mutable key and changing it after insertion breaks lookups.

Try it yourself

How would you find the most frequent word?

Reveal answer and reasoning

freq.maxByOrNull { it.value }?.key; with ties, it returns the first one encountered.

Further reading