Scala: объединить карту

дополнительные параметры desc для кода Ege Özcan

function dynamicSort(property, desc) {
    if (desc) {
        return function (a, b) {
            return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
        }   
    }
    return function (a, b) {
        return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
    }
}
29
задан senia 18 November 2013 в 11:54
поделиться

10 ответов

Используя стандартную библиотеку 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))
54
ответ дан drexin 18 November 2013 в 11:54
поделиться

Вы можете использовать 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 s в качестве значений в Map.

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

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

15
ответ дан senia 18 November 2013 в 11:54
поделиться

Это простейшая реализация, которую я мог придумать,

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))
20
ответ дан tiran 18 November 2013 в 11:54
поделиться

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

import cats.implicits._

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

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

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

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

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

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

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

10
ответ дан David Castillo 18 November 2013 в 11:54
поделиться

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

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

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

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

  import scalaz.Scalaz._
  Map1 |+| Map2
8
ответ дан Nimrod007 18 November 2013 в 11:54
поделиться

Вы можете использовать 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)
2
ответ дан x4444 18 November 2013 в 11:54
поделиться

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

val target = map1.clone()
val source = map2.clone()
source.foreach(e => target += e._1 -> e._2)
1
ответ дан smishra 18 November 2013 в 11:54
поделиться
left.keys map { k => k -> List(left(k),right(k)) } toMap

Это сжато и будет работать, предполагая, что ваши две карты left и right. Не уверен насчет эффективности.

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

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

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

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 с из-за иерархии типов 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, но это требует немного больше работы.

1
ответ дан Luciano 18 November 2013 в 11:54
поделиться
m2.foldLeft(m1.mapValues{List[CommonType](_)}) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, List.empty) :+ v)
}

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

1
ответ дан Alx 18 November 2013 в 11:54
поделиться

Начиная с Scala 2.13, другое решение, основанное только на стандартной библиотеке, состоит в использовании groupMap , который (как следует из его названия) является эквивалентом groupBy, за которым следует mapValues:

// val m1 = Map(1 -> "a", 2 -> "b")
// val m2 = Map(2 -> "c", 3 -> "d")
(m1.toSeq ++ m2).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"))). Для краткости m2 неявно преобразуется в Seq для адаптации к типу m1.toSeq - но вы можете сделать это явно, используя m2.toSeq.

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

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

1
ответ дан Xavier Guihot 18 November 2013 в 11:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: