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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With