38

Map[String, Double] のリストがあり、それらの内容を単一の Map[String, Double] にマージしたいと考えています。これを慣用的な方法で行うにはどうすればよいですか?折り目でこれができるはずだと思います。何かのようなもの:

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

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

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

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

4

8 に答える 8

48

さて、あなたはできる:

mapList reduce (_ ++ _)

衝突の特別な要件を除いて。

その特別な要件があるため、おそらく次のようなことを行うのが最善でしょう(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
}

次に、Pimp My Library パターンを介してこのメ​​ソッドをマップ クラスに追加し、" ++"の代わりに元の例で使用できます。

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

これは 2.8 で書かれたので2.7 にもkeysIteratorなりますが、 and 、 become などの用語で書く必要があるかもしれませんが、あまり変わらないはずです。keysfilterKeysfiltermap&**

于 2009-08-11T21:27:09.757 に答える
28

これはどう:

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の両方で動作します。

于 2009-08-12T07:40:03.697 に答える
26

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

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

于 2014-08-20T07:06:15.053 に答える
2

興味深いことに、これを少しいじくり回して、私は次のようになりました(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)の明示的な型が必要だと文句を言い続けました。どうしたの?

于 2009-08-11T22:45:58.973 に答える
2

私はこの質問をすぐに読んだので、何かが欠けているかどうかわかりません(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)
于 2012-09-11T23:55:18.143 に答える
2

私はこれについてブログ投稿を書いたので、チェックしてください:

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

基本的に scalaz semi group を使用すると、これをかなり簡単に実現できます

次のようになります。

  import scalaz.Scalaz._
  listOfMaps reduce(_ |+| _)
于 2014-07-29T13:44:23.933 に答える
0

その使用法は、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(_)(_ + _) } 
于 2014-06-15T20:46:48.080 に答える