我经历红宝石koans,我有一个小麻烦了解此代码将运行:传递给Hash.new的块或对象何时创建或运行?
hash = Hash.new {|hash, key| hash[key] = [] }
如果在哈希任何值,什么时候新的数组会被分配给一个给定的密钥在哈希?第一次访问哈希值时没有首先分配哈希值是否会发生?请帮助我了解何时为任何给定的散列键创建完全默认值。
我经历红宝石koans,我有一个小麻烦了解此代码将运行:传递给Hash.new的块或对象何时创建或运行?
hash = Hash.new {|hash, key| hash[key] = [] }
如果在哈希任何值,什么时候新的数组会被分配给一个给定的密钥在哈希?第一次访问哈希值时没有首先分配哈希值是否会发生?请帮助我了解何时为任何给定的散列键创建完全默认值。
为了让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
不没有钥匙k
,h[k] #=> nil
,所以表达beco MES
(h[k] = nil || []) << v
成为
(h[k] = []) << v
所以
h[k] #=> [v]
注意h[k]
上平等的左侧使用方法Hash#[]=,而h[k]
在右边采用Hash#[]。
该解决方案要求没有散列值等于nil
。
第三溶液
的第三种方法是给散列的默认值。如果散列h
没有一个关键k
,h[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
不具有关键k
,h[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"]}
免去了两行代码。
哪个最好?
就我个人而言,我对第二个和第三个解决方案漠不关心。两者都在实践中使用。
将新密钥添加到散列时会调用该块。在这种特定的情况下:
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
如果指定了一个块,它将与哈希对象和重点调用,应返回默认值。如果需要,块的责任是将值存储在哈希中。
这是你有一个哈希其值全部阵列和你不希望每次检查,看看是否哈希键已经存在和那个时代的语法糖在添加新元素之前,空数组已经被初始化。它允许这样的:
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]
。这通常不是期望/预期的行为。
提示:在您的块中添加一个“puts”,当* *完全运行时,您将立即看到它。 –