web-dev-qa-db-ja.com

キーでマップをマージ

2つのマップがあるとします。

val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")

これらのマップをキーでマージし、いくつかの関数を適用して値を収集したいと思います(この特定のケースでは、それらをseqに収集し、次のようにします。

val c = Map(1 -> Seq("one", "un"), 2 -> Seq("two", "deux"), 3 -> Seq("three", "trois"))

これを行うには、いい、慣用的な方法があるはずだと感じています。

24
Submonoid

_scala.collection.immutable.IntMap_ には、あなたが望むことを正確に実行するintersectionWithメソッドがあります(私は信じています):

_import scala.collection.immutable.IntMap

val a = IntMap(1 -> "one", 2 -> "two", 3 -> "three", 4 -> "four")
val b = IntMap(1 -> "un", 2 -> "deux", 3 -> "trois")

val merged = a.intersectionWith(b, (_, av, bv: String) => Seq(av, bv))
_

これにより、IntMap(1 -> List(one, un), 2 -> List(two, deux), 3 -> List(three, trois))が得られます。 aでのみ発生するキーを正しく無視することに注意してください。

補足として:Scalaの Haskellの_Data.Map_ からunionWithintersectionWithなどの関数が必要なことがよくあります。基本の_collection.Map_トレイトではなく、IntMapでのみ利用可能であるという原則的な理由はないと思います。

21
Travis Brown
val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")

val c = a.toList ++ b.toList
val d = c.groupBy(_._1).map{case(k, v) => k -> v.map(_._2).toSeq}
//res0: scala.collection.immutable.Map[Int,Seq[Java.lang.String]] =
        //Map((2,List(two, deux)), (1,List(one, un), (3,List(three, trois)))
19
Infinity

Scalazは、|+|が使用可能な任意のタイプAに対してメソッドSemigroup[A]を追加します。

各値が単一要素のシーケンスになるようにマップをマッピングした場合、これは非常に簡単に使用できます。

scala> a.mapValues(Seq(_)) |+| b.mapValues(Seq(_))
res3: scala.collection.immutable.Map[Int,Seq[Java.lang.String]] = Map(1 -> List(one, un), 2 -> List(two, deux), 3 -> List(three, trois))
14
Ben James

そのため、どちらのソリューションにも満足できませんでした(新しいタイプを作成したいので、セミグループはあまり適切でないと感じ、Infinityのソリューションは非常に複雑に見えました)。そのため、今のところこれを使用しました。私はそれが改善されるのを見てうれしいです:

def merge[A,B,C](a : Map[A,B], b : Map[A,B])(c : (B,B) => C) = {
  for (
    key <- (a.keySet ++ b.keySet);
    aval <- a.get(key); bval <- b.get(key)
  ) yield c(aval, bval)
}
merge(a,b){Seq(_,_)}

キーがどちらのマップにも存在しない場合(他のソリューションとは異なります)に何も返さない動作が必要でしたが、これを指定する方法は素晴らしいでしょう。

2
Submonoid

他の解決策を探す前の私の最初のアプローチは次のとおりです。

for (x <- a) yield 
  x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten

Aまたはbにのみ存在する要素を回避するには、フィルターが便利です。

(for (x <- a) yield 
  x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten).filter (_._2.size == 2)

B.get(x._1)はオプションを返すため、フラット化が必要です。フラット化を機能させるには、最初の要素もオプションである必要があるため、ここでx._2を使用することはできません。

シーケンスの場合も機能します。

scala> val b = Map (1 -> Seq(1, 11, 111), 2 -> Seq(2, 22), 3 -> Seq(33, 333), 5 -> Seq(55, 5, 5555))
b: scala.collection.immutable.Map[Int,Seq[Int]] = Map(1 -> List(1, 11, 111), 2 -> List(2, 22), 3 -> List(33, 333), 5 -> List(55, 5, 5555))

scala> val a = Map (1 -> Seq(1, 101), 2 -> Seq(2, 212, 222), 3 -> Seq (3, 3443), 4 -> (44, 4, 41214))
a: scala.collection.immutable.Map[Int,ScalaObject with Equals] = Map(1 -> List(1, 101), 2 -> List(2, 212, 222), 3 -> List(3, 3443), 4 -> (44,4,41214))

scala> (for (x <- a) yield x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten).filter (_._2.size == 2) 
res85: scala.collection.immutable.Map[Int,Seq[ScalaObject with Equals]] = Map(1 -> List(List(1, 101), List(1, 11, 111)), 2 -> List(List(2, 212, 222), List(2, 22)), 3 -> List(List(3, 3443), List(33, 333)))
2
user unknown
val fr = Map(1 -> "one", 2 -> "two", 3 -> "three")
val en = Map(1 -> "un", 2 -> "deux", 3 -> "trois")

def innerJoin[K, A, B](m1: Map[K, A], m2: Map[K, B]): Map[K, (A, B)] = {
  m1.flatMap{ case (k, a) => 
    m2.get(k).map(b => Map((k, (a, b)))).getOrElse(Map.empty[K, (A, B)])
  }
}

innerJoin(fr, en) // Map(1 -> ("one", "un"), 2 -> ("two", "deux"), 3 -> ("three", "trois")): Map[Int, (String, String)]
1

_Scala 2.13_から始めて、 groupMap を使用できます。これは(その名前が示すように)groupByの後にmapが続くのと同等です。値:

_// val map1 = Map(1 -> "one", 2 -> "two",  3 -> "three")
// val map2 = Map(1 -> "un",  2 -> "deux", 3 -> "trois")
(map1.toSeq ++ map2).groupMap(_._1)(_._2)
// Map(1 -> List("one", "un"), 2 -> List("two", "deux"), 3 -> List("three", "trois"))
_

この:

  • 2つのマップをタプルのシーケンスとして連結します(List((1, "one"), (2, "two"), (3, "three")))。簡潔にするために、_map2_は暗黙的にSeqに変換され、_map1.toSeq_のタイプに合わせられます-ただし、を使用して明示的にすることもできます。 _map2.toSeq_。

  • 最初のタプル部分に基づくgroups要素(__._1_)(groupMapのグループ部分)

  • mapsは値を2番目のタプル部分にグループ化しました(__._2_)(groupMapの一部をマップします)

0
Xavier Guihot
def merge[A,B,C,D](b : Map[A,B], c : Map[A,C])(d : (Option[B],Option[C]) => D): Map[A,D] = {
  (b.keySet ++ c.keySet).map(k => k -> d(b.get(k), c.get(k))).toMap
}

def optionSeqBiFunctionK[A]:(Option[A], Option[A]) => Seq[A] = _.toSeq ++ _.toSeq

merge(a,b)(optionSeqBiFunctionK)

0