188
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)

I want to merge them, and sum the values of same keys. So the result will be:

Map(2->20, 1->109, 3->300)

Now I have 2 solutions:

val list = map1.toList ++ map2.toList
val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum }

and

val merged = (map1 /: map2) { case (map, (k,v)) =>
    map + ( k -> (v + map.getOrElse(k, 0)) )
}

But I want to know if there are any better solutions.

Freewind
  • 193,756
  • 157
  • 432
  • 708

16 Answers16

160

The shortest answer I know of that uses only the standard library is

map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) }
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 35
    Nice solution. I like to add the hint, that `++` replaces any (k,v) from the map on the left side of `++` (here map1) by (k,v) from the right side map, if (k,_) already exists in the left side map (here map1), e.g. `Map(1->1) ++ Map(1->2) results in Map(1->2)` – Lutz Aug 17 '11 at 09:49
  • 1
    A kind of neater version: for ((k, v) <- (aa ++ bb)) yield k -> (if ((aa contains k) && (bb contains k)) aa(k) + v else v) – dividebyzero Dec 13 '14 at 01:05
  • I did somehting different previously, but here is a version of what you did, replacing the map for a `for` map1 ++ (for ((k,v) <- map2) yield k -> (v + map1.getOrElse(k,0))) – dividebyzero Dec 13 '14 at 01:41
  • @dividebyzero - Yes, that's exactly what AmigoNico did in an answer below. – Rex Kerr Dec 13 '14 at 07:55
  • Strangely, `(map1 ++ map2).map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) }` does not preserve the ordering of `map1`, while `map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) }` does. – Jus12 Aug 22 '15 at 14:22
  • @Jus12 - Maps aren't ordered. Not sure what you mean? – Rex Kerr Aug 23 '15 at 00:46
  • I was trying this with a `TreeMap` and an implicit `Ordering`. Anyway, isn't `map1 ++ map2.map{...}` equivalent to `(map1 ++ map2).map{...}`? – Jus12 Aug 23 '15 at 09:07
  • 1
    @Jus12 - No. `.` has higher precedence than `++`; you read `map1 ++ map2.map{...}` as `map1 ++ (map2 map {...})`. So one way you map `map1`s elements, and the other way you don't. – Rex Kerr Aug 23 '15 at 18:14
  • Maybe a future collections library would allow a cleaner functional solution. – matanster Nov 08 '15 at 21:02
  • 1
    @matt - Scalaz already will do it, so I'd say "an existing library already does it". – Rex Kerr Nov 08 '15 at 21:54
  • How to keep only the keys which are common to both maps? That is how to do, both intersect and merge at the same time? – Ahmadov May 16 '16 at 10:35
  • @Ahmedov - That is a different question, and a complex one if efficiency is important. If not, it's just a `flatMap` (either explicitly, or from a `for` comprehension). – Rex Kerr May 16 '16 at 13:23
144

Scalaz has the concept of a Semigroup which captures what you want to do here, and leads to arguably the shortest/cleanest solution:

scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala> val map1 = Map(1 -> 9 , 2 -> 20)
map1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 9, 2 -> 20)

scala> val map2 = Map(1 -> 100, 3 -> 300)
map2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 100, 3 -> 300)

scala> map1 |+| map2
res2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 109, 3 -> 300, 2 -> 20)

Specifically, the binary operator for Map[K, V] combines the keys of the maps, folding V's semigroup operator over any duplicate values. The standard semigroup for Int uses the addition operator, so you get the sum of values for each duplicate key.

Edit: A little more detail, as per user482745's request.

Mathematically a semigroup is just a set of values, together with an operator that takes two values from that set, and produces another value from that set. So integers under addition are a semigroup, for example - the + operator combines two ints to make another int.

You can also define a semigroup over the set of "all maps with a given key type and value type", so long as you can come up with some operation that combines two maps to produce a new one which is somehow the combination of the two inputs.

If there are no keys that appear in both maps, this is trivial. If the same key exists in both maps, then we need to combine the two values that the key maps to. Hmm, haven't we just described an operator which combines two entities of the same type? This is why in Scalaz a semigroup for Map[K, V] exists if and only if a Semigroup for V exists - V's semigroup is used to combine the values from two maps which are assigned to the same key.

