So I have a list of maps like this
val data = List(
Map[String, String]("name" -> "Bob", "food" -> "pizza", "day" -> "monday"),
Map[String, String]("name" -> "Ron", "food" -> "hotdog", "day" -> "tuesday"),
Map[String, String]("name" -> "Tim", "food" -> "pizza", "day" -> "wednesday"),
Map[String, String]("name" -> "Carl", "food" -> "hotdog", "day" -> "wednesday")
)
I want to make a Map like this from that List of maps
val result = Map("pizza" -> Map("name" -> ("Bob", "Tim"), "day" -> ("monday", "wednesday")),
"hotdog"-> Map("name" -> ("Ron", "Carl"), "day" -> ("tuesday", "wednesday")))
How can I achieve this result? Thanks
*ps I'm a beginner in Scala
CodePudding user response:
Here is a preliminary solution, there is probably an easier way of doing this with fold but I have to sketch that out separately
data.groupMap(a => a("food"))(_.filter(_._1 != "food"))
.map{
case (a,b) =>
(a, b.flatten.groupMapReduce(_._1)(a => List(a._2))(_ _))}
- You group the maps inside based on the value of
food
This gives you:
Map(
hotdog -> List(
Map(name -> Ron, food -> hotdog, day -> tuesday),
Map(name -> Carl, food -> hotdog, day -> wednesday)),
pizza -> List(
Map(name -> Bob, food -> pizza, day -> monday),
Map(name -> Tim, food -> pizza, day -> wednesday))
)
- You remove the key
foodfrom the inner maps
Map(
hotdog -> List(
Map(name -> Ron, day -> tuesday),
Map(name -> Carl, day -> wednesday)),
pizza -> List(
Map(name -> Bob, day -> monday),
Map(name -> Tim, day -> wednesday))
)
You "merge" the maps inside by using
groupMapReducewhicha) groups by the inner key (i.e.
nameandday)b) maps each value to a singleton list
c) concats the lists
Edit: Here is a single pass solution using foldLeft but I don't think I like this any better. All the key accesses are unsafe and will blow up if your entry is missing the key. So ideally you would need to use .get() to get back an option and do bunch of pattern matching
data.foldLeft(Map[String, Map[String, List[String]]]())((b, a) => {
val foodVal = a("food")
b.get(foodVal) match{
case None => b (foodVal ->
List("name" -> List(a("name")), "day" -> List(a("day"))).toMap)
case Some(v : Map[String, List[String]]) =>
b (foodVal ->
List("name" -> (v("name") : a("name")), "day" -> (v("day") : a("day"))).toMap)
}
})
CodePudding user response:
If your Scala version is 2.13.x (or later) then you can take advantage of the quite convenient groupMap().
data.groupMap(_("food"))(_ - "food").map{case (food, maps) =>
food -> maps.foldLeft(List[(String,String)]())(_ _)
.groupMap(_._1)(_._2)
}
//res0: Map[String,Map[String,List[String]]] =
// Map(pizza -> Map(name -> List(Bob, Tim), day -> List(monday, wednesday))
// , hotdog -> Map(name -> List(Ron, Carl), day -> List(tuesday, wednesday)))
