Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Many-value map in Scala

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?

like image 674
David Crawshaw Avatar asked Feb 03 '10 16:02

David Crawshaw


People also ask

Can you have multiple values in a map?

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.

Can map have multiple values for same key?

In these cases, we can use Collections such as list, set, etc. to insert multiple values into the same key in HashMap.

How many parameters does map take in Scala?

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.

What does map () do in Scala?

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.


2 Answers

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).

like image 196
Rex Kerr Avatar answered Sep 21 '22 06:09

Rex Kerr


Look into the MultiMap mix-in for Map.

like image 45
Randall Schulz Avatar answered Sep 20 '22 06:09

Randall Schulz