it-swarm.com.de

Scala: So fügen Sie eine Sammlung von Karten zusammen

Ich habe eine Liste von Map [String, Double], und ich möchte deren Inhalt in einer einzelnen Map [String, Double] zusammenführen. Wie soll ich das idiomatisch tun? Ich kann mir vorstellen, dass ich das mit einer Falte machen kann. So etwas wie:

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

Darüber hinaus möchte ich mit Schlüsselkollisionen generisch umgehen. Das heißt, wenn ich der bereits vorhandenen Karte einen Schlüssel hinzufüge, sollte ich in der Lage sein, eine Funktion anzugeben, die einen Double (in diesem Fall) zurückgibt und den vorhandenen Wert für diesen Schlüssel sowie den Wert, den ich hinzufügen möchte, übernimmt . Wenn der Schlüssel noch nicht in der Map vorhanden ist, fügen Sie ihn einfach und seinen Wert unverändert hinzu.

In meinem speziellen Fall würde ich gerne eine einzelne Map [String, Double] erstellen, sodass, wenn die Map bereits einen Schlüssel enthält, der Double zum vorhandenen Map-Wert hinzugefügt wird.

Ich arbeite mit veränderbaren Karten in meinem spezifischen Code. Ich interessiere mich jedoch für mehr generische Lösungen, wenn möglich.

32
Jeff

Wie wäre es mit diesem:

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)

Und es funktioniert sowohl in 2.7.5 als auch in 2.8.0.

26
Walter Chang

Nun, Sie könnten tun:

mapList reduce (_ ++ _)

mit Ausnahme der besonderen Anforderung für eine Kollision.

Da Sie diese spezielle Anforderung haben, würde das Beste vielleicht Folgendes tun (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
}

Sie können diese Methode dann über das Pimp My Library-Muster zur Kartenklasse hinzufügen und sie im ursprünglichen Beispiel anstelle von "++" verwenden:

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

Während dies in 2.8 geschrieben wurde, wird keysIterator für 2.7 zu keys. filterKeys muss möglicherweise in Form von filter und map geschrieben werden, & wird zu ** und sollte daher nicht zu unterschiedlich sein.

39

Ich bin überrascht, dass noch niemand mit dieser Lösung gekommen ist:

myListOfMaps.flatten.toMap

Macht genau das, was Sie brauchen:

  1. Führt die Liste zu einer einzelnen Karte zusammen
  2. Weicht doppelte Schlüssel aus

Beispiel:

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 wandelt die Liste der Maps in eine flache Liste von Tupeln um, toMap verwandelt die Liste der Tupel in eine Map, in der alle doppelten Schlüssel entfernt werden

21
Electric Coffee

Ich lese diese Frage schnell, daher bin ich mir nicht sicher, ob mir etwas fehlt (wie es für 2.7.x oder kein scalaz funktionieren muss):

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)

Sie können die Monoiddefinition für Double ändern und eine andere Methode zum Akkumulieren der Werte erhalten. Hier erhalten Sie das Maximum:

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)
3
huynhjl

Interessant, ein bisschen herumnudeln, habe ich folgendes bekommen (am 2.7.5):

Allgemeine Karten:

   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:_*)
    }
  }

Aber der Mensch, das ist abscheulich bei der Projektion und beim Forcen und bei der ToList und so weiter. Separate Frage: Wie kann man besser damit umgehen?

Für veränderliche Maps, mit denen ich mich in meinem Code befasste, und mit einer weniger allgemeinen Lösung habe ich Folgendes erhalten: 

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
    }
  }

Das scheint etwas sauberer zu sein, funktioniert aber nur mit veränderlichen Maps, wie sie geschrieben werden. Interessanterweise habe ich zuerst die obigen Schritte (bevor ich die Frage gestellt hatte) ausprobiert: /: anstelle von foldLeft, aber ich erhielt Schreibfehler. Ich dachte /: und foldLeft waren im Grunde gleichwertig, aber der Compiler beschwerte sich immer wieder, dass ich explizite Typen für (m, s) brauche. Was ist damit?

2
Jeff

Ich habe einen Blogbeitrag darüber geschrieben, schaut mal rein:

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

im Grunde können Sie dies mit der scalaz semi-Gruppe ganz leicht erreichen

würde ungefähr so ​​aussehen:

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

Starten Sie Scala 2.13, eine andere Lösung, die doppelte Schlüssel behandelt und nur basierend auf der Standardbibliothek besteht aus dem Zusammenführen der Maps als Sequenzen (flatten), bevor Sie den new groupMapReduce - Operator, der (wie der Name schon sagt) einem groupBy entspricht, gefolgt von einem Mapping und einem Reduzierungsschritt gruppierter Werte:

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)

Diese:

  • flattens (verkettet) die Karten als Folge von Tupeln (List(("hello", 1.1), ("world", 2.2), ("goodbye", 3.3), ("hello", 4.4))), die alle Schlüssel/Werte (auch doppelte Schlüssel) beibehalten.

  • groups Elemente basierend auf ihrem ersten Tuple-Teil (_._1) (Gruppenteil von group MapReduce)

  • maps gruppierte Werte zu ihrem zweiten Tuple-Teil (_._2) (Map-Teil der Gruppe Map Reduce)

  • reduces zugeordnete gruppierte Werte (_+_) anhand ihrer Summe (es kann jedoch eine beliebige reduce: (T, T) => T-Funktion sein) (Teil von groupMap reduzieren Reduzieren )


Der Schritt groupMapReduce kann als One-Pass-Version betrachtet werden, die äquivalent ist zu:

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

eine oneliner helper-func, deren einsatz fast so sauber liest wie mit 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)

für die ultimative Lesbarkeit wickeln Sie es in einen impliziten benutzerdefinierten Typ ein:

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(_)(_ + _) } 
0
bernstein