Scala: объединить карты по ключу
Скажем, у меня есть две карты:
val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")
Я хочу объединить эти карты по ключу, применяя некоторую функцию для сбора значений (в данном конкретном случае я хочу собрать их в последовательность, давая:
val c = Map(1 -> Seq("one", "un"), 2->Seq("two", "deux"), 3->Seq("three", "trois"))
Такое чувство, что должен быть хороший идиоматичный способ сделать это - какие-нибудь предложения? Я рад, если решение включает скаляз.
6 ответов
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
, intersectionWith
и др. функции от Хаскелла Data.Map
в Скале. Я не думаю, что есть какая-либо принципиальная причина, что они должны быть доступны только на 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)))
Скалаз добавляет метод |+|
для любого типа 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))
Запуск 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"))
Эта:
Объединяет две карты как последовательность кортежей (
List((1, "one"), (2, "two"), (3, "three"))
). Для краткости,map2
это неявно преобразуется вSeq
согласовывать сmap1.toSeq
тип - но вы можете сделать его явным, используяmap2.toSeq
.group
s элементов на основе их первой части кортежа (_._1
) (групповая часть групповой карты)map
s сгруппировали значения во вторую часть кортежа (_._2
) (часть карты группы Map)
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)]
Поэтому я не был доволен ни одним из этих решений (я хочу создать новый тип, так что полугруппа на самом деле не чувствует себя уместно, а решение 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)
Flatten необходим, потому что b.get (x._1) возвращает Option. Чтобы сгладить работу, первый элемент также должен быть опцией, поэтому мы не можем просто использовать 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)))
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)