如何在字符串数组中的nils上映射时避免出现NoMethodError异常?

时间:2017-09-12 23:30:57

标签: arrays ruby string exception-handling null

问题

在回应related question时,@ DanRio问了这个follow-up question

  

如果数组中的元素为nil,则使用array.map!(&:upcase)会给出一个无方法错误。我该如何解决这个问题?

因为这超出了原始问题的范围,我将代表他在此处发布。

守则

这是用户询问的代码片段:

array = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
array.map!(&:upcase)

2 个答案:

答案 0 :(得分:2)

问题

在原始问题中,数组中的值都是String对象。但是,如果阵列的内容没有提前知道,那么当然可能有些元素可能是零。由于nil不会#respond_to?:upcase,数组中的任何nils都会触发您看到的NoMethodError异常。

潜在解决方案

有很多方法可以解决这个问题,包括:

  1. 在映射之前使用Array#compact删除nils。
  2. 使用rescue明确处理例外。
  3. 使用Array#map!的块形式使用Ruby的新方法(从Ruby 2.3.0开始)safe navigation operator调用该方法。
  4. 我将重点关注答案其余部分的最后一项。

    使用Ruby的安全导航操作员

    哪个答案最好取决于上下文,但我推荐常见案例的安全导航操作符。除了发行说明之外,我无法在任何地方找到它,但它的工作原理与Rails的Object#try方法非常相似。

    不是直接使用map! &:upcase映射每个元素,而是使用安全导航仅在响应它的对象上调用#upcase。例如:

    array = ["Monday", "Tuesday", "Wednesday", "Thursday", nil, "Friday"]
    array.map! { |e| e&.upcase }
    #=> ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", nil, "FRIDAY"]
    

答案 1 :(得分:0)

以下是不同方法的基准:

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)

您可以看到契约是最快的,但其结果与其他结果之间存在重要差异。 在结果中包含任何nil值,如果跳过某些项,则结果将是不同的长度。

所以我想说如果你想让结果不保留nil,那么使用compact,否则使用布尔或安全导航,它们的表现大致相同。