Wie erstelle ich einen Durchschnitt aus einem Ruby-Array?


208

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

Wenn ich das Array habe:

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

Die Mittelung würde mir 3,375 geben.


11
Wenn Sie 21,75 als Durchschnitt dieser Zahlen erhalten, stimmt etwas nicht ...
ceejayoz

2
dotty, nicht sicher, wie Sie 21,75 erhalten haben, aber der Durchschnitt / Mittelwert für diesen Datensatz ist 3,375 und die Summe ist 27. Ich bin nicht sicher, welche Art von Aggregationsfunktion 21,75 ergeben würde. Bitte überprüfen Sie noch einmal und stellen Sie sicher, dass der Durchschnitt wirklich das ist, wonach Sie suchen!
Paul Sasik

2
Ich habe keine Ahnung, woher ich 21.75 habe. Muss so etwas wie 0 + 48 + 2 + 5 + 0 + 2 + 6 auf dem Rechner gedrückt haben!
Dotty

16
Da dies auch als Ruby-on-Rails gekennzeichnet ist, sollten Sie aktive Datensatzberechnungen prüfen, wenn Sie ein ActiveRecord-Array mitteln. Person.average (: age ,: country => 'Brazil') gibt das Durchschnittsalter von Personen aus Brasilien zurück. Ziemlich cool!
Kyle Heironimus

Antworten:


259

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 benötigen, um Probleme durch die Ganzzahldivision zu vermeiden. Sie können auch 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 definieren, wie es ein Arrayanderer Kommentator vorgeschlagen hat, aber Sie müssen die Ganzzahldivision vermeiden, da sonst Ihre Ergebnisse falsch sind. Dies gilt auch nicht generell für jeden möglichen Elementtyp (offensichtlich macht ein Durchschnitt nur für Dinge Sinn, die gemittelt werden können). 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 noch nie gesehen injecthaben, ist es nicht so magisch, wie es scheinen mag. Es iteriert über jedes Element und wendet dann einen Akkumulatorwert darauf an. Der Akku wird dann an das nächste Element übergeben. In diesem Fall ist unser Akkumulator einfach eine Ganzzahl, die die Summe aller vorherigen Elemente widerspiegelt.

Edit: Kommentator Dave Ray schlug eine nette Verbesserung vor.

Bearbeiten: Der Vorschlag von Kommentator Glenn Jackman arr.inject(:+).to_fist auch nett, aber vielleicht ein bisschen zu klug, wenn Sie nicht wissen, was los ist. Das :+ist ein Symbol; Wenn es zum Injizieren übergeben wird, wendet es die durch das Symbol benannte Methode (in diesem Fall die Additionsoperation) auf jedes Element gegen den Akkumulatorwert an.


6
Sie können to_f und? Bediener durch Übergabe eines zu injizierenden Anfangswertes : arr.inject(0.0) { |sum,el| sum + el } / arr.size.
Dave Ray

103
Oder: arr.inject (: +). To_f / arr.size # => 3.375
Glenn Jackman

5
Ich denke nicht, dass dies das Hinzufügen zur Array-Klasse rechtfertigt, da es nicht auf alle Typen verallgemeinerbar ist, die Arrays enthalten können.
Sarah Mei

8
@ John: Das ist nicht genau die Konvertierung von Symbol # in_proc - es ist Teil der injectSchnittstelle, die in der Dokumentation erwähnt wird. Der to_procBetreiber ist &.
Chuck

21
Wenn Sie Rails verwenden, Array#injectist dies hier übertrieben. Einfach benutzen #sum. ZBarr.sum.to_f / arr.size
Nickh

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

Eine Version davon, die nicht verwendet wird, instance_evalwäre:

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

4
Ich finde es nicht zu klug. Ich denke, es löst das Problem idiomatisch. Dh es wird reduziert, was genau richtig ist. Programmierer sollten ermutigt werden, zu verstehen, was richtig ist, warum es richtig ist, und sich dann zu verbreiten. Für eine triviale Operation wie durchschnittlich, wahr, muss man nicht "klug" sein. Wenn man jedoch versteht, was "reduzieren" für einen trivialen Fall ist, kann man damit beginnen, es auf viel komplexere Probleme anzuwenden. upvote.
pduey

3
warum die Notwendigkeit für instance_eval hier?
tybro0103

10
instance_evalMit dieser Option können Sie den Code ausführen, während Sie ihn nur aeinmal angeben , damit er mit anderen Befehlen verkettet werden kann. Dh random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f } stattrandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
Benjamin Manns

2
Ich weiß nicht, dass die Verwendung von instance_eval auf diese Weise nur seltsam erscheint, und es sind viele Fallstricke damit verbunden, die diesen Ansatz zu einer schlechten Idee machen, IMO. (Wenn Sie beispielsweise versucht haben, auf eine Instanzvariable oder eine Methode selfin diesem Block zuzugreifen , treten Probleme auf.) instance_evalDies gilt eher für Metaprogrammierung oder DSL.
Ajedi32

1
@ Ajedi32 Ich stimme zu, verwenden Sie dies nicht in Ihrem Anwendungscode. Es war jedoch sehr schön, in meine Antwort einfügen zu können (:
animiertes

94

Ich glaube, die einfachste Antwort ist

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

1
Ich habe einen Moment gebraucht , um es zu finden - reduceist eine Methode des EnumerableMixins von Array. Und trotz seines Namens stimme ich dem @ShuWu zu ... es sei denn, Sie verwenden Rails, die implementiert werden sum.
Tom Harrison

Ich sehe hier Lösungen, von denen ich weiß, dass sie sehr ordentlich aussehen, aber ich fürchte, wenn ich meinen Code in Zukunft lese, werden sie Kauderwelsch mögen. Danke für die saubere Lösung!
Atmosx

Auf meinem System ist dies 3x schneller als die akzeptierte Antwort.
Sergio

48

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

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

3
Ich wusste nicht, dass #sum von Rails hinzugefügt wurde! Vielen Dank für den Hinweis.
Denny Abraham

11
Nach Weihnachten 2016 (Rubin 2.4), Array wird eine hat sumMethode, so ist dies eine richtige Antwort nach 6 Jahren zu sein scheint, würdig der Nostradamus Auszeichnung.
Steenslag


9

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)

