Pregunta

Tengo una Lista de Mapa[String, Double], y me gustaría combinar sus contenidos en un único Mapa[String, Double].¿Cómo debo hacer esto en un idiomáticas manera?Yo me imagino que debe ser capaz de hacer esto con un pliegue.Algo así como:

val newMap = Map[String, Double]() /: listOfMaps { (accumulator, m) => ... }

Además, me gustaría identificador de clave de colisiones en una forma genérica.Es decir, si puedo agregar una clave del mapa que ya existe, yo debería ser capaz de especificar una función que devuelve un Double (en este caso) y toma el valor existente para esa clave, más el valor estoy tratando de agregar.Si la clave no existe todavía en el mapa, a continuación, acaba de añadir a su valor inalterado.

En mi caso particular me gustaría construir un único Mapa[String, Double] tal que si el mapa contiene una clave, luego el Doble será añadido a la mapa existente de valor.

Estoy trabajando con los mapas en mi código específico, pero estoy interesado en obtener más soluciones genéricas, si es posible.

¿Fue útil?

Solución

¿Qué tal este:

def mergeMap[A, B](ms: List[Map[A, B]])(f: (B, B) => B): Map[A, B] =
  (Map[A, B]() /: (for (m <- ms; kv <- m) yield kv)) { (a, kv) =>
    a + (if (a.contains(kv._1)) kv._1 -> f(a(kv._1), kv._2) else kv)
  }

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
val mm = mergeMap(ms)((v1, v2) => v1 + v2)

println(mm) // prints Map(hello -> 5.5, world -> 2.2, goodbye -> 3.3)

Y funciona tanto en 2.7.5 y 2.8.0.

Otros consejos

Bueno, se podría hacer:

mapList reduce (_ ++ _)

a excepción de la necesidad especial para la colisión.

Dado que usted tiene ese requisito especial, tal vez lo mejor sería hacer algo como esto (2.8):

def combine(m1: Map, m2: Map): Map = {
  val k1 = Set(m1.keysIterator.toList: _*)
  val k2 = Set(m2.keysIterator.toList: _*)
  val intersection = k1 & k2

  val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key)))
  val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_)) 
  r2 ++ r1
}

A continuación, puede añadir este método a la clase mapa a través del patrón de Pimp My Library, y utilizarlo en el ejemplo original en lugar de "++":

class CombiningMap(m1: Map[Symbol, Double]) {
  def combine(m2: Map[Symbol, Double]) = {
    val k1 = Set(m1.keysIterator.toList: _*)
    val k2 = Set(m2.keysIterator.toList: _*)
    val intersection = k1 & k2
    val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key)))
    val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_))
    r2 ++ r1
  }
}

// Then use this:
implicit def toCombining(m: Map[Symbol, Double]) = new CombiningMap(m)

// And finish with:
mapList reduce (_ combine _)

Mientras que esto fue escrito en 2.8, por lo que se convierte en keysIterator keys de 2,7, podría tener que ser escrito en términos de filterKeys y filter map, & convierte **, y así sucesivamente, no debe ser demasiado diferente.

Me sorprende que nadie llegar a esta solución aún:

myListOfMaps.flatten.toMap

Es exactamente lo que necesita:

  1. combina la lista a un solo mapa
  2. elimina a los duplicados de las llaves

Ejemplo:

scala> List(Map('a -> 1), Map('b -> 2), Map('c -> 3), Map('a -> 4, 'b -> 5)).flatten.toMap
res7: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 4, 'b -> 5, 'c -> 3)

flatten convierte a la lista de mapas en una lista plana de tuplas, toMap convierte a la lista de tuplas en un mapa con todos los duplicados de las llaves retiradas

Me leer esta pregunta rápidamente así que no estoy seguro de si me falta algo (como tiene que trabajar para 2.7.x o ninguna scalaz):

import scalaz._
import Scalaz._
val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms.reduceLeft(_ |+| _)
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2)

Puede cambiar la definición de monoid doble y obtener otra forma de acumular los valores, aquí conseguir el máximo:

