Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 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)