Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge two HashMaps in Scala

I need to create

def MergeWith[Id, X, Y, Z](x: HashMap[Id, X], y: HashMap[Id, Y], f: (X, Y) => Z): HashMap[Id, Z]

I want to:

1) iterate over x

2) for keys that are presented in y yield f(x[key], y[key]) into a result: HashMap[Id, Z]

For everything I invent I get compilation errors I don't understand how to fight.

For example,

def andWith[K, X, Y, Z] (x: HashMap[K, X], y: HashMap[K, Y], f: (X, Y) => Z): HashMap[K, Z] = {
  for {
    (x_name, x_value) <- x
    if y.contains(x_name)
  } yield x_name -> f(x_value, y.get(x_name))
}

produces

Error:(14, 39) type mismatch;
 found   : Option[Y]
 required: Y
 } yield x_name -> f(x_value, y.get(x_name))
                              ^

which is predictable, but I fail to unwrap Option.

like image 832
Aleksandr Pakhomov Avatar asked Mar 18 '23 17:03

Aleksandr Pakhomov


1 Answers

The following is a very concise and reasonably idiomatic way of writing this in Scala:

def mergeWith[K, X, Y, Z](xs: Map[K, X], ys: Map[K, Y])
  (f: (X, Y) => Z): Map[K, Z] =
    xs.flatMap {
      case (k, x) => ys.get(k).map(k -> f(x, _))
    }

Note that I'm using Map instead of HashMap and that I've changed some of the identifier names slightly. I've also put the function into its own parameter list, which can make the syntax a little cleaner for the user.

This is pretty dense code, so I'll unpack what's going on a bit. For every key-value pair (k, v) in the xs map, we use ys.get(k) to get an Option[Y] that will be Some[Y] if the key exists in ys and None otherwise. We use map on the Option to change the Y value inside (if it exists) into a pair (K, Z).

The value of the entire ys.get(k).map(k -> f(x, _)) part is therefore Option[(K, Z)], which is a collection of exactly one or zero pairs. If we had used map on our xs, we'd have ended up with a Seq[Option[(K, Z)]], but because we've used flatMap, this can be collapsed into a Map[K, Z], which is what we want (and what we've specified as the return type of the method.

We can try it out:

scala> val mapX = Map('a -> 1, 'b -> 2)
mapX: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 1, 'b -> 2)

scala> val mapY = Map('b -> "foo", 'c -> "bar")
mapY: scala.collection.immutable.Map[Symbol,String] = Map('b -> foo, 'c -> bar)

scala> mergeWith(mapX, mapY) { (x, y) => (x, y) }
res0: Map[Symbol,(Int, String)] = Map('b -> (2,foo))

Which is what we wanted.

like image 58
Travis Brown Avatar answered Mar 28 '23 11:03

Travis Brown