Comment trouver une moyenne à partir d'un tableau?
Si j'ai le tableau:
[0,4,8,2,5,0,2,6]
La moyenne me donnerait 3,375.
Comment trouver une moyenne à partir d'un tableau?
Si j'ai le tableau:
[0,4,8,2,5,0,2,6]
La moyenne me donnerait 3,375.
Réponses:
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 de division entière. Vous pouvez également faire:
arr = [5, 6, 7, 8]
arr.inject(0.0) { |sum, el| sum + el } / arr.size
=> 6.5
Vous pouvez le définir comme faisant partie d' Array
un autre commentateur l'a suggéré, mais vous devez éviter la division entière ou vos résultats seront erronés. En outre, cela n'est généralement pas applicable à tous les types d'éléments possibles (évidemment, une moyenne n'a de sens que pour les choses qui peuvent être moyennées). Mais si vous voulez suivre cette voie, utilisez ceci:
class Array
def sum
inject(0.0) { |result, el| result + el }
end
def mean
sum / size
end
end
Si vous ne l'avez pas vu inject
auparavant, ce n'est pas aussi magique qu'il y paraît. Il itère sur chaque élément et lui applique ensuite 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 belle amélioration.
Edit: La proposition du commentateur Glenn Jackman, en utilisant arr.inject(:+).to_f
, est bien aussi mais peut-être un peu trop intelligente si vous ne savez pas ce qui se passe. Le :+
est un symbole; lorsqu'il est passé à l'injection, il applique la méthode nommée par le symbole (dans ce cas, l'opération d'addition) à chaque élément par rapport à la valeur de l'accumulateur.
arr.inject(0.0) { |sum,el| sum + el } / arr.size
.
inject
interface, mentionnée dans la documentation. L' to_proc
opérateur est &
.
Array#inject
c'est exagéré ici. Utilisez simplement #sum
. Par exemplearr.sum.to_f / arr.size
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
instance_eval
vous permet d'exécuter le code en ne spécifiant a
qu'une seule fois, afin qu'il puisse être enchaîné avec d'autres commandes. C'est-à-dire random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f }
au lieu derandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
self
intérieur de ce bloc, vous rencontrez des problèmes.) instance_eval
Est plus pour la métaprogrammation ou DSL.
Je crois que la réponse la plus simple est
list.reduce(:+).to_f / list.size
reduce
est une méthode de Enumerable
mixage utilisée par Array
. Et malgré son nom, je suis d'accord avec le @ShuWu ... sauf si vous utilisez Rails qui implémente sum
.
J'espérais pour Math.average (valeurs), mais pas de chance.
values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
sum
méthode, donc cela semble être une réponse correcte après 6 ans, digne du prix Nostradamus.
Les versions Ruby> = 2.4 ont une méthode de somme Enumerable # .
Et pour obtenir une moyenne à 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
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)
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
plutôt que 0?
Permettez-moi de mettre quelque chose en compétition 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
Je dois admettre, cependant, que «essayer» est un assistant Rails. Mais vous pouvez facilement résoudre ce problème:
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 exact 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 passez à:
class Array;def avg;reduce(0.0,:+).try(:/,size);end;end
le résultat pour les tableaux vides ne sera pas une exception comme je m'y attendais, mais à la place, il renvoie NaN ... Je n'ai jamais vu cela auparavant dans Ruby. ;-) Semble être un comportement spécial de la classe Float ...
0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float
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
c'est qu'il 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 manière purement fonctionnelle, nous devons garder une trace de 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 ça marche, ajoutez quelques put:
[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 également 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(:/)
end.method
utilisée en rubis, merci pour ça!
arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Pour le plaisir du 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
Array#average
.Je faisais la même chose assez souvent, donc j'ai pensé qu'il était prudent de simplement étendre la Array
classe avec une average
méthode simple . Cela ne fonctionne pas pour autre chose qu'un tableau de nombres comme des entiers ou des flottants ou des décimales, mais c'est pratique lorsque vous l'utilisez correctement.
J'utilise Ruby on Rails, donc je l'ai placé config/initializers/array.rb
mais vous pouvez le placer n'importe où qui est inclus au 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
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
Résout la division par zéro, division entière et est facile à lire. Peut être facilement modifié si vous choisissez qu'un tableau vide 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
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])
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize
Courte mais utilisant une variable d'instance
a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size
plutôt que de créer une variable d'instance.
Vous pouvez essayer quelque chose comme ceci:
a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0