首先阅读 类方法 Hash#new 的文档。你会看到有三种形式:
new → new_hash
new(obj) → new_hash
new {|hash, key| block } → new_hash
创建一个空哈希
第一种形式用于创建空哈希:
h = Hash.new #=> {}
比较常用的写法:
h = {} #=> {}
使用Hash#new 创建散列的其他两种方法在散列尚未包含键时为键/值对建立默认值。
Hash.new 带参数
您可以通过以下两种方式之一创建具有默认值的哈希:
Hash.new(<default value>)
或
h = Hash.new # or h = {}
h.default = <default value>
假设哈希的默认值为 4;那就是:
h = Hash.new(4) #=> {}
h[:pop] = 7 #=> 7
h[:pop] += 1 #=> 8
h[:pop] #=> 8
h #=> {:pop=>8}
h[:chips] #=> 4
h #=> {:pop=>8}
h[:chips] += 1 #=> 5
h #=> {:pop=>8, :chips=>5}
h[:chips] #=> 5
请注意,默认值不会影响:pop 的值。那是因为它是通过分配创建的:
h[:pop] = 7
h[:chips] 本身仅返回默认值 (4);它确实不将键/值对 :chips=>4 添加到哈希中!我重复一遍:它确实不将键/值对添加到哈希中。这很重要!
h[:chips] += 1
是以下的简写:
h[:chips] = h[:chips] + 1
由于哈希 h 没有键 :chips 在计算等号右侧的 h[:chips] 时,它返回默认值 4,然后添加 1 使其成为 5 并且值被分配给h[:chips],它将键值对:chips=>5 添加到哈希中,如下行所示。最后一行仅报告现有键 :chips 的值。
那么为什么要建立一个默认值呢?我敢说,主要原因是能够用零初始化它,所以你可以使用:
h[k] += 1
而不是
k[k] = (h.key?(k)) ? h[k] + 1 : 1
或诀窍:
h[k] = (h[k] ||= 0) + 1
(仅当哈希值不为 nil 时才有效)。顺便说一句,key? 又名has_key?。
我们可以将默认值设为字符串吗?当然:
h = Hash.new('magpie')
h[:bluebird] #=> "magpie"
h #=> {}
h[:bluebird] = h[:bluebird] #=> "magpie"
h #=> {:bluebird=>"magpie"}
h[:redbird] = h[:redbird] #=> "magpie"
h #=> {:bluebird=>"magpie", :redbird=>"magpie"}
h[:bluebird] << "jay" #=> "magpiejay"
h #=> {:bluebird=>"magpiejay", :redbird=>"magpiejay"}
您可能对最后一行感到摸不着头脑:为什么h[:bluebird] << "jay" 导致h[:redbird] 发生变化?也许这可以解释这里发生了什么:
h[:robin] #=> "magpiejay"
h[:robin].object_id #=> 2156227520
h[:bluebird].object_id #=> 2156227520
h[:redbird].object_id #=> 2156227520
h[:robin] 仅返回默认值,我们看到它已从“magpie”更改为“magpiejay”。现在查看object_id 的默认值以及与键:bluebird 和:redbird 关联的值。正如你所看到的,所有的值都是同一个对象,所以如果我们改变一个,我们就会改变所有其他的,包括默认值。现在很明显为什么h[:bluebird] << "jay" 更改了默认值。
我们可以通过添加一只庄严的鹰来进一步澄清这一点:
h[:eagle] #=> "magpiejay"
h[:eagle] += "starling" #=> "magpiejaystarling"
h[:eagle].object_id #=> 2157098780
h #=> {:bluebird=>"magpiejay", :redbird=>"magpiejay", :eagle=>"magpiejaystarling"}
因为
h[:eagle] += "starling" #=> "magpiejaystarling"
相当于:
h[:eagle] = h[:eagle] + "starling"
我们在等号右侧创建了一个新对象并将其分配给h[:eagle]。这就是为什么键 :bluebird 和 :redbird 的值不受影响并且 h[:eagle] 具有不同的 object_id 的原因。
如果我们写:Hash.new([]) 或Hash.new({}),我们也会遇到类似的问题。如果有任何理由使用这些默认值,我不知道它们。默认值是空字符串、数组或散列当然非常有用,但为此您需要Hash.new 的第三种形式,它需要一个块。
Hash.new 带块
我们现在考虑Hash#new 的第三个也是最终版本,它需要一个块,如下所示:
Hash.new { |h,k| ??? }
您可能认为这是极其复杂和微妙的,肯定比其他两种形式的方法更难掌握。如果是这样,那你就错了。它实际上很简单,如果你认为它看起来像这样:
Hash.new { |h,k| h[k] = ??? }
换句话说,Ruby 对您说:“哈希 h 没有键 k。您希望它的值是多少?现在考虑以下问题:
h7 = Hash.new { |h,k| h[k]=7 }
hs = Hash.new { |h,k| h[k]='cat' }
ha = Hash.new { |h,k| h[k]=[] }
hh = Hash.new { |h,k| h[k]={} }
h7[:a] += 3 #=> 10
hs[:b] << 'nip' #=> "catnip"
ha[:c] << 4 << 6 #=> [4, 6]
ha[:d] << 7 #=> [7]
ha #=> {:c=>[4, 6], :d=>[7]}
hh[:k].merge({b: 4}) #=> {:b=>4}
hh #=> {}
hh[:k].merge!({b: 4} ) #=> {:b=>4}
hh #=> {:k=>{:b=>4}}
请注意,您不能编写 ha = Hash.new { |h,k| [] }(或等效的 ha = Hash.new { [] })并期望将 h[k] => [] 添加到哈希中。你可以在街区内做任何你喜欢的事情;您既不需要也不限于为键指定值。实际上,在块中,Ruby 实际上是在说,“一个不在散列中的键已被引用而没有值。我给你这个引用和一个对散列的引用。这将允许你添加那个键如果这是您想要做的,则为哈希添加一个值,但是您在此块中所做的完全是您的业务。”
哈希 h7、hs、ha 和 hh 的默认值分别是数字 7(尽管简单地输入 7 作为参数会更容易)、一个空字符串、一个空数组或空哈希。可能最后两个是Hash#new 与块的最常见用法,如:
array = [[:a, 1], [:b, 3], [:a, 4], [:b, 6]]
array.each_with_object(Hash.new {|h,k| h[k] = []}) { |(k,v),h| h[k] << v }
#=> {:a=>[1, 4], :b=>[3, 6]}
这就是Hash#new 最后一种形式的全部内容。