Ruby dizisinden nasıl ortalama oluşturabilirim?


209

Bir diziden ortalama nasıl bulunur?

Ben dizi varsa:

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

Ortalama bana 3.375 verir.


11
Bu sayıların ortalaması olarak 21.75 alıyorsanız, bir şeyler çok yanlış ...
ceejayoz

2
dotty, nasıl 21.75 var emin değilim ama bu veri kümesi için ortalama / ortalama 3.375 ve toplam 27 olduğunu. Ne tür bir toplama işlevi 21.75 verecek emin değilim. Lütfen tekrar kontrol edin ve ortalamanın gerçekten peşinde olduğunuzdan emin olun!
Paul Sasik

2
21.75 nereden geldiğime dair hiçbir fikrim yok. Hesap makinesinde 0 + 48 + 2 + 5 + 0 + 2 + 6 gibi bir şey basmış olmalı!
dotty

16
Bu aynı zamanda raylar üzerinde ruby ​​olarak etiketlendiğinden, bir ActiveRecord dizisinin ortalamasını alıyorsanız aktif kayıt hesaplamaları dikkate alınmaya değer. Person.average (: yaş,: ülke => 'Brezilya'), Brezilya'daki insanların ortalama yaşını döndürür. Oldukça havalı!
Kyle Heironimus

Yanıtlar:


259

Bunu dene:

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

.to_fTamsayı bölümünden herhangi bir sorundan kaçınmak isteyeceğinizi not edin . Ayrıca şunları da yapabilirsiniz:

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

Bunu Arraybaşka bir yorumcunun önerdiği şekilde tanımlayabilirsiniz , ancak tamsayı bölünmesinden kaçınmanız gerekir, aksi takdirde sonuçlarınız yanlış olur. Ayrıca, bu genellikle olası her öğe türü için geçerli değildir (açıkçası, ortalama yalnızca ortalaması alınabilecek şeyler için anlamlıdır). Ancak bu rotaya gitmek istiyorsanız, şunu kullanın:

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

  def mean 
    sum / size
  end
end

Daha injectönce görmediyseniz , göründüğü kadar büyülü değildir. Her öğenin üzerinde yinelenir ve sonra ona bir akümülatör değeri uygular. Akümülatör daha sonra bir sonraki elemana verilir. Bu durumda, akümülatörümüz önceki tüm öğelerin toplamını yansıtan bir tamsayıdır.

Edit: Yorumcu Dave Ray hoş bir gelişme teklif etti.

Edit: Yorumcu Glenn Jackman, kullanarak arr.inject(:+).to_f, önerisi de güzel ama ne olduğunu bilmiyorsanız belki biraz zekice. :+Bir semboldür; enjekte edildiğinde, akümülatör değerine karşı her öğeye sembol (bu durumda toplama işlemi) adlı yöntemi uygular.


6
To_f ve? bir başlangıç değeri ileterek kullanıcı enjekte etmek için: arr.inject(0.0) { |sum,el| sum + el } / arr.size.
Dave Ray

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

5
Array sınıfının içerebileceği tüm türler için genelleştirilemediğinden, bunun Array sınıfına eklenmesini gerektirdiğini düşünmüyorum.
Sarah Mei

8
@John: Bu tam olarak Symbol # to_proc dönüşümü değil - injectdokümantasyonda belirtilen arayüzün bir parçası . to_procOperatörüdür &.
Chuck

21
Rails kullanıyorsanız, Array#injectburada aşırı dolu. Sadece kullan #sum. Örneğinarr.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

Bunun kullanmayan bir sürümü instance_eval:

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

4
Çok zeki olduğunu düşünmüyorum. Sorunu deyimsel olarak çözdüğünü düşünüyorum. Yani, tam olarak doğru olan indirgeme kullanır. Programcılar neyin doğru, niçin doğru olduğunu anlamaya teşvik edilmeli ve sonra çoğaltılmalıdır. Ortalama, doğru gibi önemsiz bir işlem için birinin "zeki" olması gerekmez. Ancak önemsiz bir durum için "indirgeme" nin ne olduğunu anlayarak, bunu çok daha karmaşık sorunlara uygulamaya başlayabiliriz. upvote.
pduey

