这是一个代码
irb(main):085:0> h = Hash.new([])
=> {}
irb(main):086:0> h['a'] = 'sdfds'
=> "sdfds"
irb(main):087:0> h
=> {"a"=>"sdfds"}
irb(main):088:0> h['b'].push(h['a'] )
=> ["sdfds"]
irb(main):089:0> h
=> {"a"=>"sdfds"}
irb(main):090:0> h['b']
=> ["sdfds"]
irb(main):091:0> h['c']
=> ["sdfds"]
irb(main):092:0> h
=> {"a"=>"sdfds"}
我试图做的是让h [b]像通常的阵列一样。然而,发生的是h [b]和h [c]现在有一个新的默认值。我希望h [b]有这个新值,但似乎push实际上没有推到一个不存在的值。
然后打印h实际上只显示h [a]
这是为什么?虽然红宝石被广泛使用,但这确实不值得一试,但这些是可能不是首选的特殊行为。它因人而异。的更新 的 然而,正确的行为并没有表现出任何可疑之处:
irb(main):104:0> h = Hash.new([])
=> {}
irb(main):105:0> h['a'] = [1,2,3,'dsfds']
=> [1, 2, 3, "dsfds"]
irb(main):106:0> h['b'] += h['a']
=> [1, 2, 3, "dsfds"]
irb(main):107:0> h
=> {"a"=>[1, 2, 3, "dsfds"], "b"=>[1, 2, 3, "dsfds"]}
另一种意想不到的混乱行为
irb(main):093:0> h = [1,2,3]
=> [1, 2, 3]
irb(main):094:0> h.shift(0)
=> []
irb(main):095:0> h
=> [1, 2, 3]
irb(main):096:0> h.unshift(0)
=> [0, 1, 2, 3]
irb(main):097:0> h.shift(10)
=> [0, 1, 2, 3]
irb(main):098:0> h.shift(90)
=> []
irb(main):099:0> h
=> []
irb(main):100:0> h = [1,2,3]
=> [1, 2, 3]
irb(main):101:0> h.shift(100)
=> [1, 2, 3]
irb(main):102:0> h
=> []
irb(main):103:0> h.shift(90)
=> []
我甚至不知道这里的问题,如果它不足以让你感到惊讶,但我想读一些关于这种奇怪行为的解释。让我想想我是否应该在生产环境中使用它。
答案 0 :(得分:1)
如果随后通过与散列条目不对应的键访问此散列,则返回的值取决于用于创建散列的新样式。在第一种形式中,访问返回
nil
。如果指定了obj
,则此单个对象将用于所有默认值。
在这里,您为h
创建了一个哈希对象,其默认值为所有非现有密钥的相同数组。
h = Hash.new([])
现在,您实际上调用了Hash#[]=
。因此,键:a
添加到哈希中,其值与10
相关联。
h[:a] = 10
只有在调用Hash#[]=
时,密钥才会添加到哈希值,但您的h[:b]
与Hash#[]
相同。因此,:b
不会作为键添加,而是使用行h = Hash.new([])
返回您设置的默认数组。因此,h[:b]
实际上为您提供了默认数组,并且您正在调用Array#push
方法。
h[:b].push(11)
因此,根据上述说明,您无法在哈希:b
中看到键h
。
h # => {:a=>10}
h[:b].push(11)
导致默认数组现在只有[11]
元素。因此,现在h[:c]
会为您提供默认数组,即[11]
。
h[:c] # => [11]
Array#shift(n)
实际上是指array.slice!(0, n)
。现在Array#sice!
告诉我们 - 删除由索引(可选择长度元素)或范围给出的元素。返回已删除的对象(或多个对象),如果索引已经删除则为零范围。
为什么h.shift(0) # => []
?
根据文档shift(n) → new_ary
,如果你传递参数,那么你会得到一个数组的结果。现在你提供了0
,这意味着,你不要我想要删除h
中的任何元素。所以根据定义,你得到一个空数组。
请看下面的代码:
(arup~>~)$ pry --simple-prompt
>> h = [1,2,3]
=> [1, 2, 3]
>> h.shift(10)
=> [1, 2, 3]
>> h
=> []
现在再次阅读文档If a number n is given, returns an array of the first n elements (or less) just like array.slice!(0, n) does.。 h.shift(10)
为您提供[1, 2, 3]
,这是3个元素数组,同时也会从h
中删除元素。因此,上次h
为您提供[]
。
h = Hash.new([])
h['a'] = [1,2,3,'dsfds']
h['b'] += h['a']
h # => {"a"=>[1, 2, 3, "dsfds"], "b"=>[1, 2, 3, "dsfds"]}
此处,h['b'] += h['a']
实际上是h['b'] = h['b'] + h['a']
。现在,您的哈希没有密钥'b'
,因此h['b']
会为您提供默认数组[]
。现在,行h['b'] = h['b'] + h['a']
变为h[b] = [] + [1,2,3,'dsfds']
。现在[] + [1,2,3,'dsfds']
会给你[1,2,3,'dsfds']
,这只是一个Array#+
方法调用。最后h['b'] += h['a']
是一个简单的Hash#[]=
方法调用,因此键"b"
已添加到哈希h
,值为[1,2,3,'dsfds']
。现在h['a']
和h['b']
显示相同的数组,但否,这些数组不是相同的数组对象,但是,它们包含相同的元素。 记住 Array#+
创建了新数组对象。
答案 1 :(得分:1)
根据文件Hash#new
返回一个新的空哈希。如果随后通过与散列条目不对应的键访问此散列,则返回的值取决于用于创建散列的新样式。在第一种形式中,访问返回nil。如果指定了obj,则此单个对象将用于所有默认值。
这意味着在为构造函数指定对象时,它将返回作为默认值,该值与 set 不同。
default_value = []
=> []
h = Hash.new(default_value)
=> {}
h['b'].push 'asdf'
=> ['asdf']
default_value
=> ['asdf']
当您使用+=
运算符时,您实际上正在分配一个新值 - h['b'] += h['a']
就像在说h['b'] = h['b'] + h['a']
,这就像说h['b'] = default_value + h['a']
关于Array.shift的行为:
例如,如果给出数字n,返回前n个元素的数组(或更少),就像array.slice!(0,n)那样。 ary只包含其余元素,不包括转移到new_ary的内容。另见#unshift的相反效果。
h.shift(100)
将返回数组中的所有元素([1,2,3]
),并使数组不带元素([]
)
答案 2 :(得分:1)
在创建新哈希时,有两种方法可用于定义在无法检索匹配元素时要返回的默认值 - 作为传递给新方法或通过块(也传递给新)的值,每次发生“哈希未命中”时都会评估。
将默认值传递给new对于简单值,整数非常有用,所以这很有效:
h = Hash.new(0)
你会得到你所期望的。调用h [:no_such_key_here]返回0而不是nil。
当您尝试在复杂对象(如哈希或数组)上使用该语法时,问题就出现了。由于默认值是通过引用存储的,因此可能会以意外的方式无意中更改默认值。试试这个:
h = Hash.new([])
h[:a].__id__ == h[:b].__id__
这个,你可能会惊讶地发现,会回归真实。不仅为缺少的键返回了一个数组,而且还返回了 THE SAME 数组。因此将其更改为h [:a]会将其更改为h [:b]。
可能不是你想要的,这使我们第二种方式来定义一个默认值 - 使用一个块:
h = Hash.new {|hash, key| hash[key] = [] }
h[:a].__id__ == h[:b].__id__
通过使用每次引用缺失键时计算的块,每次都会创建一个新的唯一哈希值。
有点棘手,但这有点道理。这是使用dup使用这些嵌套对象的浅拷贝遇到的同类问题。但这是另一个话题!