In Scala 2.8, I have an immutable map with multiple values for each key:
Map[T,Iterable[U]]
Is there a superior representation? Secondly, how would you generate such a map from
Iterable[(T,U)]
? I am presently using:
def toGroupedMap[T,U](vals: Iterable[(T,U)]): Map[T,Iterable[U]] =
vals.groupBy(_._1).map({ case (s,it) => (s,it.map(_._2)) }).toMap
Which works, but feels clunky.
EDIT: I should specify that I am working with immutable data. Is there an immutable equivalent to MultiMap?
The Map interface stores the elements as key-value pairs. It does not allow duplicate keys but allows duplicate values. HashMap and LinkedHashMap classes are the widely used implementations of the Map interface. But the limitation of the Map interface is that multiple values cannot be stored against a single key.
In these cases, we can use Collections such as list, set, etc. to insert multiple values into the same key in HashMap.
The Scala Map type has two type parameters, for the key type and for the value type. So a Map[String, Int] maps strings to integers, while a Map[Int, Set[String]] maps integers to sets of strings. Scala provides both immutable and mutable maps.
map() method is a member of TraversableLike trait, it is used to run a predicate method on each elements of a collection. It returns a new collection.
If you don't really need immutability, then as others have said, MultiMap
is the way to go. If you do really need immutability, then the approach you've taken is as easy as anything else; there isn't anything built in (AFAIK), and any creation of a immutable MultiMap is going to take a lot more work than the method you've got there.
Whether the representation is superior depends on your usage. Do you often want to do things with all values corresponding to one key? Can you insert the same value multiple times into your map? If yes to both, your representation is the right one.
If you want the same value inserted at most once at one key, then you should use Set[U]
instead of Iterable[U]
(which can easily be done by adding .toSet
to it.map(_._2)
).
If you dislike having to deal with sets/iterables and are just putting up with it (i.e. you'd really rather just have key-value pairs than key-setofvalues pairs), you'd have to write a wrapper class around the map that presents a single map interface and would do the right thing with +, -, and iterator.
Here's an example that turned out a little longer than I had anticipated (here formatted for cut and paste into the REPL):
import scala.collection._
class MapSet[A,B](
val sets: Map[A,Set[B]] = Map[A,Set[B]]()
) extends Map[A,B] with MapLike[A,B,MapSet[A,B]] {
def get(key: A) = sets.getOrElse(key,Set[B]()).headOption
def iterator = new Iterator[(A,B)] {
private val seti = sets.iterator
private var thiskey:Option[A] = None
private var singles:Iterator[B] = Nil.iterator
private def readyNext {
while (seti.hasNext && !singles.hasNext) {
val kv = seti.next
thiskey = Some(kv._1)
singles = kv._2.iterator
}
}
def hasNext = {
if (singles.hasNext) true
else {
readyNext
singles.hasNext
}
}
def next = {
if (singles.hasNext) (thiskey.get , singles.next)
else {
readyNext
(thiskey.get , singles.next)
}
}
}
def +[B1 >: B](kv: (A,B1)):MapSet[A,B] = {
val value:B = kv._2.asInstanceOf[B]
new MapSet( sets + ((kv._1 , sets.getOrElse(kv._1,Set[B]()) + value)) )
}
def -(key: A):MapSet[A,B] = new MapSet( sets - key )
def -(kv: (A,B)):MapSet[A,B] = {
val got = sets.get(kv._1)
if (got.isEmpty || !got.get.contains(kv._2)) this
else new MapSet( sets + ((kv._1 , got.get - kv._2)) )
}
override def empty = new MapSet( Map[A,Set[B]]() )
}
and we can see that this works as desired like so:
scala> new MapSet() ++ List(1->"Hi",2->"there",1->"Hello",3->"Bye")
res0: scala.collection.Map[Int,java.lang.String] = Map(1 -> Hi, 1 -> Hello, 2 -> there, 3 -> Bye)
scala> res0 + (2->"ya")
res1: scala.collection.Map[Int,java.lang.String] = Map(1 -> Hi, 1 -> Hello, 2 -> there, 2 -> ya, 3 -> Bye)
scala> res1 - 1
res2: scala.collection.Map[Int,java.lang.String] = Map(2 -> there, 2 -> ya, 3 -> Bye)
(though if you wanted to get a MapSet back after ++, you'd need to override ++; the Map hierarchy doesn't have its own builders to take care of things like this).
Look into the MultiMap mix-in for Map.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With