The problem with a missing key
A missing key often just means "none yet": a shop with no plums has 0 plums. But on a Kotlin map, reading a missing key gives null:
val stock = mutableMapOf("apples" to 5)
val t: Int? = stock["plums"]
println(t)
null
The type is Int?, a number or null, so you can't use it in arithmetic until you deal with the null. Forcing it with !! crashes the program when the key is missing.
getOrDefault
getOrDefault(key, default) gives the key's value when the key is there and the default when it isn't. Either way the answer is a plain Int, never null.
println(stock.getOrDefault("apples", 0))
println(stock.getOrDefault("plums", 0))
println(stock.size)
5
0
1
Reading this way doesn't add the key: stock still has one entry afterwards. The default is only what you get back, not something the map stores.
Other ways to say the same thing
- The Elvis operator.
stock["plums"] ?: 0uses the right side when the left isnull. It gives0here too. - getOrElse.
stock.getOrElse("plums") { -1 }takes a small block instead of a value, which is handy when computing the default takes some work. It gives-1here. - getOrPut.
stock.getOrPut("dan") { 0 }on a mutable map also gives0for a missing key, but it stores that default as well, so the map grows. Use it only when you do want the key added.
Pick getOrDefault when you just want a number to calculate with and the map should stay as it is.
Traps
- The default has to fit the value type. For a map of
Intcounts, 0 is a natural default; for text, an empty string""may be. - A key that is present keeps its own value, even if that value is 0. The default only applies to missing keys.
- Keys match exactly, so
"Plums"is a different key from"plums".
A common use: counting
A default of 0 is what makes counting short. Each time a letter appears, read its count so far (0 if it is new), add 1 and store the result:
val letters = mutableMapOf<Char, Int>()
for (c in "banana") letters[c] = letters.getOrDefault(c, 0) + 1
println(letters)
{b=1, a=3, n=2}
Without getOrDefault you would need an if to handle the first time a letter shows up. Notice that the read and the write are separate steps: getOrDefault only reads, and the assignment is what adds the key.
For a map of text values the default is text too: words.getOrDefault("b", "none") gives none when "b" is missing, and words.getOrDefault("b", "") gives an empty string.
The exercise
stock holds apples and pears. The starter code reads the pears with stock["pears"]!!, which is safe because the key is there, and prints the pears line. For the plums, the key is missing, so step 1 uses getOrDefault with 0, and step 2 prints the line the same way as the pears line.
val stock = mutableMapOf("apples" to 5, "pears" to 2)
val pears = stock["pears"]!!
println("We have " + pears + " pears")
val plums = stock.getOrDefault("plums", 0)
println("We have " + plums + " plums")
We have 2 pears
We have 0 plums
Key takeaways
- A missing key reads as
null, with the typeInt?. getOrDefault(key, default)gives a plain value and leaves the map unchanged.?: defaultandgetOrElseare alternatives;getOrPutalso stores the default.
