Search code examples
scaladictionarycollectionsscalazscala-cats

mutable.Map deep merge


Is there a concise way to deeply merge two mutable maps in Scala?

case class K1(i: Int)
case class K2(i: Int)

def deepMerge(map: mutable.Map[K1, Map[K2, List[Int]]],
              mergee: mutable.Map[K1, Map[K2, List[Int]]]
): Unit = ???

Examples:

I.

val map = mutable.Map(K1(1) -> Map(K2(1) -> List(1)))
val mergee = mutable.Map(K1(1) -> Map(K2(1) -> List(2)))

deepMerge(map, mergee)
map = mutable.Map(K1(1) -> Map(K2(1) -> List(1, 2)))

II.

val map = mutable.Map(K1(1) -> Map(K2(1) -> List(1)))
val mergee = mutable.Map(K1(1) -> Map(K2(2) -> List(1)))

deepMerge(map, mergee)
map = mutable.Map(K1(1) -> Map(K2(1) -> List(1), K2(2) -> List(1)))

III.

val map = mutable.Map(K1(1) -> Map(K2(1) -> List(1)))
val mergee = mutable.Map(K1(2) -> Map(K2(2) -> List(1)))

deepMerge(map, mergee)
map = mutable.Map(K1(1) -> Map(K2(1) -> List(1)), K1(2) -> Map(K2(2) -> List(1)))

I.e if there are the same key presented in both of the maps then the values the keys correspond to (List[Int]) are merged.

Is there a way to implemented it concisely avoiding lots of checking if the particular key is presented or not in another map? Using FP libs like scalaz or cats is also ok.


Solution

  • I'm adding another answer using cats.

    What you're describing is actually the behaviour of cats.Semigroup. So you could just use combine (|+|) operator to deep merge maps:

    import cats.implicits._
    import cats._
    
    case class K1(i: Int)
    case class K2(i: Int)
    
    val map = Map(K1(1) -> Map(K2(1) -> List(1)))
    val mergee = Map(K1(1) -> Map(K2(1) -> List(2)))
    
    val deepMerged = map |+| mergee
    
    println(deepMerged) // HashMap(K1(1) -> HashMap(K2(1) -> List(1, 2)))
    

    Problem is that cats lib doesn't provide an instance of Semigroup for mutable.Map , but you could derive it from one for immutable:

    import cats.implicits._
    import scala.collection.immutable
    import scala.collection.mutable
    import cats._
    
    //here I derivive Semigroup instance for mutable.Map from instance for immutable.Map
    implicit def mutableMapSemigroup[K, V: Semigroup]: Semigroup[mutable.Map[K, V]] = Semigroup[immutable.Map[K, V]].imap(c => mutable.Map.from(c))(c => immutable.Map.from(c))
    
    case class K1(i: Int)
    case class K2(i: Int)
    
    val map = mutable.Map(K1(1) -> mutable.Map(K2(1) -> List(1)))
    val mergee = mutable.Map(K1(1) -> mutable.Map(K2(1) -> List(2)))
    
    println(map |+| mergee)
    

    But keep in mind, this actually converts mutable map to immutable then does merging and then converts back to the mutable map, so it's probably not very efficient.