Home > database >  scala groupby by the value of the map in a list of maps
scala groupby by the value of the map in a list of maps

Time:01-07

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))(_    _))}
  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)
  }
}) 

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)))
  •  Tags:  
  • Related