Ruby 使用哈希默认值时出现奇怪的意外行为(消失/更改值),例如Hash.new([])

Ruby 使用哈希默认值时出现奇怪的意外行为(消失/更改值),例如Hash.new([]),ruby,hash,Ruby,Hash,考虑以下代码: h = Hash.new(0) # New hash pairs will by default have 0 as values h[1] += 1 #=> {1=>1} h[2] += 2 #=> {2=>2} 没关系,但是: h = Hash.new([]) # Empty array as default value h[1] <<= 1 #=> {1=>[1]} ← Ok h[

考虑以下代码:

h = Hash.new(0)  # New hash pairs will by default have 0 as values
h[1] += 1  #=> {1=>1}
h[2] += 2  #=> {2=>2}
没关系,但是:

h = Hash.new([])  # Empty array as default value
h[1] <<= 1  #=> {1=>[1]}                  ← Ok
h[2] <<= 2  #=> {1=>[1,2], 2=>[1,2]}      ← Why did `1` change?
h[3] << 3   #=> {1=>[1,2,3], 2=>[1,2,3]}  ← Where is `3`?

但事实远非如此。发生了什么以及如何获得预期的行为?

您指定哈希的默认值是对特定(最初为空)数组的引用

我想你想要:

h = Hash.new { |hash, key| hash[key] = []; }
h[1]<<=1 
h[2]<<=2 
h=Hash.new{| Hash,key | Hash[key]=[];}

h[1]您指定散列的默认值是对特定(最初为空)数组的引用

我想你想要:

h = Hash.new { |hash, key| hash[key] = []; }
h[1]<<=1 
h[2]<<=2 
h=Hash.new{| Hash,key | Hash[key]=[];}

h[1]运算符
+=
在应用于这些散列时按预期工作

[1] pry(main)> foo = Hash.new( [] )
=> {}
[2] pry(main)> foo[1]+=[1]
=> [1]
[3] pry(main)> foo[2]+=[2]
=> [2]
[4] pry(main)> foo
=> {1=>[1], 2=>[2]}
[5] pry(main)> bar = Hash.new { [] }
=> {}
[6] pry(main)> bar[1]+=[1]
=> [1]
[7] pry(main)> bar[2]+=[2]
=> [2]
[8] pry(main)> bar
=> {1=>[1], 2=>[2]}
这可能是因为
foo[bar]+=baz
foo[bar]=foo[bar]+baz
的语法糖,当计算
=
右侧的
foo[bar]
时,它返回默认值对象,
+
操作符不会更改它。左手是
[]=
方法的语法糖,它不会更改默认值


请注意,这不适用于
foo[bar]运算符
+=
,当应用于那些散列时,它们会按预期工作

[1] pry(main)> foo = Hash.new( [] )
=> {}
[2] pry(main)> foo[1]+=[1]
=> [1]
[3] pry(main)> foo[2]+=[2]
=> [2]
[4] pry(main)> foo
=> {1=>[1], 2=>[2]}
[5] pry(main)> bar = Hash.new { [] }
=> {}
[6] pry(main)> bar[1]+=[1]
=> [1]
[7] pry(main)> bar[2]+=[2]
=> [2]
[8] pry(main)> bar
=> {1=>[1], 2=>[2]}
这可能是因为
foo[bar]+=baz
foo[bar]=foo[bar]+baz
的语法糖,当计算
=
右侧的
foo[bar]
时,它返回默认值对象,
+
操作符不会更改它。左手是
[]=
方法的语法糖,它不会更改默认值


请注意,这不适用于
foo[bar]首先,请注意,此行为适用于随后发生变化的任何默认值(例如哈希和字符串),而不仅仅是数组

TL;DR:如果您想要最惯用的解决方案,而不在乎为什么,请使用
Hash.new{| h,k | h[k]=[]}


什么不起作用 为什么
Hash.new([])
不起作用 让我们更深入地了解为什么
Hash.new([])
不起作用:

h = Hash.new([])
h[0] << 'a'  #=> ["a"]
h[1] << 'b'  #=> ["a", "b"]
h[1]         #=> ["a", "b"]

