Max Hager
Max Hager

Reputation: 49

Count occurence of key values from several maps grouped by a key in scala 2.11.x

Imagine the following list of maps (which could be potentially longer):

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

How can I get to the following form:

Map("very late" -> Set(("wind",Map("high" -> 2)), ("rain",Map("heavy" -> 1, "none" -> 1))),
"on time" -> Set(("wind",Map("none" -> 1)), ("rain",Map("slight" -> 1))))

Upvotes: 0

Views: 121

Answers (3)

Here goes another implementation, which uses the keySet method of Map to join two maps together.
Also, note that I changed the output type from a Map to a Set of tuples whose second value is another Map. To three nested Maps which, IMHO, makes more sense.

def groupMaps[K, V](groupingKey: K, data: List[Map[K, V]]): Map[V, Map[K, Map[V, Int]]] =
  data.foldLeft(Map.empty[V, Map[K, Map[V, Int]]]) {
    case (acc, map) =>
      map.get(key = groupingKey).fold(ifEmpty = acc) { groupingValue  =>
        val newValues = (map - groupingKey).map {
          case (key, value) =>
            key -> Map(value -> 1)
        }
        
        val finalValues = acc.get(key = groupingValue).fold(ifEmpty = newValues) { oldValues =>
          (oldValues.keySet | newValues.keySet).iterator.map { key =>
            val oldMap = oldValues.getOrElse(key = key, default = Map.empty[V, Int])
            val newMap = newValues.getOrElse(key = key, default = Map.empty[V, Int])
            
            val finalMap = (oldMap.keySet | newMap.keySet).iterator.map { value =>
              val oldCount = oldMap.getOrElse(key = value, default = 0)
              val newCount = newMap.getOrElse(key = value, default = 0)
              
              value -> (oldCount + newCount)
            }.toMap
            
            key -> finalMap
          }.toMap
        }
        
        acc.updated(key = groupingValue, finalValues)
      }
  }

Which can be used like this:

val maps =
  List(
    Map("wind" -> "none", "rain" -> "none", "class" -> "on time"),
    Map("wind" -> "none", "rain" -> "slight", "class" -> "on time"),
    Map("wind" -> "none", "rain" -> "slight", "class" -> "late"),
    Map("wind" -> "none", "rain" -> "slight")
  )
val result = groupMaps(groupingKey = "class", maps)
// val result: Map[Strig, Map[String, Map[String, Int]]] =
//  Map(
//    on time -> Map(wind -> Map(none -> 2), rain -> Map(none -> 1, slight -> 1)),
//    late -> Map(wind -> Map(none -> 1), rain -> Map(slight -> 1))
//  )

If you need to maintain the output type you asked for, then you can just do a .mapValue(_.toSet) at the end of the foldLeft


You can see the code running here

Upvotes: 1

jwvh
jwvh

Reputation: 51271

This will get you what you want.

val maps = List(...)

maps.groupBy(_.getOrElse("class","no-class"))
  .mapValues(_.flatMap(_ - "class").groupBy(_._1)
    .mapValues(_.map(_._2).groupBy(identity)
      .mapValues(_.length)
    ).toSet
  )

The problem is, what you want isn't a good place to be.

The result type is Map[String,Set[(String,Map[String,Int])]] which is a terrible hodgepodge of collection types. Why Set? What purpose does that serve? How is this useful? How do you retrieve meaningful data from it?

This looks like an XY problem.

Upvotes: 1

Johny T Koshy
Johny T Koshy

Reputation: 3922

Here are two versions.

First using Set, which looks like what you want,

val grouped2 = maps.foldLeft(Map.empty[String, Set[(String, Map[String, Int])]]) {
  case (acc, map) =>
    map.get("class").fold(acc) { key =>
      val keyOpt = acc.get(key)
      if (keyOpt.isDefined) {
        val updatedSet = (map - "class").foldLeft(Set.empty[(String, Map[String, Int])]) {
          case (setAcc, (k1, v1)) =>
            keyOpt.flatMap(_.find(_._1 == k1)).map { tup =>
              setAcc + ((k1, tup._2.get(v1).fold(tup._2 ++ Map(v1 -> 1))(v => tup._2 + ((v1, v + 1)))))
            }.getOrElse(setAcc + (k1 -> Map(v1 -> 1)))
        }
        acc.updated(key, updatedSet)
      } else {
        acc + (key -> (map - "class").map(tup => (tup._1, Map(tup._2 -> 1))).toSet)
      }
    }
}

and then using a Map,

val grouped1 = maps.foldLeft(Map.empty[String, Map[String, Map[String, Int]]]) {
  case (acc, map) =>
    map.get("class").fold(acc) { key =>
      val keyOpt = acc.get(key)
      if (keyOpt.isDefined) {
        val updatedMap = (map - "class").foldLeft(Map.empty[String, Map[String, Int]]) {
          case (mapAcc, (k1, v1)) =>
            keyOpt.flatMap(_.get(k1)).map{ statMap =>
              mapAcc + ((k1, statMap.get(v1).fold(statMap ++ Map(v1 -> 1))(v => statMap + ((v1, v + 1)))))
            }.getOrElse(mapAcc + (k1 -> Map(v1 -> 1)))
        }
        acc.updated(key, updatedMap)
      } else {
        acc + (key -> (map - "class").map(tup => (tup._1, Map(tup._2 -> 1))))
      }
    }
}

I was playing with Map version and changed it to Set. In a few days, I don't think I will understand everything above just by looking at it. So, I have tried to make it as understandable as possible for me. Adapt this to your own solution or wait for others.

Upvotes: 1

Related Questions