Cristian Boariu
Cristian Boariu

Reputation: 9621

Conditional group by in Scala

I have a case class like:

 case class ReportData(
    building: Option[String] = None,
    serial: Option[String] = None,
    `type`: Option[String] = None,
    model: Option[String] = None,
    machine: Option[String] = None)

and other fields too.

I have a list of these objects, for instance:

val reports = List(report1, report2, report3, report11, report12)

The request is to do some specific complex operations with specific fields of these class, for instance group these list only by the fields received as a parameter: groupFields="building,serial"

This is an example of a fine group by:

val reportsGroupBoth = reports.groupBy(p => (p.building.getOrElse(""), p.serial.getOrElse("")))

butI want to make it conditional depending of the fields received so the only way I am thinking is to make group by separated for each field (I will add if condition later now I just want to receive the same result as the above group by if I make them separated, result being of type: Map[(String, String),List[agile.ReportData]]

so I tried:

// group by building (if building is defined in groupFields list)
val reportsGroup1 = reports.groupBy(p => p.building)

which is fine but this one is wrong:

// group by building and serial (if building and serial are in groupFields list)
val reportsGroup2 = reportsGroup1 map {
        case (key, value) => (key, value.groupBy(v => v.serial).keys) -> value.groupBy(v => v.serial).values
    }

So the question is how to modify reportsGroup2 that in the end it will have the same result as reportsGroupBoth of tpye Map[(String, String),List[agile.ReportData]]

Upvotes: 1

Views: 5685

Answers (2)

Bask.ws
Bask.ws

Reputation: 843

If you want simply to group by two nonempty fields

reports.groupBy(r => (r.building, r.serial)).flatMap({
   case ((Some(b),Some(s)),v) => Some((b,s) -> v)
   case _ => None
})

If you want to group after group, like you suggested:

val reportsGroup2 = reportsGroup1.map({
   case (key, value) => value.groupBy(v => (key, v.serial))
}).flatten.flatMap({
   case ((Some(b),Some(s)),v) => Some((b,s) -> v)
   case _ => None
}).toMap

You will get the same

Maybe you want to setup some grouping policy, like:

val buildingSerialPolicy = 
   Seq[ReportGroup => Option[String](_.building, _.serial)

And then just call needed policy:

val removeEmptyKeys = ...

report.groupBy(r => 
   buildingSerialPolicy.map(_.apply(r))).flatMap(removeEmptyKeys)

Upvotes: 1

wingedsubmariner
wingedsubmariner

Reputation: 13667

This will work:

val reportsGroup1 = reports.groupBy(p => p.building.getOrElse(""))

val reportsGroup2 = reportsGroup1 flatMap {
  case (key, value) => value.groupBy(v => v.serial.getOrElse("")).map {
    case (key2, value) => ((key, key2), value)
  }
}

We take each of the groups of the first groupBy and do a groupBy on it, then add the first key back in. Each of these separate groups of groups are combined and returned as one map.

Upvotes: 2

Related Questions