h[0].object_id == h[1].object_id  #=> true
h  #=> {}

每个
[]
调用返回的数组只是默认值,我们一直在修改它,因此现在包含了新值。首先,请注意,此行为适用于随后发生变化的任何默认值(例如哈希和字符串),而不仅仅是数组

TL;DR:如果您想要最惯用的解决方案,而不在乎为什么,请使用
Hash.new{| h,k | h[k]=[]}


什么不起作用 为什么
Hash.new([])
不起作用 让我们更深入地了解为什么
Hash.new([])
不起作用:

h = Hash.new([])
h[0] << 'a'  #=> ["a"]
h[1] << 'b'  #=> ["a", "b"]
h[1]         #=> ["a", "b"]

h[0].object_id == h[1].object_id  #=> true
h  #=> {}
每个
[]
调用返回的数组只是默认值,我们一直在修改它,因此现在包含了新值。因为当你写的时候

h = Hash.new([])
将数组的默认引用传递给散列中的所有元素。因为散列中的所有元素都引用相同的数组

如果希望散列中的每个元素都引用单独的数组,则应使用

h = Hash.new{[]} 
有关它在ruby中如何工作的更多详细信息,请浏览以下内容: 当你写作时

h = Hash.new([])
将数组的默认引用传递给散列中的所有元素。因为散列中的所有元素都引用相同的数组

如果希望散列中的每个元素都引用单独的数组,则应使用

h = Hash.new{[]} 
有关它在ruby中如何工作的更多详细信息,请浏览以下内容:

如何为每个新哈希使用单独的数组实例?该块版本在每次调用时为您提供新的
array
实例。也就是说:
h=Hash.new{| Hash,key | Hash[key]=[];put Hash[key].object|id};h[1]#=>16348490;h[2]#=>16346570
。另外:如果您使用设置值的块版本(
{hash,key{hash[key]=[]}
),而不是简单生成值的块版本(
{[]}
),那么您只需要
如何为每个新哈希使用单独的数组实例?该块版本在每次调用时为您提供新的
数组
实例。也就是说:
h=Hash.new{| Hash,key | Hash[key]=[];put Hash[key].object|id};h[1]#=>16348490;h[2]#=>16346570
。另外:如果您使用设置值的块版本(
{hash,key{hash[key]=[]}
),而不是简单生成值的块版本(
{[]}
),那么您只需要
很好的解释。在ruby 2.1.1
Hash.new{[]}
Hash.new([])
对我来说是一样的,只是缺少预期的
好的解释。它似乎是在ruby 2.1.1
Hash上。new{[]}
Hash相同。new([])
对于我来说,缺少预期的
,值得一提的是,使用“可变方式”也会导致每次哈希查找都存储一个键值对(因为在块中发生了赋值),这可能并不总是需要的。@johncip不是每个查找,只是每个键的第一个查找。但我明白你的意思,我稍后会在答案中补充这一点;谢谢哎呀,太邋遢了。你是对的,当然,这是第一次查找未知密钥。我几乎感觉像是
{[]}
在使用默认值初始化哈希时对差异进行了非常清楚的解释。值得一提的是,使用“可变方式”也会导致每个哈希查找存储一个键值对(因为在块中有一个赋值),这可能并不总是需要的。@johncip不是每个查找,只是每个键的第一个查找。但我明白你的意思,我稍后会在答案中补充这一点;谢谢哎呀,太邋遢了。你是对的,当然,这是第一次查找未知密钥。我几乎觉得
{[]}
在使用默认值初始化哈希时,对差异进行了非常清楚的解释这是错误的,
hash.new{[]}
不起作用。有关详细信息,请参阅。这也是另一个答案中提出的解决方案。这是错误的
h = Hash.new([].freeze)
h[0] += ['a']  #=> ["a"]
h[1] += ['b']  #=> ["b"]
h[2]           #=> []
h              #=> {0=>["a"], 1=>["b"]}
h = Hash.new([])
h = Hash.new{[]}