Two kinds of map, two ways to update
Scala has two families of maps, and updating works differently in each.
An immutable Map, the default, can't be changed. To "update" it you call updated, which returns a new map with the change and leaves the original as it was. You must keep the new map somewhere, typically in a var:
var ages = Map("Ana" -> 12)
ages = ages.updated("Ana", 13)
println(ages("Ana")) // 13
A mutable map, from scala.collection.mutable, changes in place. Assigning to a key replaces its value in the same map object, so a val is enough:
val scores = scala.collection.mutable.Map("Alice" -> 95, "Bob" -> 87)
scores("Alice") = 99
println(scores("Alice")) // 99
The assignment syntax
The line scores("Alice") = 99 looks like assigning to a function call. Scala translates it into scores.update("Alice", 99), so both spellings do the same thing. The same rule makes buffer(0) = 5 work on arrays and buffers.
The assignment sets the value for the key and returns nothing. If the key already exists, its value is replaced. If it doesn't, a new entry is added:
val scores = scala.collection.mutable.Map("Alice" -> 95, "Bob" -> 87)
scores("Alice") = 99 // replaces the value, size stays 2
scores("Dan") = 50 // adds a new entry, size becomes 3
println(scores.size) // 3
The exercise
The starting code has a mutable map with two entries. The task is to change Alice's score to 99.
- Keep the existing
scoresdeclaration unchanged. - Below it, write
scores("Alice") = 99. The key goes in parentheses, and the new value goes after the=.
val scores = scala.collection.mutable.Map("Alice" -> 95, "Bob" -> 87)
scores("Alice") = 99
Afterwards scores("Alice") is 99 and scores("Bob") is still 87. The name scores was declared with val and was never reassigned: only the contents of the map changed.
Reading the result back
After an update, the new value is visible immediately through any name that refers to the same mutable map. Two names that point at one map share its changes, so a function that receives scores and runs scores("Alice") = 99 changes the caller's map too. An immutable map never behaves like this, which makes it safer to pass around when you don't want surprises.
Update based on the old value
A common pattern is to compute the new value from the current one, such as counting occurrences. The key might not exist yet, so read it with getOrElse and a starting value:
val counts = scala.collection.mutable.Map[String, Int]()
for (word <- List("a", "b", "a")) {
counts(word) = counts.getOrElse(word, 0) + 1
}
println(counts.toList.sorted) // List((a,2), (b,1))
Reading with counts(word) instead would throw NoSuchElementException the first time a word appears, because there is no entry to read yet.
Common mistakes
- Using this syntax on an immutable map. With a plain
Map(...), the lineages("Ana") = 13is rejected by the compiler, because an immutable map has noupdatemethod. Either switch to a mutable map or useupdated. - Forgetting to keep the result of
updated. Writingages.updated("Ana", 13)on its own line builds a new map that nobody keeps.agesstill holds12. - Misspelling the key. An update with
"alice"doesn't change"Alice". It adds a second entry under the new key. - Using
:=or=>. The update is a plain=. The arrow->is only for building pairs.
Other ways to change a mutable map
val scores = scala.collection.mutable.Map("Alice" -> 95, "Bob" -> 87)
scores.update("Bob", 1) // same as scores("Bob") = 1
scores -= "Alice" // removes the entry for "Alice"
println(scores) // HashMap(Bob -> 1)
Key takeaways
- On a mutable map,
map(key) = valuereplaces the value for an existing key and adds an entry for a new one. - On an immutable map,
updatedreturns a new map, so store it in avaror anotherval. valfixes the name, not the contents of a mutable map.
