2

I am trying to get my head around Scala for comprehensions as they relate to Maps. I have the following code and my intent is to break out the key-value pair, do something to the value and return the modified Map. Am I using the right function or should I use something else?

val kvpair = Map("a" -> 1, "b" -> 2, "c" -> 3)

def multiplyValues(map: Map[Char, Int]) = {

          for {
              char <- map._1  
              value <- map._2 * 2
          } yield (char, value )
  }
Ben Reich
  • 16,222
  • 2
  • 38
  • 59
Faktor 10
  • 1,868
  • 2
  • 21
  • 29

4 Answers4

6

In Map, method mapValues conveys this requirement; for instance,

kvpair.mapValues(_ * 2)

doubles each value in the map.

elm
  • 20,117
  • 14
  • 67
  • 113
  • Note that `mapValues` is lazy, so if you write `val m = kvpair.mapValues(_ * 2)` the values will be recomputed every time you use `m`. This often isn't a big deal if you're aware of it and careful, but it's easy to forget. – Travis Brown Apr 22 '15 at 13:33
3

As simple as :

def multiplyValues(map: Map[Char, Int]) =
  for {
    (char,value) <- map
    modValue = value * 2
  } yield (char, modValue)

or

def multiplyValues(map: Map[Char, Int]) =
  for ((char,value) <- map) yield (char, value* 2)

or even

def multiplyValues(map: Map[Char, Int]) = map mapValues (_ * 2)
Odomontois
  • 15,918
  • 2
  • 36
  • 71
  • Very many thanks the first for {} yield () solution has helped me a lot. – Faktor 10 Apr 22 '15 at 11:38
  • @Faktor10 You should probably also know, this form have far more general use than only in collections. It could be applied also to [`Option`s](http://www.scala-lang.org/api/current/index.html#scala.Option), [`Either`s](http://www.scala-lang.org/api/current/index.html#scala.util.Either), [`Future`s](http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future) of [different kinds](http://doc.akka.io/docs/akka/snapshot/java/futures.html), [test generators](https://www.scalacheck.org/files/scalacheck_2.11-1.12.2-api/index.html#org.scalacheck.Gen) – Odomontois Apr 22 '15 at 11:51
  • @Faktor10 so you should undertand [this concept](http://docs.scala-lang.org/tutorials/FAQ/yield.html), and [the more fundamental laws](http://en.wikipedia.org/wiki/Monad_%28functional_programming%29) behind it – Odomontois Apr 22 '15 at 11:53
  • awesome thank you for these links, I am also slowly working through the classic first edition [Bird and Wadlers Introduction to Functional Programming](http://www.amazon.com/Introduction-Functional-Programming-International-Computing/dp/0134841891) – Faktor 10 Apr 22 '15 at 13:39
1

As other have stated here, the method you might be looking for is called mapValues:

val myMap = Map("one" -> 1, "two" -> 2)
val newMap = myMap.mapValues(_ + 1)  

println(newMap) //Map(one -> 2, two -> 3)

But you have to be very careful with that method, since its underlying implementation is a view, meaning it is lazily evaluated. Since most of the other methods on immutable.Map do not behave similarly, this can be surprising, confusing, or lead to bugs. For example:

val it = Iterator.from(0)
val myMap = Map("one" -> 1, "two" -> 2)
val newMap = myMap.mapValues(_ + it.next)

println(newMap) //Map(one -> 3, two -> 5), as expected
//Try printing again, though:
println(newMap) //Map(one -> 5, two -> 7), I thought this map was immutable!

So the values in newMap are reevaluated each time the map is iterated! This is done as an optimization by delaying evaluation of the map and avoiding iteration when possible. This might be counter-intuitive, and this method even has several other questions and blog posts dedicated to it because of issues related to this confusion, worth reading if you are interested in using this method.

One way to gain a stable mapping while using the concise mapValues syntax is to force the underlying view implementation of the map:

val newMap = myMap.mapValues(_ + it.next).view.force //now stable!

You can also call toSeq and then toMap, but this has performance implications.

At then end of the day, it might be simpler to just use a regular map or a for comprehension:

//If you prefer "map":
myMap.map { case (key, value) => (key, value + 1) }
//If you prefer "for"
for { (key, value) <- map } yield (key, value + 1)
Community
  • 1
  • 1
Ben Reich
  • 16,222
  • 2
  • 38
  • 59
-2

While there are others (possible more elegant) ways to do this (see @elm's answer) this one is perfectly fine if you want to use/learn about for comprehensions.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post. – Alex Salauyou Apr 22 '15 at 11:16
  • @SashaSalauyou The question is: "Am I using the right function?" and the answer is "Yes" or to quote myself verbatim: "this one is perfectly fine". How does that not answer the question? – Jens Schauder Apr 22 '15 at 11:42