1

Can someone recommend a functional way to transform the map specified below from

Map("host.config.autoStart.powerInfo[1].startOrder" -> -1,
    "host.config.autoStart.powerInfo[1].startAction" ->  "None",
    "host.config.autoStart.powerInfo[1].key" ->  "vm-XXX",
    "host.config.autoStart.powerInfo[0].key" ->  "vm-YYY",
    "host.config.autoStart.powerInfo[0].startOrder" ->  -1,
    "host.config.autoStart.powerInfo[0].startAction" ->  "None")

to

Map("host.config.autoStart.powerInfo" -> Map(
        1 -> Map("startOrder" -> -1,
                "startAction" -> "None",
                "key" -> "vm-639"),
        0 -> Map("startOrder" -> -1,
                "startAction" -> "None",
                "key" -> "vm-641")))
  1. Extract what is before the subscript and make that the key
  2. Extract the number between the subscript [x] and make that the key of value
Richard Sitze
  • 8,262
  • 3
  • 36
  • 48
conikeec
  • 209
  • 2
  • 14

2 Answers2

3

A one line (long) solution:

val R = """([^\[]+)\[(\d+)\]\.(.+)""".r
m.map{ case(R(h,i,k),v) => (h,i,k,v) }.groupBy(_._1).mapValues(_.groupBy(_._2).mapValues{ _.map{case(h,i,k,v) => (k,v)}.toMap} )
res1: scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,scala.collection.immutable.Map[String,Any]]] = 
Map(host.config.autoStart.powerInfo -> 
  Map(1 -> Map(startAction -> None, 
               startOrder -> -1, 
               key -> vm-XXX), 
      0 -> Map(key -> vm-YYY, 
      startAction -> None, 
      startOrder -> -1)
))

Or write it more or less readable:

m.map{ case(R(h,i,k),v) => (h,i,k,v) }
 .groupBy(_._1).mapValues{ value =>
   value.groupBy(_._2).mapValues{ _.map{case(h,i,k,v) => (k,v)}.toMap}
 }
Eastsun
  • 18,526
  • 6
  • 57
  • 81
2

Edit: added some comments to the code to make it easier to see what's going on

Copied from my REPL:

scala> val re = """(.+)\[(\d+)\]\.(.+)""".r // Creates a regex to grab the key values
re: scala.util.matching.Regex = (.+)\[(\d+)\]\.(.+)

scala> val m = Map("host.config.autoStart.powerInfo[1].startOrder" -> -1,"host.config.autoStart.powerInfo[1].startAction" -> "None","host.config.autoStart.powerInfo[1].key" -> "vm-XXX","host.config.autoStart.powerInfo[0].key" -> "vm-YYY","host.config.autoStart.powerInfo[0].startOrder" -> -1,"host.config.autoStart.powerInfo[0].startAction" -> "None")
m: scala.collection.immutable.Map[String,Any] = Map(host.config.autoStart.powerInfo[0].key -> vm-YYY, host.config.autoStart.powerInfo[0].startAction -> None, host.config.autoStart.powerInfo[0].startOrder -> -1, host.config.autoStart.powerInfo[1].startAction -> None, host.config.autoStart.powerInfo[1].startOrder -> -1, host.config.autoStart.powerInfo[1].key -> vm-XXX)

scala> val tempList = m map { // Construct a temporary list of Tuples with all relevant values
     | case (key, value) => key match {
     |    case re(p, i, k) => (p, i, k, value)
     | }}
tempList: scala.collection.immutable.Iterable[(String, String, String, Any)] = List((host.config.autoStart.powerInfo,0,key,vm-YYY), (host.config.autoStart.powerInfo,0,startAction,None), (host.config.autoStart.powerInfo,0,startOrder,-1), (host.config.autoStart.powerInfo,1,startAction,None), (host.config.autoStart.powerInfo,1,startOrder,-1), (host.config.autoStart.powerInfo,1,key,vm-XXX))

scala> val accumulator = Map[String, Map[String, Map[String, Any]]]()
accumulator: scala.collection.immutable.Map[String,Map[String,Map[String,Any]]] = Map()


scala> val result = tempList.foldLeft(accumulator) {
     |  case (acc, e) => { 
     |   val middleMap = acc.getOrElse(e._1, Map[String, Map[String, Any]]())
     |   val innerMap = middleMap.getOrElse(e._2, Map[String, Any]())
     |   acc + (e._1 -> (middleMap + (e._2 -> (innerMap + (e._3 -> e._4)))))
     | }}
result: scala.collection.immutable.Map[String,Map[String,Map[String,Any]]] = Map(host.config.autoStart.powerInfo -> Map(0 -> Map(key -> vm-YYY, startAction -> None, startOrder -> -1), 1 -> Map(startAction -> None, startOrder -> -1, key -> vm-XXX)))
Emil L
  • 20,219
  • 3
  • 44
  • 65
  • I see now that I missed the part that `"host.config.autoStart.powerInfo"` was supposed to be a key to an outer map. To fix that just add another group to the regex `re` and then make the `foldLeft` fold over a Map of Maps of Maps... You should probably seriously contemplate another datastructure though :) – Emil L Mar 09 '13 at 19:22
  • Thanks Emil ... Excellent illustration .. Appreciate it .... Based on your suggestion, if I apply another regex grouping to extract the main key ,i.e. val re = """(.+)\[(\d+)\]\.(.+)""".r Can you please illustrate the foldLeft part on tmpList like you advised. If you can kindly edit the code with the changes it can really help ... Agree on the messiness of the structure :) – conikeec Mar 09 '13 at 19:51
  • 1
    @conikeec: If the answer helps you than upvote and/or accept it. – kiritsuku Mar 09 '13 at 21:20
  • @EmilH Emil ... Just noticed that the output does not match the required output as specified above. There seems to be an issue with the accumulator in the code segment ... – conikeec Mar 10 '13 at 07:17
  • @conikeec Your are right, there was a typo on the line defining the innerMap. Changed it so the it returns the expected result. The second solution Eastsun provided is cleaner though. – Emil L Mar 10 '13 at 09:24