Scala 中的嵌套默认映射

发布于 2024-12-27 08:35:44 字数 456 浏览 1 评论 0原文

我正在尝试在 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。谁能解释一下这是怎么回事?

I'm trying to construct nested maps in Scala, where both the outer and inner map use the "withDefaultValue" method. For example, the following :

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()

So the map, when addressed by the appropriate keys, gives me back what I put in. However, the map itself appears empty! Even m.size returns 0 in this example. Can anyone explain what's going on here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

温暖的光 2025-01-03 08:35:44

简短的回答

这绝对不是一个错误。

长答案

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 中,因此返回默认值 default。此时的default是一个空的Map。

m(1)(2) = 5

由于 m(1) 返回 default,因此此操作将 5 作为键 2 的值存储在 default 中。没有向 Map m 写入任何内容,因为 m(1) 解析为 default,它完全是一个单独的 Map。我们可以通过查看 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))

Short answer

It's definitely not a bug.

Long answer

The behavior of withDefaultValue is to store a default value (in your case, a mutable map) inside the Map to be returned in the case that they key does not exist. This is not the same as a value that is inserted into the Map when they key is not found.

Let's look closely at what's happening. It will be easier to understand if we pull the default map out as a separate variable so we can inspect is at will; let's call it default

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

So default is a mutable map (that has its own default value). Now we can create m and give default as the default value.

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

Now whenever m is accessed with a missing key, it will return default. Notice that this is the exact same behavior as you have because withDefaultValue is defined as:

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

Notice that it's d: B and not d: => B, so it will not create a new map each time the default is accessed; it will return the same exact object, what we've called default.

So let's see what happens:

m(1) // Map()

Since key 1 is not in m, the default, default is returned. default at this time is an empty Map.

m(1)(2) = 5

Since m(1) returns default, this operation stores 5 as the value for key 2 in default. Nothing is written to the Map m because m(1) resolves to default which is a separate Map entirely. We can check this by viewing default:

default // Map(2 -> 5)

But as we said, m is left unchanged

m // Map()

Now, how to achieve what you really wanted? Instead of using withDefaultValue, you want to make use of getOrElseUpdate:

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

Notice how we see op: => B? This means that the argument op will be re-evaluated each time it is needed. This allows us to put a new Map in there and have it be a separate new Map for each invalid key. Let's take a look:

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

No default values needed here.

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

Key 1 doesn't exist, so we insert a new HashMap, and return that new value. We can check that it was inserted as we expected. Notice that 1 maps to the newly added empty map and that they 3 was not added anywhere because of the behavior explained above.

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

Likewise, we can update the Map as expected:

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

and check that it was added:

m2 // Map(1 -> Map(2 -> 6))
荒路情人 2025-01-03 08:35:44

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

withDefaultValue is used to return a value when the key was not found. It does not populate the map. So you map stays empty. Somewhat like using getOrElse(a, b) where b is provided by withDefaultValue.

笨死的猪 2025-01-03 08:35:44

我刚刚遇到了完全相同的问题,很高兴找到 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。

I just had the exact same problem, and was happy to find dhg's answer. Since typing getOrElseUpdate all the time is not very concise, I came up with this little extension of the idea that I want to share:
You can declare a class that uses getOrElseUpdate as default behavior for the () operator:

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))
}

Now you can do what you want to do like this:

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

Which does now result in map containing 5 (or rather: containing a DefaultDict containing the value 5 for the key 2).

软的没边 2025-01-03 08:35:44

您所看到的是您创建了一个 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)

What you're seeing is the effect that you've created a single Map[Int, Int] this is the default value whenever the key isn't in the outer map.

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

To get the effect that you're looking for, you'll have to wrap the Map with an implementation that actually inserts the default value when a key isn't found in the Map.

Edit:

I'm not sure what your actual use case is, but you may have an easier time using a pair for the key to a single 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)
扮仙女 2025-01-03 08:35:44

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

I know it's a bit late but I've just seen the post while I was trying to solve the same problem.
Probably the API are different from the 2012 version but you may want to use withDefaultinstead that withDefaultValue.
The difference is that withDefault takes a function as parameter, that is executed every time a missed key is requested ;)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文