Home > Software design >  Scala Map with mutable default value always point to the same object
Scala Map with mutable default value always point to the same object

Time:03-08

Scala version:3.1

I want to create a String -> Int map, that each key may point to many Int.

Therefore I choose map with default mutable Buffer[Int].

But it seems the all the keys always point to the same Buffer[Int]

Notice it is also weird in the first m("a") = 1 that, it print out nothing after this = but default value changed so that it print two 1 in the next println.

Also, how I can fix the problem that mutable.Map's = is overriden by Buffer's =. I am expecting the map insert a new Buffer if the key doens't exist, then call = of the buffer.

  import scala.collection.mutable
  val m: mutable.Map[String, mutable.Buffer[Int]] =
    mutable.HashMap.empty.withDefaultValue(mutable.Buffer.empty)

  m("a")  = 1
  println(m) // Map()

  m("a") = m("a")  = 1

  println(m) // Map(a -> ArrayBuffer(1, 1))

  m("b") = m("b")  = 2 // Map(a -> ArrayBuffer(1, 1, 2), b -> ArrayBuffer(1, 1, 2)) , not expecting this, key is correct but

  println(m) // Map(a -> ArrayBuffer(1, 1, 2), b -> ArrayBuffer(1, 1, 2))

  m("a") = m("a")  = 2

  println(m) // Map(a -> ArrayBuffer(1, 1, 2, 2), b -> ArrayBuffer(1, 1, 2, 2)) , 
  // this is not as I expected. The keys are correct, however their values are all the same ArrayBuffer

CodePudding user response:

TL;DR: withDefaultValue is useless here.

Note the signature of withDefaultValue is:

def withDefaultValue(d: V): Map[K, V]

The parameter d is taken by value, not by name (=> V), therefore it is only evaluated once. Therefore any key not present in the original map will return the same empty buffer you created once.

And what you get withDefaultValue (and withDefault) is a new map-like object, not the original map. Only assigning the results (m("a") = ...) will change it.

Note: The default is only used for apply. Other methods like get, contains, iterator, keys, etc. are not affected by withDefaultValue.

Consider this example:

val m = new HashMap[Integer, Buffer[String]]()
val mm = m.withDefault(_ => Buffer.empty)
mm(1).append("4")
mm(1) // ArrayBuffer()

What you probably want is getOrElseUpdate (you can define a helper function if you'd like) with the signature

getOrElseUpdate(key: K, defaultValue: => V): V

Note how defaultValue is called by name this time, and each call will create a new buffer:

val m: mutable.Map[String, mutable.Buffer[Int]] =
    mutable.HashMap.empty

def mm(key: String): mutable.Buffer[Int] = m.getOrElseUpdate(key, mutable.Buffer.empty)

mm("a")  = 1
mm("b")  = 2
// now buffers at `a` and `b` are distinct

CodePudding user response:

You can use withDefault, as is passes the parameter as a function, not by value as withDefaultValue does, therefore it is evaluated again each time:

val m: mutable.Map[String, mutable.Buffer[Int]] =
    mutable.HashMap.empty.withDefault(_ => mutable.Buffer.empty)
  • Related