¿Cómo puedo crear un promedio a partir de una matriz Ruby?


¿Cómo conseguiría encontrar un promedio de una matriz?

Si tengo el array:

[0,4,8,2,5,0,2,6]

El promedio me daría 3.375.

Gracias!

Author: the Tin Man, 2009-08-27

16 answers

Prueba esto:

arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5

Tenga en cuenta el .to_f, que usted 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 Array como otro comentarista ha sugerido, pero debe evitar la división de enteros o sus resultados serán incorrectos. Además, esto no es generalmente aplicable a todos los tipos de elementos posibles (obviamente, un promedio solo tiene sentido para las 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 has visto inject antes, no es tan mágico como podría parecer. Itera sobre cada elemento y luego le aplica un valor acumulador. El acumulador es entonces entregado al siguiente elemento. En este caso, nuestro acumulador es simplemente un entero que refleja la suma de todos los elementos anteriores.

Edit: El comentarista Dave Ray propuso una buena mejora.

Editar: La propuesta del comentarista Glenn Jackman, usando arr.inject(:+).to_f, también es agradable, pero tal vez un poco demasiado inteligente si no sabes lo que está pasando. El :+ es un símbolo; cuando se pasa a inject, aplica el método nombrado por el símbolo (en este caso, la operación de adición) a cada elemento contra el valor acumulador.

 230
Author: John Feminella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-10 14:40:33
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 usa instance_eval sería:

a = [0,4,8,2,5,0,2,6]
a.reduce(:+) / a.size.to_f #=> 3.375
 100
Author: Corban Brook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-07 14:31:22

Creo que la respuesta más simple es

list.reduce(:+).to_f / list.size
 88
Author: Shu Wu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-24 20:16:57

Esperaba Matemáticas.promedio (valores), pero no hay tal suerte.

values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
 42
Author: Denny Abraham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-02 13:05:46

Ruby versions >= 2.4 tiene un método Enumerable#sum.

Y para obtener el promedio de coma flotante, puede usar Entero#fdiv

arr = [0,4,8,2,5,0,2,6]

arr.sum.fdiv(arr.size)
# => 3.375
 14
Author: Santhosh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-01 08:10:26

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
 4
Author: Boris Stitnicky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-07 10:37:14

Permítanme traer algo a la competencia que resuelve la división por cero problema:

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

Debo admitir, sin embargo, que "try" 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 es cero. El promedio de nada es nada, no 0. Así que ese es el comportamiento esperado. Sin embargo, si cambia a:

class Array;def avg;reduce(0.0,:+).try(:/,size);end;end

El resultado para matrices vacías no será una excepción como esperaba, sino que devuelve NaN... He nunca había visto eso en Ruby. ;- ) Parece ser un comportamiento especial de la clase Float...

0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float
 4
Author: hurikhan77,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-21 11:56:56

Algunos benchmarking de las mejores soluciones (en orden de las más eficientes):

Gran matriz:

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)

Pequeños arreglos:

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)
 4
Author: mr.musicman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-23 14:36:39
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
 3
Author: astropanic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-23 11:57:02

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.tamaño al final.

Para resolver esto puramente funcionalmente necesitamos llevar un registro 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 un array, podríamos usar desestructuring 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, añade algunos puts:

[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(:/)
 3
Author: bjelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-02 12:22:13

No tiene ruby en este pc, pero algo hasta este punto debería funcionar:

values = [0,4,8,2,5,0,2,6]
total = 0.0
values.each do |val|
 total += val
end

average = total/values.size
 2
Author: saret,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-28 07:53:04
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
 1
Author: erik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-27 14:01:37
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
 1
Author: Matt Stevens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-21 21:44:24

arr = [0,4,8,2,5,0,2,6] average = arr.inject(&:+).to_f / arr.size => 3.375

 1
Author: Rahul Patel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-28 13:29:02

Podrías intentar algo como lo siguiente:

2.0.0-p648 :009 >   a = [1,2,3,4,5]
 => [1, 2, 3, 4, 5]
2.0.0-p648 :010 > (a.sum/a.length).to_f
 => 3.0
 0
Author: Paul Marclay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-07 17:17:19
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize

Corto pero usando la variable de instancia

 -1
Author: Alex Leschenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-01 20:10:04