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)
CodePudding user response:
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.
CodePudding user response:
A union of the keySets followed by mapping the keys to tuples of gets 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))
// )
