【问题标题】:Ruby way: catch division by zeroRuby方式:捕捉除以零
【发布时间】:2011-04-03 16:48:44
【问题描述】:

我有以下方法来计算平均值:

def compute_average(a,b,c,d,e)
  total = [a,b,c,d,e].sum.to_f
  average = [a, 2*b, 3*c, 4*d, 5*e].sum / total
  average.round(2)
end

这没什么特别的,但它有一个问题,我认为所有平均方程都有:如果输入全为零,它可能会被零除。

所以,我想到了这样做:

def compute_average(a,b,c,d,e)
  total = [a,b,c,d,e].sum.to_f
  if total==0
    average = 0.00
  else
    average = [a, 2*b, 3*c, 4*d, 5*e].sum / total
    average.round(2)
  end
end

...这行得通,但对我来说感觉很笨拙。有没有更优雅的“Ruby Way”来避免这种被零除的问题?

我希望我有一个“除非那时”运算符,比如...

average = numerator / denominator unless denominator == 0 then 0

有什么建议吗?

【问题讨论】:

  • Array#sum吗?我没有。
  • 这是一个奇怪的平均函数。更正常的平均值(算术/几何)除以元素的数量,因此除非您尝试取空集的平均值,否则它们并没有真正的问题。
  • 嗯,是的,原因是我用它来计算投票数的平均值,所以 a、b、c、d 和 e 是 1 星、2 星的数量、3 星、4 星和 5 星的总票数,所以我正在计算所有选票的“平均星数”。是的,我想这有点奇怪。
  • @sawa -- 这很有趣,我只尝试过一次,它工作了,所以我现在使用它,否则我需要或想要很多括号。我在 Ruby 1.9.2 ...
  • 是的,rails 将它添加到 ActiveSupport 中。通过reduce 0, &:+添加自己很容易。

标签: ruby refactoring divide-by-zero


【解决方案1】:

您可以使用nonzero?,如:

def compute_average(a,b,c,d,e)
  total = [a,b,c,d,e].sum.to_f
  average = [a, 2*b, 3*c, 4*d, 5*e].sum / (total.nonzero? || 1)
end

更多的人会更熟悉使用三元运算符(total == 0 ? 1 : total),所以这是另一种可能性。

【讨论】:

  • 我喜欢这个,也喜欢 Jakub 的方法,但是你的把作业放在前面——我意识到这不是真的必要,但我喜欢它,因为它可以帮助我记住我在做什么我稍后会看这个。
  • 这在compute_average(1,0,0,0,-1) #=> -4 的情况下会给出不同的结果,而@Andrew 和我的会在那时给出0
  • 有点太微妙了……它依赖于当总数为0时分子也为0的事实,这是一个正交事实。三元运算的简单用法似乎更清楚:average = (total.zero? ? 0 : [a, 2*b, 3*c, 4*d, 5*e].sum / total).round(2 )
  • 这个函数只有在假设正输入时才是正确的——考虑到它计算星星可能是一个合理的假设。
  • 这是一个很好的观点 - 尽管在这种情况下没有负面输入的可能性。感谢您的提示!
【解决方案2】:

通常依靠rescue捕获异常,然后返回默认值:

def compute_average(a, b, c, d, e)
  total = [a, b, c, d, e].sum.to_f
  average = [ a, 2*b, 3*c, 4*d, 5*e ].sum / total
  average.round(2)
  rescue ZeroDivisionError
    0.0
end

我也想写:

average = numerator / denominator unless denominator == 0 then 0

作为

average = (denominator == 0) ? 0 : numerator / denominator

【讨论】:

  • 如果救援被触发的次数超过5% of the time,这将是非常低效的,但至少你没有隐藏其他错误。需要注意的是,如果任何参数为 nil,该函数仍会抛出 TypeError: NilClass can't be coerced into Fixnum(抱歉删除和推荐。我不小心拍了 Google 搜索 URL。)
【解决方案3】:

