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]运算符+=
在应用于这些散列时按预期工作
[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 #=> {}
每个[]
调用返回的数组只是默认值,我们一直在修改它,因此现在包含了新值。因为当你写的时候
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]=[]}
),而不是简单生成值的块版本({[]}
),那么您只需要很好的解释。它似乎是在ruby 2.1.1Hash上。new{[]}
与Hash相同。new([])
对于我来说,缺少预期的,值得一提的是,使用“可变方式”也会导致每次哈希查找都存储一个键值对(因为在块中发生了赋值),这可能并不总是需要的。@johncip不是每个查找,只是每个键的第一个查找。但我明白你的意思,我稍后会在答案中补充这一点;谢谢哎呀,太邋遢了。你是对的,当然,这是第一次查找未知密钥。我几乎觉得{[]}
在使用默认值初始化哈希时,对差异进行了非常清楚的解释这是错误的,hash.new{[]}
不起作用。有关详细信息,请参阅。这也是另一个答案中提出的解决方案。
h[2] <<= 'c' #=> ["a", "b", "c"]
h #=> {2=>["a", "b", "c"]}
h[2] = (h[2] << 'c')
h = Hash.new { [] }
h[0] << 'a' #=> ["a"]
h[1] <<= 'b' #=> ["b"]
h #=> {1=>["b"]}
h = Hash.new { |h, k| h[k] = [] }
h[0] << 'a' #=> ["a"]
h[1] << 'b' #=> ["b"]
h #=> {0=>["a"], 1=>["b"]}
h1 = Hash.new { |h, k| h[k] = [] }
h1[:x]
h1 #=> {:x=>[]}
h2 = Hash.new { [] }
h2[:x]
h2 #=> {}
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{[]}