26

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

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的类将允许:Counterincrement

// 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 的加速。显然,修改闭包的构造通常可以移到循环之外,所以恕我直言,与运行不必要的操作两次。

4

3 回答 3

29

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

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)
于 2013-03-19T17:05:34.993 回答
4

开始Scala 2.13Map#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)
于 2019-03-28T19:49:17.697 回答
1

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

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

collection.mutable.Map.withDefaultValue()不会为后续请求的键保留默认值。

于 2017-10-17T02:03:09.117 回答