1

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)
        }
      }
pme
  • 14,156
  • 3
  • 52
  • 95
Arjun Karnwal
  • 379
  • 3
  • 13

1 Answers1

2

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.

Steve Sowerby
  • 1,136
  • 1
  • 7
  • 7
  • Any reason for removing the `iterator`? – Luis Miguel Mejía Suárez Feb 17 '20 at 19:29
  • Because it wasn't needed. Unless I'm missing something? – Steve Sowerby Feb 17 '20 at 19:30
  • It improves the performance of the function, since it will apply the `map`, the `collect` and the `toMap` in just one iteration. – Luis Miguel Mejía Suárez Feb 17 '20 at 19:34
  • OK fair enough. I didn't know that would actually happen in earlier Scala versions. And probably not that relevant here. Still on Scala 2.11 myself where it seems only take, drop and slice are bunched. Maybe that changes for 2.12 or 2.13. – Steve Sowerby Feb 17 '20 at 19:39
  • Sorry, I didn't understand that. – Luis Miguel Mejía Suárez Feb 17 '20 at 19:45
  • Scala 2.11. iterator on array goes to IndexedSeqLike which is commented: multiple `take`, `drop`, and `slice` operations on this iterator are bunched together for better efficiency. – Steve Sowerby Feb 17 '20 at 20:23
  • I think additional fusing only came in with Scala 2.12? – Steve Sowerby Feb 17 '20 at 20:24
  • 1
    Ham, I think you are a little bit confused about what **Iterators** are and how they work. They are one-use lazy collections, all operations on them will simply append a computation to be run until some action is called, like `next()` or `toMap`. They shouldn't be passed around because it is pretty easy to forget that they are mutable and just one use, but they are pretty useful when you have a chain of methods on a single collection _(like in this case)_. I believe your confusion comes from the source code, but in `2.11` all operations are optimized - anyways, I will stop here, too off-topic. – Luis Miguel Mejía Suárez Feb 17 '20 at 20:38
  • 1
    Oh I get iterators. I just rarely use them directly and had never thought about how it must be combining the operations to support the chaining of methods in that way. And was mixing it up in my memory with some work on fusing strict collections operations elsewhere. So I learned something new today. Thanks :-) – Steve Sowerby Feb 17 '20 at 23:37
  • @SteveSowerby Thanks for your answer. How can I filter out those values if the keys are not present so that there is no exception? – Arjun Karnwal Feb 18 '20 at 18:48
  • @ArjunKarnwal If you filter the keyMap based on having a matching key in tentMap that should do the trick. The simplest way to do that is via `filterKeys`. So something like: ```val finalMap = keyMap.filterKeys(tentMap.contains).groupBy { case (k, _) => tentMap(k) }``` – Steve Sowerby Feb 18 '20 at 21:11
  • @SteveSowerby I thought of using same but filterKeys is deprecated in scala 13 and cant use that. So can you provide an alternative ? Also I would like to map the value as `Map[String, TenantSetting]` where TenantSetting is `final case class TenantSettings(requesters: Map[String, String])` How I am doing right now is `val finalMap = keyMap.groupBy { case (k, _) => tentMap(k) }.map { case (key, value) => (key, TenantSettings(value)) }` Is there a better way to do this ? – Arjun Karnwal Feb 18 '20 at 23:34
  • @SteveSowerby Did you get a chance to look into my comment above ? – Arjun Karnwal Feb 19 '20 at 21:11
  • You can map and filter at the same time using `collect`. That would probably be the answer So something like `collect { case (k,v) if tentMap.contains(k) => (k,TenantSettings(v)) }` – Steve Sowerby Feb 23 '20 at 16:06
  • Take 2: You can map and filter at the same time using `collect`. That would probably be the answer So something like `keyMap.collect { case (k,v) if tentMap.contains(k) => (k,TenantSettings(tentMap(k))) }.groupBy { case (k,_) => k }`. Although if doing that I'm not sure what purpose `keyMap` then has as you're throwing the value away from that, no? – Steve Sowerby Feb 23 '20 at 16:13