Jak utworzyć średnią z tablicy Ruby?


209

Jak znaleźć średnią z tablicy?

Jeśli mam tablicę:

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

Uśrednianie dałoby mi 3,375.


11
Jeśli otrzymujesz 21,75 jako średnią z tych liczb, coś jest bardzo nie tak ...
ceejayoz

2
dotty, nie jestem pewien, jak otrzymałeś 21,75, ale średnia / średnia dla tego zestawu danych wynosi 3,375, a suma wynosi 27. nie jestem pewien, jaki rodzaj funkcji agregacji dałby 21,75. Proszę dokładnie sprawdzić i upewnić się, że średnia jest naprawdę tym, czego szukasz!
Paul Sasik

2
Nie mam pojęcia, skąd wziąłem 21,75. Musiałem nacisnąć coś takiego jak 0 + 48 + 2 + 5 + 0 + 2 + 6 na kalkulatorze!
dotty

16
Ponieważ jest to również oznaczone jako rubin na szynach, warto sprawdzić aktywne obliczenia rekordów, jeśli uśredniasz tablicę ActiveRecord. Osoba. Średnia (: wiek,: kraj => „Brazylia”) zwraca średni wiek osób z Brazylii. Całkiem fajne!
Kyle Heironimus

Odpowiedzi:


259

Spróbuj tego:

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

Zwróć uwagę na to .to_f, co chcesz uniknąć problemów z dzieleniem liczb całkowitych. Możesz także:

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

Możesz zdefiniować go jako część, Arrayjak sugerował inny komentator, ale musisz unikać dzielenia liczb całkowitych, w przeciwnym razie wyniki będą błędne. Ponadto nie ma to ogólnie zastosowania do każdego możliwego typu elementu (oczywiście średnia ma sens tylko dla rzeczy, które można uśrednić). Ale jeśli chcesz wybrać tę trasę, skorzystaj z tego:

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

  def mean 
    sum / size
  end
end

Jeśli jeszcze tego nie widziałeś inject, nie jest to tak magiczne, jak mogłoby się wydawać. Iteruje po każdym elemencie, a następnie stosuje do niego wartość akumulatora. Akumulator jest następnie przekazywany do następnego elementu. W tym przypadku nasz akumulator jest po prostu liczbą całkowitą, która odzwierciedla sumę wszystkich poprzednich elementów.

Edycja: Komentator Dave Ray zaproponował miłą poprawę.

Edycja: Używanie propozycji komentatora Glenna Jackmana arr.inject(:+).to_fjest również miłe, ale może zbyt sprytne, jeśli nie wiesz, co się dzieje. To :+jest symbol; przekazany do wtrysku, stosuje metodę nazwaną przez symbol (w tym przypadku operację dodawania) do każdego elementu w stosunku do wartości akumulatora.


6
Możesz wyeliminować to_f i? Operator przepuszczając wartości początkowej do wstrzykiwania: arr.inject(0.0) { |sum,el| sum + el } / arr.size.
Dave Ray

103
Lub: arr.inject (: +). To_f / arr.size # => 3.375
glenn jackman 27.08.2009

5
Nie sądzę, aby uzasadniało to dodanie do klasy Array, ponieważ nie można go uogólnić na wszystkie typy, które mogą zawierać tablice.
Sarah Mei

8
@John: To nie jest dokładnie konwersja Symbol # na_proc - to część injectinterfejsu, o której mowa w dokumentacji. to_procOperator &.
Chuck

21
Jeśli używasz Railsów, Array#injecttutaj jest przesada. Po prostu użyj #sum. Np.arr.sum.to_f / arr.size
nick

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

Wersja tego, która nie używa instance_eval, to:

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

4
Nie sądzę, żeby to było zbyt sprytne. Myślę, że to rozwiązuje problem idiomatycznie. Tzn. Używa redukcji, co jest dokładnie poprawne. Programiści powinni być zachęcani do zrozumienia, co jest poprawne, dlaczego jest poprawne, a następnie propagowania. W przypadku trywialnej operacji, takiej jak średnia, prawda, nie trzeba być „sprytnym”. Ale rozumiejąc, czym jest „redukcja” w trywialnym przypadku, można zacząć stosować go do znacznie bardziej złożonych problemów. głosować.
pduey,

