提升你的 Ruby 技能:使用哈希 each each_key/each_value keys/ values key?/ value? fetch dig transform_keys(仅在 Ruby 2.5 及以上版本中可用)transform_values(仅在 Ruby 2.5 及以上版本中可用)select slice rejection chaining BOOM!💥

2025-06-07

提升你的 Ruby 技能:使用哈希

each

each_key/each_value

keys/values

key?/value?

fetch

dig

transform_keys(仅适用于 Ruby 2.5 及以上版本)

transform_values(仅适用于 Ruby 2.5 及以上版本)

select

slice

reject

chaining

轰!💥

上周我讲解了 Ruby 数组,并分享了一些我认为最有用的操作方法。这周我想聊聊哈希!在深入讲解我最喜欢的方法之前,我想先简单介绍一下 Ruby 中的哈希是什么以及它是如何工作的。哈希的定义直接来自文档本身:

哈希 (Hash) 是一个类似字典的集合(如果你是 Java 开发者,可以想象一下地图),它包含唯一的键及其值。哈希也被称为关联数组(说实话,我以前从未听说过这个,但是🤷),它与数组类似,但数组使用整数作为索引,而哈希允许使用任何对象类型。

这个定义中的一个关键点(双关语😉)是哈希允许你使用任何对象类型作为索引。大多数人在想到哈希时都会想到这一点。

{ a: 1, b: 2, c: 3 }
Enter fullscreen mode Exit fullscreen mode

但是 Ruby 中的哈希值远不止于此!例如,所有这些都是哈希值。

# String Keys
{ "a" => 1, "b" => 2, "c" => 3 }

# Integer Keys 
{ 1 => "a", 2 => "b", 3 => "c" } 

# Symbol Keys (Hashrocket notation) 
{ :a => 1, :b => 2, :c => 3 } 

# Array Keys
{ ['a'] => 1, ['b'] => 2, ['c'] => 3 } 

# Hash Keys 
{ { :a => 1 } => 2, { :b => 2 } => 3 } 
Enter fullscreen mode Exit fullscreen mode

任何你想要的对象类型都可以作为哈希键。既然我们已经了解了语义和定义,那就开始讨论方法吧!

注意:就像数组教程一样,如果您看到irb代码片段,则表示我正在使用 Ruby 控制台。此外,如果您想要不带任何解释的代码,我制作了一个代码速查表(CHEAT SHEET) 。

each

使用哈希表最有价值的功能之一就是对其进行迭代。最简单的循环方法之一是使用each。我不知道你的情况,但我刚开始使用 Ruby 时,我的哈希each语句是这样的。

irb> { a: 1, b: 2, c: 3 }.each do |pair|
  puts "#{pair.first} #{pair.last}"
end
a 1
b 2
c 3
=> {:a=>1, :b=>2, :c=>3}
Enter fullscreen mode Exit fullscreen mode

在这里,我执行each方法的方式与数组相同。区别在于它pair本身是一个数组。第一个元素是我的键,第二个元素是我的值。注意:对每个键/值对执行完代码块后,将返回我们迭代的原始哈希值。

上面的方法确实有效,但还有一种更巧妙的方法,你可以通过将两个参数传递给你的代码块来区分这两个参数。这意味着你可以像这样重写上面的代码:

irb> { a: 1, b: 2, c: 3 }.each do |key, value|
  puts "#{key} #{value}"
end
a 1
b 2
c 3
=> {:a=>1, :b=>2, :c=>3}
Enter fullscreen mode Exit fullscreen mode

现在我们有两个独立的变量,一个代表键,一个代表值。

each_key/each_value

但是如果我们不需要所有的键和值怎么办?如果我们只想要键或值怎么办?每种情况都有相应的方法!

irb> { a: 1, b: 2, c: 3 }.each_key do |key|
  puts key
end
a
b 
c
=> {:a=>1, :b=>2, :c=>3}

irb> { a: 1, b: 2, c: 3 }.each_value do |value|
  puts value
end
1
2 
3
=> {:a=>1, :b=>2, :c=>3}
Enter fullscreen mode Exit fullscreen mode

请注意,无论我们使用each_key还是each_value,在完成迭代后我们仍然会得到原始哈希值。

keys/values

假设你只需要一个包含键的数组,或者只需要一个包含哈希值的数组。为此,你可以使用keysvalues。两者都会返回给定哈希值的键或值的数组。

