2010-02-03 14 views
5

W Scala 2.8, mam mapę niezmienne z wieloma wartościami dla każdego klucza:Wiele wartości w Scala

Map[T,Iterable[U]] 

Czy istnieje lepsza reprezentacja? Po drugie, w jaki sposób wygenerowałbyś taką mapę z

Iterable[(T,U)] 

? Obecnie używam:

def toGroupedMap[T,U](vals: Iterable[(T,U)]): Map[T,Iterable[U]] = 
    vals.groupBy(_._1).map({ case (s,it) => (s,it.map(_._2)) }).toMap 

Który działa, ale czuje się niezgrabny.

EDYCJA: Należy określić, że pracuję z niezmiennymi danymi. Czy istnieje niezmienny odpowiednik MultiMap?

+1

Cóż, nie mogę wymyślić żadnego sposobu poprawy tego, co masz. –

Odpowiedz

4

Jeśli naprawdę nie potrzeba niezmienności, a następnie jak mówili inni, MultiMap jest droga. Jeśli naprawdę potrzebujesz niezmienności, to podejście, które podjąłeś, jest tak łatwe, jak wszystko inne; nie ma nic wbudowanego (AFAIK), a każde stworzenie niezmiennej MultiMapy zajmie znacznie więcej pracy niż metoda, którą tam masz.

To, czy przedstawienie jest lepsze, zależy od sposobu użycia. Czy często chcesz robić rzeczy ze wszystkimi wartościami odpowiadającymi jednemu kluczowi? Czy możesz wstawić tę samą wartość wiele razy do swojej mapy? Jeśli tak dla obu, twoja reprezentacja jest właściwa.

Jeśli chcesz taką samą wartość włożonej co najwyżej raz na jednym kluczu, należy użyć Set[U] zamiast Iterable[U] (co można łatwo zrobić, dodając .toSet do it.map(_._2)).

Jeśli nie lubisz mieć do czynienia z setami/iterabelami i po prostu się z tym pogodzisz (to znaczy, że wolisz raczej pary klucz-wartość niż pary klucz-wartość_wartości), musisz napisać klasę opakowania wokół mapy, która przedstawia pojedynczy interfejs mapy i zrobiłaby to, co trzeba, za pomocą +, - i iteratora.

Oto przykład, który okazał się nieco dłużej niż się spodziewali (tutaj sformatowana do wycinania i wklejania do 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]]()) 
} 

i widzimy, że to działa zgodnie z oczekiwaniami tak:

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) 

(gdybyś chciał odzyskać MapSet po ++, musiałbyś zastąpić ++, Hierarchia Map nie ma własnych budowniczych, aby zająć się takimi sprawami).

2

Zaglądnij do miksowania MultiMap dla mapy.

+0

Odskoczyłem od MultiMap, ponieważ jest zmienne i nie wydaje się mieć niezmiennego odpowiednika. –

0

Multimap jest tym, czego potrzebujesz. Oto przykład tworzenia jednego, a następnie dodawania do niego wpisów z listy [(String, Int)]. Jestem pewien, że jest ładniejsza droga.

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

scala> List(("a", 1), ("a", 2), ("b", 3)).map(e => a.addBinding(e._1, e._2))              
res0: List[scala.collection.mutable.HashMap[String,scala.collection.mutable.Set[Int]] with scala.collection.mutable.MultiMap[String,Int]] = List(Map(a -> Set(1, 2), b -> Set(3)), Map(a -> Set(1, 2), b -> Set(3)), Map(a -> Set(1, 2), b -> Set(3))) 

scala> a("a") 
res2: scala.collection.mutable.Set[Int] = Set(1, 2)