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!
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.
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
Je crois que la réponse la plus simple est
list.reduce(:+).to_f / list.size
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
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
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
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
Quelques analyses comparatives des meilleures solutions (dans l'ordre des plus efficaces):
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)
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(:/)
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
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
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
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
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
arr = [0,4,8,2,5,0,2,6]
average = arr.inject(&:+).to_f / arr.size
=> 3.375
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])
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