web-dev-qa-db-ja.com

Scala:マップのコレクションをマージする方法

Map [String、Double]のリストがあり、それらのコンテンツを1つのMap [String、Double]にマージしたいと思います。これを慣用的な方法でどのように行うべきですか?折り目でこれができると思います。何かのようなもの:

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

さらに、キーの衝突を一般的な方法で処理したいと考えています。つまり、既存のマップにキーを追加すると、Double(この場合)を返し、そのキーの既存の値と追加しようとしている値を受け取る関数を指定できるはずです。 。キーがまだマップに存在しない場合は、キーとその値を変更せずに追加します。

私の特定のケースでは、単一のMap [String、Double]を作成して、マップにすでにキーが含まれている場合、既存のマップ値にDoubleが追加されるようにします。

私は特定のコードで可変マップを操作していますが、可能であれば、より一般的なソリューションに興味があります。

35
Jeff

これはどう:

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)

また、2.7.5と2.8.0の両方で機能します。

27
Walter Chang

まだ誰もこの解決策を考え出していないことに驚いています。

myListOfMaps.flatten.toMap

あなたが必要とするものを正確に行います:

  1. リストを単一のマップにマージします
  2. 重複するキーを取り除きます

例:

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はマップのリストをタプルのフラットリストに変換し、toMapはタプルのリストを重複キーをすべて削除したマップに変換します

22
Electric Coffee

私はこの質問をすぐに読んでいるので、何かが欠けているかどうかはわかりません(2.7.xで機能する必要がある、または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)

Doubleのモノイド定義を変更して、値を累積する別の方法を取得できます。ここでは最大値を取得します。

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

_Scala 2.13_を開始する重複キーを処理するであり、唯一の標準ライブラリに基づくである別のソリューションは、Mapsをシーケンス(flatten)は、新しい groupMapReduce 演算子を適用する前に(その名前が示すとおり)groupByの後にマッピングとグループ化された値の削減ステップが続くものと同等です。

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

この:

  • flattens(連結)マップをタプルのシーケンス(List(("hello", 1.1), ("world", 2.2), ("goodbye", 3.3), ("hello", 4.4)))として保持します。これにより、すべてのキー/値(重複キーも含む)が保持されます

  • 最初のタプルパーツに基づくgroups要素(__._1_)(group MapReduceのグループパーツ)

  • mapsグループ化された値を2番目のタプルパーツにグループ化(__._2_)(グループのマップパーツMap Reduce)

  • reducesは、それらの合計を取ることによってグループ化された値(__+__)をマップしました(ただし、reduce: (T, T) => T関数にすることができます)(groupMapの一部を減らすReduce


groupMapReduceステップは ワンパスバージョン と同等と見なすことができます。

_list.groupBy(_._1).mapValues(_.map(_._2).reduce(_ + _))
_
2
Xavier Guihot

これを少しおもしろく見て、次のようになりました(2.7.5)。

一般的な地図:

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

しかし、それは、射影と強制、およびtoListとその他のことで恐ろしいことです。別の質問:フォールド内でそれを処理するためのより良い方法は何ですか?

私がコードで扱っていた可変マップの場合、あまり一般的ではない解決策で、これを取得しました:

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

少しすっきりしているように見えますが、変更されたマップでのみ機能します。興味深いことに、最初に(質問する前に)foldLeftの代わりに/:を使用して上記を試しましたが、タイプエラーが発生しました。 /:とfoldLeftは基本的に同等であると思っていましたが、コンパイラは(m、s)の明示的な型が必要だと不平を言い続けました。どうしたの?

2
Jeff

私はこれについてブログ記事を書きました、それをチェックしてください:

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

基本的にscalazセミグループを使用すると、これをかなり簡単に達成できます

次のようになります:

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

oneliner helper-func、その使用法は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)

究極の読みやすさのために、それを暗黙のカスタム型でラップします:

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