Слияние карт по ключу
Скажем, у меня есть две карты:
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"))
Такое чувство, что должен быть хороший идиоматичный способ сделать это.
Ответы
Ответ 1
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
и т.д. функций из Haskell Data.Map
в Scala, Я не думаю, что есть принципиальная причина, что они должны быть доступны только на IntMap
, а не в базовом признаке collection.Map
.
Ответ 2
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)))
Ответ 3
Scalaz добавляет метод |+|
для любого типа A
, для которого доступен <<22 > .
Если вы сопоставили свои Карты так, чтобы каждое значение было одноэлементной последовательностью, вы могли бы использовать это довольно просто:
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))
Ответ 4
Таким образом, я был не совсем доволен решением (я хочу создать новый тип, поэтому полугруппа действительно не уместна, а решение 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(_,_)}
Мне хотелось, чтобы поведение ничего не возвращалось, когда ключ не присутствовал ни на одной карте (которая отличается от других решений), но способ указать это было бы хорошо.
Ответ 5
Вот мой первый подход, прежде чем искать другие решения:
for (x <- a) yield
x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten
Чтобы избежать элементов, которые существуют только в или 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)))
Ответ 6
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)]
Ответ 7
Начиная с Scala 2.13
, вы можете использовать K)(f:A=>B):scala.collection.immutable.Map[K,CC[B]] rel="nofollow noreferrer"> 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
элементы на основе их первой части кортежа (_._1
) (групповая часть группы Map)
-
map
сгруппированные значения с их второй частью кортежа (_._2
) (часть карты группы Map)