Home > Blockchain >  How to group a list of maps by key Scala 2.11.x
How to group a list of maps by key Scala 2.11.x

Time:12-19

Given the following list of maps (list could be longer):

List(
    Map[String,String]("wind"->"none", "rain"->"none", "class"->"on time"),
    Map[String,String]("wind"->"none", "rain"->"slight", "class"->"on time"),
    Map[String,String]("wind"->"none", "rain"->"slight", "class"->"late"),
    ...
)

How can I group the maps that I have something like this:

"on time" -> ("wind"->"none", "rain"->"none", "wind"->"none", "rain"->"slight")
"late" -> ("wind"->"none", "rain"->"slight")

I get stuck at working on several maps.

CodePudding user response:

Another option:

val maps =
  List(
    Map[String, String]("wind" -> "none", "rain" -> "none", "class" -> "on time"),
    Map[String, String]("wind" -> "none", "rain" -> "slight", "class" -> "on time"),
    Map[String, String]("wind" -> "none", "rain" -> "slight", "class" -> "late"),
    Map[String, String]("wind" -> "none", "rain" -> "slight")
  )

val grouped = maps.foldLeft(Map.empty[String, List[(String, String)]]) {
  case (acc, map) if map.contains("class") =>
    val key = map("class")
    if (acc.contains(key))
      acc.updated(key, acc(key)    (map - "class").toList)
    else 
      acc   (key -> (map - "class").toList)
  case (acc, _) => acc
}

CodePudding user response:

Here's one way to go about it. (Scala 2.13.x)

val maps = List(...)
val rslt = maps.groupMap(_("class"))(_ - "class")
               .map{case (k,v) => k -> v.flatten}

I know the map() call can be simplified to a mapValues() but that's been deprecated in 2.13 so I don't use it.

This will, of course, throw at runtime if there are any Map elements in the List without a "class" key.

CodePudding user response:

In vanilla Scala 2.12\2.11 assuming the starting point:

val maps =
  List(
    Map[String, String]("wind" -> "none", "rain" -> "none", "class" -> "on time"),
    Map[String, String]("wind" -> "none", "rain" -> "slight", "class" -> "on time"),
    Map[String, String]("wind" -> "none", "rain" -> "slight", "class" -> "late")
  )

you could fold everything:

maps
  .filter(_.contains("class")) // guarantee "class" key exists
  .map(m => m("class") -> (m - "class").toList)
  .foldLeft(Map.empty[String, List[(String, String)]]) { 
    case (acc, (key, values)) if acc.contains(key) => 
      acc.updated(key, acc(key)    values)
    case (acc, (key, values)) => 
      acc   ((key, values))
  }

This logic is safe with any input as it will filter out Map[_,_] without class key. You could remove that .map(..) and do everything in one go in the fold.

If you can guarantee the input is non-empty and contains key class you could use reduceLeft instead of foldLeft and drop the filter.

Bonus the logic is compatible with Iterator[_]:

maps
  .iterator
  .filter(..)
  .map(..)
  .foldLeft(..)(..)
}
  • Related