使用具有默认值的哈希表进行工作

3

正在学习使用Ruby编码。我正在学习哈希(hashes),但是不理解这段代码:count = Hash.new(0)。它说0是一个默认值,但是当我在irb上运行它时,它给出了一个空的哈希{}。如果0是一个默认值,为什么我看不到类似于count ={0=>0}的东西呢?还是零是累加器,但不参与键或值?谢谢

4个回答

7

如果您尝试访问哈希表中不存在的键,0将作为后备返回值。

例如:

count = Hash.new -> count['key'] => nil

相比之下,

count = Hash.new(0) -> count['key'] => 0


1
也许你还想回答这一部分:"或者零是一个累加器,但不会进入键或值?" 因为这是一个很好的问题,并无意中识别了一个常见的 Ruby 坑。 - engineersmnky
@engineersmnky,你能否重新表述一下OP的问题?我不明白他所问的是否是“累加器”,或者常见的Ruby陷阱是什么。 - melcher
2
@melcher Hash默认值有两个常见的陷阱:(1) 例如Hash.new([]),会意外共享引用,因此h = Hash.new([]); h[:v].push(11)会产生副作用;(2) Hash.new(some_value)没有自动创建新键,所以h = Hash.new(0); puts h[:v]会输出0但不会将:v添加为键,但如果你使用h = Hash.new { |h, k| h[k] = 0 }开始,则会添加。不确定指的是哪一个。 - mu is too short
1
@muistooshort 是的,我正在撰写一个答案来解决那些陷阱,因为我认为对于这样的问题值得重复,只是我不太理解“累加器”的含义...那是指类似于inject参数的东西吗? - melcher
1
这个回答并不是特别有用,因为它没有解答问题:“何时应该使用带有参数但不带块的Hash::new形式创建哈希表?” - Cary Swoveland

6

进一步阐述 @jeremy-ramos 的回答和 @mu-is-too-short 的评论。

使用默认哈希值有两个常见的“坑点”。

1. 无意中共享引用。

Ruby 使用你传递作为缺失键的默认值时 完全相同的 内存对象。

对于不可变对象(如0),没有问题。但是,你可能想编写以下代码:

hash = Hash.new([])
hash[key] << value

或者

hash = Hash.new({})
hash[key][second_key] = value

这将不会按预期工作。取而代之的是,hash[unknown_key] 会为每个键返回完全相同的数组/哈希对象,而不是返回一个新的、空的数组或哈希。因此执行以下代码:
hash = Hash.new([])
hash[key1] << value1
hash[key2] << value2

这将导致哈希表中的key1key2都指向同一个包含[value1, value2]的数组对象。

请参见相关问题

解决方案

要解决此问题,可以使用默认块参数创建哈希表(每当访问缺少的键时调用该参数,并允许您为缺失的键分配一个值)。

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

2. 用默认值分配丢失的键

当你访问一个返回默认值的不存在的键时,你可能期望哈希表现在包含具有返回值的该键。它不会这样做。Ruby不会修改哈希,它只是返回默认值。例如:

hash = Hash.new(0) #$> {} 
hash.keys.empty? #$> true
hash[:foo] #$> 0
hash[:foo] == 0 #$> true
hash #$> {}
hash.keys.empty? #$> true

解决方案

使用块级方法可以解决这种混淆,其中键值可以明确设置。


2
这正是我在@jeremyramos的回答下发表评论时想要表达的。原帖中的问题非常有洞察力(无论是否有意),我认为它值得得到充分的关注。 - engineersmnky

3
Hash.new 的文档对此并不十分清晰。我希望下面的示例能够阐明区别和 Hash.new(0) 的一种常见用法。
第一段代码使用了 Hash.new(0),哈希表具有默认值 0,当遇到新键时,它们的值为 0。这种方法可用于计算数组中的字符数。
第二段代码失败了,因为键的默认值(未被赋值时)是 nil。这个值无法在加法(计数)中使用,会产生错误。
count = Hash.new(0)

puts "count=#{count}"
# count={}

%w[a b b c c c].each do |char|
  count[char] += 1
end

puts "count=#{count}"
# count={"a"=>1, "b"=>2, "c"=>3}


count = Hash.new

puts "count=#{count}"

%w[a b b c c c].each do |char|
  count[char] += 1
  # Fails: in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)
end

puts "count=#{count}"

参见:

"Hash.new(0)" 和 "{}" 有什么区别?


2

TL;DR 当你使用 Hash.new 初始化哈希表时,你可以设置默认值或默认过程(如果给定的键不存在,则返回该值)

关于理解这个魔法的问题,首先你需要知道 Ruby 哈希表有默认值。要访问默认值,可以使用 Hash#default 方法

这个默认值默认情况下是 nil

hash = {}

hash.default # => nil

hash[:key] # => nil

您可以使用Hash#default=来设置默认值。

hash = {}

hash.default = :some_value

hash[:key] # => :some_value

非常重要的提示:使用可变对象作为默认值是危险的,因为会出现像这样的副作用:
hash = {}
hash.default = []

hash[:key] # => []

hash[:other_key] << :some_item # will mutate default value

hash[:key] # => [:some_value]
hash.default # => [:some_value]

hash # => {}

为了避免这种情况,您可以使用 Hash#default_procHash#default_proc= 方法。
hash = {}

hash.default_proc # => nil

hash.default_proc = proc { [] }

hash[:key] # => []

hash[:other_key] << :some_item # will not mutate default value
hash[:other_key] # => [] # because there is no this key

hash[:other_key] = [:symbol]
hash[:other_key] << :some_item
hash[:other_key] # => [:symbol, :some_item]

hash[:key] # => [] # still empty array as default

设置default会取消default_proc,反之亦然

hash = {}

hash.default = :default

hash.default_proc = proc { :default_proc }

hash[:key] # => :default_proc

hash.default = :default

hash[:key] # => :default

hash.default_proc # => nil

回到 Hash.new

当你向这个方法传递参数时,你初始化默认值

hash = Hash.new(0)

hash.default # => 0
hash.default_proc # => nil

当您将块传递给此方法时,您将初始化默认的proc。
hash = Hash.new { 0 }

hash.default # => nil
hash[:key] # => 0

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