Search code examples
scalanestedmapsimmutability

Scala create immutable nested map


I have a situation here

I have two strins

val keyMap = "anrodiApp,key1;iosApp,key2;xyz,key3"
val tentMap = "androidApp,tenant1; iosApp,tenant1; xyz,tenant2"

So what I want to add is to create a nested immutable nested map like this

tenant1 -> (andoidiApp -> key1, iosApp -> key2),
tenant2 -> (xyz -> key3)

So basically want to group by tenant and create a map of keyMap

Here is what I tried but is done using mutable map which I do want, is there a way to create this using immmutable map

case class TenantSetting() {
  val requesterKeyMapping = new mutable.HashMap[String, String]()
}

val requesterKeyMapping = keyMap.split(";")
      .map { keyValueList => keyValueList.split(',')
        .filter(_.size==2)
        .map(keyValuePair => (keyValuePair[0],keyValuePair[1]))
        .toMap
      }.flatten.toMap

    val config = new mutable.HashMap[String, TenantSetting]

    tentMap.split(";")
      .map { keyValueList => keyValueList.split(',')
        .filter(_.size==2)
        .map { keyValuePair =>
          val requester = keyValuePair[0]
          val tenant = keyValuePair[1]
          if (!config.contains(tenant)) config.put(tenant, new TenantSetting)
          config.get(tenant).get.requesterKeyMapping.put(requester, requesterKeyMapping.get(requester).get)
        }
      }

Solution

  • The logic to break the strings into a map can be the same for both as it's the same syntax.

    What you had for the first string was not quite right as the filter you were applying to each string from the split result and not on the array result itself. Which also showed in that you were using [] on keyValuePair which was of type String and not Array[String] as I think you were expecting. Also you needed a trim in there to cope with the spaces in the second string. You might want to also trim the key and value to avoid other whitespace issues.

    Additionally in this case the combination of map and filter can be more succinctly done with collect as shown here: How to convert an Array to a Tuple? The use of the pattern with 2 elements ensures you filter out anything with length other than 2 as you wanted. The iterator is to make the combination of map and collect more efficient by only requiring one iteration of the collection returned from the first split (see comments below). With both strings turned into a map it just needs the right use of groupByto group the first map by the value of the second based on the same key to get what you wanted. Obviously this only works if the same key is always in the second map.

    def toMap(str: String): Map[String, String] =
      str
        .split(";")
        .iterator
        .map(_.trim.split(','))
        .collect { case Array(key, value) => (key.trim, value.trim) }
        .toMap
    
    val keyMap = toMap("androidApp,key1;iosApp,key2;xyz,key3")
    val tentMap = toMap("androidApp,tenant1; iosApp,tenant1; xyz,tenant2")
    
    val finalMap = keyMap.groupBy { case (k, _) => tentMap(k) }
    

    Printing out finalMap gives:

    Map(tenant2 -> Map(xyz -> key3), tenant1 -> Map(androidApp -> key1, iosApp -> key2))
    

    Which is what you wanted.