Start learning free

LESSON 259 OF 414 · HASH TABLE

Maps in Scala: Immutable and Mutable Hash Maps

Lesson Read the full explanation

A Scala Map is useful when you need fast lookup by key.

val scores = Map("Ana" -> 90, "Ben" -> 75)
println(scores.contains("Ana"))

Assignment
Follow the Coding Tutorial and practice using maps.


Hint
Use key -> value pairs when creating a map.


STEP 1 OF 7
    Ctrl + Enter to check

    SCALA · HASH TABLE

    Hash Map in Scala, explained

    What a map is

    A map stores key-value pairs and finds a value by its key. Looking a key up, adding a pair, changing a value and removing a pair all take constant time on average, which makes maps the standard tool for counting, grouping and remembering what you have already seen.

    Scala has two families of maps. The default Map is immutable, and scala.collection.mutable.Map can be changed in place. Both are written with key -> value pairs, where -> builds a tuple of the key and the value.

    Immutable maps

    val scores = Map("Ana" -> 90, "Ben" -> 75)
    println(scores.contains("Ana"))   // true
    println(scores("Ana"))            // 90
    println(scores.get("Ana"))        // Some(90)
    println(scores.get("Cy"))         // None
    println(scores.getOrElse("Cy", 0)) // 0
    

    The ways of reading a key behave differently:

    • map(key) returns the value, and throws a NoSuchElementException with the message key not found: Cy when the key is missing.
    • map.get(key) returns an Option: Some(value) or None.
    • map.getOrElse(key, default) returns the value or the default you supply.
    • map.contains(key) returns a Boolean.

    An immutable map is never modified. An operation such as + returns a new map and leaves the original untouched:

    val more = scores + ("Cy" -> 60)
    println(scores.size) // 2
    println(more.size)   // 3
    

    Mutable maps

    When you want to update a map as the program runs, which is typical for counting, use the mutable one:

    val counts = scala.collection.mutable.Map[String, Int]()
    counts("x") = counts.getOrElse("x", 0) + 1
    println(counts) // HashMap(x -> 1)
    

    Writing counts("x") = value adds the key or replaces its value. Reading counts("x") for a key that isn't there throws, which is why the update above reads with getOrElse and a default of 0. The val only fixes which map the name refers to; the contents can still change.

    The common operations on a mutable map:

    • m(key) = value adds or replaces a pair.
    • m += (key -> value) does the same with a tuple.
    • m.remove(key) deletes the pair and returns an Option with the old value: Some(5) if the key was there, None otherwise.
    • m.getOrElseUpdate(key, default) returns the value, and stores the default first if the key was missing.
    • m.keys and m.values give the keys and the values.

    The exercise, step by step

    The lesson tracks how many fruits of each kind there are.

    1. Create the map. Declare fruits as a scala.collection.mutable.Map[String, Int] holding "apples" -> 3, "bananas" -> 5 and "grapes" -> 2.
    2. Add four apples. Read the current count with getOrElse("apples", 0), add 4 and store the sum back with index assignment.
    3. Check for pears. contains tells whether a key exists. Negate it with ! and print Out of pears when the key is absent.
    4. Remove the bananas with remove("bananas").
    5. Report every fruit. Loop over fruits.keys with the variable fruit and print an interpolated string that reads the count with ${fruits(fruit)}.
    val fruits: scala.collection.mutable.Map[String, Int] = scala.collection.mutable.Map(
        "apples" -> 3,
        "bananas" -> 5,
        "grapes" -> 2
    )
    
    fruits("apples") = fruits.getOrElse("apples", 0) + 4
    
    if (!fruits.contains("pears")) {
        println("Out of pears")
    }
    
    fruits.remove("bananas")
    
    for (fruit <- fruits.keys) {
        println(s"We have ${fruits(fruit)} $fruit.")
    }
    

    The program prints:

    Out of pears
    We have 7 apples.
    We have 2 grapes.
    

    A few details are worth noting. The type annotation on fruits is optional here, because Scala infers the same type from the right-hand side. In the string s"We have ${fruits(fruit)} $fruit.", the s in front turns on interpolation: $fruit inserts a variable, and ${...} evaluates a whole expression. Finally, fruits.remove("bananas") returns Some(5), which the program simply ignores.

    Common mistakes

    • Relying on iteration order. A hash map doesn't promise any particular order for its keys. If the order matters, sort the keys first: fruits.keys.toList.sorted.
    • Calling map(key) on a missing key. It throws. Use get, getOrElse or contains when the key might be absent.
    • Expecting an immutable map to change. scores + ("Cy" -> 60) builds a new map. Using the original afterwards still shows two entries.
    • Forgetting the full name. The plain name Map is the immutable one. The mutable one needs scala.collection.mutable.Map, or an import.

    Key takeaways

    • A map finds a value by key in constant time on average.
    • Map is immutable by default; scala.collection.mutable.Map changes in place.
    • map(key) throws for a missing key, while get returns an Option and getOrElse returns a default.
    • m(key) = value adds or replaces, and remove deletes.
    • Don't depend on the order in which keys come back.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free