2016-11-24 47 views
2

我经历红宝石koans,我有一个小麻烦了解此代码将运行:传递给Hash.new的块或对象何时创建或运行?

hash = Hash.new {|hash, key| hash[key] = [] } 

如果在哈希任何值,什么时候新的数组会被分配给一个给定的密钥在哈希?第一次访问哈希值时没有首先分配哈希值是否会发生?请帮助我了解何时为任何给定的散列键创建完全默认值。

+0

提示:在您的块中添加一个“puts”,当* *完全运行时,您将立即看到它。 –

回答

1

为了让Ruby的新手们受益,我已经讨论了解决这个问题的其他方法,包括这个问题的实质内容。

任务

假设你将得到一个数组

arr = [[:dog, "fido"], [:car, "audi"], [:cat, "lucy"], [:dog, "diva"], [:cat, "bo"]] 

,并希望创建哈希

{ :dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"] } 

首先解决

h = {} 
arr.each do |k,v| 
    h[k] = [] unless h.key?(k) 
    h[k] << v 
end 
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]} 

这是非常简单的。

解决方法二

更多红宝石般就是写:

h = {} 
arr.each { |k,v| (h[k] ||= []) << v } 
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]} 

当红宝石看到(h[k] ||= []) << v她做的第一件事就是它扩大到

(h[k] = h[k] || []) << v 

如果h不没有钥匙kh[k] #=> nil,所以表达beco MES

(h[k] = nil || []) << v 

成为

(h[k] = []) << v 

所以

h[k] #=> [v] 

注意h[k]上平等的左侧使用方法Hash#[]=,而h[k]在右边采用Hash#[]

该解决方案要求没有散列值等于nil

第三溶液

的第三种方法是给散列的默认值。如果散列h没有一个关键kh[k]返回默认值。有两种类型的默认值。

如果空数组被作为参数传递到Hash::new传递默认值作为参数传递给Hash::new

,该值将成为默认值:

a = [] 
a.object_id 
    #=> 70339916855860 
g = Hash.new(a) 
    #=> {} 

g[k]返回[]h没有钥匙k。 (然而,哈希没有改变。)这个构造具有重要的用途,但在这里不合适。要知道为什么,假设我们写

x = g[:cat] << "bo" 
    #=> ["bo"] 
y = g[:dog] << "diva" 
    #=> ["bo", "diva"] 
x #=> ["bo", "diva"] 

这是因为:cat:dog值都设置为同一个对象,一个空数组。我们可以通过检查object_id看看这样:

x.object_id 
    #=> 70339916855860 
y.object_id 
    #=> 70339916855860 

Hash::new给予它返回默认值

默认值的第二种形式是执行块运算的块。如果我们用一个块定义的散列:

h = Hash.new { |h,k| h[key] = [] } 

然后如果h不具有关键kh[k]将被设置为等于由块返回,在此情况下为空数组的值。请注意,块变量h是新创建的空散列。因此,这使我们能够写出

h = Hash.new { |h,k| h[k] = [] } 
arr.each { |k,v| h[k] << v } 
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]} 

作为传递到块中的第一个元素是arr.first,块变量通过评估

k, v = arr.first 
    #=> [:dog, "fido"] 
k #=> :dog 
v #=> "fido" 

块计算分配的值是

h[k] << v 
    #=> h[:dog] << "fido" 

但由于h尚未(有)钥匙:dog,该块被触发,设置为h[k] equ人到[],然后将该空数组附加有“汪汪”,使

h #=> { :dog=>["fido"] } 

类似地,后的arr接下来的两个元件被传递到块中,我们有

h #=> { :dog=>["fido"], :car=>["audi"], :cat=>["lucy"] } 

当下一个的arr(第四)元素传递到块,我们评估

h[:dog] << "diva" 

但现在h确实有一个关键,所以德发ult不适用,我们以

h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy"]} 

arr的最后一个元素进行类似的处理。

需要注意的是,使用Hash ::新与块的时候,我们可以写这样的事:

h = Hash.new { launch_missiles("any time now") } 

在这种情况下h[k]将被设定为等于launch_missiles返回值。换句话说,任何事情都可以在该块中完成。

更红宝石般

最后,写

h = Hash.new { |h,k| h[k] = [] } 
arr.each { |k,v| h[k] << v } 
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]} 

的多个红宝石般的方法是使用Enumerable#each_with_object

arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |k,v| h[k] << v } 
    #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]} 

免去了两行代码。

哪个最好?

就我个人而言,我对第二个和第三个解决方案漠不关心。两者都在实践中使用。

1

将新密钥添加到散列时会调用该块。在这种特定的情况下:

hash["d"] #calls the block and store [] as a value of "d" key 
hash["d"] #should print [] 

欲了解更多信息,请访问:https://docs.ruby-lang.org/en/2.0.0/Hash.html

如果指定了一个块,它将与哈希对象和重点调用,应返回默认值。如果需要,块的责任是将值存储在哈希中。

0

使生活更轻松

这是你有一个哈希其值全部阵列和你不希望每次检查,看看是否哈希键已经存在和那个时代的语法糖在添加新元素之前,空数组已经被初始化。它允许这样的:

hash[:new_key] << new_element

,而不是这样的:

hash[:new_key] = [] unless hash[:new_key] 
hash[:new_key] << new_element 

解决了一个老问题

这也是指定的哈希值的默认值的更简单的方法,它看起来替代像这样:

hash = Hash.new([])

这种方法的问题是相同的数组对象被用作所有键的默认值。所以

hash = Hash.new([]) 
hash[:a] << 1 
hash[:b] << 2 

将返回[1, 2]对于任何hash[:a]hash[:b],或为此事甚至hash[:foo]。这通常不是期望/预期的行为。

相关问题