How to access and update a value in a mutable map of map of maps

13,596

Solution 1

You don't have to use "apply" just do it normally with "()"

m("normal")("home")("scores") = 1

Solution 2

You can write

m("normal").apply("home").apply("scores")

as

m("normal")("home")("scores")

However I'm not sure if such a structure is a good idea. Maybe you should consider encapsulating this functionality in a specialized class.

Solution 3

Adding a local helper function is always good way to reduce code duplication:

class MapExamplesSO {
  def test {
    import scala.collection.mutable.Map
    // The m map is essentially an accumulator
    var m = Map("normal" -> 
                Map("home" -> Map("wins" -> 0, "scores" -> 0),
                    "away" -> Map("wins" -> 0, "scores" -> 0)))


    //Local Helper returns (Old, New)
    def updateScore(k1 : String,k2 : String,k3 : String)
                   (f : Int => Int) : (Int, Int) = {
      val old = m(k1)(k2)(k3)
      m(k1)(k2)(k3) = f(old)
      (old, m(k1)(k2)(k3))
    }

    assert(m("normal")(home")("scores") === 0)
    assert(updateScore("normal","home","scores")(_+1)._2 === 1)
    assert(updateScore("normal","home","scores")(_+2)._2 === 3)
  }
}

[Edit made code tighter]

Solution 4

Less verbose:

assert(m("normal")("home")("scores") === 0)

val s1 = m("normal")("home")("scores") + 1
m("normal")("home")("scores") = s1

assert(m("normal")("home")("scores") === 1)

val s2 = m("normal")("home")("scores") + 2
m("normal")("home")("scores") = s2

assert(m("normal")("home")("scores") === 3)

This takes advantage of the fact that both apply and update have syntactic sugars for them as seen above. Shorter still:

// On REPL, put both these definitions inside an object instead
// of entering them on different lines
def scores = m("normal")("home")("scores")
def scores_=(n: Int) = m("normal")("home")("scores") = n

assert(scores === 0)

val s1 = scores + 1
scores = s1

assert(scores === 1)

val s2 = scores + 2
scores = s2

// Just so you see these updates are being made to the map:
assert(m("normal")("home")("scores") === 3)

Which takes advantage of the syntactic sugar for getters and setters (the getter definition must exist for the setter definition to work).

Share:
13,596
user272735
Author by

user272735

Updated on June 04, 2022

Comments

  • user272735
    user272735 almost 2 years

    I've a three-level data structure (indentation and line breaks for readability):

    scala> import scala.collection.mutable.Map
    import scala.collection.mutable.Map
    
    scala> val m = Map("normal" -> Map("home" -> Map("wins" -> 0, "scores" -> 0),
                                       "away" -> Map("wins" -> 0, "scores" -> 0)))
    m: scala.collection.mutable.Map[java.lang.String,
       scala.collection.mutable.Map[java.lang.String,
       scala.collection.mutable.Map[java.lang.String,Int]]] = 
    Map((normal,Map(away -> Map(wins -> 0, scores -> 0),
         home -> Map(wins -> 0, scores -> 0))))
    

    Accessing the innermost data (scores) requires a lot of typing:

    import org.scalatest.{Assertions, FunSuite}
    
    class MapExamplesSO extends FunSuite with Assertions {
      test("Update values in a mutable map of map of maps") {
        import scala.collection.mutable.Map
        // The m map is essentially an accumulator
        val m = Map("normal" -> 
                    Map("home" -> Map("wins" -> 0, "scores" -> 0),
                        "away" -> Map("wins" -> 0, "scores" -> 0)
                      )
              )
        //
        // Is there a less verbose way to increment the scores ?
        //
        assert(m("normal").apply("home").apply("scores") === 0)
    
        val s1 = m("normal").apply("home").apply("scores") + 1
        m("normal").apply("home").update("scores", s1)
    
        assert(m("normal").apply("home").apply("scores") === 1)
    
        val s2 = m("normal").apply("home").apply("scores") + 2
        m("normal").apply("home").update("scores", s2)
    
        assert(m("normal").apply("home").apply("scores") === 3)
      }
    }
    

    Is there a less verbose way to modify the value of scores ?

    I'm a Scala newbie, so all other observations of the code above are also welcome.

  • user272735
    user272735 over 13 years
    Clever ! (I just hope not too clever:) Took me a few readings before I realised what is going on here. Great answer, thanks !
  • user272735
    user272735 over 13 years
    This is also a very helpful answer - thanks ! I wish many upvotes to you too. Getter/setter explained for beginners: dustinmartin.net/2009/10/getters-and-setters-in-scala
  • Mark
    Mark over 7 years
    That works in this example, but won't work if m("normal")("home") hasn't already been defined.