Scala 中的嵌套默认映射

Posted

技术标签:

【中文标题】Scala 中的嵌套默认映射【英文标题】:Nested Default Maps in Scala 【发布时间】:2012-01-18 03:30:29 【问题描述】:

我正在尝试在 Scala 中构建嵌套映射,其中外部和内部映射都使用“withDefaultValue”方法。例如,以下内容:

val m = HashMap.empty[Int, collection.mutable.Map[Int,Int]].withDefaultValue( HashMap.empty[Int,Int].withDefaultValue(3))
m(1)(2)
res: Int = 3
m(1)(2) = 5
m(1)(2) 
res: Int = 5
m(2)(3) = 6
m
res : scala.collection.mutable.Map[Int,scala.collection.mutable.Map[Int,Int]] = Map()

因此,当通过适当的键寻址时,地图会将我输入的内容返回给我。但是,地图本身似乎是空的!在这个例子中,甚至 m.size 返回 0。谁能解释这里发生了什么?

【问题讨论】:

【参考方案1】:

简答

这绝对不是错误。

长答案

withDefaultValue 的行为是将默认值(在您的情况下为可变映射)存储在要返回的 Map 中,以防它们的键不存在。这与未找到键时插入到 Map 中的值不同。

让我们仔细看看发生了什么。如果我们将默认映射作为一个单独的变量拉出来,这样我们就可以随意检查,这将更容易理解;我们就叫它default

import collection.mutable.HashMap
val default = HashMap.empty[Int,Int].withDefaultValue(3)

所以default 是一个可变映射(有自己的默认值)。现在我们可以创建m 并将default 作为默认值。

import collection.mutable.Map => MMap
val m = HashMap.empty[Int, MMap[Int,Int]].withDefaultValue(default)

现在,每当使用缺少的键访问 m 时,它将返回 default。请注意,这与您的行为完全相同,因为 withDefaultValue 定义为:

def withDefaultValue (d: B): Map[A, B]

注意是d: B而不是d: => B,所以每次访问默认不会创建新地图;它将返回完全相同的对象,我们称之为default

那么让我们看看会发生什么:

m(1) // Map()

由于密钥 1 不在 m 中,因此返回默认值 defaultdefault此时是一个空地图。

m(1)(2) = 5

由于m(1) 返回default,此操作将5 作为键2 的值存储在default 中。没有任何内容写入地图m,因为m(1) 解析为default,这完全是一个单独的地图。我们可以通过查看default来检查这一点:

default // Map(2 -> 5)

但正如我们所说,m 保持不变

m // Map()

现在,如何实现你真正想要的?而不是使用withDefaultValue,你想使用getOrElseUpdate

def getOrElseUpdate (key: A, op: ⇒ B): B

注意我们如何看到op: => B?这意味着每次需要时都会重新评估参数op。这允许我们在其中放置一个新 Map 并让它成为每个无效键的单独新 Map。一起来看看吧:

val m2 = HashMap.empty[Int, MMap[Int,Int]]

这里不需要默认值。

m2.getOrElseUpdate(1, HashMap.empty[Int,Int].withDefaultValue(3)) // Map()

键 1 不存在,因此我们插入一个新的 HashMap,并返回该新值。我们可以检查它是否按预期插入。请注意,1 映射到新添加的空映射,并且由于上述行为,它们 3 没有添加到任何地方。

m2 // Map(1 -> Map())

同样,我们可以按预期更新地图:

m2.getOrElseUpdate(1, HashMap.empty[Int,Int].withDefaultValue(1))(2) = 6

并检查它是否已添加:

m2 // Map(1 -> Map(2 -> 6))

【讨论】:

我现在明白了;故障有很大帮助。有趣的是,我最初对函数应该如何工作的偏见有多么强烈地阻止我看到实际发生的事情。【参考方案2】:

withDefaultValue 用于在找不到键时返回一个值。它不填充地图。所以你的地图保持空白。有点像使用getOrElse(a, b),其中bwithDefaultValue 提供。

【讨论】:

当然,但是当我调用 m(1)(2) = 5 时,应该将值 5 放在地图中。那么,为什么执行此“update()”后地图显示为空? @nomad 我对这里的有趣行为很感兴趣,但这一切实际上都很有意义。更新后 m is 仍然为空,因为您更新的内容是 not m;您更新了 m 的默认值之一。也就是说,您更新了 m(1) 引用的映射,直到 m(1) 获得实际值。由于您更改了 that 映射的大小,并且由于您从未更新过任何 m 的 键值对,因此 m 就它而言仍然是空的。 【参考方案3】:

我刚刚遇到了完全相同的问题,很高兴找到 dhg 的答案。由于一直输入 getOrElseUpdate 不是很简洁,所以我想出了这个我想分享的想法的小扩展: 您可以声明一个使用 getOrElseUpdate 作为 () 运算符的默认行为的类:

class DefaultDict[K, V](defaultFunction: (K) => V) extends HashMap[K, V] 
  override def default(key: K): V = return defaultFunction(key)
  override def apply(key: K): V = 
    getOrElseUpdate(key, default(key))

现在你可以像这样做你想做的事了:

var map = new DefaultDict[Int, DefaultDict[Int, Int]](
  key => new DefaultDict(key => 3))
map(1)(2) = 5

这会导致 map 包含 5(或者更确切地说:包含一个 DefaultDict,其中包含键 2 的值 5)。

【讨论】:

请不要。谢谢。【参考方案4】:

您看到的是您创建了一个Map[Int, Int] 的效果,这是当键不在外部映射中时的默认值。

scala> val m = HashMap.empty[Int, collection.mutable.Map[Int,Int]].withDefaultValue( HashMap.empty[Int,Int].withDefaultValue(3))
m: scala.collection.mutable.Map[Int,scala.collection.mutable.Map[Int,Int]] = Map()

scala> m(2)(2)
res1: Int = 3

scala> m(1)(2) = 5

scala> m(2)(2)
res2: Int = 5

要获得您正在寻找的效果,您必须将Map 包装为当在Map 中找不到键时实际插入默认值的实现。

编辑:

我不确定您的实际用例是什么,但您可能会更轻松地使用一对作为单个 Map 的密钥。

scala> val m = HashMap.empty[(Int, Int), Int].withDefaultValue(3)
m: scala.collection.mutable.Map[(Int, Int),Int] = Map()

scala> m((1, 2))
res0: Int = 3

scala> m((1, 2)) = 5

scala> m((1, 2))
res3: Int = 5

scala> m
res4: scala.collection.mutable.Map[(Int, Int),Int] = Map((1,2) -> 5)

【讨论】:

【参考方案5】:

我知道这有点晚了,但我刚刚在尝试解决同样的问题时看到了这个帖子。 API 可能与 2012 版本不同,但您可能希望使用 withDefault 而不是 withDefaultValue。 不同之处在于withDefault 将函数作为参数,每次请求丢失的键时都会执行该函数;)

【讨论】:

以上是关于Scala 中的嵌套默认映射的主要内容,如果未能解决你的问题,请参考以下文章

Scala访问修饰符

Scala Play:如何使用重复值和嵌套值呈现表单映射?

如何在scala中将嵌套映射[string,string]作为点分隔的键和值字符串

《快学Scala》第四章——映射和数组

包和引入

Scala API中的选项和命名默认参数是不是像油和水?