Reputation: 4996
I want to combine two Maps with the same type of key, but different type of value.
The result should have values containing with both value types (optional, because some values could be present in only one of the input Maps)
The type anotation is
def combineMaps[T, U, V](map1: Map[T, U], map2: Map[T, V]): Map[T, (Option[U], Option[V])] = {
???
}
I know it could be achieved with complicated code like:
(map1.mapValues(Some(_) -> None).toList ++ map2.mapValues(None -> Some(_)).toList) // List[(T, (Option[U], Option[V]))]
.groupBy(_._1) // Map[T, List[(T, (Option[U], Option[V]))]]
.mapValues(_.map(_._2)) // Map[T, List[(Option[U], Option[V])]]
.mapValues { list => (
list.collectFirst { case (Some(u), _) => u },
list.collectFirst { case (_, Some(v)) => v }
) } // Map[T, (Option[U], Option[V])]
Although the code is working, it does not benefit from the fact that every key in a Map is present only once. Method .toList
drop this type information.
I am looking for some elegant scala way to do it (possibly with cats/scalaz, but best without them)
Upvotes: 0
Views: 420
Reputation: 22439
A union of the keySet
s followed by mapping the keys to tuples of get
s should do it:
def combineMaps[T, U, V](m1: Map[T, U], m2: Map[T, V]): Map[T, (Option[U], Option[V])] =
(m1.keySet union m2.keySet).map(k => (k, (m1.get(k), m2.get(k)))).toMap
Example:
combineMaps(Map('a'->1, 'b'->2, 'c'->3), Map('a'->10.0, 'c'->30.0, 'd'->40.0))
// res1: Map[Char,(Option[Int], Option[Double])] = Map(
// a -> (Some(1),Some(10.0)), b -> (Some(2),None), c -> (Some(3),Some(30.0)), d -> (None,Some(40.0))
// )
Upvotes: 1
Reputation: 22840
Yeah cats has you covered, this is what the Align
typeclass provides.
You only need to do:
m1.align(m2)
That will return a Map[T, Ior[U, V]]
which is better than a pair of Options
since Ior
preserves the fact that at least one of the two elements must exists.
Upvotes: 3