提升你的 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 }
但是 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 }
任何你想要的对象类型都可以作为哈希键。既然我们已经了解了语义和定义,那就开始讨论方法吧!
each
each_key
/each_value
keys
/values
key?
/value?
transform_keys
/transform_values
dig
fetch
select
reject
slice
chaining
注意:就像数组教程一样,如果您看到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}
在这里,我执行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}
现在我们有两个独立的变量,一个代表键,一个代表值。
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}
请注意,无论我们使用each_key
还是each_value
,在完成迭代后我们仍然会得到原始哈希值。
keys
/values
假设你只需要一个包含键的数组,或者只需要一个包含哈希值的数组。为此,你可以使用keys
或values
。两者都会返回给定哈希值的键或值的数组。
irb> { a: 1, b: 2, c: 3 }.keys
=> [:a, :b, :c]
irb> { a: 1, b: 2, c: 3 }.values
=> [1, 2, 3]
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
这很简单,但我想指出一些细微差别。首先,当你查找键时,必须确保你查找的是合适的数据类型。例如,如果你有一个包含符号键的哈希表,那么搜索字符串将返回 false。
irb> { a: 1, b: 2 }.key?("b")
=> false
确保您搜索的数据类型与您的键或值的数据类型相匹配。
fetch
现在我们知道了如何检查是否有特定的键或值,但是如果我们真的想查找某个键对应的值呢?嗯,总有标准的方法来做这件事,这是我首先学到的。
irb> h = { a: 1, b: 2, c: 3 }
irb> h[:a]
=> 1
irb> h[:d]
=> nil
简单易懂。如果键存在,我们就返回它的值。如果键不存在,就返回 nil。但如果我们的逻辑更复杂怎么办?例如,如果我们想返回键的值,或者,如果键不存在,我们想返回默认值,比如 0,该怎么办?根据我们目前掌握的知识,我们可以这样做。
h = { a: 1, b: 2, c: 3 }
if h[:a]
result = h[:a]
else
result = 0
end
虽然能用,但代码量太大了。我们可以用一个简单的方法替换这么大的代码块。它fetch
有fetch
很多选项和行为,让我们逐一分析。
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
2) fetch
WITH 参数会像之前一样,如果值存在,就会返回。这里就变得巧妙了,可以帮我们完成最初的用例:如果你传递了一个参数,而键不存在,它就会返回该参数!🔥
irb> h = { a: 1, b: 2, c: 3 }
irb> h.fetch(:a, 0)
=> 1
irb> h.fetch(:d, 0)
=> 0
dig
fetch
当你有一个单层哈希表并希望使用键返回值时,这种方法非常有效。但是如果你有多个嵌套哈希表怎么办?例如
h = { a: { b: { c: 1 } } }
通常要获取 c 的值,你需要这样做
irb> h[:a][:b][:c]
=> 1
它会遍历嵌套的哈希值并返回值。但是,如果您不确定所有这些层级是否存在,该怎么办?例如,假设您正在使用 API 获取用户数据。有时您会找到所需的用户并将其返回。
{ user: { first: 'mary', last: 'sue' } }
其他时候,你找不到你想要的用户,然后你得到了这个,
{ status: 'not found' }
在这种情况下,我们不能仅仅假设我们拥有用户信息,因为如果我们没有用户信息,我们最终会引发错误。
irb> h = { status: 'not found' }
irb> h[:user][:first]
=> NoMethodError: undefined method `[]' for nil:NilClass
为了避免这种情况,我们可以这样做
h = { status: 'not found' }
if h[:user].present?
result = h[:user][:first]
else
result = nil
end
这确保了如果我们有用户数据,就返回名字。如果没有数据,则返回 nil。其实,我们可以通过使用 来避免所有这些问题dig
。dig
提取由键对象序列指定的嵌套值。但是,如果任何一个嵌套键不存在,它将返回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
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 }
它确实有效,但你猜对了,现在有一个更好的方法。这个更好的方法是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 }
重要提示 transform_keys
:(以上) 和transform_values
(以下) 仅适用于 Ruby 2.5 及以上版本。如果您遇到以下错误,则您使用的 Ruby 版本可能低于 2.5 版本,NoMethodError: undefined method
transform_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 }
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 }
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 }
但 Ruby 有一个更简单的方法,那就是使用slice
. slice
,它将返回一个仅包含你通过参数请求的键的哈希值。上面的代码可以简化为
h = { a: 1, b: 2, c: 3, d: 4 }
new_hash = h.slice(:a, :d)
# new_hash = { :a => 1, :d => 4 }
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 }
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"}
根据我们上面学到的知识,我们来分解一下这里发生的事情。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