Рубиновый способ: поймать деление на ноль - PullRequest
19 голосов
/ 03 апреля 2011

У меня есть следующий метод для вычисления среднего:

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

... и это работает, но мне это кажется грязным. Существует ли более элегантный «Рубиновый путь», позволяющий избежать этого деления на ноль?

Я хотел бы, чтобы у меня был оператор «если не тогда», как ...

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

Есть предложения?

Ответы [ 8 ]

60 голосов
/ 03 апреля 2011

Вы можете использовать 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), так что это еще одна возможность.

7 голосов
/ 04 апреля 2011

Обычно используется 1001 * для захвата исключения, а затем для возврата значения по умолчанию:

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
6 голосов
/ 15 декабря 2014

Несмотря на то, что это устаревшая тема, я подумал, что могу добавить простой вкладыш, который можно использовать ...

@average = variable1 / variable2 rescue 0
5 голосов
/ 20 декабря 2013

Для меня самый чистый способ:

numerator / denominator rescue 0

Это также избавляет вас от обработки 0 / 0.

Как указывает @Andrew, это действительно только для целых чисел.См. Комментарии к этому ответу для получения дополнительной информации.

3 голосов
/ 03 апреля 2011
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
1 голос
/ 11 ноября 2014

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
0 голосов
/ 03 апреля 2011

/ не возвращает ошибку деления на ноль, если либо число, которое нужно разделить, либо знаменатель является числом с плавающей запятой.

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

В более общем случае, в ruby1.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
0 голосов
/ 03 апреля 2011

Я не особо разбираюсь в Ruby, но я бы сделал это так:

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

Возможно, есть лучший ответ, но этого может быть достаточно.

...