虽然这是一个过时的线程,但我想我会用一个你可以使用的简单的衬里来插话......

@average = variable1 / variable2 rescue 0

【讨论】:

  • 非常优雅的解决方案。
  • 不要这样做,内联救援很糟糕。它们将掩盖该行中发生的每个错误(考虑variable1 是一个执行某些计算的方法名称),并且很难调试。
【解决方案4】:
def compute_average(a,b,c,d,e)
  total = (a+b+c+d+e).to_f
  total.zero? ? 0 : ((a + 2*b + 3*c + 4*d + 5*e) / total).round(2)
end

【讨论】:

  • 这里的三元运算符不是更惯用吗?
  • 是的,双问号在视觉上并不好看(而且你总是忘记写第二个!)。但我想最后你会习惯的。
【解决方案5】:

对我来说,最干净的方法是:

numerator / denominator rescue 0

它还使您免于处理0 / 0

正如@Andrew 指出的那样,这仅对整数有效。有关更多信息,请参阅此答案的 cmets。

【讨论】:

  • 只是检查一下,如果你在后缀中使用救援它只会保存当前表达式? IE。这行得通吗? a = raise 'not this' rescue 'this'; a #=> 'this'
  • 所以,只是摆弄这个,如果你的 args 是一个浮点数,那么这将不起作用,因为它会返回无穷大,所以这只有在你做整数时才可行。对于除法,我认为您通常会对分数结果感兴趣,因此浮点数可能是等式的一部分。好主意,不过!
  • 我刚刚在浮子上使用了这种技术。除法之后,它返回 NaN 而不是触发救援。咕噜。
  • 如果救援被触发的次数超过5% of the time,这将是非常低效的,并且它可以hide other errors 使调试变得困难。 (很抱歉删除和推荐。我不小心拍了谷歌搜索网址。)
【解决方案6】:

TL;DR:一种可能的解决方案

def compute_average(*values)

  # This makes sure arrays get flattened to a single array.
  values.flatten!

  # Throws away all nil values passed as arguments.
  values.reject!(&:nil?)

  # Throws away all non-numeric values.
  # This includes trashing strings that look like numbers, like "12".
  values.keep_if{ |v| v.is_a? Numeric }

  total = values.sum.to_f
  return Float::NAN if total.zero?

  # I'm not sure what this business is
  #   average = [a, 2*b, 3*c, 4*d, 5*e].sum / total
  # but it can be translated to
  average = values.each_with_index.map{ |v,i| v*(i+1) }.sum / total

  average.round(2)
end

这可以防止所有情况:

compute_average(1,2,3,4,5)
=> 3.67

compute_average(0,0,0,0,0)
=> NaN

compute_average(1,2,nil,4,5)
=> 3.08

compute_average(1,2,"string",4,5)
=> 3.08

compute_average(1)
=> 1.0

compute_average([1,2,3,4,5])
=> 3.67

compute_average
=> NaN

原函数:

def compute_average(a,b,c,d,e)
  total = [a,b,c,d,e].sum.to_f
  average = [a, 2*b, 3*c, 4*d, 5*e].sum / total
  average.round(2)
end

考虑检查零:

def compute_average(a,b,c,d,e)
  total = [a,b,c,d,e].sum.to_f
  return if total.zero?
  average = [a, 2*b, 3*c, 4*d, 5*e].sum / total
  average.round(2)
end

此更改仅适用于一种情况:

compute_average(1,2,3,4,5)
# => 3.67

compute_average(0,0,0,0,0)
# => nil

compute_average(1,2,nil,4,5)
# => TypeError: NilClass can't be coerced into Fixnum

compute_average(1,2,"string",4,5)
# => TypeError: String can't be coerced into Fixnum

compute_average(1)
# => ArgumentError: wrong number of arguments calling `compute_average` (1 for 5)

compute_average([1,2,3,4,5])
# => ArgumentError: wrong number of arguments calling `compute_average` (1 for 5)

