2013-04-28 10 views
9

Jaki byłby funkcjonalny sposób na zip dwa słowniki w Scala?Zip dwa HashMaps (lub słowniki)

map1 = new HashMap("A"->1,"B"->2) 
map2 = new HashMap("B"->22,"D"->4) // B is the only common key 

zipper(map1,map2) powinna dać coś podobnego do

Seq(("A",1,0), // no A in second map, so third value is zero 
     ("B",2,22), 
     ("D",0,4)) // no D in first map, so second value is zero 

Jeśli nie działa, inny styl jest również ceniona

+2

Haskell Data.Map ma wspaniały kombinator o nazwie "unionWith", który sprawi, że będzie to niezwykle łatwe. To i jego odpowiednik w przecięciu są niezwykle przydatne i żałuję, że są one dostępne tylko w "IntMap" i "LongMap" Scala (głównie dlatego, że są one tłumaczone z Haskella, jak przypuszczam). – copumpkin

+0

@copumpkin: Haskell jest niesamowity! Sprawdzę związek. Po prostu sprawdziłem IntMap i jest on teraz zastępowany przez HashMap z 2.8 na – RAbraham

+0

. To nie jest tak naprawdę zastąpione przez 'HashMap', pomimo tego, co sugeruje komentarz do dokumentacji. Są to różne struktury, a "IntMap" ma inne właściwości. Czasami naprawdę nie chcesz funkcji mieszania, szczególnie gdy chcesz zachować porządek danych. 'IntMap' może w większości zaimplementować' SortedMap', ale nie sądzę, że teraz to zrobi. Jedną z nich jest to, że następuje po "niepodpisanym" zamawianiu, ale nie jest trudno sprawić, by zachowywał się jak podpisany, jeśli tego właśnie potrzebujesz. – copumpkin

Odpowiedz

13
def zipper(map1: Map[String, Int], map2: Map[String, Int]) = { 
    for(key <- map1.keys ++ map2.keys) 
    yield (key, map1.getOrElse(key, 0), map2.getOrElse(key, 0)) 
} 


scala> val map1 = scala.collection.immutable.HashMap("A" -> 1, "B" -> 2) 
map1: scala.collection.immutable.HashMap[String,Int] = Map(A -> 1, B -> 2) 

scala> val map2 = scala.collection.immutable.HashMap("B" -> 22, "D" -> 4) 
map2: scala.collection.immutable.HashMap[String,Int] = Map(B -> 22, D -> 4) 

scala> :load Zipper.scala 
Loading Zipper.scala... 
zipper: (map1: Map[String,Int], map2: Map[String,Int])Iterable[(String, Int, Int)] 

scala> zipper(map1, map2) 
res1: Iterable[(String, Int, Int)] = Set((A,1,0), (B,2,22), (D,0,4)) 

Uwaga korzystając get jest prawdopodobnie korzystne getOrElse w tym przypadku. None służy do określenia, że ​​wartość nie istnieje zamiast użycia 0.

+1

piękny kod :) – RAbraham

+0

Czy jest jakiś sposób na uzyskanie "zera" parametru typu? Zipper mógłby być generyczny oprócz tych brzydkich '0' –

+0

Nie jestem pewien, czy byłby to właściwy kierunek generalizowania, ale można by spojrzeć na 'Monoid', który ma operację (asocjacyjną) i zero. Samo zero nie miałoby większego sensu, ponieważ to, co czyni z niego zero, to sposób interakcji z innymi rzeczami. Jak zasugerowałem w moim drugim komentarzu, myślę, że "unionWith [A, B] (x: Mapa [A, B], y: Mapa [A, B]) (f: (B, B) => B): Mapa [A, B] "jest tutaj idealnym podejściem. Funkcja łączenia jest używana tylko wtedy, gdy istnieje nakładanie się, więc nie potrzebujesz nawet pojęcia zero. Bardziej ogólne: 'unionWith [K, A, B, C]) (x: Mapa [K, A], y: Mapa [K, B]) (l: A => C, r: B => C, b : (A, B) => C): Mapa [K, C] '. –