views:

204

answers:

3

I have a mutable HashMap and would like to use it like a default-dictionary. The obvious method appears to be to use getOrElse and provide the default value each time as a second value. However this seems a little inelegant in my use case since the default value doesn't change.

var x = HashMap(1 -> "b", 2 -> "a", 3 -> "c")

println(x.getOrElse(4, "_")
println(x.getOrElse(5, "_"))
// And so on...
println(x.getOrElse(10, "_"))

Is there any way to create a HashMap (or similar class) such that attempting to access undefined keys returns a default value set on the creation of the HashMap? I notice that HashMap.default is just set to throw an exception but I wonder if this can be changed...

A: 
scala> val x = HashMap(1 -> "b", 2 -> "a", 3 -> "c").withDefaultValue("-")
x: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,b), (2,a), (3,c))

scala> x(3)
res0: java.lang.String = c

scala> x(5)
res1: java.lang.String = -

EDIT:

For mutable.HashMap, you could do the following:

scala> import collection.mutable
import collection.mutable

scala> val x = new mutable.HashMap[Int, String] {
     |   override def apply(key: Int) = super.get(key) getOrElse "-"
     | }
x: scala.collection.mutable.HashMap[Int,String] = Map()

scala> x += (1 -> "a", 2 -> "b", 3 -> "c")
res9: x.type = Map((2,b), (1,a), (3,c))

scala> x(2)
res10: String = b

scala> x(4)
res11: String = -

There might be a better way to do this. Wait for others to respond.

missingfaktor
That only appears to work with immutable HashMaps.
@PythonPower: You had not specified it in your original question that you were using `mutable.HashMap`. I don't know how to do it with `mutable.HashMap`. I'll have to look into the API.
missingfaktor
Yes, sorry I didn't specify -- I've edited my question to make it clear now.
A: 

I'm more a java guy... but if getOrElse is not final, why don't you just extend HasMap and provide something like this:

override def getOrElse(k: Int, default: String) = {
  return super.getOrElse(k,"_")
}

Note: syntax is probably screwed up but hopefully you'll get the point

Pablo Fernandez
Yes, I think it is possible to extend HashMap. In which case it'd probably be easier to change the HashMap.default method to return what I want. The constructor could take the default value and the HashMap.default method would just return that. I'm hoping there's an easier method, however.
+4  A: 

Try this:

import collection.mutable.HashMap
val x = new HashMap[Int,String]()  { override def default(key:Int) = "-" }
x += (1 -> "b", 2 -> "a", 3 -> "c")

Then:

scala> x(1)
res7: String = b

scala> x(2)
res8: String = a

scala> x(3)
res9: String = c

scala> x(4)
res10: String = -
huynhjl
+1. Wonder why `mutable.HashMap` doesn't provide a `withDefaultValue` method like `immutable.HashMap`.
missingfaktor
It's horrible syntax, but it does the trick. Maybe the lack of withDefaultValue for mutable HashMaps should be reported as a bug?
There is a "!!! todo: move to general maps?" comment in the neighboring `withDefault` in http://www.scala-lang.org/api/current/scala/collection/immutable/Map.html. So may be this is not too much of a stretch. The scala-user mailing list is probably a good place to inquire about this and see if a bug should be reported.
huynhjl
@PythonPower an enhancement ticket was submitted http://lampsvn.epfl.ch/trac/scala/ticket/3829.
huynhjl
Thanks for reporting it! It's really nice to see Scala being so actively improved.