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

Comment 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 éviter pour éviter les problèmes de division en 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 comme partie d’un Array comme l’a suggéré un autre commentateur, mais vous devez éviter la division en nombres entiers ou vos résultats seront erronés. De plus, 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 choses qui peuvent être moyennées). Mais si vous voulez aller dans 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 , ce n’est pas aussi magique que cela puisse paraître. 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 sum de tous les éléments précédents.

Edit: Commenter Dave Ray a proposé une belle amélioration.

Edit: La proposition du arr.inject(:+).to_f 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’elle est passée à injecter, elle applique la méthode nommée par le symbole (dans ce cas, l’opération d’ajout) à chaque élément par rapport à 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 ce 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.average (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 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 l’amusement 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 

Permettez-moi de mettre quelque chose en concurrence 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 avouer, cependant, que “essayer” est une aide de 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 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 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 l’avais prévu mais à la place, il renvoie NaN … Je n’ai jamais vu ça 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 
 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 

Ne pas avoir 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 

Quelques benchmarking 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) 

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 la taille à la fin.

pour résoudre ce problème de manière purement fonctionnelle, nous devons suivre deux valeurs: la sum 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 la 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 sum et le compte, mais nous devons d’abord déclarer la structure:

 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(:/) 
 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, la division entière et est facile à lire. Peut être facilement modifié si vous choisissez de retourner un tableau vide 0.

J’aime aussi cette variante, mais c’est un peu plus verbeux.

 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

Vous pourriez essayer quelque chose comme ceci:

 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 
 [1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize 

Courte mais en utilisant une variable d’instance