scala - tutorial - manual de navisworks 2018 en español pdf
Scala: combina mapas por clave (6)
Digamos que tengo dos mapas:
val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")
Quiero fusionar estos mapas por clave, aplicando alguna función para recopilar los valores (en este caso particular, quiero recopilarlos en un seq, dando:
val c = Map(1 -> Seq("one", "un"), 2->Seq("two", "deux"), 3->Seq("three", "trois"))
Se siente como si hubiera una forma agradable e idiomática de hacer esto, ¿alguna sugerencia? Estoy contento si la solución implica scalaz.
Así que no estaba del todo contento con ninguna de las soluciones (quiero construir un nuevo tipo, por lo que semigroup no parece realmente apropiado, y la solución de Infinity parecía bastante compleja), así que me he ido con esto por el momento. Estaría feliz de verlo mejorado:
def merge[A,B,C](a : Map[A,B], b : Map[A,B])(c : (B,B) => C) = {
for (
key <- (a.keySet ++ b.keySet);
aval <- a.get(key); bval <- b.get(key)
) yield c(aval, bval)
}
merge(a,b){Seq(_,_)}
Quería el comportamiento de no devolver nada cuando una clave no estaba presente en ninguno de los mapas (que difiere de otras soluciones), pero una forma de especificar esto sería agradable.
Este es mi primer enfoque antes de buscar las otras soluciones:
for (x <- a) yield
x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten
Para evitar elementos que existen solo en aob, un filtro es útil:
(for (x <- a) yield
x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten).filter (_._2.size == 2)
Aplanar es necesario, porque b.get (x._1) devuelve una Opción. Para que Flatten funcione, el primer elemento también debe ser una opción, por lo que no podemos usar x._2 aquí.
Para las secuencias, también funciona:
scala> val b = Map (1 -> Seq(1, 11, 111), 2 -> Seq(2, 22), 3 -> Seq(33, 333), 5 -> Seq(55, 5, 5555))
b: scala.collection.immutable.Map[Int,Seq[Int]] = Map(1 -> List(1, 11, 111), 2 -> List(2, 22), 3 -> List(33, 333), 5 -> List(55, 5, 5555))
scala> val a = Map (1 -> Seq(1, 101), 2 -> Seq(2, 212, 222), 3 -> Seq (3, 3443), 4 -> (44, 4, 41214))
a: scala.collection.immutable.Map[Int,ScalaObject with Equals] = Map(1 -> List(1, 101), 2 -> List(2, 212, 222), 3 -> List(3, 3443), 4 -> (44,4,41214))
scala> (for (x <- a) yield x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten).filter (_._2.size == 2)
res85: scala.collection.immutable.Map[Int,Seq[ScalaObject with Equals]] = Map(1 -> List(List(1, 101), List(1, 11, 111)), 2 -> List(List(2, 212, 222), List(2, 22)), 3 -> List(List(3, 3443), List(33, 333)))
Scalaz agrega un método |+|
para cualquier tipo A
para el que esté disponible un Semigroup[A]
.
Si asignó sus Mapas para que cada valor fuera una secuencia de elemento único, entonces podría usar esto de manera bastante simple:
scala> a.mapValues(Seq(_)) |+| b.mapValues(Seq(_))
res3: scala.collection.immutable.Map[Int,Seq[java.lang.String]] = Map(1 -> List(one, un), 2 -> List(two, deux), 3 -> List(three, trois))
scala.collection.immutable.IntMap
tiene un método intersectionWith
que hace exactamente lo que quieres (creo):
import scala.collection.immutable.IntMap
val a = IntMap(1 -> "one", 2 -> "two", 3 -> "three", 4 -> "four")
val b = IntMap(1 -> "un", 2 -> "deux", 3 -> "trois")
val merged = a.intersectionWith(b, (_, av, bv: String) => Seq(av, bv))
Esto le da IntMap(1 -> List(one, un), 2 -> List(two, deux), 3 -> List(three, trois))
. Tenga en cuenta que ignora correctamente la clave que solo ocurre en a
.
Como nota al margen: a menudo me encontré que quería las unionWith
, intersectionWith
, etc., de Haskell Data.Map
en Scala. No creo que haya ninguna razón de principio que IntMap
que solo deberían estar disponibles en IntMap
, en lugar de en la collection.Map
base. Rasgo de IntMap
.
val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")
val c = a.toList ++ b.toList
val d = c.groupBy(_._1).map{case(k, v) => k -> v.map(_._2).toSeq}
//res0: scala.collection.immutable.Map[Int,Seq[java.lang.String]] =
//Map((2,List(two, deux)), (1,List(one, un), (3,List(three, trois)))
val fr = Map(1 -> "one", 2 -> "two", 3 -> "three")
val en = Map(1 -> "un", 2 -> "deux", 3 -> "trois")
def innerJoin[K, A, B](m1: Map[K, A], m2: Map[K, B]): Map[K, (A, B)] = {
m1.flatMap{ case (k, a) =>
m2.get(k).map(b => Map((k, (a, b)))).getOrElse(Map.empty[K, (A, B)])
}
}
innerJoin(fr, en) // Map(1 -> ("one", "un"), 2 -> ("two", "deux"), 3 -> ("three", "trois")): Map[Int, (String, String)]