2

I would like to merge nested maps, but I can't figure out how to merge the inner maps.

var a = Map[String,Map[String,String]]()
a = a + ("key1" -> Map("subkey1" -> "a"))
a = a + ("key1" -> Map("subkey2" -> "b"))
a = a + ("key2" -> Map("subkey1" -> "c"))

I would like to merge all of these such that I get the following result:

Map("key1" -> Map("subkey1" -> "a", "subkey2" -> "b"), "key2" -> Map("subkey1" -> "c"))

Is there any standard method for this?

2
  • 1
    What if there is a collision between keys in the sub-map? Like two subkey1 under the key1 map. Commented Apr 12, 2015 at 0:18
  • @m-z Ideally that collision could be handled by a 2-arity function. In my case the signature would be more like Map[String,Map[String,Seq[String]]] and then cons (add?) the values together. Commented Apr 12, 2015 at 0:26

4 Answers 4

1

If it's fine to use Scalaz - semigroups may help:

import scalaz._, Scalaz._
val map1 = Map("key1" -> Map("subkey1" -> "a"))
val map2 = Map("key1" -> Map("subkey2" -> "b"))
val map3 = Map("key2" -> Map("subkey1" -> "c"))

scala> map1 |+| map2 |+| map3
res0: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,String]] = 
   Map(key2 -> Map(subkey1 -> c), key1 -> Map(subkey2 -> b, subkey1 -> a))

The only restriction - your value should have Semigroup defined in order to handle collisions:

trait A
object A1 extends A
object A2 extends A 

implicit val ASemigroup = new Semigroup[A] {
  def append(a: A, b: => A) : A = a //"choose first" strategy
}

val map1 = Map("key1" -> Map("subkey1" -> (A1: A)))
val map2 = Map("key1" -> Map("subkey1" -> (A2: A)))

scala> map1 |+| map2
res8: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,A]] = 
   Map(key1 -> Map(subkey1 -> A1$@2cb79bd1))

Btw, strings already have Semigroup defined on them, so collision will lead to string's concatenation there.

0

There isn't anything that works well for this that's built in, but getOrElse is your friend here. The core of the logic, if you are sure there are no submap collections, looks like

val x = a.getOrElse(key, mutable.Map.empty[String,String])
a = a + (key -> (x ++ subMap))

If you may have a collision, you need to do something other than ++--possibly use the same trick again to get the subkey and update the value if it exists.

0

It seems there isn't direct method provided for this. You can provide a helper method.

def mergeUpdate[K1, K2, V](base: Map[K1, Map[K2, V]], tuple: (K1, Map[K2, V])) = {
  base + (tuple._1 -> (base.getOrElse(tuple._1, Map.empty) ++ tuple._2))
}

then rewrite your code:

var a = Map[String,Map[String,String]]()
a = mergeUpdate(a, ("key1" -> Map("subkey1" -> "a")))
a = mergeUpdate(a, ("key1" -> Map("subkey2" -> "b")))
a = mergeUpdate(a, ("key2" -> Map("subkey1" -> "c")))

// =>
a: scala.collection.immutable.Map[String,Map[String,String]] = Map(key1 -> Map(subkey1 -> a, subkey2 -> b), key2 -> Map(subkey1 -> c))
0

If you can pull other dependencies, you can try this

scala> import com.daodecode.scalax.collection.extensions._
import com.daodecode.scalax.collection.extensions._

scala> val m1 = "key1" -> Map("subkey1" -> "a")
m1: (String, scala.collection.immutable.Map[String,String]) = (key1,Map(subkey1 -> a))

scala> val m2 = "key1" -> Map("subkey2" -> "b")
m2: (String, scala.collection.immutable.Map[String,String]) = (key1,Map(subkey2 -> b))

scala> val m3 = "key2" -> Map("subkey1" -> "c")
m3: (String, scala.collection.immutable.Map[String,String]) = (key2,Map(subkey1 -> c))

scala> Seq(m1, m2, m3).toCompleteMap.
  mapValues(_.foldLeft(Map.empty[String,String]){ 
    case (acc, m) => acc.mergedWith(m)(_ + _)})
res0: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,String]] = Map(key2 -> Map(subkey1 -> c), key1 -> Map(subkey1 -> a, subkey2 -> b))

toCompleteMap and mergedWith are extensions methods from https://github.com/jozic/scalax-collection. It's published to maven central

(_ + _) here is your own collision resolution function

Not the answer you're looking for? Browse other questions tagged or ask your own question.