Ruby:将Hash存储在具有默认值Hash的Hash中,用于不存在的键[重复]

u5i3ibmn  于 9个月前  发布在  Ruby
关注(0)|答案(2)|浏览(160)

此问题在此处已有答案

Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])(4个答案)
22天前关闭
为什么尝试存储Hash.new({})的嵌套值会这样工作?实际上在幕后发生了什么?它的用例是什么?
当创建具有默认值{}的哈希时,

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

字符串
查询一个不存在的键将返回一个空的哈希值

a[:foo]
=> {}


然而,当试图将值分配给尚不存在的键时,

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=>{}}


除了编写方法之外,是否有其他方法可以在存储值时获得哈希以创建嵌套哈希,而在获取值时则不需要?

icomxhvb

icomxhvb1#

记住,在Ruby 1中,你存储的是一个默认的 object reference,而不是一个 cloned 的默认值。即使这不是你的意图,你也要求任何缺少的键的默认值都是 same object。对于像Hash.new(0)这样的简单值,当一个新值被赋值时,默认值不会改变,它是 replaced,但是对于嵌套的hash,你显式地改变了值。
你想要的是最小化地表达为:

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和其他语言也表现出这种行为,至少对于非原语类型来说是这样。

ac1kyiln

ac1kyiln2#

如果你不想为fetch操作创建空哈希值,你可以使用#freeze来保护默认值不被意外修改。

a = Hash.new({}.freeze)
a[:foo]  #=> {}
a #=> {} still empty, no :foo key created
a[:foo][:bar] = 'baz' #FrozenError because you're trying to modify the default value
a[:foo] = a[:foo].merge(:bar => 'baz')
a #=> {:foo=>{:bar=>"baz"}}

字符串

相关问题