【问题标题】:When is a block or object that is passed to Hash.new created or run?何时创建或运行传递给 Hash.new 的块或对象?
【发布时间】:2016-11-24 22:56:14
【问题描述】:

我正在浏览 ruby​​ koans,但我无法理解何时运行此代码:

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

如果散列中没有值,新数组何时分配给散列中的给定键?第一次访问哈希值而不首先分配它时会发生这种情况吗?请帮助我了解何时为任何给定的哈希键创建了确切的默认值。

【问题讨论】:

  • 提示:将puts 添加到您的块中,您会立即看到,当它完全运行时。

标签: ruby hash


【解决方案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"]}

这很简单。

第二种解决方案

更像Ruby的写法:

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

当 Ruby 看到 (h[k] ||= []) &lt;&lt; v 时,她做的第一件事就是将其扩展为

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

如果h没有键kh[k] #=&gt; nil,那么表达式就变成了

(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_ids 看到这一点:

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]设置为等于[],然后该空数组会附加“fido”,因此

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

同样,在arr 的下两个元素被传递到我们拥有的块之后

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

arr 的下一个(第四个)元素被传递给块时,我们评估

h[:dog] << "diva"

但现在h 确实有一个密钥,所以默认值不适用,我们最终得到

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

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

注意,当使用带有块的 Hash::new 时,我们可以这样写:

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

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

更像Ruby

最后,更像 Ruby 的写法

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"]}

这样就省去了两行代码。

哪个最好?

就个人而言,我对第二种和第三种解决方案无动于衷。两者都在实践中使用。

【讨论】:

  • 非常彻底的回答
【解决方案2】:

当您向散列添加新键时,将调用该块。在那种特定情况下:

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

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

【讨论】:

    【解决方案3】:

    让生活更轻松

    对于那些你有一个哈希值都是数组并且你不想每次都检查哈希键是否已经存在并且空数组在添加新元素之前已经初始化的时候的语法糖.它允许这样做:

    hash[:new_key] &lt;&lt; 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
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-10-07
      • 1970-01-01
      • 2013-11-16
      • 2017-02-03
      • 1970-01-01
      • 1970-01-01
      • 2013-04-30
      相关资源
      最近更新 更多