3
skąd potrzeba tutaj instance_eval?
tybro0103

10
instance_evalpozwala uruchomić kod, określając tylko ajeden raz, dzięki czemu można go połączyć z innymi poleceniami. Tj. random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f } Zamiastrandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
Benjamin Manns,

2
Nie wiem, używanie instancji w ten sposób wydaje się po prostu dziwne i wiąże się z nią wiele błędów, które sprawiają, że takie podejście jest złym pomysłem, IMO. (Na przykład, jeśli spróbujesz uzyskać dostęp do zmiennej instancji lub metody selfwewnątrz tego bloku, możesz napotkać problemy.) instance_evalTo więcej w przypadku metaprogramowania lub DSL.
Ajedi32,

1
@ Ajedi32 Zgadzam się, nie używaj tego w swoim kodzie aplikacji. Było jednak bardzo miło móc wkleić do mojej repliki (:
animatedgif

94

Uważam, że najprostsza odpowiedź brzmi

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

1
Znalezienie go zajęło mi chwilę - reducejest to metoda Enumerablemiksu używana przez Array. I pomimo jego nazwy, zgadzam się z @ShuWu ... chyba że używasz Railsów, które implementują sum.
Tom Harrison

Widzę tutaj rozwiązania, które wiem, że wyglądają niezwykle schludnie, ale obawiam się, że jeśli przeczytam mój kod w przyszłości, polubią bełkot. Dzięki za czyste rozwiązanie!
atmosx

W moim systemie jest to 3 razy szybciej niż zaakceptowana odpowiedź.
sergio

48

Miałem nadzieję, że Math. Średnia (wartości), ale bez takiego szczęścia.

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

3
Nie wiedziałem, że #sum został dodany przez Railsy! Dzięki za zwrócenie na to uwagi.
Denny Abraham

11
Po Bożym Narodzeniu 2016 (Ruby 2.4) Array będzie miał summetodę, więc wydaje się, że jest to poprawna odpowiedź po 6 latach, godna nagrody Nostradamus.
steenslag


9

Niektóre testy porównawcze najlepszych rozwiązań (w kolejności najbardziej wydajnych):

Duża tablica:

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)

Małe tablice:

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)

Twój test porównawczy jest trochę błędny. Benchmark / ips jest w rzeczywistości lepszy dla tego rodzaju porównań. Sugerowałbym również użycie tablicy zapełnionej losowo liczbami ujemnymi i dodatnimi, a także liczb zmiennoprzecinkowych, aby uzyskać bardziej realistyczny wynik. Przekonasz się, że parametr instance_eval jest wolniejszy niż array.sum.fdiv. O około 8x dla pływaków. i około x1,12 dla liczb całkowitych. Różne systemy operacyjne dają różne wyniki. na moim komputerze Mac niektóre z tych metod są 2 razy wolniejsze niż na mojej Linux Droplet
konung

Również metoda sumowania używa wzoru Gaussa na zakresach zamiast obliczania sumy.
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
Zwraca nieprawidłowe wartości z powodu podziału na liczby całkowite. Wypróbuj na przykład z [2,3] .mean, który zwraca 2 zamiast 2,5.
John Feminella

1
Dlaczego pusta tablica powinna mieć sumę nilzamiast 0?
Andrew Grimm

1
Ponieważ można uzyskać różnicę między [] a [0]. I myślę, że każdy, kto chce prawdziwego środka, może skorzystać z to_i lub zamienić powyższy zero na 0
astropaniczny

4

Pozwól, że przyniosę coś do rywalizacji, która rozwiązuje problem dzielenia przez zero:

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

Muszę jednak przyznać, że „try” to pomocnik Railsów. Ale możesz to łatwo rozwiązać:

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

