2010-02-03 7 views
5

En Scala 2.8, tengo un mapa inmutable con varios valores para cada tecla:relación calidad precio en Scala

Map[T,Iterable[U]] 

¿Hay una representación superior? En segundo lugar, ¿cómo generaría un mapa de este tipo desde

Iterable[(T,U)] 

? Actualmente estoy usando:

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

Que funciona, pero se siente torpe.

EDIT: Debo especificar que estoy trabajando con datos inmutables. ¿Hay un equivalente inmutable a MultiMap?

+1

Bueno, no puedo pensar en ninguna forma de mejorar lo que tienes. –

Respuesta

4

Si realmente no necesita inmutabilidad, a continuación, como otros han dicho, MultiMap es el camino a seguir. Si realmente necesitas inmutabilidad, entonces el enfoque que has tomado es tan fácil como cualquier otra cosa; no hay nada incorporado (AFAIK), y cualquier creación de un MultiMap inmutable requerirá mucho más trabajo que el método que tienes allí.

Si la representación es superior depende de su uso. ¿A menudo quieres hacer cosas con todos los valores correspondientes a una tecla? ¿Puedes insertar el mismo valor varias veces en tu mapa? Si es así para ambos, su representación es la correcta.

Si desea que el mismo valor insertado como máximo una vez en una llave, se debe usar en lugar de Set[U]Iterable[U] (que se puede hacer fácilmente mediante la adición de .toSet-it.map(_._2)).

Si no te gusta tener que lidiar con sets/iterables y solo lo estás soportando (es decir, prefieres tener pares clave-valor que pares key-setofvalues), deberías escribir una clase contenedora alrededor del mapa que presenta una única interfaz de mapa y haría lo correcto con +, -, e iterador.

Aquí hay un ejemplo que resultó un poco más de lo que había anticipado (en este formato para cortar y pegar en el 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]]()) 
} 

y podemos ver que esto funciona como se desea de este modo:

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) 

(aunque si quisieras recuperar un MapSet después de ++, necesitarías anular ++; la jerarquía de Mapas no tiene sus propios creadores para encargarse de cosas como esta).

2

Mire en la combinación de MultiMap para Mapa.

+0

Me resistí a MultiMap porque es mutable y no parece tener una contraparte inmutable. –

0

Un multimap es lo que necesita. Aquí hay un ejemplo de cómo crear uno y luego agregarle entradas desde una Lista [(String, Int)]. Estoy seguro de que hay una manera más bonita.

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) 
Cuestiones relacionadas