Scala: слияние карт по ключу

Скажем, у меня две карты:

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

Кажется, должен быть хороший, идиоматический способ сделать это – любые предложения? Я рад, если решение включает в себя скалаз.

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 .

В качестве побочного примечания: я часто обнаружил, что unionWith получить unionWith , intersectionWith и т. Д. Из Data.Map ‘s Data.Map в Scala. Я не думаю, что есть принципиальная причина, что они должны быть доступны только в IntMap , а не в базовой collection.Map .

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

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

Таким образом, я не был доволен решением (я хочу создать новый тип, поэтому полугруппа действительно не подходит, а решение 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(_,_)} 

Я хотел, чтобы поведение ничего не возвращалось, когда ключ не присутствовал ни на одной карте (которая отличается от других решений), но способ указать это было бы неплохо.

Вот мой первый подход, прежде чем искать другие решения:

 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))) 
 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)] 
Давайте будем гением компьютера.