scalaNewb
scalaNewb

Reputation: 11

Scala: HashMap with different data types for different keys possible?

I am totally new to programming with Scala and I have the following problem:

I need a HashMap which can contain many data types (Int, String, etc..). In C++ I would use BOOST's MultiMap. I have heard that in Scala there is a MultiMap trait available. Basically what I want to to is the following:

val map = HashMap[String, ListBuffer[_]]

The concrete datatype of the ListBuffer's elements are to be determined during runtime. When I test this implementation in console the following works:

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

scala> val b = new ListBuffer[String]()
b: scala.collection.mutable.ListBuffer[String] = ListBuffer()

scala> val c = new ListBuffer[Int]()
c: scala.collection.mutable.ListBuffer[Int] = ListBuffer()

scala> b += "String"
res0: b.type = ListBuffer(String)

scala> c += 1
res1: c.type = ListBuffer(1)

scala> a += "String Buffer" -> b
res2: a.type = Map((String Buffer,ListBuffer(String)))

scala> a += "This is an Int Buffer" -> c
res3: a.type = Map((String Buffer,ListBuffer(String)), (This is an Int Buffer,ListBuffer(1)))

So basically it works. My first question is, whether there is a possiblity to implement the same behaviour in Scala without using the ListBuffer as layer of indirection.

E.g getting a Map with the following content: Map((String, 1),(String, "String value"), ...)

When I am now trying to use the ListBuffer-Implementation above, I get the following type mismatch error:

found   : _$1 where type _$1
required: _$3 where type _$3

I am basically trying to do the following:

I use an iterator to iterate over the map's keys:

var valueIds = new ListBuffer[Int]()
val iterator = map.keys
iterator.foreach(key => { valueIds += setValue((map.apply(key)).last) }

setValue returns Int and is a method which has to do something with the ListBuffers last element.

Does anybody know how to fix the above mentioned type mismatch?

Thanks for your help!

Regards

Upvotes: 1

Views: 6416

Answers (4)

elbowich
elbowich

Reputation: 1951

Let me suggest one more way:

import scala.collection.mutable

val map = mutable.Map.empty[String, Vector[Any]].withDefaultValue(Vector.empty)
map("strings") :+= "one"
map("ints") :+= 1
map("ints") ++= Seq(1, 2)

assert {
  map("default") == Vector.empty && // no side effects here
  !map.contains("default")
}

Upvotes: 0

Moritz
Moritz

Reputation: 14212

If you use the placeholder _ as a type parameter this gets translated into an existential type, i.e. your definition ListBuffer[_] becomes ListBuffer[A] forSome { type A }. This means that the compiler does not know anything about that type A and cannot make any assumptions about it.

The easiest fix would be to simply use a ListBuffer[Any] and wrap the map with something like this:

val m = new HashMap[String,ListBuffer[Any]]

def get(key: String) =
  m.getOrElseUpdate(key, new ListBuffer())

get("Strings") += "a"
get("Strings") += "b" += "c"
get("Ints") += 1 += 2
// m is now:
// Map(Ints -> ListBuffer(1, 2), Strings -> ListBuffer(a, b, c))

Upvotes: 1

Rex Kerr
Rex Kerr

Reputation: 167901

Do you need multiple values stored per key? If so, using a ListBuffer or other collection is necessary anyway. (Scala's MultiMaps store sets, so if you need to hold duplicates they won't work.)

If you do not need multiple values stored per key, then you just want the Any type:

scala> val map = collection.mutable.HashMap[String,Any]()
map: scala.collection.mutable.HashMap[String,Any] = Map()

scala> map += "One" -> 1
res1: map.type = Map((One,1))

scala> map += "Two" -> "ii"
res2: map.type = Map((Two,ii), (One,1))

scala> map += "Three" -> None
res3: map.type = Map((Three,None), (Two,ii), (One,1))

where you'll now presumably need to do pattern matching or use collect to do anything useful to the values:

scala> map.values.foreach(_ match { case i: Int => println("We stored the number "+i) })
We stored the number 1

scala> map.values.collect{ case i: Int => i }
res4: Iterable[Int] = List(1)

Upvotes: 6

JR Boyens
JR Boyens

Reputation: 287

Scala has a MultiMap class

scala> import scala.collection.mutable.{HashMap, MultiMap, Set}     
import scala.collection.mutable.{HashMap, MultiMap, Set}

scala> val a = new HashMap[String, Set[Any]] with MultiMap[String, Any]
a: scala.collection.mutable.HashMap[String,scala.collection.mutable.Set[Any]] with scala.collection.mutable.MultiMap[String,Any] = Map()

scala> a.addBinding("Pants", 1)
res0: a.type = Map((Pants,Set(1)))

scala> a.addBinding("Pants", 2)
res1: a.type = Map((Pants,Set(1, 2)))

scala> a.addBinding("Trousers", 3)
res2: a.type = Map((Trousers,Set(3)), (Pants,Set(1, 2)))

scala> a.mapValues { v => v.last }
res3: scala.collection.Map[String,Any] = Map((Trousers,3), (Pants,2))

scala> val valueIds = a.values.flatten
valueIds: Iterable[Any] = List(3, 1, 2)

I think that makes sense when looking at your code as to what you want.

Upvotes: 4

Related Questions