2

I'm fairly new to Scala and functional approaches in general. I have a Map that looks something like this:

val myMap: Map[String, List[Int]]

I want to end up something that maps the key to the total of the associated list:

val totalsMap: Map[String, Int]

My initial hunch was to use a for comprehension:

val totalsMap = for (kvPair <- myMap) {
    kvPair._2.foldLeft(0)(_+_)
}

But I have no idea what I would put in the yield() clause in order to get a map out of the for comprehension.

alexkrishnan
  • 1,175
  • 8
  • 8

3 Answers3

5

You can use mapValues for this,

val totalMap = myMap.mapValues(_.sum)

But mapValues will recalculate the sum every time you get a key from the Map. e.g. If you do totalMap("a") multiple times, it will recalculate the sum each time.

If you don't want this, you should use

val totalMap = myMap map { 
  case (k, v) => k -> v.sum
}
samthebest
  • 30,803
  • 25
  • 102
  • 142
ntn
  • 1,167
  • 1
  • 8
  • 11
  • It will. You can check the behaviour here: https://github.com/scala/scala/blob/2.11.x/src/library/scala/collection/MapLike.scala#L244-257 def get(key: A) = self.get(key).map(f) – ntn Sep 02 '14 at 22:28
  • 2
    I was not aware of this. Is there any way to force evaluation of a "view"? I suppose you just construct a new collection instance from it? – JimN Sep 02 '14 at 22:31
  • 2
    You can force evaluation by using val totalMap = myMap.mapValues(_.sum).view.force – ntn Sep 02 '14 at 22:39
  • @ntn does that mean that if I access the view multiple times it will not be re-calculated if I use view.force? – alexkrishnan Sep 02 '14 at 23:20
  • 1
    +1 Any answer saying mapValues must mention that it is a view! They wrote a whole book about it. http://scalapuzzlers.com/#pzzlr-037 – som-snytt Sep 03 '14 at 00:00
2

mapValues would be more suited for this case:

val m = Map[String, List[Int]]("a" -> List(1,2,3), "b" -> List(4,5,6))

m.mapValues(_.foldLeft(0)(_+_))

res1: scala.collection.immutable.Map[String,Int] = Map(a -> 6, b -> 15)

Or without foldLeft:

m.mapValues(_.sum)
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
1
val m = Map("hello" -> Seq(1, 1, 1, 1), "world" -> Seq(1, 1))
for ((k, v) <- m) yield (k, v.sum)

yields

Map(hello -> 4, world -> 2)`

The for comprehension will return whatever monadic type you give it. In this case, m is a Map, so that's what's going to come out. The yield must return a tuple. The first element (which becomes the key in each Map entry) is the word you're counting, and the second element (you guessed it, the value in each Map entry) becomes the sum of the original sequence of counts.

  • Ah, thanks for giving me a better feel for how the syntax of for works. Unfortunately reduce is not an option for me because my actual use case involves a List[object_containing_an_int_that_needs_to_be_unboxed] but I didn't mention that in the question so that's my fault not yours! – alexkrishnan Sep 02 '14 at 22:30
  • How about "reduce(_.getMyInt + _.getMyInt)" – JimN Sep 02 '14 at 22:33
  • ah, yes, well, you can replace .reduce with .foldLeft(0). In any case, `mapValues` looks like a simpler option. – Chris Albright Sep 02 '14 at 22:34