Best way to merge two maps and sum the values of s

2018-12-31 21:33发布

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.

标签: scala map merge
12条回答
初与友歌
2楼-- · 2018-12-31 22:02

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)) }
查看更多
素衣白纱
3楼-- · 2018-12-31 22:02

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)
查看更多
弹指情弦暗扣
4楼-- · 2018-12-31 22:03

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

查看更多
梦寄多情
5楼-- · 2018-12-31 22:05

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 ++.

查看更多
余生无你
6楼-- · 2018-12-31 22:05

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
查看更多
骚的不知所云
7楼-- · 2018-12-31 22:07

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)
查看更多
登录 后发表回答