Reputation: 11
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
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
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
Reputation: 167901
Do you need multiple values stored per key? If so, using a ListBuffer
or other collection is necessary anyway. (Scala's MultiMap
s 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
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