compute_average
# => ArgumentError: wrong number of arguments calling `compute_average` (0 for 5)

考虑使用内联 rescue

def compute_average(a,b,c,d,e)
  total = [a,b,c,d,e].sum.to_f
  average = [a, 2*b, 3*c, 4*d, 5*e].sum / total rescue 0
  average.round(2)
end

此更改仅针对一种情况提供保护:

compute_average(1,2,3,4,5)
# => 3.67

compute_average(0,0,0,0,0)
# => NaN

compute_average(1,2,nil,4,5)
# => TypeError: NilClass can't be coerced into Fixnum

compute_average(1,2,"string",4,5)
# => TypeError: String can't be coerced into Fixnum

compute_average(1)
# => ArgumentError: wrong number of arguments calling `compute_average` (1 for 5)

compute_average([1,2,3,4,5])
# => ArgumentError: wrong number of arguments calling `compute_average` (1 for 5)

compute_average
# => ArgumentError: wrong number of arguments calling `compute_average` (0 for 5)

使用内联 rescue 有另一个后果。考虑一下这个错字:

def compute_average(a,b,c,d,e)
  total = [a,b,c,d,e].sum.to_f
  average = [a, 2*b, 3*c, 4*d, 5*e].smu / total rescue 0
  #                                 ^^^
  average.round(2)
end

compute_average(1,2,3,4,5)
# => 0.0

compute_average(0,0,0,0,0)
# => 0.0

考虑使用rescue

def compute_average(a,b,c,d,e)
  total = [a,b,c,d,e].sum.to_f
  average = [a, 2*b, 3*c, 4*d, 5*e].sum / total
  average.round(2)
rescue ZeroDivisionError
  0.0
end

这样更好,因为它不会隐藏错误,但可以防止出现与上述倾斜 rescue 相同的情况。

另一个我称之为正常平均计算的版本

顺便说一句,我熟悉的平均操作是使用总数/计数计算的,所以这里有一个版本。

def compute_average(*values)

  # This makes sure arrays get flattened to a single array.
  values.flatten!

  # Throws away all nil values passed as arguments.
  values.reject!(&:nil?)

  # Throws away all non-numeric values.
  # This includes trashing strings that look like numbers, like "12".
  values.keep_if{ |v| v.is_a? Numeric }

  total = values.sum.to_f
  count = values.count
  return Float::NAN if count.zero?

  total / count
end

这可以防止所有情况:

compute_average(1,2,3,4,5)
=> 3.0

compute_average(0,0,0,0,0)
=> 0.0

compute_average(1,2,nil,4,5)
=> 3.0

compute_average(1,2,"string",4,5)
=> 3.0

compute_average(1)
=> 1.0

compute_average([1,2,3,4,5])
=> 3.0

compute_average
=> NaN

【讨论】:

    【解决方案7】:

    我不是一个 Ruby-ist,但我会这样做:

    average = denominator.nonzero? ? numerator/denominator : 0
    

    可能有更好的答案,但这可能就足够了。

    【讨论】:

    • nonzero? 在 Numeric 类上,所以如果 denominatornil 这仍然会引发错误。
    【解决方案8】:

    如果要被除的数字或分母是浮点数,/ 不会返回除零错误。

    def compute_average(a,b,c,d,e)
      total = [a,b,c,d,e].sum.to_f
      average = [a, 2*b, 3*c, 4*d, 5*e].sum / total
      average.finite? ? average.round(2) : 0.0
    end
    

    更一般地说,在 ruby​​1.9 下,

    def compute_average *args
      average = args.to_enum.with_index.map{|x, w| x * w}.sum / args.sum.to_f
      average.finite? ? average.round(2) : 0.0
    end
    

    【讨论】:

      猜你喜欢
      • 2011-03-05
      • 2011-06-12
      • 2016-02-12
      • 1970-01-01
      • 2011-09-01
      • 1970-01-01
      相关资源
      最近更新 更多