Ihr Benchmark ist ein bisschen falsch. Benchmark / IP ist für diese Art von Vergleichen tatsächlich besser. Außerdem würde ich vorschlagen, ein Array zu verwenden, das zufällig mit negativen und positiven Zahlen sowie Gleitkommazahlen gefüllt ist, um ein realistischeres Ergebnis zu erzielen. Sie werden feststellen, dass instance_eval langsamer als array.sum.fdiv ist. Um ca. 8x für Schwimmer. und ungefähr x1.12 für ganze Zahlen. Außerdem führen unterschiedliche Betriebssysteme zu unterschiedlichen Ergebnissen. Auf meinem Mac sind einige dieser Methoden zweimal so langsam wie auf meinem Linux Droplet
konung

Auch die Summenmethode verwendet die Gaußsche Formel für Bereiche, anstatt die Summe zu berechnen.
Santhosh

4
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

2
Dies gibt aufgrund der Ganzzahldivision falsche Werte zurück. Versuchen Sie es zum Beispiel mit [2,3] .mean, das 2 statt 2,5 zurückgibt.
John Feminella

1
Warum sollte ein leeres Array eine Summe von nil0 haben?
Andrew Grimm

1
Weil Sie den Unterschied zwischen [] und [0] erhalten können. Und ich denke, jeder, der einen echten Mittelwert will, kann to_i verwenden oder die obige Null durch eine 0 ersetzen
astropanic

4

Lassen Sie mich etwas in den Wettbewerb bringen, das das Problem der Division durch Null löst:

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 jedoch zugeben, dass "try" ein Rails-Helfer ist. Aber Sie können dies 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

Übrigens: Ich denke, es ist richtig, dass der Durchschnitt einer leeren Liste Null ist. Der Durchschnitt von nichts ist nichts, nicht 0. Das ist also erwartetes Verhalten. Wenn Sie jedoch zu Folgendem wechseln:

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

Das Ergebnis für leere Arrays ist keine Ausnahme, wie ich erwartet hatte, sondern gibt NaN zurück ... Das habe ich in Ruby noch nie gesehen. ;-) Scheint ein besonderes Verhalten der Float-Klasse zu sein ...

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

4

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 brauchen 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 hat diese Lösung verbessert: Anstatt das Argument r als Array zu verwenden, könnten wir die Destrukturierung verwenden, um es sofort in zwei Variablen zu zerlegen

[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(:/)

Dies ist das erste Mal, dass ich end.methodin Rubin verwendet sehe , danke dafür!
Epigene

Das an die Injektionsmethode übergebene Array kann verteilt werden. arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Santhosh

@ Santhosh: Ja, das ist viel besser lesbar! Ich würde dies jedoch nicht als "Zerstreuen" bezeichnen, sondern als "Zerstören". Tony.pitluga.com/2011/08/08/destructuring-with-ruby.html
bjelli

3

Zur öffentlichen 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

1
Wenn dies bei der Abstimmung höher gewesen wäre, hätte ich es nicht verstanden! Sehr gut.
Matt Stevens

Klar ist besser als klug , dieser Code ist nicht klar.
Sebastian Palma

2

Haben Sie keinen Rubin 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

2

Hinzufügen Array#average .

Ich habe ziemlich oft das Gleiche gemacht, deshalb fand ich es klug, die ArrayKlasse nur mit einem einfachen zu erweiternaverage Methode zu erweitern. Es funktioniert nur für ein Array von Zahlen wie Integer oder Floats oder Decimals, aber es ist praktisch, wenn Sie es richtig verwenden.

Ich verwende Ruby on Rails, also habe ich dies platziert, config/initializers/array.rbaber Sie können es überall platzieren, was beim Booten usw. enthalten ist.

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
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length

4
Dies gibt aufgrund der Ganzzahldivision falsche Werte zurück. Wenn zum Beispiel a [2, 3] ist, ist das erwartete Ergebnis 2,5, aber Sie werden 2 zurückgeben.
John Feminella

1
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, Integer Division und ist leicht zu lesen. Kann leicht geändert werden, wenn ein leeres Array 0 zurückgibt.

Ich mag diese Variante auch, aber sie ist etwas wortreicher.

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

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

1

Diese Methode kann hilfreich sein.

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
Willkommen zum Stapelüberlauf hier Original Poster der Frage möchte die Antwort als 3.375 und Ihre Lösung ergibt 3. i, e 27/8 = 3.
Ajay Barot

Vielen Dank für Ihre Kommentare. Ich weiß, dass das Originalplakat der Frage als 3.375 beantwortet werden soll, und genau das macht diese Methode, da ich der Variablen 'var' einen Gleitkommawert (dh 0,0) gegeben habe. Munim Munna Ich muss dir zustimmen, es gibt in der Tat eine ähnliche Ans.
Kishor Budhathoki

0

Ohne das Array wiederholen zu müssen (zB perfekt für Einzeiler):

[1, 2, 3, 4].then { |a| a.sum.to_f / a.size }

-1
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize

Kurz, aber mit Instanzvariable


2
Ich würde a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_sizelieber eine Instanzvariable erstellen.
Andrew Grimm

-1

Sie könnten Folgendes ausprobieren:

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.