3

I need to update (retrieve and increase) two different values bound to two keys in a map. The two keys can coincide sometimes. I have the following code for now:

// val map: Map[Int, Int]
// val key1, key2: Int
if (key1 == key2) {
  tailRecFunction(someArg, map 
    + Tuple2(key1, 2 + map.getOrElse(key1, 0)))
} else {
  tailRecFunction(someArg, map 
    + Tuple2(key1, 1 + map.getOrElse(key1, 0))
    + Tuple2(key2, 1 + map.getOrElse(key2, 0)))
}

As you can see, if you use the else block when key1 == key2, then the value at key1 == key2 will incorrectly be increased by 1 instead of 2 --- the second tuple erroneously updates the original value, rather than the value applied by the first tuple.
Is there a cleaner way to write this?

mucaho
  • 2,119
  • 20
  • 35
  • why is this downvoted? please specify a reason so I know where I made the mistake! – mucaho Feb 24 '14 at 21:20
  • this question should be linked to [Idiomatic way to update value in a Map based on previous value](http://stackoverflow.com/q/9003874/3041008) – mucaho Feb 26 '14 at 11:56

2 Answers2

5

First of all, you can simplify the map to return 0 when no key is present:

val map0 = Map.empty[Int, Int] withDefaultValue 0

Then you can safely call map(key) instead of map.getOrElse(key, 0).

Second, you can use the arrow-association syntax to create Tuple2 instances. I.e. key -> value instead of Tuple2(key, value).

Third, I would not introduce the if ... then ... else at all. Just update the map for each key successively:

def addKey(map: Map[Int, Int], key: Int) = map + (key -> (map(key) + 1))

val map1 = addKey(map0, key1)
val map2 = addKey(map1, key2)
tailRecFunction(someArg, map2)
0__
  • 66,707
  • 21
  • 171
  • 266
  • Thanks, great suggestions! (Ad3): I can still avoid a val definition with `val newMap = addKey(addKey(oldMap, key1), key2)` – mucaho Feb 24 '14 at 22:39
  • 1
    I would strongly recommend not using `withDefaultValue` in this way, especially with immutable maps. This is one of the warts in scala's collection libraries. See this debate amongst the compiler developers https://groups.google.com/forum/#!topic/scala-internals/R4fTU_5XVZs/discussion. For example, https://gist.github.com/drstevens/9201268 – drstevens Feb 25 '14 at 02:04
  • 1
    @drstevens In the case of histograms and counting elements, `withDefaultValue(0)` is perfectly sensible IMO, but yes you should be careful when you transform the map. – 0__ Feb 25 '14 at 06:14
  • Another [SO answer](http://stackoverflow.com/a/5831667/3041008) suggests using the `~` operator from the __ScalaZ__ library, which avoids the dangers of `withDefaultValue`. – mucaho Feb 25 '14 at 14:05
0

Another SO Answer suggests using the Monoid characteristics of Map (ScalaZ):

tailRecFunction(someArg, map |+| Map(key1 -> 1) |+| Map(key2 -> 1))

The |+| operator will sum values that belong to the same key.

Community
  • 1
  • 1
mucaho
  • 2,119
  • 20
  • 35
  • Similar principle as in [Best way to merge two maps and sum the values of same key?](http://stackoverflow.com/q/7076128/3041008) – mucaho Feb 26 '14 at 12:05