如何更新可变映射中的现有值?

9

有没有一种优雅的方式可以更新Map中已经存在的值?

这看起来太可怕了:

val a = map.get ( something ) 
if ( a != null ) // case .. excuse my old language
   a.apply( updateFunction )
else 
   map.put ( something, default )
3个回答

12

大多数情况下,您可以插入一些可以在创建时更新的内容(例如,如果它是计数,则将0放入其中,然后更新为1,而不仅仅是将1放入开始)。 在这种情况下,

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

在极少数情况下,如果您无法以这种方式组织事物,

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

(但你必须两次引用something。)


但最终是否与Kane建议的相同呢? - cybye
@cybye - getOrElseUpdate 目前的实现方式也是双重查找,但至少它有潜力通过库更新进行优化(对于可以在添加时更新的情况),也许在 2.11 版本中会针对性能进行优化。 - Rex Kerr
但是这将需要API更改。值内没有状态更改的支持:op => B不适用于该值。所以可能这只针对初始值。 - cybye
1
@cybye - 这不是你清楚地要求的。如果你想要 map.changeValue(key, f),那么没有这样的方法。如果你的值是可变的,那么我上面写的代码将起作用。 - Rex Kerr
对的,问题是:有没有办法访问和更改已经存在于Scala映射中的值的状态(好吧,这不是很清楚的表述)。我认为最有效的方法是像Kane建议的那样使用“简单标准的Scala匹配”,但略微修改以适应用例。可能Martin希望我们以这种方式进行累积...(顺便说一句,这并不低效)。 - cybye
@Rex - 优先选择库函数而不是自定义函数的观点非常好。这是确保您的应用程序在Scala运行时性能提高的情况下表现更佳的好方法。此外,感谢您演示了“getOrElseUpdate()”-我之前不知道它。 - Steven Levine

4

这是我通常写的东西......不确定是否有更好的解决方案。

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

实际上,对于可变映射,updateput是相同的,但我通常在现有条目上使用update,而在新条目上使用put,只是为了可读性。
另一件事是,如果你可以在不检查键是否存在的情况下确定最终值,你可以简单地写map(key) = value,它会自动创建/替换条目。
最后,像map(key) += 1这样的语句实际上在Map中起作用(这通常适用于具有update函数的集合),许多简单的数字操作也是如此。
要解决双重放置问题,请使用可变对象而不是不可变值:
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
}

如我在评论中提到的,HashMap 的底层结构是 HashTable,它实际上采用了可变包装类的方法。尽管 HashMap 是一个更好的包装类,但有时你仍需要进行重复计算。请保留 HTML 标记。

这是一个双重的 put(或 get 或至少 hash/find)吗? - cybye
抱歉,我不明白您所说的“double put”是什么意思...您能详细解释一下吗? - Kane
map(key) 方法获取 key 的哈希值,在地图中搜索该键(使用哈希值作为提示,并通过检查 equal(other) == true 直到找到它),然后将关联的值设置为 updatedValue。在这之前,map.get(key) 也发生了同样的事情...因此这个过程发生了两次.. - cybye
@cybye - 这是一个双重查找,一次在get上,一次在put或apply上。 - Rex Kerr
@cybye 我明白你的意思。实际上我也有同样的感觉,你正在进行一些重复计算。我对此有两点疑虑:1.HashMap已经被优化得很好了,所以键访问几乎是常数时间,所以你不需要太担心它。2.另一个可能性是,使你的value成为可变对象。而不是将新值替换到其中,你只需获取该值对象并对其进行修改。这意味着在创建条目(key,value)之后,您永远不必再次调用put。但我想这种方法有点违反正确使用Map的方式。 - Kane
@cybye 如果你深入研究HashMap的实现,你会发现底层数据结构实际上是HashTable,而HashTable正是使用了这种可变的方法来实现。HashMap可以被看作是对HashTable的一个很好的封装,提供了许多方便的功能,只是有时候你需要进行一些虚拟操作。 - Kane

0

我很蠢,你是(相当)正确的:

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

测试 谢谢


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接