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

时间:2015-02-07 20:34:13

标签: scala dictionary containers

我知道几个类似的问题。他们不帮助我 - 如果没有现有密钥,代码就不起作用。

我只需要一些很好的方法来附加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。

4 个答案:

答案 0 :(得分:13)

最简单的方法:

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)

请注意,存在一整类可能的变体:

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。如果你也想知道为什么没有"更新(d)如果坚持"功能 - 请参阅@Rex Kerr的回答here

答案 1 :(得分:11)

您可以为此目的创建自己的功能:

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)

答案 2 :(得分:5)

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]

更新指定键及其当前可选映射值的映射(如果存在当前映射,则为Some,否则为None)。

如果重新映射函数返回Some(v),则映射将用新值v更新。如果重新映射函数返回None,则映射将被删除(如果最初不存在,则保持不存在)。如果函数本身引发异常,则重新引发该异常,并且当前映射保持不变。

答案 3 :(得分:1)

有一种明确的方法:

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

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

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

如果您公开了一个可变状态的引用,那么任何其他actor都可以更改它的值,从而失去引用透明性。 所有对可变集合的引用都非常长,并且难以使用,这并非偶然。这是开发人员给你的消息

毫不奇怪,代码仍然保持不变,在地图实例化时进行了一些微小的更改。

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