3
Neden burada example_eval gereksinimi?
tybro0103

10
instance_evalyalnızca bir akez belirtirken kodu çalıştırmanızı sağlar , böylece diğer komutlarla zincirlenebilir. Yani random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f } yerinerandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
Benjamin Manns

2
Bilmiyorum, example_eval bu şekilde kullanmak sadece garip görünüyor, ve bu yaklaşımı kötü bir fikir, IMO yapan bir sürü gotchas var. (Örneğin, değişken veya selfbu bloğun içindeki bir yönteme erişmeye ve örnek oluşturmaya çalıştıysanız , sorun instance_evalyaşarsınız .) Meta programlama veya DSL için daha fazladır.
Ajedi32

1
@ Ajedi32 Kabul ediyorum, bunu uygulama kodunuzda kullanmayın. Ancak benim repl yapıştırmak çok güzeldi (:
animatedgif

94

En basit cevabın

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

1
Onu bulmam biraz zaman aldı - tarafından kullanılan karışımın reducebir yöntemidir . Ve ismine rağmen, @ShuWu'ya katılıyorum ... uygulayan Rails'i kullanmadığınız sürece . EnumerableArraysum
Tom Harrison

Burada çözümler görüyorum, bu son derece temiz görünüyor, ama korkarım gelecekte kodumu okursam korkarım. Temiz çözüm için teşekkürler!
atmosx

Sistemimde bu, kabul edilen yanıttan 3 kat daha hızlı.
sergio

48

Math.average (değerleri) umuyordum, ama böyle bir şans yok.

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

3
#Sum'un Rails tarafından eklendiğini fark etmedim! Bunu işaret ettiğiniz için teşekkürler.
Denny Abraham

11
Noel 2016'dan sonra (Ruby 2.4), Array'ın bir yöntemi olacaksum , bu nedenle Nostradamus ödülüne layık olan 6 yıl sonra doğru bir cevap gibi görünüyor.
steenslag


9

En iyi çözümlerin bazı kıyaslamaları (en verimli sırayla):

Büyük Dizi:

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)

Küçük Diziler:

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)

Karşılaştırmanız biraz yanlış. benchmark / ips aslında bu tür karşılaştırmalar için daha iyidir. Ayrıca, daha gerçekçi bir sonuç elde etmek için, negatif ve pozitif sayılar ve şamandıralarla rastgele doldurulmuş bir Array kullanmanızı öneririm. İnstance_eval öğesinin array.sum.fdiv dosyasından daha yavaş olduğunu göreceksiniz. Şamandıralar için yaklaşık 8x. ve tamsayılar için yaklaşık x1.12. Ayrıca, farklı işletim sistemleri farklı sonuçlar verecektir.
Mac'imde

Ayrıca sum yöntemi, toplamı hesaplamak yerine aralıklarda Gauss formülünü kullanır.
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
Bu, tamsayı bölümü nedeniyle yanlış değerler döndürür. Örneğin, 2,5 yerine 2 döndüren [2,3] .mean ile deneyin.
John Feminella

1
Boş bir dizinin toplamı neden nil0 yerine toplamalı?
Andrew Grimm

1
Çünkü [] ve [0] arasındaki farkı elde edebilirsiniz. Ve bence gerçek bir ortalama isteyen herkes to_i'yi kullanabilir veya yukarıdaki
nil'i

4

Ayrımı sıfır sorunla çözen bir şeyi rekabete sokayım:

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

Ancak, "denemek" bir Rails yardımcısı olduğunu itiraf etmeliyim. Ancak bunu kolayca çözebilirsiniz:

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

BTW: Boş bir listenin ortalamasının sıfır olduğunu düşünüyorum. Hiçbir şeyin ortalaması hiçbir şey değildir, 0 değildir. Yani beklenen davranış budur. Ancak, şu şekilde değiştirirseniz:

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

