web-dev-qa-db-fra.com

Comment créer une moyenne à partir d'un tableau Ruby?

Comment pourrait-on trouver une moyenne d'un tableau?

Si j'ai le tableau:

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

La moyenne me donnerait 3,375.

Merci!

188
dotty

Essaye ça:

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

Notez le .to_f, que vous voudrez pour éviter tout problème lié à la division des nombres entiers. Vous pouvez aussi faire:

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

Vous pouvez le définir dans le cadre de Array comme l'a suggéré un autre intervenant, mais vous devez éviter la division entière, sinon vos résultats seront erronés. En outre, cela ne s'applique généralement pas à tous les types d'éléments possibles (évidemment, une moyenne n'a de sens que pour les éléments pouvant être moyennés). Mais si vous voulez emprunter cette voie, utilisez ceci:

class Array
  def sum
    inject(0.0) { |result, el| result + el }
  end

  def mean 
    sum / size
  end
end

Si vous n'avez jamais vu inject auparavant, ce n'est pas aussi magique que cela puisse paraître. Il itère sur chaque élément, puis lui applique une valeur d'accumulateur. L'accumulateur est ensuite remis à l'élément suivant. Dans ce cas, notre accumulateur est simplement un entier qui reflète la somme de tous les éléments précédents.

Edit: Le commentateur Dave Ray a proposé une amélioration intéressante.

Edit: La proposition de Glenn Jackman, utilisant arr.inject(:+).to_f, est également agréable, mais peut-être un peu trop intelligente si vous ne savez pas ce qui se passe. Le :+ est un symbole; lorsqu'il est passé à injecter, il applique la méthode nommée par le symbole (dans ce cas, l'opération d'addition) à chaque élément en fonction de la valeur de l'accumulateur.

239
John Feminella
a = [0,4,8,2,5,0,2,6]
a.instance_eval { reduce(:+) / size.to_f } #=> 3.375

Une version de ceci qui n'utilise pas instance_eval serait:

a = [0,4,8,2,5,0,2,6]
a.reduce(:+) / a.size.to_f #=> 3.375
103
Corban Brook

Je crois que la réponse la plus simple est

list.reduce(:+).to_f / list.size
88
Shu Wu

J'espérais pour Math.aver (valeurs), mais pas de chance.

values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
44
Denny Abraham

Les versions Ruby> = 2.4 ont une méthode Enumerable # sum

Et pour obtenir la moyenne en virgule flottante, vous pouvez utiliser Integer # fdiv

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

arr.sum.fdiv(arr.size)
# => 3.375

Pour les anciennes versions:

arr.reduce(:+).fdiv(arr.size)
# => 3.375
23
Santhosh

Permettez-moi de mettre en concurrence quelque chose qui résout le problème de la division par zéro:

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

Cependant, je dois admettre que "essayer" est une aide de Rails. Mais vous pouvez facilement résoudre ceci:

class Object;def try(*options);self&&send(*options);end;end
class Array;def avg;reduce(:+).try(:to_f).try(:/,size);end;end

BTW: Je pense qu'il est correct que la moyenne d'une liste vide est nulle. La moyenne de rien n'est rien, pas 0. C'est donc le comportement attendu. Cependant, si vous changez pour:

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

le résultat pour les tableaux vides ne fera pas exception, comme prévu, mais renvoie NaN ... Je ne l'avais jamais vu auparavant avec Ruby. ;-) Semble être un comportement spécial de la classe Float ...

0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float
4
hurikhan77

Pour amuser le public, encore une autre solution:

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
Boris Stitnicky

Quelques analyses comparatives des meilleures solutions (dans l'ordre des plus efficaces):

Grand tableau:

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)

Petits tableaux:

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
mr.musicman

ce que je n'aime pas dans la solution acceptée

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

est que cela ne fonctionne pas vraiment de manière purement fonctionnelle . nous avons besoin d’une variable arr pour calculer arr.size à la fin.

pour résoudre ce problème de façon purement fonctionnelle, nous devons suivre deux valeurs : la somme de tous les éléments et le nombre d’éléments.

[5, 6, 7, 8].inject([0.0,0]) do |r,ele|
    [ r[0]+ele, r[1]+1 ]
end.inject(:/)
=> 6.5   

Santhosh a amélioré cette solution: au lieu que l'argument r soit un tableau, nous pourrions utiliser la déstructuration pour le séparer immédiatement en deux variables.

[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele| 
   [ sum + ele, size + 1 ]
end.inject(:/)

si vous voulez voir comment cela fonctionne, ajoutez quelques options:

[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

Nous pourrions aussi utiliser une structure au lieu d’un tableau pour contenir la somme et le nombre, mais nous devons ensuite déclarer la structure en premier:

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
bjelli
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
astropanic

Vous n'avez pas Ruby sur ce PC, mais quelque chose dans cette mesure devrait fonctionner:

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

average = total/values.size
2
saret
a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : a.reduce(:+)/a.size.to_f
=> 3.375

Résout la division par zéro, division entière et est facile à lire. Peut être facilement modifié si vous choisissez d'avoir un tableau vide qui renvoie 0.

J'aime aussi cette variante, mais elle est un peu plus verbeuse.

a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : [a.reduce(:+), a.size.to_f].reduce(:/)
=> 3.375
1
Matt Stevens
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
1
erik

Ajoutez Array#average.

Comme je faisais souvent la même chose, j’ai pensé qu’il était prudent de simplement élargir la classe Array avec une méthode simple average. Cela ne fonctionne pas pour autre chose qu'un tableau de nombres comme des entiers, des flottants ou des décimales, mais c'est pratique quand vous l'utilisez correctement.

J'utilise Ruby on Rails donc je l'ai placé dans config/initializers/array.rb, mais vous pouvez le placer n'importe où dans le démarrage, 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
1
Joshua Pinter

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

1
Rahul Patel

Cette méthode peut être utile.

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])
0
Kishor Budhathoki

Vous pouvez essayer quelque chose comme ce qui suit:

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
Paul Marclay