在 Ruby 的哈希数据类型中,“默认值是同一个对象”。

3

我刚接触Ruby并在运行Ruby Koans。在Ruby Koans的about_hashes.rb文件中,有一个关于给哈希表赋默认值的例子。

hash = Hash.new([])
hash[:one] << "uno"
hash[:two] << "dos"

puts hash[:one]  # this is  ["uno", "dos"]

这里的hash[:one]hash[:two]以及任何键值,如hash[:three](不存在的键值),都具有值["uno"和"dos"]。我不明白"<<"在这里是如何使用的。此外,当我尝试提取哈希表的键和值或者打印键/值时,它们为空。
puts (hash.values.size)  # size is 0 here
puts (hash.keys.size)    # size is 0 
puts hash.values         # nothing gets printed
puts hash.keys           #nothing gets printed.

所以这里究竟发生了什么?如果哈希表中没有存储它们作为键或值,那么值被存储在哪里呢。

在下一个示例中,当哈希表被定义为

hash = Hash.new {|hash, key| hash[key] = [] }
hash[:one] << "uno"
hash[:two] << "dos"
puts hash[:one]  #this is "uno"
puts hash[:two] #this is "dos"
puts hash[:three] # this is undefined.

我猜在第二个例子中,哈希正在用一个空数组初始化所有的键。因此,当使用 "<<" 运算符时,“uno”被附加到空数组中?我对这两个例子感到困惑。我不知道它们两个都在发生什么。我在谷歌上也没有找到太多关于这个例子的信息。如果有人能帮我解释这两个例子,那就太好了。提前感谢。
2个回答

5

hash = Hash.new([])

这个语句创建了一个空哈希表,其默认值是一个空数组。如果 hash 没有键 khash[k] 返回默认值 []。这很重要:仅仅返回默认值不会修改哈希表。

当你写下以下代码:

hash[:one] << "uno" #=> ["uno"]

hash有一个键:one之前,hash[:one]被替换为默认值,因此我们有:

[] << "uno" #=> ["uno"]

这就解释了为什么hash不会改变:

hash #=> {}

现在开始写:

hash[:two] << "dos" #=> ["uno", "dos"]
hash                #=> {}

为了理解为什么会得到这个结果,让我们将上述内容改写如下:
hash = Hash.new([])       #=> {} 
hash.default              #=> [] 
hash.default.object_id    #=> 70209931469620 
a = (hash[:one] << "uno") #=> ["uno"] 
a.object_id               #=> 70209931469620 
hash.default              #=> ["uno"]  
b = (hash[:two] << "dos") #=> ["uno", "dos"] 
b.object_id               #=> 70209931469620 
hash.default              #=> ["uno", "dos"]  

所以您可以看到,其 object_id70209931469620 的默认数组是唯一的数组,被添加了 "uno" 和 "dos"。

如果我们写成:

hash[:one] = hash[:one] << "uno"
  #=> hash[:one] = [] << "uno" => ["uno"]
hash #=> { :one=>"uno" }

我们得到了我们所期望的,但不要太快:
hash[:two] = hash[:two] << "dos"
  #=> ["uno", "dos"] 
hash
  #=> {:one=>["uno", "dos"], :two=>["uno", "dos"]} 

这还不是我们想要的,因为两个键都具有相同数组值。

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

hash没有一个键key时,该语句会导致块被执行。这样会通过添加键值对1来改变哈希表。

所以现在:

hash[:one] << "uno"

导致阻塞的原因:
{ |h,k| h[k] = [] }

制作任务:

hash[:one] = []

之后:

hash[:one] << "uno"

"uno"添加到一个空数组中,该数组是键:one的值,我们可以验证:

hash #=> { :one=>"uno" }

这与编写以下代码具有相同的效果:

hash[:one] = (hash[:one] || []) << "uno"

当没有默认值时,(hash[:one] ||= [])的扩展版本是(hash[:one] ||= []) << "uno"

同样地,

hash[:two] << "dos" #=> ["dos"] 
hash #=> {:one=>["uno"], :two=>["dos"]}

这通常是我们想要的结果。

1 然而,块不需要改变哈希值。该块可以包含任何代码,例如:{ puts "Have a nice day" }


谢谢。这解答了我的疑惑。 - Sharvari Nagesh

4

hash = Hash.new(INITIAL_VALUE)是创建哈希表的语法,它具有默认值。默认值是整个哈希表本身的“属性”,当访问不存在的键时,将返回该值。

因此,在您的第一个示例中:

hash = Hash.new([]) # return a reference to an empty array for unknown keys

等同于:

initial = []
hash = Hash.new(initial)

因此,当您调用以下代码时:
hash[:one] << "uno"

实际上你正在调用 hash[:one],它返回了 initial,然后对其调用了 #<< 方法。换句话说,这些后续的调用与下面的代码等同:

initial << "uno"
initial << "dos"

我想现在已经很清楚为什么它们都共享相同的值了。而哈希本身仍然为空(在上面的任何调用中,都使用了initial)。看:

hash.merge! { one: "uno", three: "tres" }
hash[:one] # defined, since merge above
#⇒ "uno"
hash[:two] # undefined, initial will be returned
#⇒ []
hash[:two] << 'dos'
hash[:two] # defined, but defined as a reference to initial
#⇒ ["dos"]
hash[:two] = 'zwei' # redefined
#⇒ "zwei"
hash[:four] # undefined, initial
#⇒ ["dos"]

希望能对您有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接