scala groupby by the value of the map in a list of maps

Viewed 129

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

1 Answers

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))(_ ++ _))}
  1. 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))
)
  1. You remove the key food from 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))
)
  1. You "merge" the maps inside by using groupMapReduce which

    a) groups by the inner key (i.e. name and day)

    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)
  }
}) 
Related