val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
Я хочу объединить их и суммировать значения одних и тех же ключей. Таким образом, результат будет:
Map(2->20, 1->109, 3->300)
Теперь у меня есть 2 решения:
val list = map1.toList ++ map2.toList
val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum }
и
val merged = (map1 /: map2) { case (map, (k,v)) =>
map + ( k -> (v + map.getOrElse(k, 0)) )
}
Но я хочу знать, есть ли лучшие решения.
map1 ++ map2
Ответы:
В Scalaz есть концепция полугруппы, которая фиксирует то, что вы хотите здесь сделать, и приводит, возможно, к кратчайшему / наиболее чистому решению:
В частности, бинарный оператор для
Map[K, V]
комбинирует ключи карт, складываяV
оператор полугруппы по любым дублирующимся значениям. Стандартная полугруппа дляInt
использует оператор сложения, поэтому вы получаете сумму значений для каждого дублирующего ключа.Изменить : немного больше деталей, согласно запросу пользователя 482745.
Математически полугруппа - это просто набор значений вместе с оператором, который принимает два значения из этого набора и производит другое значение из этого набора. Таким образом,
+
добавляемые целые числа - это, например, полугруппа - оператор объединяет два целых числа для создания другого целого.Вы также можете определить полугруппу по набору «всех карт с заданным типом ключа и типом значения», при условии, что вы можете придумать какую-то операцию, которая объединяет две карты, чтобы создать новую, которая каким-то образом является комбинацией двух. входы.
Если на обеих картах нет ключей, это тривиально. Если один и тот же ключ существует на обеих картах, то нам нужно объединить два значения, на которые отображается ключ. Хм, разве мы не описали оператор, который объединяет два объекта одного типа? Вот почему в Scalaz полугруппа для
Map[K, V]
существует тогда и только тогда, когда полугруппа дляV
существует -V
используется полугруппа для объединения значений из двух карт, которые назначены одному и тому же ключу.Так как
Int
здесь тип значения, «коллизия» на1
ключе разрешается путем целочисленного сложения двух отображенных значений (как это делает оператор полугруппы Int), следовательно100 + 9
. Если бы значения были Strings, коллизия привела бы к объединению строк двух сопоставленных значений (опять же, потому что это то, что делает оператор полугруппы для String).(И что интересно, поскольку конкатенация строк не является коммутативной, то есть
"a" + "b" != "b" + "a"
результирующая операция полугруппы также не является. Таким образом,map1 |+| map2
она отличается отmap2 |+| map1
случая String, но не от случая Int.)источник
scalaz
смысл.A
иOption[A]
), настолько велика, что я не мог поверить, что они действительно были одного типа. Я только начал смотреть на Скалаз. Я не уверен, что достаточно умен ...Самый короткий ответ, который я знаю, который использует только стандартную библиотеку,
источник
++
заменяет любое (k, v) из карты на левой стороне++
(здесь map1) на (k, v) с правой стороны карты, если (k, _) уже существует слева дополнительная карта (здесь map1), напримерMap(1->1) ++ Map(1->2) results in Map(1->2)
for
map1 ++ (для ((k, v) <- map2), получим k -> (v + map1.getOrElse (k, 0 ))).
имеет более высокий приоритет, чем++
; ты читаешьmap1 ++ map2.map{...}
какmap1 ++ (map2 map {...})
. Итак, одним способом вы отображаетеmap1
элементы, а другим - нет.Быстрое решение:
источник
Что ж, теперь в библиотеке Scala (по крайней мере, в 2.10) есть то, что вы хотели - объединенная функция. НО он представлен только в HashMap, а не в Map. Это несколько сбивает с толку. Кроме того, подпись громоздка - не могу представить, зачем мне дважды нужен ключ и когда мне нужно создать пару с другим ключом. Но тем не менее, он работает и намного чище, чем предыдущие «родные» решения.
Также в скаладоке упоминается, что
источник
MergeFunction
.private type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)
Это может быть реализовано как Monoid с простым Scala. Вот пример реализации. При таком подходе мы можем объединить не только 2, но и список карт.
Реализация черты Monoid на основе карт, которая объединяет две карты.
Теперь, если у вас есть список карт, которые необходимо объединить (в данном случае только 2), это можно сделать, как показано ниже.
источник
источник
Я написал в блоге об этом, проверьте это:
http://www.nimrodstech.com/scala-map-merge/
в основном используя скалаз полугруппу, вы можете достичь этого довольно легко
будет выглядеть примерно так:
источник
Вы также можете сделать это с кошками .
источник
import cats.implicits._
. Импортimport cats.instances.map._ import cats.instances.int._ import cats.syntax.semigroup._
не намного более многословный ...import cats.implicits._
Запуск
Scala 2.13
, другое решение только на основе стандартной библиотеки состоит в заменеgroupBy
части вашего решения сgroupMapReduce
которым (как предполагает его название) является эквивалентомgroupBy
последующегоmapValues
и уменьшить шаг:Это:
Объединяет две карты в виде последовательности кортежей (
List((1,9), (2,20), (1,100), (3,300))
). Для краткости,map2
это неявно преобразуется вSeq
адаптации к типуmap1.toSeq
- но вы можете выбрать , чтобы сделать его явным использованиемmap2.toSeq
,group
элементы, основанные на их первой части кортежа (групповая часть группы MapReduce),map
s сгруппированные значения для их второй части кортежа (часть карты группы Map Reduce),reduce
s сопоставленные значения (_+_
) путем суммирования их (уменьшить часть groupMap Reduce ).источник
Вот что я в итоге использовал:
источник
Ответ Анджея Дойла содержит отличное объяснение полугрупп, которое позволяет использовать
|+|
оператор для объединения двух карт и суммирования значений для соответствующих ключей.Существует множество способов определить, что что-то является экземпляром класса типов, и, в отличие от OP, вы, возможно, не захотите специально суммировать свои ключи. Или, возможно, вы захотите работать на объединении, а не на пересечении. Scalaz также добавляет дополнительные функции
Map
для этого:https://oss.sonatype.org/service/local/repositories/snapshots/archive/org/scalaz/scalaz_2.11/7.3.0-SNAPSHOT/scalaz_2.11-7.3.0-SNAPSHOT-javadoc.jar/!/ index.html # scalaz.std.MapFunctions
Ты можешь сделать
источник
Самый быстрый и простой способ:
Таким образом, каждый элемент сразу добавляется на карту.
Второй
++
способ:В отличие от первого способа, вторым способом для каждого элемента на второй карте будет создан новый список, который будет объединен с предыдущей картой.
case
Выражение неявно создает новый список , используяunapply
метод.источник
Это то, что я придумал ...
источник
Используя шаблон класса типов, мы можем объединить любой тип Numeric:
Использование:
Слияние последовательности карт:
источник
У меня есть небольшая функция для работы, она находится в моей маленькой библиотеке для некоторых часто используемых функций, которых нет в стандартной библиотеке. Это должно работать для всех типов карт, изменяемых и неизменяемых, не только HashMaps
Вот использование
https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith
А вот и тело
https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190
источник