Jing Huang
Jing Huang

Reputation: 41

Convert Seq[Option[Map[String, Any]]] to Option[Map[String, Any]]

How to Convert Seq[Option[Map[String, Any]]] to Option[Map[String, Any]]

So skip any option that is None, and keep the valid Maps and merge them. If every option is None, then the final option should be None as well.

Upvotes: 0

Views: 555

Answers (3)

jq170727
jq170727

Reputation: 14625

Here is an example with flatten,fold, ++ and a match at the end to provide Some or None.

baz.scala

package baz
object baz {
  // fixtures
  val x0 = Seq[Option[Map[String, Any]]]()
  val x1 = Seq[Option[Map[String, Any]]](None)
  val x2 = Seq[Option[Map[String, Any]]](Some(Map("a" -> 1, "b" -> "two")))
  val x3 = Seq[Option[Map[String, Any]]](Some(Map("a" -> 1, "b" -> "two")), Some(Map("c" -> 3.0)), None)

  def f(x: Seq[Option[Map[String, Any]]]) =
    x.flatten.fold(Map[String, Any]())((a,b) => a ++ b) match { case m if m.isEmpty => None case m => Some(m) }
}

Sample run

bash-3.2$ scalac baz.scala && scala -classpath .
Welcome to Scala 2.13.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_66).
Type in expressions for evaluation. Or try :help.

scala> import baz.baz._
import baz.baz._

scala> x0 -> f(x0)
res0: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(),None)

scala> x1 -> f(x1)
res1: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(None),None)

scala> x2 -> f(x2)
res2: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(Some(Map(a -> 1, b -> two))),Some(Map(a -> 1, b -> two)))

scala> x3 -> f(x3)
res3: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(Some(Map(a -> 1, b -> two)), Some(Map(c -> 3.0)), None),Some(Map(a -> 1, b -> two, c -> 3.0)))

scala> :quit

Upvotes: 0

Leo C
Leo C

Reputation: 22439

A flatMap along with groupMapReduce, followed by an Option filter should do the job:

val listOfMaps: List[Option[Map[String, Any]]] =
  List(Some(Map("a"->"p", "b"->2)), None, Some(Map("a"->"q", "c"->"r")))

val mergedMap = listOfMaps.
  flatMap(_.getOrElse(Map.empty[String, Any])).
  groupMapReduce(_._1)(t => List[Any](t._2))(_ ::: _)
// mergedMap: Map[String, List[Any]] = 
//   Map("a" -> List("p", "q"), "b" -> List(2), "c" -> List("r"))

Option(mergedMap).filter(_.nonEmpty)
// res1: Option[Map[String, List[Any]]] =
//   Some(Map("a" -> List("p", "q"), "b" -> List(2), "c" -> List("r")))

A few notes:

  1. groupMapReduce is available only on Scala 2.13+.

  2. If you must stick to Seq instead of List, simply replace method ::: with ++ in groupMapReduce.

  3. It is assumed that merging of the Maps means aggregating the Map values of a common key into a List. Replace groupMapReduce with toMap if keeping only one of the Map values of a common key is wanted instead.

  4. This solution treats Some(Map(.empty[String, Any])) the same as None.

Upvotes: 1

Tim
Tim

Reputation: 27356

This is a one-line solution

val in: Seq[Option[Map[String, Any]]] = ???

val out: Option[Map[String, Any]] =
  in.flatten.headOption.map(_ => in.flatten.reduce(_ ++ _))

The in.flatten.headOption is a simple way of getting Some if at least one of the elements is Some or None if they are all None.

The reduce just combines all the Maps into one.

This can clearly be optimised by avoiding the duplicate in.flatten call.

Upvotes: 0

Related Questions