separate - split in ruby example
Ruby way: captura la división por cero. (8)
Tengo el siguiente método para calcular un promedio:
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
No es nada especial, pero tiene un problema que espero que tengan todas las ecuaciones promedio: podría dividirse por cero si todas las entradas son cero.
Entonces, pensé en hacer esto:
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
... y eso funciona, pero a mí me parece una tontería. ¿Hay una forma más elegante, "Ruby Way" para evitar esta división por cero problema?
Lo que desearía tener era un operador "a menos que entonces", como ...
average = numerator / denominator unless denominator == 0 then 0
¿Alguna sugerencia?
TL; DR: Una posible solución
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
Esto protege contra todos los casos:
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
Función original:
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
Considere verificar si hay cero:
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
Este cambio solo protege contra un caso:
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)
Considere usar un rescue
línea
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
Este cambio solo protege contra un caso, también:
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)
Usar un rescue
línea tiene otra consecuencia. Considere este error tipográfico:
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
Considera usar un 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
Esto es mejor, ya que no oculta errores, pero protege contra el mismo escenario que el rescue
inclinación anterior.
Otra versión con lo que yo llamaría un cálculo promedio normal.
Como nota al margen, la operación promedio con la que estoy familiarizado se calcula usando total / count, así que aquí hay una versión que lo hace.
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
Esto protege contra todos los casos:
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
¿Puedes usar el nonzero?
, como en:
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
Más personas estarían más familiarizadas con el uso del operador ternario (total == 0 ? 1 : total)
, por lo que esa es otra posibilidad.
Es común confiar en el rescue
para capturar la excepción y luego devolver el valor predeterminado:
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
También escribiría:
average = numerator / denominator unless denominator == 0 then 0
como
average = (denominator == 0) ? 0 : numerator / denominator
No soy muy Rubista, pero lo haría así:
average = denominator.nonzero? ? numerator/denominator : 0
Probablemente hay una mejor respuesta, pero esto podría ser suficiente.
Para mí, la forma más limpia es:
numerator / denominator rescue 0
También le ahorra el manejo de 0/0.
Como @Andrew señala, esto solo es válido para enteros. Ver los comentarios a esta respuesta para más información.
Si bien este es un hilo desactualizado, pensé que estaría de acuerdo con un simple forro que puedes usar ...
@average = variable1 / variable2 rescue 0
/
no devuelve un error de división cero si el número a dividir o el denominador es un flotante.
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
Más generalmente, bajo 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
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