如何在可变映射中访问/初始化和更新值?

时间:2013-03-19 16:26:08

标签: scala dictionary

考虑使用可变映射来跟踪事件/计数的简单问题,即:

val counts = collection.mutable.Map[SomeKeyType, Int]()

我目前增加点数的方法是:

counts(key) = counts.getOrElse(key, 0) + 1
// or equivalently
counts.update(key, counts.getOrElse(key, 0) + 1)

这种感觉有些笨拙,因为我必须指定两次密钥。在性能方面,我还希望key必须在地图中找到两次,我想避免。有趣的是,如果Int提供某种修改自身的机制,则不会发生此访问和更新问题。例如,允许从Int更改为提供Counter功能的increment类:

// not possible with Int
counts.getOrElseUpdate(key, 0) += 1
// but with a modifiable counter
counts.getOrElseUpdate(key, new Counter).increment

不知怎的,我总是期望在可变映射中使用以下功能(有点类似于transform但没有返回新集合和具有默认值的特定键):

// fictitious use
counts.updateOrElse(key, 0, _ + 1)
// or alternatively
counts.getOrElseUpdate(key, 0).modify(_ + 1)

但据我所知,这样的功能并不存在。在一般情况下(性能和语法方面),这样的f: A => A就地修改可能性是否有意义?可能我只是在这里遗漏了一些东西...我想这个问题必须有一些更好的解决方案,不需要这样的功能吗?

更新

我应该澄清一点,我知道withDefaultValue但问题仍然存在:执行两次查找仍然两次慢于一次,无论是否为O (1)操作与否。坦率地说,在很多情况下,我会非常乐意实现因子2的加速。显然,修改闭包的构造通常可以移动到循环之外,所以这不是一个大问题,而不是运行不必要地操作两次。

3 个答案:

答案 0 :(得分:24)

您可以使用默认值创建地图,这样您就可以执行以下操作:

scala> val m = collection.mutable.Map[String, Int]().withDefaultValue(0)
m: scala.collection.mutable.Map[String,Int] = Map()

scala> m.update("a", m("a") + 1)

scala> m
res6: scala.collection.mutable.Map[String,Int] = Map(a -> 1)

正如Impredicative所提到的,地图查找很快,所以我不担心2次查找。

<强>更新

正如Debilski指出的那样,你可以通过以下方式更简单地做到这一点:

scala> val m = collection.mutable.Map[String, Int]().withDefaultValue(0)
scala> m("a") += 1
scala> m
 res6: scala.collection.mutable.Map[String,Int] = Map(a -> 1)

答案 1 :(得分:2)

Scala 2.13开始,Map#updateWith达到了这个确切目的:

map.updateWith("a")({
  case Some(count) => Some(count + 1)
  case None        => Some(1)
})
  

def updateWith(key:K)(remappingFunction:(Option [V])=> Option [V]):Option [V]


例如,如果密钥不存在:

val map = collection.mutable.Map[String, Int]()
// map: collection.mutable.Map[String, Int] = HashMap()

map.updateWith("a")({ case Some(count) => Some(count + 1) case None => Some(1) })
// Option[Int] = Some(1)
map
// collection.mutable.Map[String, Int] = HashMap("a" -> 1)

,如果密钥存在:

map.updateWith("a")({ case Some(count) => Some(count + 1) case None => Some(1) })
// Option[Int] = Some(2)
map
// collection.mutable.Map[String, Int] = HashMap("a" -> 2)

答案 2 :(得分:1)

我想懒惰地初始化我的可变映射而不是做折叠(为了提高内存效率)。 collection.mutable.Map.getOrElseUpdate()方法符合我的目的。我的地图包含一个可变对象,用于求和值(同样,效率)。

        val accum = accums.getOrElseUpdate(key, new Accum)
        accum.add(elem.getHours, elem.getCount)

collection.mutable.Map.withDefaultValue()不保留后续请求密钥的默认值。