Wie erstelle ich einen Durchschnitt von einem Ruby-Array?

Wie würde man einen Durchschnitt von einem Array finden?

Wenn ich das Array habe:

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

Mittelwertbildung würde mir 3.375 geben.

Vielen Dank!

Versuche dies:

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

Beachten Sie die .to_f , die Sie zur Vermeidung von Problemen mit der Ganzzahl-Division .to_f . Sie können auch Folgendes tun:

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

Sie können es als Teil von Array wie ein anderer Kommentator vorgeschlagen hat, aber Sie müssen eine Ganzzahl-Division vermeiden oder Ihre Ergebnisse werden falsch sein. Dies gilt auch nicht generell für jeden möglichen Elementtyp (offensichtlich macht ein Durchschnitt nur Sinn für Dinge, die gemittelt werden können). Aber wenn Sie diesen Weg gehen möchten, verwenden Sie Folgendes:

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

Wenn Sie vorher noch keine inject gesehen haben, ist es nicht so magisch, wie es erscheinen mag. Es iteriert über jedes Element und wendet dann einen Akkumulatorwert an. Der Akkumulator wird dann an das nächste Element übergeben. In diesem Fall ist unser Akkumulator einfach eine ganze Zahl, die die Summe aller vorherigen Elemente widerspiegelt.

Edit: Kommentator Dave Ray schlug eine nette Verbesserung vor.

Edit: Commenter Glenn Jackmans Vorschlag, mit arr.inject(:+).to_f , ist auch nett, aber vielleicht ein bisschen zu schlau, wenn Sie nicht wissen, was los ist. Das :+ ist ein Symbol; Wenn es an inject übergeben wird, wendet es die durch das Symbol (in diesem Fall die Additionsoperation) bezeichnete Methode auf jedes Element gegen den Akkumulatorwert an.

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

Eine Version von dieser, die instance_eval nicht verwendet, wäre:

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

Ich glaube, die einfachste Antwort ist

 list.reduce(:+).to_f / list.size 

Ich hatte auf Math.verglichen (Werte) gehofft, aber kein solches Glück.

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

Ruby-Versionen> = 2.4 hat eine Enumerable # sum- Methode.

Um den Fließkommawert zu erhalten, können Sie Integer # fdiv verwenden

 arr = [0,4,8,2,5,0,2,6] arr.sum.fdiv(arr.size) # => 3.375 

Für die öffentliche Unterhaltung, noch eine andere Lösung:

 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 

Lassen Sie mich etwas in Konkurrenz bringen, das das Problem der Division durch Null triggers:

 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 

Ich muss allerdings zugeben, dass “try” ein Rails-Helfer ist. Aber Sie können das leicht lösen:

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

BTW: Ich denke, es ist richtig, dass der Durchschnitt einer leeren Liste gleich Null ist. Der Durchschnitt von nichts ist nichts, nicht 0. Also das ist das erwartete Verhalten. Wenn Sie jedoch zu:

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

Das Ergebnis für leere Arrays wird keine Ausnahme sein, wie ich erwartet hatte, stattdessen gibt es NaN zurück … Das habe ich noch nie in Ruby gesehen. 😉 Scheint ein besonderes Verhalten der Float-class zu sein …

 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 

Einige Benchmarking von Top-Lösungen (in der Reihenfolge der effizientesten):

Großes Array:

 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) 

Kleine Arrays:

 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) 

Habe keinen Ruby auf diesem PC, aber etwas in diesem Ausmaß sollte funktionieren:

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

Was ich an der akzeptierten Lösung nicht mag

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

ist, dass es nicht wirklich rein funktional funktioniert. Wir benötigen eine Variable arr, um arr.size am Ende zu berechnen.

Um dies rein funktional zu lösen, müssen wir zwei Werte verfolgen: die Summe aller Elemente und die Anzahl der Elemente.

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

Santhosh verbesserte diese Lösung: Anstatt das Argument r als Array zu verwenden, könnten wir das Destrukturieren verwenden, um es sofort in zwei Variablen aufzuspalten

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

Wenn Sie sehen möchten, wie es funktioniert, fügen Sie einige Puts hinzu:

 [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 

Wir könnten auch eine Struktur anstelle eines Arrays verwenden, um die Summe und die Anzahl zu enthalten, aber dann müssen wir zuerst die Struktur deklarieren:

 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 

Löst Division durch Null, ganzzahlige Division und ist leicht zu lesen. Kann leicht geändert werden, wenn Sie sich für ein leeres Array entscheiden, geben Sie 0 zurück.

Ich mag diese Variante auch, aber es ist ein bisschen wortreicher.

 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

Sie könnten etwas wie das folgende versuchen:

 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