如果键存在或不插入新元素,则向 Scala 映射中的元素添加数字的好方法

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

我知道几个类似的问题。他们没有帮助我 - 如果没有现有密钥,代码将无法工作。

我只需要一些好的方法来附加带有值的 Map,将其添加到现有键(如果它确实存在)或作为新键(如果 map 不包含适当的键)。

以下代码有效,但我不喜欢它:

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)
}

有什么更好的吗? 当前的 Scala 版本是 2.10.4,我目前无法切换到 2.11。 可变映射不是 100% 限制但首选。

例如,similar question,但我还需要考虑不存在的密钥的情况,该密钥未在此处说明。至少我们应该理解 a.get(key) 可以是 None 或者添加一些更好的方法。 |+| 是个好主意,但我想保留基本的 Scala 2。10.x。

您可以为此目的创建自己的函数:

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)

最短的方法:

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

总的来说:

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

如果您的字典是不可变的,则相同:

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

所以我看不出有任何理由在这里使用可变集合。

如果您不喜欢所有这些 Option.map 事情:

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

请注意,有整个 class 种可能的变化:

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

那么,为什么它不是标准函数? IMO,因为所有变体仍然可以作为单线实现。如果您想要具有所有功能的库,可以将其实现为一行代码,您知道,它是 Scalaz :)。

P.S。如果您还想知道为什么没有 "update(d) if persist" 函数 - 请参阅@Rex Kerr 的回答 here

一个比较明确的方法:

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

不过,我完全不鼓励使用可变集合。最好将内部可变状态保持为包含不可变字段的变量。

这是因为一个简单的规则,你不应该暴露你的内部状态,除非它是完全必要的,如果你这样做,你应该尽可能减少可能产生的潜在副作用。

如果公开可变状态的引用,任何其他参与者都可以更改它的值,从而失去引用透明度。 所有对可变集合的引用都非常长且难以使用,这并非偶然。这是开发者给你的消息

不足为奇,代码仍然保持不变,在地图实例化时有一些微小的变化。

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 

Scala 2.13 introduced updatedWith method 这似乎是根据键的存在有条件地更新地图的最惯用的方法。

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)

也可以使用它删除值:

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

摘自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.

对于你的可变地图,你可以这样做:

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)