irb> { a: 1, b: 2, c: 3 }.keys 
=> [:a, :b, :c]
irb> { a: 1, b: 2, c: 3 }.values 
=> [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

key?/value?

另外两个非常直接的方法是键和值谓词。如果你想知道哈希表中是否存在某个键或值,那么你可以使用key?value?

irb> { a: 1, b: 2 }.key?(:b)
=> true
irb> { a: 1, b: 2 }.value?(2)
=> true
irb> { a: 1, b: 2 }.value?(5)
=> false
Enter fullscreen mode Exit fullscreen mode

这很简单,但我想指出一些细微差别。首先,当你查找键时,必须确保你查找的是合适的数据类型。例如,如果你有一个包含符号键的哈希表,那么搜索字符串将返回 false。

irb> { a: 1, b: 2 }.key?("b")
=> false
Enter fullscreen mode Exit fullscreen mode

确保您搜索的数据类型与您的键或值的数据类型相匹配。

fetch

现在我们知道了如何检查是否有特定的键或值,但是如果我们真的想查找某个键对应的值呢?嗯,总有标准的方法来做这件事,这是我首先学到的。

irb> h = { a: 1, b: 2, c: 3 }
irb> h[:a] 
=> 1
irb> h[:d] 
=> nil
Enter fullscreen mode Exit fullscreen mode

简单易懂。如果键存在,我们就返回它的值。如果键不存在,就返回 nil。但如果我们的逻辑更复杂怎么办?例如,如果我们想返回键的值,或者,如果键不存在,我们想返回默认值,比如 0,该怎么办?根据我们目前掌握的知识,我们可以这样做。

h = { a: 1, b: 2, c: 3 }
if h[:a]
  result = h[:a]
else
  result = 0
end
Enter fullscreen mode Exit fullscreen mode

虽然能用,但代码量太大了。我们可以用一个简单的方法替换这么大的代码块。它fetchfetch很多选项和行为,让我们逐一分析。

1)fetch不带参数时,如果值存在则返回该值。如果值不存在,fetch则会引发错误。如果你想在找不到键时引发错误,这非常有用。

irb> h = { a: 1, b: 2, c: 3 }
irb> h.fetch(:a) 
=> 1
irb> h.fetch(:d) 
KeyError: key not found: :d
    from (irb):13:in `fetch'
    from (irb):13
Enter fullscreen mode Exit fullscreen mode

2) fetchWITH 参数会像之前一样,如果值存在,就会返回。这里就变得巧妙了,可以帮我们完成最初的用例:如果你传递了一个参数,而键不存在,它就会返回该参数!🔥

irb> h = { a: 1, b: 2, c: 3 }
irb> h.fetch(:a, 0) 
=> 1
irb> h.fetch(:d, 0) 
=> 0
Enter fullscreen mode Exit fullscreen mode

dig

fetch当你有一个单层哈希表并希望使用键返回值时,这种方法非常有效。但是如果你有多个嵌套哈希表怎么办?例如

h = { a: { b: { c: 1 } } }
Enter fullscreen mode Exit fullscreen mode

通常要获取 c 的值,你需要这样做

irb> h[:a][:b][:c] 
=> 1
Enter fullscreen mode Exit fullscreen mode

它会遍历嵌套的哈希值并返回值。但是,如果您不确定所有这些层级是否存在,该怎么办?例如,假设您正在使用 API 获取用户数据。有时您会找到所需的用户并将其返回。

{ user: { first: 'mary', last: 'sue' } }
Enter fullscreen mode Exit fullscreen mode

其他时候,你找不到你想要的用户,然后你得到了这个,

{ status: 'not found' }
Enter fullscreen mode Exit fullscreen mode

在这种情况下,我们不能仅仅假设我们拥有用户信息,因为如果我们没有用户信息,我们最终会引发错误。

irb> h = { status: 'not found' }
irb> h[:user][:first] 
=> NoMethodError: undefined method `[]' for nil:NilClass
Enter fullscreen mode Exit fullscreen mode

为了避免这种情况,我们可以这样做

h = { status: 'not found' }
if h[:user].present?
  result = h[:user][:first]
else
  result = nil
end
Enter fullscreen mode Exit fullscreen mode

这确保了如果我们有用户数据,就返回名字。如果没有数据,则返回 nil。其实,我们可以通过使用 来避免所有这些问题digdig提取由键对象序列指定的嵌套值。但是,如果任何一个嵌套键不存在,它将返回nil。🎉

irb> h = { user: { first: 'mary', last: 'sue' } }
irb> h.dig(:user, :first) 
=> 'mary'
irb> h = { status: 'not found' }
irb> h.dig(:user, :first) 
=> nil
Enter fullscreen mode Exit fullscreen mode

dig当你想遍历一个哈希表,但又不确定它的结构时,这非常有用。它允许你直接操作哈希表,而不必担心处理一堆错误或present?使用 if/else 块进行一堆检查。

transform_keys(仅适用于 Ruby 2.5 及以上版本)

现在我们要更进一步。如果我们有一个哈希表,其中所有的键都是符号,但我们想把它们全部改成字符串,该怎么办?早期我们会这样做。

new_hash = {}
{ a: 1, b: 2, c: 3 }.each do |key, value|
  new_hash[key.to_s] = value
end
# new_hash = { "a" => 1, "b" => 2, "c" => 3 }
Enter fullscreen mode Exit fullscreen mode

