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.
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.
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.
Ich bin überrascht, dass noch niemand mit dieser Lösung gekommen ist:
myListOfMaps.flatten.toMap
Macht genau das, was Sie brauchen:
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
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)
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?
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(_ |+| _)
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 Map
s 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:
flatten
s (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.
group
s Elemente basierend auf ihrem ersten Tuple-Teil (_._1
) (Gruppenteil von group MapReduce)
map
s gruppierte Werte zu ihrem zweiten Tuple-Teil (_._2
) (Map-Teil der Gruppe Map Reduce)
reduce
s 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(_ + _))
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(_)(_ + _) }