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

Scala: карта слияния

Как слить карты, как показано ниже:

Map1 = Map(1 -> Class1(1), 2 -> Class1(2))
Map2 = Map(2 -> Class2(1), 3 -> Class2(2))

После объединения.

Merged = Map( 1 -> List(Class1(1)), 2 -> List(Class1(2), Class2(1)), 3 -> Class2(2))

Может быть List, Set или любой другой коллекцией, у которой есть атрибут размера.

4b9b3361

Ответ 1

Используя стандартную библиотеку lib, вы можете сделать это следующим образом:

// convert maps to seq, to keep duplicate keys and concat
val merged = Map(1 -> 2).toSeq ++ Map(1 -> 4).toSeq
// merged: Seq[(Int, Int)] = ArrayBuffer((1,2), (1,4))

// group by key
val grouped = merged.groupBy(_._1)
// grouped: scala.collection.immutable.Map[Int,Seq[(Int, Int)]] = Map(1 -> ArrayBuffer((1,2), (1,4)))


// remove key from value set and convert to list
val cleaned = grouped.mapValues(_.map(_._2).toList)
// cleaned: scala.collection.immutable.Map[Int,List[Int]] = Map(1 -> List(2, 4))

Ответ 2

Это простейшая реализация, с которой я мог бы прийти,

val m1 = Map(1 -> "1", 2 -> "2")
val m2 = Map(2 -> "21", 3 -> "3")

def merge[K, V](m1:Map[K, V], m2:Map[K, V]):Map[K, List[V]] = 
  (m1.keySet ++ m2.keySet) map { i => i -> (m1.get(i).toList ::: m2.get(i).toList) } toMap

merge(m1, m2) // Map(1 -> List(1), 2 -> List(2, 21), 3 -> List(3))

Ответ 3

Вы можете использовать scalaz:

import scalaz._, Scalaz._

val m1 = Map('a -> 1, 'b -> 2)
val m2 = Map('b -> 3, 'c -> 4)

m1.mapValues{List(_)} |+| m2.mapValues{List(_)}
// Map('b -> List(2, 3), 'c -> List(4), 'a -> List(1))

Вы можете использовать Set(_) вместо List(_), чтобы получить Set как значения в Map.

См. Полугруппа в чит-лист scalaz (или обучающий скалаз) для получения более подробной информации о |+|.

Для Int |+| работает как +, для List - как ++, для Map он применяет |+| к значениям тех же ключей.

Ответ 4

Один чистый способ сделать это с кошками:

import cats.implicits._

Map(1 -> "Hello").combine(Map(2 -> "Goodbye"))
//Map(2 -> Goodbye, 1 -> Hello)

Важно отметить, что обе карты должны быть одного типа (в данном случае, Map[Int, String]).

Длинное объяснение:

combine самом деле не является членом карты. Импортируя cats.implicits, вы вводите в область видимости cat встроенные моноидные экземпляры cat, а также некоторые неявные классы, которые включают краткий синтаксис.

Вышеуказанное эквивалентно этому:

Monoid[Map[Int, String]].combine(Map(1 -> "Hello"), Map(2 -> "Goodbye"))

Там, где мы используем функцию "summoner" Monoid, чтобы получить экземпляр Monoid [Map [Int, String]] в области видимости и использовать его функцию объединения.

Ответ 5

Я написал сообщение в блоге об этом, проверьте:

http://www.nimrodstech.com/scala-map-merge/

в основном с использованием полугруппы scalaz вы можете достичь этого довольно легко

будет выглядеть примерно так:

  import scalaz.Scalaz._
  Map1 |+| Map2

Ответ 6

Если вы не хотите возиться с оригинальными картами, вы можете сделать что-то вроде следующего

val target = map1.clone()
val source = map2.clone()
source.foreach(e => target += e._1 -> e._2)

Ответ 7

left.keys map { k => k -> List(left(k),right(k)) } toMap

Является кратким и будет работать, если ваши две карты left и right. Не уверен в эффективности.

Но ваш вопрос немного неоднозначен по двум причинам. Вы не указываете

  • Отношение подтипирования между значениями (т.е. class1, class2),
  • Что произойдет, если карты имеют разные ключи.

В первом случае рассмотрим следующий пример:

val left = Map("foo" ->1, "bar" ->2)
val right = Map("bar" -> 'a', "foo" -> 'b')

В результате получается

res0: Map[String,List[Int]] = Map(foo -> List(1, 98), bar -> List(2, 97))

Обратите внимание, что Char были преобразованы в Int s из-за иерархии типов scala. В более общем плане, если в вашем примере class1 и class2 не связаны, вы вернетесь к List[Any]; это, вероятно, не то, что вы хотели.

Вы можете обойти это, отбросив конструктор List из моего ответа; это вернет Tuple, которые сохраняют тип:

res0: Map[String,(Int, Char)] = Map(foo -> (1,b), bar -> (2,a))

Вторая проблема - это то, что происходит, когда у вас есть карты, у которых нет одинаковых ключей. Это приведет к исключению key not found. Иными словами, вы делаете левое, правое или внутреннее соединение двух карт? Вы можете устранить тип соединения, переключившись на right.keys или right.keySet ++ left.keySet для правого/внутреннего соединений соответственно. Позже будет работать проблема с отсутствующим ключом, но, возможно, это не то, что вы хотите, может быть, вам нужно вместо этого использовать левое или правое соединение. В этом случае вы можете использовать метод withDefault Map, чтобы каждый ключ возвращал значение, например. None, но для этого требуется немного больше работы.

Ответ 8

Вы можете использовать foldLeft для объединения двух Карт того же типа

def merge[A, B](a: Map[A, B], b: Map[A, B])(mergef: (B, Option[B]) => B): Map[A, B] = {
  val (big, small) = if (a.size > b.size) (a, b) else (b, a)
  small.foldLeft(big) { case (z, (k, v)) => z + (k -> mergef(v, z.get(k))) }
}

def mergeIntSum[A](a: Map[A, Int], b: Map[A, Int]): Map[A, Int] =
  merge(a, b)((v1, v2) => v2.map(_ + v1).getOrElse(v1))

Пример:

val a = Map("a" -> 1, "b" -> 5, "c" -> 6)
val b = Map("a" -> 4, "z" -> 8)
mergeIntSum(a, b)
res0: Map[String,Int] = Map(a -> 5, b -> 5, c -> 6, z -> 8)

Ответ 9

m2.foldLeft(m1.mapValues{List[CommonType](_)}) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, List.empty) :+ v)
}

Как отмечено jwvh, тип списка должен быть указан явно, если Class1 не относится к верхнему типу для класса 2. CommonType - это тип, который является верхней границей для Class1 и Class2.

Ответ 10

Начиная с Scala 2.13, другое решение, основанное только на стандартной библиотеке, состоит в использовании K)(f:A=>B):scala.collection.immutable.Map[K,CC[B]] rel="nofollow noreferrer">groupMap, который (как следует из его названия) является эквивалентом groupBy за которым следует mapValues:

// val m1 = Map(1 -> "a", 2 -> "b")
// val m2 = Map(2 -> "c", 3 -> "d")
(m1.toSeq ++ m2.toSeq).groupMap(_._1)(_._2)
// Map[Int,Seq[String]] = Map(2 -> List("b", "c"), 1 -> List("a"), 3 -> List("d"))

Это:

  • объединяет две карты в виде последовательности кортежей (List((1,"a"), (2,"b"), (2,"c"), (3,"d")))

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

  • map сгруппированные значения с их второй частью кортежа (часть карты группы Map)