wake-up-neo.com

Wie erstelle ich einen Durchschnitt aus einem Ruby-Array?

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!

188
dotty

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.

239
John Feminella
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
103
Corban Brook

Ich glaube die einfachste Antwort ist

list.reduce(:+).to_f / list.size
88
Shu Wu

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
44
Denny Abraham

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
23
Santhosh

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
4
hurikhan77

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
4
Boris Stitnicky

Einige Benchmarks für 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)
4
mr.musicman

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(:/)
3
bjelli
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
3
astropanic

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

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
1
Joshua Pinter

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

1
Rahul Patel

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])
0

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
0
Paul Marclay