Roman Nikitchenko
Roman Nikitchenko

Reputation: 13046

Nice way to add number to element in Scala map if key exists or insert new element it not

I know about couple of similar questions. They don't help me - code does not work if there is no existing key.

I need just some nice approach to append Map with value adding it to existing key (if it does exist) or putting as NEW key (if map does not contain appropriate key).

Following code works but I don't like it:

val a = collection.mutable.Map(("k1" -> 1), ("k2" -> 5))
val key = "k1"

val elem = a.get(key)
if (elem == None) {
    a += ("k5" -> 200)
} else {
    a.update(key, elem.get + 5)
}

Any point to better one? Current Scala version is 2.10.4 and I cannot currently switch to 2.11. Mutable map is not 100% limitation but preferred.

Here is, for example, similar question but I also need to account case of non-existing key which is not accounted there. At least we should understand a.get(key) could be None or add some better approach. Good idea was |+| but I'd like to keep basic Scala 2.10.x.

Upvotes: 17

Views: 20763

Answers (5)

JeanMarc
JeanMarc

Reputation: 326

For your mutable map, you can just do:

val a = collection.mutable.Map(("k1" -> 1), ("k2" -> 5))
val key = "k1"

a.update(key, a.getOrElse(key, 0) + 5)

a  // val res1: scala.collection.mutable.Map[String,Int] = HashMap(k1 -> 6, k2 -> 5)

Upvotes: 1

Vilius
Vilius

Reputation: 794

Scala 2.13 introduced updatedWith method which seems to be the most idiomatic way to update a map conditionally on the existence of the key.

val a = Map(("k1" -> 1), ("k2" -> 5))

val a1 = a.updatedWith("k1") { 
  case Some(v) => Some(v + 5)
  case None => Some(200) 
}
println(a1) // Map(k1 -> 6, k2 -> 5)

One may also remove values using it:

val a2 = a.updatedWith("k2") { 
  case Some(5) => None
  case v => v 
}
println(a2) // Map(k1 -> 1)

An excerpt from the Scala Standard Library reference:

def updatedWith[V1 >: V](key: K)(remappingFunction: (Option[V]) => Option[V1]): Map[K, V1]

Update a mapping for the specified key and its current optionally-mapped value (Some if there is current mapping, None if not).

If the remapping function returns Some(v), the mapping is updated with the new value v. If the remapping function returns None, the mapping is removed (or remains absent if initially absent). If the function itself throws an exception, the exception is rethrown, and the current mapping is left unchanged.

Upvotes: 7

Maximiliano Felice
Maximiliano Felice

Reputation: 367

One somewhat clear way to do this:

val a = collection.mutable.Map[String, Int]() withDefault insertNewValue

def insertNewValue(key: String): Int =
  a += key -> getValueForKey(key)
  a(key)
}

def getValueForKey(key: String): Int = key.length

Still, I totally discourage the use of mutable collections. It's preferable to keep internal mutable state as variables holding immutable fields.

This is because of a simple rule, you shouldn't expose your internal state unless it's totally necessary, and if you do, you should diminish as much as possible the potential side effects that may produce.

If you expose a reference of a mutable state, any other actor can change it's values, thus losing referential transparency. It's not by chance that all references to mutable collections are quite long, and difficult to use. That's a developer's message for you

Not surprisingly, the code still remains the same, with some minimal changes at the map instantiation.

var a = Map[String, Int]() withDefault insertNewValue

def insertNewValue(key: String): Int = {
  a += key -> getValueForKey(key)
  a(key)
}

def getValueForKey(key: String): Int = key.length 

Upvotes: 1

dk14
dk14

Reputation: 22374

The shortest way to do that:

a += a.get(key).map(x => key -> (x + 5)).getOrElse("k5" -> 200)

In general:

a += a.get(k).map(f).map(k -> _).getOrElse(kv)

Same if your dictionary is immutable:

m + m.get(k).map(f).map(k -> _).getOrElse(kv)

so I don't see any reason to use mutable collection here.

If you don't like all these Option.map things:

m + (if (m.contains(k)) k -> f(m(k)) else kv)

Note, that there is a whole class of possible variations:

k1 -> f(m(k1)) else k2 -> v2 //original
k1 -> f(m(k1)) else k1 -> v2
k1 -> f(m(k2)) else k2 -> v2
k1 -> f(m(k2)) else k1 -> v2
k2 -> v2 else k1 -> f(m(k1)) 
k1 -> v2 else k1 -> f(m(k1))
k2 -> v2 else k1 -> f(m(k2))
k1 -> v2 else k1 -> f(m(k2))
... //v2 may also be a function from some key's value

So, Why it's not a standard function? IMO, because all variations still can be implemented as one-liners. If you want library with all functions, that can be implemented as one-liners, you know, it's Scalaz :).

P.S. If yu also wonder why there is no "update(d) if persist" function - see @Rex Kerr 's answer here

Upvotes: 21

Jean Logeart
Jean Logeart

Reputation: 53829

You can create you own function for that purpose:

def addOrUpdate[K, V](m: collection.mutable.Map[K, V], k: K, kv: (K, V), 
                      f: V => V) {
  m.get(k) match {
    case Some(e) => m.update(k, f(e))
    case None    => m += kv
  }
}

addOrUpdate(a, "k1", "k5" -> 200, (v: Int) => v + 5)

Upvotes: 18

Related Questions