访问/初始化和更新可变映射中的值

blu*_*e10 23 dictionary scala

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

val counts = collection.mutable.Map[SomeKeyType, Int]()
Run Code Online (Sandbox Code Playgroud)

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

counts(key) = counts.getOrElse(key, 0) + 1
// or equivalently
counts.update(key, counts.getOrElse(key, 0) + 1)
Run Code Online (Sandbox Code Playgroud)

这在某种程度上感觉有点笨拙,因为我必须指定两次密钥.在性能方面,我也希望key在地图中找到两次,我想避免.有趣的是,如果Int提供某种机制来修改自身,就不会发生这种访问和更新问题.从提供函数IntCounter类更改increment为例如允许:

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

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

// fictitious use
counts.updateOrElse(key, 0, _ + 1)
// or alternatively
counts.getOrElseUpdate(key, 0).modify(_ + 1)
Run Code Online (Sandbox Code Playgroud)

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

更新:

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

col*_*red 25

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

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)
Run Code Online (Sandbox Code Playgroud)

正如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)
Run Code Online (Sandbox Code Playgroud)

  • 注意`m("a")+ = 1`是糖(^),对于`m("a")= m("a")+ 1`是糖的m.update("a",m( "a")+ 1)`.(^或者更确切地说是糖,因为`+ =`方法可能直接在`mutable.Map`上实现) (3认同)
  • 需要明确的是, (_+1) 不是闭包,因为它不会关闭任何自由变量。在调用站点没有为它创建对象;它只是对在初始化期间一劳永逸地创建的函数的引用。 (2认同)

Xav*_*hot 6

开始Scala 2.13,Map#updateWith正是为了这个目的:

map.updateWith("a")({
  case Some(count) => Some(count + 1)
  case None        => Some(1)
})
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

如果密钥存在:

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)
Run Code Online (Sandbox Code Playgroud)