Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to get a Scala HashMap to automatically initialize values?'

Tags:

scala

I thought it could be done as follows

val hash = new HashMap[String, ListBuffer[Int]].withDefaultValue(ListBuffer())
hash("A").append(1)
hash("B").append(2)
println(hash("B").head)

However the above prints the unintuitive value of 1. I would like

hash("B").append(2)

To do something like the following behind the scenes

if (!hash.contains("B")) hash.put("B", ListBuffer())
like image 691
deltanovember Avatar asked Dec 02 '22 01:12

deltanovember


2 Answers

Use getOrElseUpdate to provide the default value at the point of access:

scala> import collection.mutable._
import collection.mutable._

scala> def defaultValue = ListBuffer[Int]()
defaultValue: scala.collection.mutable.ListBuffer[Int]

scala> val hash = new HashMap[String, ListBuffer[Int]]
hash: scala.collection.mutable.HashMap[String,scala.collection.mutable.ListBuffer[Int]] = Map()

scala> hash.getOrElseUpdate("A", defaultValue).append(1)

scala> hash.getOrElseUpdate("B", defaultValue).append(2)

scala> println(hash("B").head)
2
like image 165
oxbow_lakes Avatar answered Dec 18 '22 10:12

oxbow_lakes


withDefaultValue uses exactly the same value each time. In your case, it's the same empty ListBuffer that gets shared by everyone.

If you use withDefault instead, you could generate a new ListBuffer every time, but it wouldn't get stored.

So what you'd really like is a method that would know to add the default value. You can create such a method inside a wrapper class and then write an implicit conversion:

class InstantiateDefaults[A,B](h: collection.mutable.Map[A,B]) {
  def retrieve(a: A) = h.getOrElseUpdate(a, h(a))
}
implicit def hash_can_instantiate[A,B](h: collection.mutable.Map[A,B]) = {
  new InstantiateDefaults(h)
}

Now your code works as desired (except for the extra method name, which you could pick to be shorter if you wanted):

val hash = new collection.mutable.HashMap[
  String, collection.mutable.ListBuffer[Int]
].withDefault(_ => collection.mutable.ListBuffer())

scala> hash.retrieve("A").append(1)

scala> hash.retrieve("B").append(2)

scala> hash("B").head
res28: Int = 2

Note that the solution (with the implicit) doesn't need to know the default value itself at all, so you can do this once and then default-with-addition to your heart's content.

like image 22
Rex Kerr Avatar answered Dec 18 '22 09:12

Rex Kerr