So because Int is the value type here, the "collision" on the 1 key is resolved by integer addition of the two mapped values (as that's what Int's semigroup operator does), hence 100 + 9. If the values had been Strings, a collision would have resulted in string concatenation of the two mapped values (again, because that's what the semigroup operator for String does).

(And interestingly, because string concatenation is not commutative - that is, "a" + "b" != "b" + "a" - the resulting semigroup operation isn't either. So map1 |+| map2 is different from map2 |+| map1 in the String case, but not in the Int case.)

Chris
  • 1,335
  • 10
  • 19
Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
  • 37
    Brilliant! First practical example where `scalaz` made sense. – soc Aug 16 '11 at 11:34
  • 6
    No kidding! If you start looking for it ... it's all over the place. To quote erric torrebone author of specs and specs2:"First you learn Option and you start seeing it everywhere. Then you learn Applicative and it's the same thing. Next?" Next are even more functional concepts. And those greatly help you structure your code and solve problems nicely. – AndreasScheinert Aug 16 '11 at 12:00
  • 4
    Actually, I'd been looking for Option for five years when I finally found Scala. The difference between a Java object reference that *might* be null and one that cannot be (i.e. between `A` and `Option[A]`) is so huge, I couldn't believe they were really the same type. I *just* started looking at Scalaz. I'm not sure I'm smart enough... – Michael Lorton Aug 17 '11 at 00:39
  • 1
    There is Option for Java too, see Functional Java. Do not have any fear, learning is fun. And functional programming doesn't teach you new things (only) but instead offers you the programmer help with providing terms, vocabulary to tackle problems. The OP question is a perfect example. The concept of a Semigroup is so simple, you use it every day say for e.g. Strings. The real power appears if you identify this abstraction, name it and finally apply it to other types then just String. – AndreasScheinert Aug 17 '11 at 16:56
  • 1
    How it is possible that it will result in 1 -> (100 + 9) ? Can you please show me "stack trace"? Thx. P.S.: I am asking here to make answer more clear. – user482745 Feb 02 '12 at 12:33
  • 1
    Is there a way to not import all of `scalaz`? – Kevin Wheeler Oct 25 '14 at 02:45
  • 1
    @KevinWheeler I believe this example is covered by `import scalaz.syntax.semigroup._; import scalaz.std.map.mapInstance ; import scalaz.std.anyVal.intInstance` – Daenyth May 26 '15 at 19:38
  • @Daenyth, minor correction that is (at least) to Scalaz 7.2.4: import `mapMonoid` instead of `mapInstance`. – Michael Ahlers Aug 07 '16 at 21:11
  • Can the author clarify `Monoid` selection regarding the given example? Addition of values is assumed with `AnyValInstances#intInstance` in scope. If you wanted a different operation—say, multiplication—I found tagging with `m1.mapValues(Multiplication(_)) |+| m2.mapValues(Multiplication(_))` produced the desired `Map(1 -> 900, 3 -> 300, 2 -> 20)`. It makes sense (see [_Tagged type_](http://eed3si9n.com/learning-scalaz/Tagged+type.html) and [_Monoid_](http://eed3si9n.com/learning-scalaz/Monoid.html) from [_Learning Scalaz_](http://eed3si9n.com/learning-scalaz)), but appears inelegant. – Michael Ahlers Aug 07 '16 at 21:30
48

Quick solution:

(map1.keySet ++ map2.keySet).map {i=> (i,map1.getOrElse(i,0) + map2.getOrElse(i,0))}.toMap
Miriam Farber
  • 18,986
  • 14
  • 61
  • 76
Matthew Farwell
  • 60,889
  • 18
  • 128
  • 171
41

Well, now in scala library (at least in 2.10) there is something you wanted - merged function. BUT it's presented only in HashMap not in Map. It's somewhat confusing. Also the signature is cumbersome - can't imagine why I'd need a key twice and when I'd need to produce a pair with another key. But nevertheless, it works and much cleaner than previous "native" solutions.

val map1 = collection.immutable.HashMap(1 -> 11 , 2 -> 12)
val map2 = collection.immutable.HashMap(1 -> 11 , 2 -> 12)
map1.merged(map2)({ case ((k,v1),(_,v2)) => (k,v1+v2) })

Also in scaladoc mentioned that

The merged method is on average more performant than doing a traversal and reconstructing a new immutable hash map from scratch, or ++.

Mikhail Golubtsov
  • 6,285
  • 3
  • 29
  • 36
  • 1
    As of right now, it's only in immutable Hashmap, not mutable Hashmap. – Kevin Wheeler Oct 25 '14 at 02:33
  • 2
    This is pretty annoying that they only have that for HashMaps to be honest. – Johan S Nov 15 '14 at 15:04
  • I can't get this to compile, it seems like the type that it accepts is private, so I can't pass in a typed function that matches. – Ryan Leach Jul 07 '15 at 20:20
  • 2
    Seems something changed in 2.11 version. Check out 2.10 scaladoc - http://www.scala-lang.org/api/2.10.1/index.html#scala.collection.immutable.HashMap There is a usual function. But in 2.11 it's `MergeFunction`. – Mikhail Golubtsov Jul 08 '15 at 06:26
  • All that has changed in 2.11 is the introduction of a type alias for this particular function type `private type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)` – EthanP Mar 10 '16 at 20:00
14

This can be implemented as a Monoid with just plain Scala. Here is a sample implementation. With this approach, we can merge not just 2, but a list of maps.

// Monoid trait

trait Monoid[M] {
  def zero: M
  def op(a: M, b: M): M
}

The Map based implementation of the Monoid trait that merges two maps.

val mapMonoid = new Monoid[Map[Int, Int]] {
  override def zero: Map[Int, Int] = Map()

  override def op(a: Map[Int, Int], b: Map[Int, Int]): Map[Int, Int] =
    (a.keySet ++ b.keySet) map { k => 
      (k, a.getOrElse(k, 0) + b.getOrElse(k, 0))
    } toMap
}

Now, if you have a list of maps that needs to be merged (in this case, only 2), it can be done like below.

val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)

val maps = List(map1, map2) // The list can have more maps.

val merged = maps.foldLeft(mapMonoid.zero)(mapMonoid.op)
Jegan
  • 1,721
  • 1
  • 20
  • 25
5

I wrote a blog post about this , check it out :

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

basically using scalaz semi group you can achieve this pretty easily

would look something like :

  import scalaz.Scalaz._
  map1 |+| map2
Nimrod007
  • 9,825
  • 8
  • 48
  • 71
  • 12
    You need to put a little more detail in your answer, preferably some implementation code. Do this also for the other similar answers you've posted, and tailor each answer to the specific question that was asked. **Rule of Thumb:** The asker should be able to benefit from your answer without clicking the blog link. – Robert Harvey Jul 29 '14 at 14:24
5

You can also do that with Cats.

import cats.implicits._

val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)

map1 combine map2 // Map(2 -> 20, 1 -> 109, 3 -> 300)
  • Eek, `import cats.implicits._`. Import `import cats.instances.map._ import cats.instances.int._ import cats.syntax.semigroup._` not much more verbose... – St.Antario Dec 14 '18 at 08:39
  • @St.Antario it's actually recommended way to have only `import cats.implicits._` – Artsiom Miklushou Nov 26 '19 at 15:03
  • Recommeded by whom? Bringing all (most of which are unused) implicit instances into the scope complicates compiler's life. And besides if one does not need, say, Applicative instance why would they bring it there? – St.Antario Nov 26 '19 at 15:18
5
map1 ++ ( for ( (k,v) <- map2 ) yield ( k -> ( v + map1.getOrElse(k,0) ) ) )
AmigoNico
  • 6,652
  • 1
  • 35
  • 45
4

Starting Scala 2.13, another solution only based on the standard library consists in replacing the groupBy part of your solution with groupMapReduce which (as its name suggests) is an equivalent of a groupBy followed by mapValues and a reduce step:

// val map1 = Map(1 -> 9, 2 -> 20)
// val map2 = Map(1 -> 100, 3 -> 300)
(map1.toSeq ++ map2).groupMapReduce(_._1)(_._2)(_+_)
// Map[Int,Int] = Map(2 -> 20, 1 -> 109, 3 -> 300)

This:

  • Concatenates the two maps as a sequence of tuples (List((1,9), (2,20), (1,100), (3,300))). For conciseness, map2 is implicitly converted to Seq to adapt to the type of map1.toSeq - but you could choose to make it explicit by using map2.toSeq,

  • groups elements based on their first tuple part (group part of groupMapReduce),

  • maps grouped values to their second tuple part (map part of groupMapReduce),

  • reduces mapped values (_+_) by summing them (reduce part of groupMapReduce).

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
3

Andrzej Doyle's answer contains a great explanation of semigroups which allows you to use the |+| operator to join two maps and sum the values for matching keys.

There are many ways something can be defined to be an instance of a typeclass, and unlike the OP you might not want to sum your keys specifically. Or, you might want to do operate on a union rather than an intersection. Scalaz also adds extra functions to Map for this purpose:

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

You can do

import scalaz.Scalaz._

map1 |+| map2 // As per other answers
map1.intersectWith(map2)(_ + _) // Do things other than sum the values
user1158559
  • 1,954
  • 1
  • 18
  • 23
2

The fastest and simplest way:

val m1 = Map(1 -> 1.0, 3 -> 3.0, 5 -> 5.2)
val m2 = Map(0 -> 10.0, 3 -> 3.0)
val merged = (m2 foldLeft m1) (
  (acc, v) => acc + (v._1 -> (v._2 + acc.getOrElse(v._1, 0.0)))
)

By this way, each of element's immediately added to map.

The second ++ way is:

map1 ++ map2.map { case (k,v) => k -> (v + map1.getOrElse(k,0)) }

Unlike the first way, In a second way for each element in a second map a new List will be created and concatenated to the previous map.

The case expression implicitly creates a new List using unapply method.

2

Here's what I ended up using:

(a.toSeq ++ b.toSeq).groupBy(_._1).mapValues(_.map(_._2).sum)
user1084563
  • 2,149
  • 17
  • 28
  • 2
    That's really not significantly different than the 1st solution proposed by the OP. – jwvh Apr 17 '19 at 21:42
1

This is what I came up with...

def mergeMap(m1: Map[Char, Int],  m2: Map[Char, Int]): Map[Char, Int] = {
   var map : Map[Char, Int] = Map[Char, Int]() ++ m1
   for(p <- m2) {
      map = map + (p._1 -> (p._2 + map.getOrElse(p._1,0)))
   }
   map
}
kaur
  • 567
  • 1
  • 7
  • 24
1

Using the typeclass pattern, we can merge any Numeric type:

object MapSyntax {
  implicit class MapOps[A, B](a: Map[A, B]) {
    def plus(b: Map[A, B])(implicit num: Numeric[B]): Map[A, B] = {
      b ++ a.map { case (key, value) => key -> num.plus(value, b.getOrElse(key, num.zero)) }
    }
  }
}

Usage:

import MapSyntax.MapOps

map1 plus map2

Merging a sequence of maps:

maps.reduce(_ plus _)
Tomer Sela
  • 481
  • 9
  • 16
0

I've got a small function to do the job, it's in my small library for some frequently used functionality which isn't in standard lib. It should work for all types of maps, mutable and immutable, not only HashMaps

Here is the usage

scala> import com.daodecode.scalax.collection.extensions._
scala> val merged = Map("1" -> 1, "2" -> 2).mergedWith(Map("1" -> 1, "2" -> 2))(_ + _)
merged: scala.collection.immutable.Map[String,Int] = Map(1 -> 2, 2 -> 4)

https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith

And here's the body

def mergedWith(another: Map[K, V])(f: (V, V) => V): Repr =
  if (another.isEmpty) mapLike.asInstanceOf[Repr]
  else {
    val mapBuilder = new mutable.MapBuilder[K, V, Repr](mapLike.asInstanceOf[Repr])
    another.foreach { case (k, v) =>
      mapLike.get(k) match {
        case Some(ev) => mapBuilder += k -> f(ev, v)
        case _ => mapBuilder += k -> v
      }
    }
    mapBuilder.result()
  }

https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190

Eugene Platonov
  • 3,145
  • 3
  • 26
  • 20
0

For anyone coming across an AnyVal error, convert the values as follows.

Error: "could not find implicit value for parameter num: Numeric[AnyVal]"

(m1.toSeq ++ m2.toSeq).groupBy(_._1).mapValues(_.map(_._2.asInstanceOf[Number].intValue()).sum)

Alex
  • 126
  • 1
  • 4
  • 1
    Use of `asInstanceOf` is a [bad practice](https://stackoverflow.com/questions/60521145/scala-cast-to-generic-type/60521450#60521450) and should be avoided. – jwvh Aug 26 '21 at 00:24
  • Will keep that in mind. Been working with scala for about a week and this made my code work lol – Alex Aug 26 '21 at 01:03