Слияние карт по ключу - PullRequest
       22

Слияние карт по ключу

24 голосов
/ 13 октября 2011

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

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

Такое чувство, что должен быть хороший идиоматический способ сделать это.

Ответы [ 7 ]

20 голосов
/ 13 октября 2011

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'sData.Map в Скале.Я не думаю, что есть какая-либо принципиальная причина, по которой они должны быть доступны только на IntMap, а не на базовой черте collection.Map.

18 голосов
/ 13 октября 2011
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)))
14 голосов
/ 13 октября 2011

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))
2 голосов
/ 13 октября 2011

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

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

1 голос
/ 01 июня 2016
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 голос
/ 14 октября 2011

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

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)))
0 голосов
/ 08 июня 2019

Начиная с 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"))

This:

  • Объединяет две карты в виде последовательности кортежей (List((1, "one"), (2, "two"), (3, "three"))).Для краткости map2 неявно преобразуется в Seq для выравнивания с типом map1.toSeq, но вы можете сделать это явным, используя map2.toSeq.

  • group s элементов на основе их первой части кортежа (_._1) (групповая часть group Map)

  • map sсгруппированные значения для их второй части кортежа (_._2) (часть карты группы Map )

...