boş Diziler için sonuç beklediğim gibi bir istisna olmayacak, ama bunun yerine NaN döndürüyor ... Daha önce hiç Ruby'de görmedim. ;-) Float sınıfının özel bir davranışı gibi görünüyor ...

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

4

kabul edilen çözüm hakkında sevmediğim şey

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

aslında tamamen işlevsel bir şekilde çalışmadığıdır. sonunda arr.size değerini hesaplamak için değişken bir arr'ye ihtiyacımız var.

Bunu tamamen işlevsel olarak çözmek için iki değeri izlememiz gerekir: tüm elemanların toplamı ve eleman sayısı.

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

Santhosh bu çözüm üzerinde gelişti: r argümanı bir dizi olmak yerine, yıkımı derhal iki değişkene ayırmak için kullanabiliriz

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

nasıl çalıştığını görmek istiyorsanız, aşağıdakileri ekleyin:

[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

Toplamı ve sayımı içermek için dizi yerine bir yapı da kullanabiliriz, ancak önce yapıyı bildirmeliyiz:

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

end.methodRuby'de ilk kez kullandığımı görüyorum , bunun için teşekkürler!
Epigene

Enjeksiyon yöntemine geçirilen dizi dağıtılabilir. arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Santhosh

@Santhosh: evet, bu çok daha okunabilir! Bu olsa "dağıtıcı" demem, ben "kurucuların" çağırır tony.pitluga.com/2011/08/08/destructuring-with-ruby.html
bjelli

3

Halka açık eğlence için, başka bir çözüm:

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
Bu oylamada daha yüksek olsaydı, bunu anlamazdım! Çok iyi.
Matt Stevens

Temiz, akıllıdan daha iyidir , bu kod parçası net değildir.
Sebastian Palma

2

Bu bilgisayarda yakut yok, ama bu ölçüde bir şey işe yarayacak:

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

average = total/values.size

2

Ekle Array#average.

Aynı şeyi sık sık yapıyordum, bu yüzden Arraysınıfı basit bir averageyöntemle genişletmenin ihtiyatlı olduğunu düşündüm . Tamsayılar, Kayan Sayılar veya Ondalık Sayılar dizisi dışında hiçbir şey için çalışmaz, ancak doğru kullandığınızda kullanışlıdır.

Ruby on Rails kullanıyorum, bu yüzden bunu yerleştirdim, config/initializers/array.rbancak önyüklemede bulunan herhangi bir yere yerleştirebilirsiniz, vb.

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
Bu, tamsayı bölümü nedeniyle yanlış değerler döndürür. Örneğin, a [2, 3] ise, beklenen sonuç 2.5'tir, ancak 2 döndürürsünüz.
John Feminella

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

Bölmeyi sıfıra, tamsayı bölümüne böler ve okunması kolaydır. Boş bir dizi 0 döndürmeyi seçerseniz kolayca değiştirilebilir.

Bu varyantı da seviyorum, ama biraz daha garip.

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

Bu yöntem yardımcı olabilir.

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
Burada taşma yığınına hoş geldiniz Sorunun Orijinal Posteri 3.375 olarak cevabı istiyor ve çözümünüz 3 veriyor. İ, e
27/8

Yorumlarınız için teşekkür ederim. Sorunun Orijinal Afişi 3.375 olarak cevap istediğini biliyorum ve bu değişken 'değişken' bir kayan nokta değeri (yani; 0.0) verdikçe ne yapar. Munim Munna Seninle aynı fikirdeyim ve aynı fikirdeyim.
Kishor Budhathoki

0

Diziyi tekrarlamak zorunda kalmadan (örn. Bir astar için mükemmel):

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

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

Kısa ancak örnek değişkeni kullanılıyor


2
a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_sizeBir örnek değişkeni oluşturmak yerine bunu yapardım .
Andrew Grimm

-1

Aşağıdaki gibi bir şey deneyebilirsiniz:

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.