I’m trying to construct nested maps in Scala, where both the outer and inner map use the “withDefaultValue” method. For example, the following :
val m = HashMap.empty[Int, collection.mutable.Map[Int,Int]].withDefaultValue( HashMap.empty[Int,Int].withDefaultValue(3))
m(1)(2)
res: Int = 3
m(1)(2) = 5
m(1)(2)
res: Int = 5
m(2)(3) = 6
m
res : scala.collection.mutable.Map[Int,scala.collection.mutable.Map[Int,Int]] = Map()
So the map, when addressed by the appropriate keys, gives me back what I put in. However, the map itself appears empty! Even m.size returns 0 in this example. Can anyone explain what’s going on here?
Short answer
It’s definitely not a bug.
Long answer
The behavior of
withDefaultValueis to store a default value (in your case, a mutable map) inside the Map to be returned in the case that they key does not exist. This is not the same as a value that is inserted into the Map when they key is not found.Let’s look closely at what’s happening. It will be easier to understand if we pull the default map out as a separate variable so we can inspect is at will; let’s call it
defaultSo
defaultis a mutable map (that has its own default value). Now we can createmand givedefaultas the default value.Now whenever
mis accessed with a missing key, it will returndefault. Notice that this is the exact same behavior as you have becausewithDefaultValueis defined as:Notice that it’s
d: Band notd: => B, so it will not create a new map each time the default is accessed; it will return the same exact object, what we’ve calleddefault.So let’s see what happens:
Since key 1 is not in
m, the default,defaultis returned.defaultat this time is an empty Map.Since
m(1)returnsdefault, this operation stores 5 as the value for key 2 indefault. Nothing is written to the Mapmbecausem(1)resolves todefaultwhich is a separate Map entirely. We can check this by viewingdefault:But as we said,
mis left unchangedNow, how to achieve what you really wanted? Instead of using
withDefaultValue, you want to make use ofgetOrElseUpdate:Notice how we see
op: => B? This means that the argumentopwill be re-evaluated each time it is needed. This allows us to put a new Map in there and have it be a separate new Map for each invalid key. Let’s take a look:No default values needed here.
Key 1 doesn’t exist, so we insert a new HashMap, and return that new value. We can check that it was inserted as we expected. Notice that 1 maps to the newly added empty map and that they 3 was not added anywhere because of the behavior explained above.
Likewise, we can update the Map as expected:
and check that it was added: