默认哈希值&阵列差异

时间:2014-02-01 15:29:20

标签: ruby arrays hashmap

这是一个代码

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)
=> []

我甚至不知道这里的问题,如果它不足以让你感到惊讶,但我想读一些关于这种奇怪行为的解释。让我想想我是否应该在生产环境中使用它。

3 个答案:

答案 0 :(得分:1)

new(obj) → new_hash

  

如果随后通过与散列条目不对应的键访问此散列,则返回的值取决于用于创建散列的新样式。在第一种形式中,访问返回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使用这些嵌套对象的浅拷贝遇到的同类问题。但这是另一个话题!