mirelon
mirelon

Reputation: 4996

Combine two Maps with same key type, but different value type in scala

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

Answers (2)

Leo C
Leo C

Reputation: 22439

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))
// )

Upvotes: 1

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

Related Questions