¿Cómo conseguiría encontrar un promedio de una matriz?
Si tengo la matriz:
[0,4,8,2,5,0,2,6]
El promedio me daría 3.375.
¿Cómo conseguiría encontrar un promedio de una matriz?
Si tengo la matriz:
[0,4,8,2,5,0,2,6]
El promedio me daría 3.375.
Respuestas:
Prueba esto:
arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5
Tenga en cuenta .to_f
lo que querrá para evitar cualquier problema de la división de enteros. También puedes hacer:
arr = [5, 6, 7, 8]
arr.inject(0.0) { |sum, el| sum + el } / arr.size
=> 6.5
Puede definirlo como parte de lo Array
que sugirió otro comentarista, pero debe evitar la división de enteros o sus resultados serán incorrectos. Además, esto generalmente no es aplicable a todos los tipos de elementos posibles (obviamente, un promedio solo tiene sentido para cosas que se pueden promediar). Pero si quieres ir por esa ruta, usa esto:
class Array
def sum
inject(0.0) { |result, el| result + el }
end
def mean
sum / size
end
end
Si no lo has visto inject
antes, no es tan mágico como podría parecer. Se itera sobre cada elemento y luego le aplica un valor acumulador. El acumulador se entrega al siguiente elemento. En este caso, nuestro acumulador es simplemente un número entero que refleja la suma de todos los elementos anteriores.
Editar: El comentarista Dave Ray propuso una buena mejora.
Editar: La propuesta del comentarista Glenn Jackman, usando arr.inject(:+).to_f
, también es buena, pero tal vez un poco demasiado inteligente si no sabes lo que está sucediendo. El :+
es un símbolo; cuando se pasa a inyectar, aplica el método nombrado por el símbolo (en este caso, la operación de suma) a cada elemento contra el valor del acumulador.
arr.inject(0.0) { |sum,el| sum + el } / arr.size
.
inject
interfaz, mencionada en la documentación. El to_proc
operador es &
.
Array#inject
es excesivo aquí. Solo úsalo #sum
. Por ejemploarr.sum.to_f / arr.size
a = [0,4,8,2,5,0,2,6]
a.instance_eval { reduce(:+) / size.to_f } #=> 3.375
Una versión de esto que no use instance_eval
sería:
a = [0,4,8,2,5,0,2,6]
a.reduce(:+) / a.size.to_f #=> 3.375
instance_eval
le permite ejecutar el código mientras solo especifica a
una vez, para que pueda encadenarse con otros comandos. Es decir, en random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f }
lugar derandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
self
dentro de ese bloque, te encontrarás con problemas). instance_eval
Es más para metaprogramación o DSL.
Creo que la respuesta más simple es
list.reduce(:+).to_f / list.size
reduce
es un método de Enumerable
mezcla utilizado por Array
. Y a pesar de su nombre, estoy de acuerdo con @ShuWu ... a menos que esté usando Rails que implementa sum
.
Esperaba Math.average (valores), pero no tuve tanta suerte.
values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
sum
método, por lo que parece ser una respuesta correcta después de 6 años, digna del premio Nostradamus.
Las versiones de Ruby> = 2.4 tienen un método de suma # Enumerable .
Y para obtener el promedio de coma flotante, puede usar Integer # fdiv
arr = [0,4,8,2,5,0,2,6]
arr.sum.fdiv(arr.size)
# => 3.375
Para versiones anteriores:
arr.reduce(:+).fdiv(arr.size)
# => 3.375
Algunos puntos de referencia de las mejores soluciones (en orden de la más eficiente):
array = (1..10_000_000).to_a
Benchmark.bm do |bm|
bm.report { array.instance_eval { reduce(:+) / size.to_f } }
bm.report { array.sum.fdiv(array.size) }
bm.report { array.sum / array.size.to_f }
bm.report { array.reduce(:+).to_f / array.size }
bm.report { array.reduce(:+).try(:to_f).try(:/, array.size) }
bm.report { array.inject(0.0) { |sum, el| sum + el }.to_f / array.size }
bm.report { array.reduce([ 0.0, 0 ]) { |(s, c), e| [ s + e, c + 1 ] }.reduce(:/) }
end
user system total real
0.480000 0.000000 0.480000 (0.473920)
0.500000 0.000000 0.500000 (0.502158)
0.500000 0.000000 0.500000 (0.508075)
0.510000 0.000000 0.510000 (0.512600)
0.520000 0.000000 0.520000 (0.516096)
0.760000 0.000000 0.760000 (0.767743)
1.530000 0.000000 1.530000 (1.534404)
array = Array.new(10) { rand(0.5..2.0) }
Benchmark.bm do |bm|
bm.report { 1_000_000.times { array.reduce(:+).to_f / array.size } }
bm.report { 1_000_000.times { array.sum / array.size.to_f } }
bm.report { 1_000_000.times { array.sum.fdiv(array.size) } }
bm.report { 1_000_000.times { array.inject(0.0) { |sum, el| sum + el }.to_f / array.size } }
bm.report { 1_000_000.times { array.instance_eval { reduce(:+) / size.to_f } } }
bm.report { 1_000_000.times { array.reduce(:+).try(:to_f).try(:/, array.size) } }
bm.report { 1_000_000.times { array.reduce([ 0.0, 0 ]) { |(s, c), e| [ s + e, c + 1 ] }.reduce(:/) } }
end
user system total real
0.760000 0.000000 0.760000 (0.760353)
0.870000 0.000000 0.870000 (0.876087)
0.900000 0.000000 0.900000 (0.901102)
0.920000 0.000000 0.920000 (0.920888)
0.950000 0.000000 0.950000 (0.952842)
1.690000 0.000000 1.690000 (1.694117)
1.840000 0.010000 1.850000 (1.845623)
class Array
def sum
inject( nil ) { |sum,x| sum ? sum+x : x }
end
def mean
sum.to_f / size.to_f
end
end
[0,4,8,2,5,0,2,6].mean
nil
lugar de 0?
Permítanme traer algo a la competencia que resuelve el problema de división por cero:
a = [1,2,3,4,5,6,7,8]
a.reduce(:+).try(:to_f).try(:/,a.size) #==> 4.5
a = []
a.reduce(:+).try(:to_f).try(:/,a.size) #==> nil
Sin embargo, debo admitir que "intentar" es un ayudante de Rails. Pero puedes resolver esto fácilmente:
class Object;def try(*options);self&&send(*options);end;end
class Array;def avg;reduce(:+).try(:to_f).try(:/,size);end;end
Por cierto: creo que es correcto que el promedio de una lista vacía sea nulo. El promedio de nada es nada, no 0. Entonces, ese es el comportamiento esperado. Sin embargo, si cambia a:
class Array;def avg;reduce(0.0,:+).try(:/,size);end;end
el resultado para las matrices vacías no será una excepción como esperaba, sino que devuelve NaN ... Nunca lo había visto antes en Ruby. ;-) Parece ser un comportamiento especial de la clase Float ...
0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float
lo que no me gusta de la solución aceptada
arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5
es que realmente no funciona de una manera puramente funcional. necesitamos una variable arr para calcular arr.size al final.
Para resolver esto de manera puramente funcional, necesitamos hacer un seguimiento de dos valores: la suma de todos los elementos y el número de elementos.
[5, 6, 7, 8].inject([0.0,0]) do |r,ele|
[ r[0]+ele, r[1]+1 ]
end.inject(:/)
=> 6.5
Santhosh mejoró esta solución: en lugar de que el argumento r sea una matriz, podríamos usar la desestructuración para separarlo inmediatamente en dos variables
[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele|
[ sum + ele, size + 1 ]
end.inject(:/)
si quieres ver cómo funciona, agrega algunas opciones:
[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele|
r2 = [ sum + ele, size + 1 ]
puts "adding #{ele} gives #{r2}"
r2
end.inject(:/)
adding 5 gives [5.0, 1]
adding 6 gives [11.0, 2]
adding 7 gives [18.0, 3]
adding 8 gives [26.0, 4]
=> 6.5
También podríamos usar una estructura en lugar de una matriz para contener la suma y el recuento, pero luego tenemos que declarar la estructura primero:
R=Struct.new(:sum, :count)
[5, 6, 7, 8].inject( R.new(0.0, 0) ) do |r,ele|
r.sum += ele
r.count += 1
r
end.inject(:/)
end.method
en rubí, ¡gracias por esto!
arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Para diversión pública, otra solución más:
a = 0, 4, 8, 2, 5, 0, 2, 6
a.reduce [ 0.0, 0 ] do |(s, c), e| [ s + e, c + 1 ] end.reduce :/
#=> 3.375
Array#average
.Estaba haciendo lo mismo con bastante frecuencia, así que pensé que era prudente extender la Array
clase con un average
método simple . No funciona para nada más que una matriz de números como enteros o flotantes o decimales, pero es útil cuando lo usa correctamente.
Estoy usando Ruby on Rails, así que coloqué esto config/initializers/array.rb
pero puedes colocarlo en cualquier lugar que esté incluido en el arranque, etc.
config/initializers/array.rb
class Array
# Will only work for an Array of numbers like Integers, Floats or Decimals.
#
# Throws various errors when trying to call it on an Array of other types, like Strings.
# Returns nil for an empty Array.
#
def average
return nil if self.empty?
self.sum / self.size
end
end
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : a.reduce(:+)/a.size.to_f
=> 3.375
Resuelve dividir por cero, división entera y es fácil de leer. Se puede modificar fácilmente si elige que una matriz vacía devuelva 0.
También me gusta esta variante, pero es un poco más prolija.
a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : [a.reduce(:+), a.size.to_f].reduce(:/)
=> 3.375
arr = [0,4,8,2,5,0,2,6]
average = arr.inject(&:+).to_f / arr.size
# => 3.375
Este método puede ser útil.
def avg(arr)
val = 0.0
arr.each do |n|
val += n
end
len = arr.length
val / len
end
p avg([0,4,8,2,5,0,2,6])
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize
Corto pero usando variable de instancia
a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size
lugar de crear una variable de instancia.
Podría intentar algo como lo siguiente:
a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0