val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
Chcę je scalić i zsumować wartości tych samych kluczy. Wynik będzie więc:
Map(2->20, 1->109, 3->300)
Teraz mam 2 rozwiązania:
val list = map1.toList ++ map2.toList
val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum }
i
val merged = (map1 /: map2) { case (map, (k,v)) =>
map + ( k -> (v + map.getOrElse(k, 0)) )
}
Ale chcę wiedzieć, czy istnieją lepsze rozwiązania.
map1 ++ map2
Odpowiedzi:
Scalaz ma koncepcję półgrupy, która przechwytuje to, co chcesz tutaj zrobić, i prowadzi do prawdopodobnie najkrótszego / najczystszego rozwiązania:
W szczególności, operator binarny for
Map[K, V]
łączy klucze map,V
zawijając operator półgrupy nad zduplikowanymi wartościami. Standardowa półgrupa dlaInt
używa operatora dodawania, więc otrzymujesz sumę wartości dla każdego zduplikowanego klucza.Edycja : trochę więcej szczegółów, zgodnie z prośbą użytkownika482745.
Matematycznie półgrupa to po prostu zbiór wartości wraz z operatorem, który pobiera dwie wartości z tego zbioru i tworzy inną wartość z tego zbioru. Na przykład liczby całkowite są półgrupą -
+
operator łączy dwie liczby całkowite, aby utworzyć kolejną liczbę int.Możesz także zdefiniowaćpółgrupę na zbiorze "wszystkich map z podanym typem klucza i typem wartości", o ile możesz wymyślić jakąś operację, która łączy dwie mapy w celu utworzenia nowej, która jest w jakiś sposób połączeniem tych dwóch wejścia.
Jeśli nie ma kluczy, które pojawiają się na obu mapach, jest to trywialne. Jeśli ten sam klucz istnieje w obu mapach, musimy połączyć dwie wartości, na które mapuje klucz. Hmm, czy nie opisaliśmy właśnie operatora, który łączy dwie jednostki tego samego typu? Dlatego w Scalaz półgrupa for
Map[K, V]
istnieje wtedy i tylko wtedy, gdy półgrupa dlaV
istnieje -V
jest używana do łączenia wartości z dwóch map, które są przypisane do tego samego klucza.Tak więc, ponieważ
Int
jest to typ wartości, "kolizja"1
klucza jest rozwiązywana przez dodanie liczb całkowitych dwóch odwzorowanych wartości (tak jak robi to operator półgrupy Int)100 + 9
. Gdyby wartości były ciągami, kolizja spowodowałaby konkatenację ciągów dwóch odwzorowanych wartości (ponownie, ponieważ tak robi operator półgrupy dla ciągu).(Co ciekawe, ponieważ konkatenacja ciągów nie jest przemienna - to znaczy
"a" + "b" != "b" + "a"
- wynikowa operacja półgrupowa też nie jest. Więcmap1 |+| map2
różni się odmap2 |+| map1
przypadku String, ale nie w przypadku Int).źródło
scalaz
miał sens.A
iOption[A]
) jest tak ogromna, że nie mogłem uwierzyć, że są naprawdę tego samego typu. I tak rozpoczęła się patrząc na Scalaz. Nie jestem pewien, czy jestem wystarczająco sprytny ...Najkrótszą odpowiedzią, jaką znam, wykorzystującą tylko bibliotekę standardową, jest
źródło
++
zamienia dowolne (k, v) z mapy po lewej stronie++
(tutaj mapa1) na (k, v) z prawej strony, jeśli (k, _) już istnieje po lewej stronie mapa boczna (tutaj mapa1), np.Map(1->1) ++ Map(1->2) results in Map(1->2)
for
mapą1 ++ (for ((k, v) <- map2) yield k -> (v + map1.getOrElse (k, 0 ))).
ma wyższy priorytet niż++
; czytaszmap1 ++ map2.map{...}
jakomap1 ++ (map2 map {...})
. Tak więc w jeden sposóbmap1
mapujesz elementy, a w drugi nie.Szybkie rozwiązanie:
źródło
Cóż, teraz w bibliotece scala (przynajmniej w 2.10) jest coś, czego chciałeś - funkcja scalona . ALE jest prezentowany tylko w HashMap, a nie w Mapie. To trochę zagmatwane. Również podpis jest nieporęczny - nie wyobrażam sobie, dlaczego potrzebowałbym klucza dwa razy i kiedy musiałbym stworzyć parę z innym kluczem. Niemniej jednak działa i jest znacznie czystszy niż poprzednie rozwiązania „natywne”.
Wspomniał o tym również w scaladoc
źródło
MergeFunction
.private type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)
Można to zaimplementować jako Monoid za pomocą zwykłej Scali. Oto przykładowa implementacja. Dzięki takiemu podejściu możemy połączyć nie tylko 2, ale także listę map.
Oparta na mapie implementacja cechy Monoid, która łączy dwie mapy.
Teraz, jeśli masz listę map do scalenia (w tym przypadku tylko 2), możesz to zrobić jak poniżej.
źródło
źródło
Napisałem o tym post na blogu, sprawdź to:
http://www.nimrodstech.com/scala-map-merge/
w zasadzie używając półgrupy scalaz możesz to osiągnąć całkiem łatwo
wyglądałoby mniej więcej tak:
źródło
Możesz to również zrobić z kotami .
źródło
import cats.implicits._
. Importujimport cats.instances.map._ import cats.instances.int._ import cats.syntax.semigroup._
niewiele więcej informacji ...import cats.implicits._
Zaczynając od
Scala 2.13
innego rozwiązania opartego wyłącznie na standardowej bibliotece, polega na zamianiegroupBy
części rozwiązania,groupMapReduce
którą (jak sama nazwa wskazuje) jest odpowiednikiem krokugroupBy
po którym następujemapValues
i redukuje:To:
Łączy dwie mapy jako sekwencję krotek (
List((1,9), (2,20), (1,100), (3,300))
). Dla zwięzłości,map2
jest niejawnie konwertowany na,Seq
aby dostosować się do typumap1.toSeq
- ale możesz zdecydować się na jawne użyciemap2.toSeq
,group
s elementy na podstawie ich pierwszej części krotki (część grupowa grupy MapReduce),map
s zgrupowane wartości do drugiej części krotki (część mapy grupy Map Reduce),reduce
s zmapowane wartości (_+_
), sumując je (zmniejsz część groupMap Reduce ).źródło
Oto, czego ostatecznie użyłem:
źródło
Odpowiedź Andrzeja Doyle'a zawiera świetne wyjaśnienie półgrup, które pozwala na użycie
|+|
operatora do połączenia dwóch map i zsumowania wartości pasujących kluczy.Istnieje wiele sposobów zdefiniowania czegoś jako instancji typeklasy iw przeciwieństwie do OP, możesz nie chcieć konkretnie sumować kluczy. Lub możesz chcieć działać na związku, a nie na skrzyżowaniu. Scalaz dodaje również dodatkowe funkcje
Map
w tym celu: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
Możesz to zrobić
źródło
Najszybszy i najprostszy sposób:
W ten sposób każdy element jest natychmiast dodawany do mapy.
Drugi
++
sposób to:W przeciwieństwie do pierwszego sposobu, w drugim przypadku dla każdego elementu na drugiej mapie zostanie utworzona nowa lista i połączona z poprzednią mapą.
case
Wyrażenie niejawnie tworzy nową listę używającunapply
metody.źródło
Oto, co wymyśliłem ...
źródło
Używając wzorca typeklas, możemy scalić dowolny typ numeryczny:
Stosowanie:
Scalanie sekwencji map:
źródło
Mam małą funkcję do wykonania tego zadania, która znajduje się w mojej małej bibliotece dla niektórych często używanych funkcji, których nie ma w standardowej bibliotece. Powinien działać dla wszystkich typów map, zmiennych i niezmiennych, nie tylko HashMaps
Oto użycie
https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith
A oto ciało
https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190
źródło