在字符串数组中映射 nils 时如何避免 NoMethodError 异常?
How can one avoid a NoMethodError exception when mapping over nils in an array of strings?
问题
回应related question, @DanRio asked this follow-up question:
If an element in the array is nil, using array.map!(&:upcase) gives a no method error on it. How would I get around this?
因为这超出了原始问题的范围,所以我代表他在这里发布。
代码
这是用户询问的代码片段:
array = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
array.map!(&:upcase)
问题
原题中,数组中的值都是String对象。但是,如果事先不知道数组的内容,那么当然有可能某些元素可能为 nil。由于 nil 不 #respond_to? :upcase,数组中的任何 nils 都会触发您看到的 NoMethodError 异常。
可能的解决方案
有多种方法可以解决这个问题,包括:
- 在映射之前使用 Array#compact 删除 nils。
- 用rescue明确处理异常。
- 使用Array#map! to invoke the method using Ruby's new (as of Ruby 2.3.0) safe navigation operator的块形式。
我将重点关注答案剩余部分的最后一项。
使用Ruby的安全导航运算符
哪个答案最好取决于上下文,但我建议在常见情况下使用安全导航运算符。除了发行说明,我在任何地方都找不到它的文档,但它的工作原理很像 Rails.
中的 Object#try 方法
您可以使用安全导航仅在响应它的对象上调用 #upcase,而不是直接使用 map! &:upcase
映射每个元素。例如:
array = ["Monday", "Tuesday", "Wednesday", "Thursday", nil, "Friday"]
array.map! { |e| e&.upcase }
#=> ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", nil, "FRIDAY"]
这是不同方法的基准:
require 'benchmark'
arr = 100_000.times.map { nil }
Benchmark.bm do |bm|
bm.report("compact then upcase\n") do
arr.compact.each &:upcase
end
bm.report("save nagivation then upcase\n") do
arr.each { |y| y&.upcase }
end
bm.report("respond_to then upcase\n") do
arr.each { |y| y.upcase if y.respond_to?(:upcase) }
end
bm.report("boolean check then upcase\n") do
arr.each { |y| y && y.upcase }
end
end
结果:
user system total real
compact then upcase
0.000000 0.000000 0.000000 ( 0.000912)
save nagivation then upcase
0.020000 0.000000 0.020000 ( 0.005030)
respond_to then upcase
0.000000 0.000000 0.000000 ( 0.009824)
boolean check then upcase
0.000000 0.000000 0.000000 ( 0.005254)
您可以看到 compact 是最快的,但它的结果与其他结果之间存在重要差异。它不会在结果中包含任何 nil 值,如果跳过某些项目,那么结果将是不同的长度。
所以我想说,如果你希望结果不保留 nil
,那么使用紧凑型,否则使用布尔或安全导航,它们的性能大致相同。
问题
回应related question, @DanRio asked this follow-up question:
If an element in the array is nil, using array.map!(&:upcase) gives a no method error on it. How would I get around this?
因为这超出了原始问题的范围,所以我代表他在这里发布。
代码
这是用户询问的代码片段:
array = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
array.map!(&:upcase)
问题
原题中,数组中的值都是String对象。但是,如果事先不知道数组的内容,那么当然有可能某些元素可能为 nil。由于 nil 不 #respond_to? :upcase,数组中的任何 nils 都会触发您看到的 NoMethodError 异常。
可能的解决方案
有多种方法可以解决这个问题,包括:
- 在映射之前使用 Array#compact 删除 nils。
- 用rescue明确处理异常。
- 使用Array#map! to invoke the method using Ruby's new (as of Ruby 2.3.0) safe navigation operator的块形式。
我将重点关注答案剩余部分的最后一项。
使用Ruby的安全导航运算符
哪个答案最好取决于上下文,但我建议在常见情况下使用安全导航运算符。除了发行说明,我在任何地方都找不到它的文档,但它的工作原理很像 Rails.
中的 Object#try 方法您可以使用安全导航仅在响应它的对象上调用 #upcase,而不是直接使用 map! &:upcase
映射每个元素。例如:
array = ["Monday", "Tuesday", "Wednesday", "Thursday", nil, "Friday"]
array.map! { |e| e&.upcase }
#=> ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", nil, "FRIDAY"]
这是不同方法的基准:
require 'benchmark'
arr = 100_000.times.map { nil }
Benchmark.bm do |bm|
bm.report("compact then upcase\n") do
arr.compact.each &:upcase
end
bm.report("save nagivation then upcase\n") do
arr.each { |y| y&.upcase }
end
bm.report("respond_to then upcase\n") do
arr.each { |y| y.upcase if y.respond_to?(:upcase) }
end
bm.report("boolean check then upcase\n") do
arr.each { |y| y && y.upcase }
end
end
结果:
user system total real
compact then upcase
0.000000 0.000000 0.000000 ( 0.000912)
save nagivation then upcase
0.020000 0.000000 0.020000 ( 0.005030)
respond_to then upcase
0.000000 0.000000 0.000000 ( 0.009824)
boolean check then upcase
0.000000 0.000000 0.000000 ( 0.005254)
您可以看到 compact 是最快的,但它的结果与其他结果之间存在重要差异。它不会在结果中包含任何 nil 值,如果跳过某些项目,那么结果将是不同的长度。
所以我想说,如果你希望结果不保留 nil
,那么使用紧凑型,否则使用布尔或安全导航,它们的性能大致相同。