9

Is there an elegant way in to update an already existing value in a Map?

This looks too scary:

val a = map.get ( something ) 
if ( a != null ) // case .. excuse my old language
   a.apply( updateFunction )
else 
   map.put ( something, default )
kiritsuku
  • 52,967
  • 18
  • 114
  • 136
cybye
  • 1,171
  • 6
  • 13

3 Answers3

12

Most of the time you can insert something that can be updated when it's created (e.g. if it's a count, you put 0 in it and then update to 1 instead of just putting 1 in to start). In that case,

map.getOrElseUpdate(something, default).apply(updateFunction)

In the rare cases where you can't organize things this way,

map(something) = map.get(something).map(updateFunction).getOrElse(default)

(but you have to refer to something twice).

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • but does the same as Kane suggested - in the end. – cybye Jan 25 '13 at 20:12
  • @cybye - `getOrElseUpdate` is also double-lookup as now implemented, but it at least has the potential to be optimized (for the I-can-update-on-addition case) with a library update (maybe in 2.11 which is supposed to target performance). – Rex Kerr Jan 25 '13 at 20:20
  • but this will need an api change. there is no support for state changes within the values: op => B is not applied to the value. so probably this is meant for initials only – cybye Jan 25 '13 at 20:28
  • 1
    @cybye - That's not clearly what you asked for. If you want `map.changeValue(key, f)`, no, there isn't one. If your value is mutable, what I wrote above will work. – Rex Kerr Jan 25 '13 at 20:40
  • right, the question was: is there any way to access and change the state of a value already present in a scala map (ok, not this clearly formulated). I think the most efficent is the 'simple standard scala match thing' as Kane suggested but slightly modified for the use case. Probably Martin wants us to do accumulations this way ... (it's not ineffecient btw). – cybye Jan 25 '13 at 21:03
  • @Rex - great point of preferring a library function over a custom one. It's a great way to ensure your app performs better as the performance of the Scala runtime improves. Also thanks for demonstrating 'getOrElseUpdate()' - didn't know about it. – Steven Levine Jan 25 '13 at 23:32
4

This is what I usually write... not sure if there are better solutions.

map.get(key) match {
  case None => map.put(key, defaultValue)
  case Some(v) => map(key) = updatedValue
}

In fact update and put are the same for mutable map, but I usually use update on existing entries and put for new ones, just for readability.

Another thing is that if you can figure out what the ultimate value is without checking the existence of the key, you can simply write map(key) = value, at it automatically creates/replaces the entry.

Finally, statements like map(key) += 1 actually works in Map (this is generally true for collections with update function), and so do many simple numeric operations.\


To solve double put, use mutable object instead of immutable values:

class ValueStore(var value: ValueType)
val map = new Map[KeyType, ValueStore]
...
map.get(key) match {
  case None => map.put(key, new ValueStore(defaultValue))
  case Some(v) => v.value = updatedValue
}

As I mentioned in the comment, the underlying structure of HashMap is HashTable, which actually use this mutable wrapper class approach. HashMap is a nicer wrap-up class but you sometimes just have to do duplicated computation.

Kane
  • 1,314
  • 2
  • 9
  • 14
  • this is a double put(or get or at least hash/find) or not? – cybye Jan 25 '13 at 19:19
  • Sorry I don't know what you mean by double put... can you explain more? – Kane Jan 25 '13 at 19:21
  • map(key) gets the hash of the key, searches the key in the map (by using the hash as a hint and searching until it finds it by checking for equal(other) == true) and then sets the value assoc with the key to updatedValue. before doing that, the same thing happend for map.get(key) .. so this is done twice .. – cybye Jan 25 '13 at 19:27
  • @cybye - It's a double lookup, once on the `get` and once on the `put` or apply. – Rex Kerr Jan 25 '13 at 20:06
  • @cybye I see what you are saying. Actually I have the same feeling too, that you are doing some duplicated computation. I have 2 points on this concern: 1.`HashMap` is nicely optimized so that key access is almost constant time, so you don't really worry about it. 2. Another possibility is, make your `value` mutable objects. Instead of replacing new values into it, you just get the value object and mutate it. This means after you create an entry (key,value), you never have to call `put` on it again. But I guess this approach is a bit against how `Map` should be correctly used. – Kane Jan 25 '13 at 20:59
  • @cybye And, if you look into the implementation of `HashMap`, you actually see that the underlying data structure is `HashTable`, which is indeed implemented using this mutable approach. `HashMap` can be viewed as a nice wrap-up of `HashTable` providing many convenient functionality, with a little sacrifice that sometimes you have to do dummy operations. – Kane Jan 25 '13 at 21:10
0

me stupid, you are (quite) right:

map.get(key) match {
  case None => map.put(key, defaultValue)
  case Some(v) => v.apply(updateFunction) // changes state of value
}

tsts thanks

cybye
  • 1,171
  • 6
  • 13