implicit val dbsg: Semigroup[Double] = semigroup((a,b) => math.max(a,b))
ms.reduceLeft(_ |+| _)
// returns Map(goodbye -> 3.3, hello -> 4.4, world -> 2.2)

Interesante, noodling con esto un poco, tengo el siguiente (en 2.7.5):

General Mapas:

   def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: Seq[scala.collection.Map[A,B]]): Map[A, B] = {
    listOfMaps.foldLeft(Map[A, B]()) { (m, s) =>
      Map(
        s.projection.map { pair =>
        if (m contains pair._1)
          (pair._1, collisionFunc(m(pair._1), pair._2))
        else
          pair
      }.force.toList:_*)
    }
  }

Pero el hombre, que es horrible con la proyección y forzando y toList y otras cosas. cuestión distinta: ¿qué es una mejor manera de tratar con que dentro del pliegue

Para Mapas mutables, que es lo que estaba tratando con en mi código, y con una solución menos general, tengo esto:

def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A, B] = {
    listOfMaps.foldLeft(mutable.Map[A,B]()) {
      (m, s) =>
      for (k <- s.keys) {
        if (m contains k)
          m(k) = collisionFunc(m(k), s(k))
        else
          m(k) = s(k)
      }
      m
    }
  }

Eso parece un poco más limpia, pero sólo funcionará con mapas mutables como está escrito. Curiosamente, intentó por primera vez de lo anterior (antes de hacer la pregunta) utilizando /: en lugar de foldLeft, pero que estaba recibiendo errores de tipo. Pensé /: foldLeft y eran básicamente equivalentes, pero el compilador seguía quejándose de que necesitaba tipos explícitos para (m, s). ¿Qué pasa con eso?

escribí un post sobre esto, echa un vistazo:

http://www.nimrodstech.com/scala-map-merge/

básicamente usando scalaz grupo semi se puede lograr esto con bastante facilidad

sería algo como:

  import scalaz.Scalaz._
  listOfMaps reduce(_ |+| _)

A partir Scala 2.13, otra solución que maneja un duplicado de las llaves y es sólo basado en el estándar de la biblioteca consiste en la fusión de los Maps como secuencias (flatten) antes de aplicar la nueva groupMapReduce operador que (como su nombre lo indica) es un equivalente de un groupBy seguido por una asignación y reducir el paso de agrupar valores:

List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
  .flatten
  .groupMapReduce(_._1)(_._2)(_ + _)
// Map("world" -> 2.2, "goodbye" -> 3.3, "hello" -> 5.5)

Este:

  • flattens (concatena) los mapas como una secuencia de tuplas (List(("hello", 1.1), ("world", 2.2), ("goodbye", 3.3), ("hello", 4.4))), que mantiene todas las claves/valores (incluso un duplicado de las llaves)

  • groups elementos basados en su primera tupla parte (_._1) (grupo parte de grupoMapReduce)

  • maps agrupan los valores de su segunda tupla parte (_._2) (mapa forma parte del grupoMapaReducir)

  • reduces asignada agrupan valores (_+_) por tomar su suma (pero puede ser cualquier reduce: (T, T) => T función) (reducir parte de groupMapReducir)


El groupMapReduce el paso puede ser visto como un de un paso de la versión equivalente a:

list.groupBy(_._1).mapValues(_.map(_._2).reduce(_ + _))

a oneliner helper-func, cuyo uso se lee casi tan limpio como el uso de scalaz:

def mergeMaps[K,V](m1: Map[K,V], m2: Map[K,V])(f: (V,V) => V): Map[K,V] =
    (m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) })

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms.reduceLeft(mergeMaps(_,_)(_ + _))
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2)

para facilitar la lectura final se envuelve en un tipo personalizado implícita:

class MyMap[K,V](m1: Map[K,V]) {
    def merge(m2: Map[K,V])(f: (V,V) => V) =
    (m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) })
}
implicit def toMyMap[K,V](m: Map[K,V]) = new MyMap(m)

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms reduceLeft { _.merge(_)(_ + _) } 
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top