为什么try 存储Hash.new({})的嵌套值的工作方式是这样的?引擎盖下到底发生了什么?它的用例是什么?

当创建具有默认值{}的哈希时,

a = Hash.new({})
=> {}
a.default
=> {}

如预期的那样,查询不存在的键将返回空散列

a[:foo]
=> {}

但是,当try 为尚不存在的键赋值时

a[:foo][:bar] = 'baz'
=> "baz"

散列仍然看起来是空的

a
=> {}

但是,获取父键将返回嵌套的散列.

a[:foo]
=> {:bar=>"baz"}

更令人困惑的是,这个新散列现在已成为父散列的缺省值

a.default
=> {:bar=>"baz"}

以便查询不存在键将返回该值

a[:biz]
=> {:bar=>"baz"}

这可以通过做以下事情来解决

a[:foo] = {} unless a.key? :foo
a[:foo][:bar] = 'baz'
a
=> {:foo=>{:bar=>"baz"}}

其他类似的问题也建议a = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) },它用于存储新的键,但也为获取操作创建空哈希,例如.

a[:baz] == 2
a
=> {:baz=>{}}

除了编写方法之外,是否有其他方法可以在存储值时获取散列以创建嵌套散列,而不是在取值时获取散列?

推荐答案

请记住,在Ruby1中,您存储的是默认的object reference,而不是默认的cloned.即使这不是您的意图,您也会要求缺省的键是same object.对于像Hash.new(0)这样的简单值,当分配一个新值时,缺省值不会改变,它是replaced,但是使用嵌套的散列可以显式地改变该值.

您想要的是更低限度地表达为:

a = Hash.new { |h,k| h[k] = { } }

如果你曾经对为什么事情最终会变成这样而感到困惑,那就用object_id来告诉你一个给定物体的"身份"吧.

请考虑:

a = Hash.new({ })
a[0].object_id == a[1].object_id
# => true

b = Hash.new { |h,k| h[k] = { } }
b[0].object_id == b[1].object_id
# => false

在这里你可以看到每个"槽"是独立的.

正如您所指出的,这些自动实例化模型的一个缺点是,它将创建您可能不一定需要的条目.为了避免这种情况,您需要更加小心,例如:

if a.key?(:baz) && a[:baz] == 2
  # ...
end

-

1至少对于非基元类型,JavaScript、Python和其他应用程序也表现出这种行为.

Ruby相关问答推荐

为什么非数字在Ruby中被转换为integer?

这个#divmod 方法输出这个结果是做什么的?

Ruby 避免类方法并保持其类型系统简单优雅的秘诀

如何使用 gsub 删除返回字符串中的/和/i?

类 SpecificationPolicy 的超类不匹配

Rb:仅通过在 Ruby 中的类上添加 each 方法,可枚举模块是如何工作的?

Ruby: initialize() vs 类体(class body)?

如何关闭 Rails 3.1 上的自动样式表/javascript 生成?

如何拯救 Ruby 中的 eval?

为什么 Matz Select 在 Ruby 中默认使字符串可变?

如何用 Ruby 覆盖 shell 中的打印行?

Ruby 模块 - 包括 do 结束块

如何在运行时判断 Ruby 中的 Gem 版本?

发现 Ruby 对象成员?

使用哈希参数进行 DRY Ruby 初始化

为什么 Ruby 无法验证 SSL 证书?

在 Ruby 中打开默认浏览器

何时使用在 Ruby 中启动子进程的每种方法

常见的 Ruby 习语

带有类名的动态类定义