BTW: Myślę, że to prawda, że ​​średnia pustej listy wynosi zero. Średnia niczego to nic, a nie 0. To jest oczekiwane zachowanie. Jeśli jednak zmienisz na:

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

wynik dla pustych tablic nie będzie wyjątkiem, jak się spodziewałem, ale zwraca NaN ... Nigdy wcześniej tego nie widziałem w Ruby. ;-) Wydaje się, że jest to szczególne zachowanie klasy Float ...

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

4

czego nie lubię w zaakceptowanym rozwiązaniu

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

jest to, że tak naprawdę nie działa w czysto funkcjonalny sposób. potrzebujemy zmiennej arr do obliczenia arr.size na końcu.

aby rozwiązać to czysto funkcjonalnie, musimy śledzić dwie wartości: sumę wszystkich elementów i liczbę elementów.

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

Santhosh ulepszył to rozwiązanie: zamiast argumentu r będącego tablicą, moglibyśmy użyć destrukcji, aby natychmiast rozdzielić ją na dwie zmienne

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

jeśli chcesz zobaczyć, jak to działa, dodaj kilka putów:

[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

Możemy również użyć struktury zamiast tablicy do przechowywania sumy i liczby, ale najpierw musimy zadeklarować strukturę:

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

Dzięki temu po raz pierwszy end.methodużyłem rubinu, dzięki za to!
Epigene

Tablica przekazana do metody wstrzykiwania może być rozproszona. arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Santhosh

@ Santhosh: tak, to o wiele bardziej czytelne! Nie nazwałbym tego jednak „rozpraszaniem”, nazwałbym to „destrukcją” tony.pitluga.com/2011/08/08/destruktury-with-ruby.html
bjelli

3

Dla rozrywki publicznej jeszcze jedno rozwiązanie:

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
Gdyby było to wyżej w głosowaniu, nie zrozumiałbym tego! Bardzo dobrze.
Matt Stevens

Wyczyść jest lepsze niż sprytne , ten fragment kodu nie jest jasny.
Sebastian Palma

2

Nie mam ruby ​​na tym komputerze, ale coś w tym zakresie powinno działać:

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

average = total/values.size

2

Dodaj Array#average.

Często robiłem to samo, więc pomyślałem, że rozsądnie byłoby po prostu rozszerzyć Arrayklasę za pomocą prostej averagemetody. Nie działa na nic poza tablicą liczb takich jak liczby całkowite, zmiennoprzecinkowe lub dziesiętne, ale jest przydatny, gdy używasz go poprawnie.

Korzystam z Ruby on Rails, więc umieściłem go, config/initializers/array.rbale możesz go umieścić w dowolnym miejscu na starcie itp.

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
Zwróci to nieprawidłowe wartości z powodu podziału na liczby całkowite. Na przykład, jeśli a wynosi [2, 3], oczekiwany wynik to 2,5, ale
zwrócisz

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

Rozwiązuje dzielenie przez zero, dzielenie liczb całkowitych i jest łatwy do odczytania. Można go łatwo zmodyfikować, jeśli wybierzesz opcję zwracania pustej tablicy 0.

Ten wariant też mi się podoba, ale jest trochę bardziej niewygodny.

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


1

Ta metoda może być pomocna.

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
Witamy w stosie przepełnienia tutaj Oryginalny plakat pytania chce odpowiedzi jako 3.375, a twoje rozwiązanie daje 3. ja, e
27/8

Dziękuję za twoje komentarze. Wiem, że oryginalny plakat pytania chce uzyskać odpowiedź jako 3.375 i to właśnie robi ta metoda, ponieważ nadałem zmiennej „var” wartość zmiennoprzecinkową (tj. 0,0). Munim Munna Muszę się z tobą zgodzić, że rzeczywiście istnieje podobny ans.
Kishor Budhathoki

0

Bez konieczności powtarzania tablicy (np. Idealne dla jedno-liniowych):

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

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

Krótka, ale przy użyciu zmiennej instancji


2
Wolałbym a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_sizetworzyć zmienną instancji.
Andrew Grimm

-1

Możesz spróbować czegoś takiego:

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.