1

I need to convert the following map:

Map(1 -> Seq("A", "E"), 2 -> Seq("D", "G"))

to:

Map("a" -> 1, "d" -> 2, "e" -> 1, "g" -> 2)

How can I solve it?

Thanks!!!

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
pfari
  • 101
  • 1
  • 6

5 Answers5

4

Use flatMap and convert inner list using map to desired form.

Warning: Seqs must not have duplicates.

scala> val map = Map(1 -> Seq("A", "E"), 2 -> Seq("D", "G"))
map: scala.collection.immutable.Map[Int,Seq[String]] = Map(1 -> List(A, E), 2 -> List(D, G))

scala> map.flatMap { case (k, v) => v.map(_.toLowerCase -> k) }
res0: scala.collection.immutable.Map[String,Int] = Map(a -> 1, e -> 1, d -> 2, g -> 2)
Nagarjuna Pamu
  • 14,737
  • 3
  • 22
  • 40
  • The `toMap` is redundant. `flatMap` called on a `Map` will be a `Map` if the returned value in the callback is an iterable of tuples. – OstrichProjects Apr 10 '18 at 16:03
  • Same as with the other answer: if you already (for whatever reason) avoid the `for`-comprehension syntax, is it really necessary to call the variable `map` if there is already `map` and `flatMap` in the expression? Wherever I see a `map` in isolation, I automatically begin searching for the collection `c` on which the method `c.map` should have been invoked... – Andrey Tyukin Apr 10 '18 at 16:12
4

Assuming that m is your input map,

for ((v, ks) <- m; k <- ks) yield (k.toLowerCase, v)

produces:

Map(a -> 1, e -> 1, d -> 2, g -> 2)

This is essentially a 1:1 translation of the recipe: "For each pair of integer value v and list of letters ks from the original map, take each letter k from ks and add the pair (k, v) to the new map".

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • To be honest, I'm somewhat surprised by the sudden rise in popularity of the desugared explicit `flatMap` and `map` here: we already have short concise `for`-syntax for exactly that, why not use it? It's as if Python users would all of the sudden start to rewrite all their list-comprehensions in terms of the `__iter__`-method... I'm confused. O_o? – Andrey Tyukin Apr 10 '18 at 18:57
  • I agree `for/yield` is easier to comprehend in many (if not most) cases. Maybe the prominence of the `flatMap` and `map` methods in almost every kind of Scala collections/containers might help advertise themselves in some way. Or, maybe people just don't want to be [slapped](http://www.flatmapthatshit.com/) for not using it. – Leo C Apr 10 '18 at 22:06
  • @LeoC As I understand [this thread](https://stackoverflow.com/questions/8559537/where-does-the-flatmap-that-s-idiomatic-expression-in-scala-come-from) for example, it's more about replacing towers of nested `match { case Some(...) => { ... match case Some(...) => ... } ... case None => }`-thingies by `flatMap`, and then eventually by `for`-comprehensions, because they don't introduce one level of indentation with each new bound variable... It's less against `for`-syntax imho. I don't know, `for` seems more appropriate in this particular case. – Andrey Tyukin Apr 10 '18 at 22:27
  • Maybe I should emphasize that I don't insist on using `for` instead of `flatMap` everywhere, indeed, one of my highest voted questions is exactly about [a case where `for`-comprehension fails badly, but `flatMap` works nicely](https://stackoverflow.com/questions/41123032/free-trampoline-recursive-program-crashes-with-outofmemoryerror). But in this particular case, I just don't see any reason not to use `for-yield`. – Andrey Tyukin Apr 10 '18 at 22:35
2

you can do something like below

val m = Map(1 -> Seq("A", "E"), 2 -> Seq("D", "G"))

println(m.flatMap(x => x._2.map(y => (y.toLowerCase, x._1))))

and you would get

Map(a -> 1, e -> 1, d -> 2, g -> 2)
Ramesh Maharjan
  • 41,071
  • 6
  • 69
  • 97
  • ...then why not go all the way and write `map.flatMap(map => map._2.map(flatMap => (flatMap.toLowerCase, map._1)))`? ;) – Andrey Tyukin Apr 10 '18 at 16:09
  • Yeah, I know... But the explicitly written out `map`-`flatMap`-method calls are by themselves already somewhat dense (that's why the `for`-syntax was invented), adding additional variable names also called `map` into the mix doesn't seem to make it easier to read. – Andrey Tyukin Apr 10 '18 at 16:19
  • you want me to delete my answer @AndreyTyukin ;) I already upvoted yours and I can't answer the same using for loops – Ramesh Maharjan Apr 10 '18 at 16:28
  • No, why... I just wanted to point out a somewhat confusing choice of variable names. It's not specific about your answer either, I've pointed it out in all proposals that called the input `Map` "map". Thanks for updating the answer, I find it much nicer already ;) – Andrey Tyukin Apr 10 '18 at 16:30
0

The other answer are correct but for someone that is new to scala they can be hard to understand. The below is simple and easy to understand

val z = Map(1 -> Seq("A", "E"), 2 -> Seq("D", "G"))
z.map{case (i,l) => l.map(s=> (s.toLowerCase->i))}.flatten.toMap
Mzf
  • 5,210
  • 2
  • 24
  • 37
  • And given that `flatten` is the same as `flatMap(identity)`, you can contract it to the even simpler `z.flatMap{ case (i, l) => l.map(s => (s.toLowerCase -> i)) }.toMap`, and because now you can drop `toMap`, you end up exactly with @pamu's solution, and if you now further simplify `flatMap { case (i, l) => ...}` into a short & tidy `for`-comprehension, you obtain `for ((i,l) <- z; s <- l) yield (s.toLowerCase -> i)`. Honestly, I don't know why everyone thinks that expanding `for`-comprehensions into the low-level `flatMap`-implementation details makes anything simpler... – Andrey Tyukin Apr 10 '18 at 18:27
  • This is exactly why scala is problematic for new programmers, they can not do or understand the flow you just described. Any case it's good that you gave it so maybe others can understand the other answers – Mzf Apr 10 '18 at 18:30
  • I don't think that it's absolutely necessary for the new programmers to be exposed to the desugaring of for-comprehensions in the first place. "For each number and sequence from the original map, take each letter from the sequence, and add the pair (letter, number) to the new map" - despite the fact that my English is not perfect, this should be comprehensible even for non-programmers. This translates directly into `for ((number, sequence) <- m; letter <- sequence) yield (letter, number)`. The `flatMap` is an irrelevant implementation detail in this case. – Andrey Tyukin Apr 10 '18 at 18:43
0

We can also use collect to achieve this

scala> val map =Map(1 -> Seq("A", "E"), 2 -> Seq("D", "G"))
map: scala.collection.immutable.Map[Int,Seq[String]] = Map(1 -> List(A, E), 2 -> List(D, G))

 scala> map.collect{case (k,v) => v.map(_.toLowerCase -> k)}.flatten.toMap
//res2: scala.collection.immutable.Map[String,Int] = Map(a -> 1, e -> 1, d -> 2, g -> 2)
Puneeth Reddy V
  • 1,538
  • 13
  • 28
  • Using `collect` in this context is equivalent to using `map` and `collect { ... }.flatten.toMap` in this case is the equivalent of just using `flatMap`. – OstrichProjects Apr 30 '19 at 14:56