Wie würde ein Durchschnitt aus einem Array ermittelt werden?
Wenn ich das Array habe:
[0,4,8,2,5,0,2,6]
Eine Mittelwertbildung würde mir 3,375 ergeben.
Vielen Dank!
Versuche dies:
arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5
Beachten Sie den .to_f
, den Sie benötigen, um Probleme mit der 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 Array
definieren, da ein anderer Kommentator vorgeschlagen hat, aber Sie müssen eine Division mit Ganzzahlen vermeiden, da sonst die Ergebnisse falsch sind. Dies gilt auch nicht generell für alle möglichen Elementtypen (offensichtlich ist ein Durchschnitt nur für Dinge geeignet, die gemittelt werden können). Wenn Sie diesen Weg jedoch 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 inject
noch nicht 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 Verbesserung von Nice vor.
Edit: Kommentator Glenn Jackmans Vorschlag, arr.inject(:+).to_f
zu verwenden, ist auch nett, aber vielleicht ein bisschen zu schlau, wenn Sie nicht wissen, was los ist. Der :+
ist ein Symbol; Bei der Übergabe an inject wendet es die mit dem Symbol bezeichnete Methode (in diesem Fall die Additionsoperation) 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 davon, 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 habe 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
Ruby-Versionen> = 2.4 haben eine Enumerable # sum -Methode.
Und um Fließkommazahlen 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 ältere Versionen:
arr.reduce(:+).fdiv(arr.size)
# => 3.375
Lassen Sie mich etwas in Konkurrenz 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 Helfer von Rails ist. Sie können das aber 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 ist das erwartete Verhalten. Wenn Sie jedoch wechseln zu:
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 ... Ich habe das noch nie in Ruby gesehen. ;-) Scheint ein besonderes Verhalten der Float-Klasse zu sein ...
0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float
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
Einige Benchmarks für Top-Lösungen (in der Reihenfolge der effizientesten):
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)
was mir an der akzeptierten Lösung nicht gefällt
arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5
ist, dass es nicht wirklich auf rein funktionelle Weise 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 verbesserte diese Lösung: Anstatt dass das Argument r ein Array ist, 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(:/)
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
Sie haben keinen Ruby auf diesem PC, aber etwas in diesem Umfang 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
a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : a.reduce(:+)/a.size.to_f
=> 3.375
Löst die Division durch Null und eine ganzzahlige Division und ist leicht zu lesen. Kann leicht geändert werden, wenn Sie ein leeres Array mit 0 zurückgeben möchten.
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
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
Array#average
hinzufügen.Ich habe dasselbe ziemlich oft gemacht, deshalb hielt ich es für ratsam, die Klasse Array
nur mit einer einfachen Methode average
zu erweitern. Es funktioniert nur für ein Array von Zahlen wie Ganzzahlen, Gleitkommazahlen oder Dezimalzahlen, aber es ist praktisch, wenn Sie es richtig verwenden.
Ich verwende Ruby on Rails, also habe ich dies in config/initializers/array.rb
platziert, aber Sie können es überall platzieren, wo es beim Booten enthalten ist usw.
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
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])
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