它确实有效,但你猜对了,现在有一个更好的方法。这个更好的方法是transform_keys. transform_keys,它允许你迭代哈希并返回一个新的哈希。新的哈希键将是针对每个原始键执行代码块的结果。

h = { a: 1, b: 2, c: 3 }
new_hash = h.transform_keys do |k| 
  k.to_s 
end  
# new_hash = { "a" => 1, "b" => 2, "c" => 3 }       
Enter fullscreen mode Exit fullscreen mode

重要提示 transform_keys:(以上) 和transform_values(以下) 仅适用于 Ruby 2.5 及以上版本。如果您遇到以下错误,则您使用的 Ruby 版本可能低于 2.5 版本,NoMethodError: undefined methodtransform_keys也不可用。for {:a=>1, :b=>2, :c=>3}:Hash

transform_values(仅适用于 Ruby 2.5 及以上版本)

transform_values工作原理与 完全相同transform_keys,只是它允许你更新值。执行完你的区块后,它将返回一个包含新值的新哈希值。

h = { a: 1, b: 2, c: 3 }
new_hash = h.transform_values do |value| 
  value * 2 
end  
# new_hash = { :a => 2, :b => 4, :c => 6 }      
Enter fullscreen mode Exit fullscreen mode

select

如果你读过我的第一个数组教程,现在我们将进入一些熟悉的领域。就像数组一样,你可以使用select哈希!select哈希的工作原理与数组基本相同,它返回一个新的哈希,其中包含你的代码块评估为 true 的键/值对。

# clunky way
new_hash = {}
h = { a: 1, b: 2, c: 3, d: 4 }
h.each do |key, value| 
  new_hash[key] = value if value.even?
end  
# new_hash = { :b => 2, :d => 4 }

# slick select way
h = { a: 1, b: 2, c: 3, d: 4 }
new_hash = h.select do |key, value| 
  value.even?
end  
# new_hash = { :b => 2, :d => 4 }
Enter fullscreen mode Exit fullscreen mode

slice

假设你有一个哈希表,你只想返回一组特定的键及其值。你可以这样做

key_list = [:a, :d]
h = { a: 1, b: 2, c: 3, d: 4 }
new_hash = h.select do |key, value| 
  key_list.include?(key)
end  
# new_hash = { :a => 1, :d => 4 }
Enter fullscreen mode Exit fullscreen mode

但 Ruby 有一个更简单的方法,那就是使用slice. slice,它将返回一个仅包含你通过参数请求的键的哈希值。上面的代码可以简化为

h = { a: 1, b: 2, c: 3, d: 4 }
new_hash = h.slice(:a, :d) 
# new_hash = { :a => 1, :d => 4 }
Enter fullscreen mode Exit fullscreen mode

reject

回到我们熟悉的数组领域。reject哈希的工作方式与数组相同。reject将返回一个新的哈希,其中包含您的块返回 false 的所有键/值对。

h = { a: 1, b: 2, c: 3, d: 4 }
new_hash = h.reject do |key, value| 
  value.even?
end  
# new_hash = { :a => 1, :c => 3 }
Enter fullscreen mode Exit fullscreen mode

chaining

最后,同样重要的是,我们需要讲解链式调用。上面所有返回哈希值的方法都可以链式调用。为了方便理解,我将使用方括号表示法,因为我认为从左到右阅读链式调用方法比从上到下阅读更容易。

下面是将上述一些方法链接在一起的示例。

h = { a: 1, b: 2, c: 3, d: 4 }
result = h.transform_keys{|k| k.to_s}.slice("a", "b").reject{|k, v| v == 2}.transform_values{|value| "hi #{value}"} 
# result = {"a"=>"hi 1"}
Enter fullscreen mode Exit fullscreen mode

根据我们上面学到的知识,我们来分解一下这里发生的事情。1
)transform_keys将哈希表中的每个键更改为字符串并返回{ "a" => 1, "b" => 2, "c" => 3 , "d" => 4}
2)slice仅选择键“a”和“b”及其值并返回{ "a" => 1, "b" => 2 }
3)reject删除所有值等于 2 的键/值对,剩下{ "a" => 1 }
4) 最后transform_values将哈希表的值更改为一个字符串,其中包含“hi”和后面的数字。最终结果是{"a"=>"hi 1"}

轰!💥


你终于读完了!希望你现在对 Ruby 哈希有了更深入的了解,并且掌握了更多可用的方法。如果你有任何疑问或需要进一步了解的地方,欢迎在评论区留言!

如果您想要快速参考这些方法,我制作了一个方便的备忘单,其中包含每个示例的代码。

文章来源:https://dev.to/molly/level-up-your-ruby-skillz-working-with-hashes-4bid
PREV
让值班不再糟糕 一个有问题的值班系统 解决方案 回报 值班不应该糟糕
NEXT
放弃目标 设定目标 奋斗开始 放手没关系