Skip to content
Lesson 14 of 19

Step 1 of 5 · Reading · ~3 min

Learn

Collections

Maps and Sets

A List answers "what is at position 3?". The two collections in this lesson answer different questions. A Map answers "what is stored under this key?", and a Set answers "have I seen this one before?".

Map: keys to values

fun main() {
    val ages = mapOf("Ada" to 36, "Grace" to 45)

    println(ages["Ada"])
    println(ages["Alan"])
    println(ages.size)
    println(ages.containsKey("Grace"))
}

//> 36
//> null
//> 2
//> true

to is not a keyword. It is an ordinary function written in infix position: "Ada" to 36 builds a Pair, and mapOf collects those pairs into a map.

What happens when the key is missing?

Look at the second line of that output again. ages["Alan"] did not throw and did not quietly return 0 — it returned null. That is deliberate. The index operator on a Map<String, Int> gives back an Int?, so the compiler forces you to decide what a missing key means before you can use the value.

WrittenResult for a missing key
ages["Alan"]null
ages["Alan"] ?: 00
ages.getOrElse("Alan") { 0 }0
ages["Alan"]!!throws NullPointerException

✓ Counting with a fallback — the standard Kotlin idiom:

fun main() {
    val counts = mutableMapOf<String, Int>()
    for (word in listOf("red", "blue", "red")) {
        counts[word] = (counts[word] ?: 0) + 1
    }
    println(counts)
}

//> {red=2, blue=1}

✗ The same loop written with !! crashes on the very first word, because that key is not in the map yet. The Elvis operator is what turns "not there" into "start at zero".

Walking a map

fun main() {
    val ages = mapOf("Ada" to 36, "Grace" to 45)
    for ((name, age) in ages) {
        println(name + " is " + age)
    }
}

//> Ada is 36
//> Grace is 45

mapOf keeps insertion order, so that output is stable rather than lucky. keys and values hand you each side of the map on its own.

Set: membership without duplicates

A Set stores each element at most once. Adding something that is already there is simply ignored.

fun main() {
    val tags = setOf("kotlin", "jvm", "kotlin")
    println(tags)
    println(tags.size)
    println("jvm" in tags)
}

//> [kotlin, jvm]
//> 2
//> true

Membership is the reason sets exist. Checking in on a List walks the whole list, which is O(n)O(n), while a hash-based Set answers in O(1)O(1) on average. Across three items that is nothing; across three hundred thousand it is the difference between instant and hopeless.

Counting distinct things

fun main() {
    val words = listOf("the", "cat", "the", "dog")

    println(words.toSet())
    println(words.toSet().size)
    println(words.distinct())
    println(words.size)
}

//> [the, cat, dog]
//> 3
//> [the, cat, dog]
//> 4

.toSet() gives back a Set; .distinct() gives back a List with first-seen order preserved. Both compare elements with equals, so they work out of the box for strings, numbers, and data classes.

Which one do I want?

Question you are askingCollection
What is the third item?List
Have I already seen this?Set
What value belongs to this key?Map
How many different values are there?Set, via .toSet().size

Each has a mutable twin — mutableListOf, mutableSetOf, mutableMapOf — and the rule from the Lists and Arrays lesson still holds: use the read-only form until you actually need to change the contents.

Your exercise

Distinct Words reads one line of space-separated words and prints how many different words appeared.

The starter splits the line into a List<String> for you. The grader's first visible test is the quick brown the — four words, three distinct — and it expects 3.

Two mistakes it catches. Printing words.size gives 4, because that counts the repeat. Printing the collection instead of its size outputs [the, quick, brown] rather than a number. Print a single integer and nothing else.

Up nextSequencesCollections

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…