Bagaimana cara membuat rata-rata dari array Ruby?


209

Bagaimana mendapatkan rata-rata dari array?

Jika saya memiliki array:

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

Rata-rata akan memberi saya 3,375.


11
Jika Anda mendapatkan 21,75 dengan rata-rata angka-angka, ada sesuatu yang sangat salah ...
ceejayoz

2
Dotty, tidak yakin bagaimana Anda mendapat 21,75 tetapi rata-rata / rata-rata untuk set data adalah 3,375 dan jumlahnya adalah 27. saya tidak yakin apa fungsi agregasi akan menghasilkan 21,75. Harap periksa kembali dan pastikan rata-rata adalah yang Anda cari!
Paul Sasik

2
Saya tidak tahu dari mana saya mendapat 21,75. Harus menekan sesuatu seperti 0 + 48 + 2 + 5 + 0 + 2 + 6 pada kalkulator!
Dotty

16
Karena ini juga ditandai ruby-on-rails, perhitungan rekaman aktif layak dilihat jika Anda rata-rata array ActiveRecord. Person.average (: usia,: negara => 'Brasil') mengembalikan usia rata-rata orang dari Brasil. Sangat keren!
Kyle Heironimus

Jawaban:


260

Coba ini:

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

Perhatikan .to_f, yang Anda inginkan untuk menghindari masalah dari divisi integer. Anda juga dapat melakukan:

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

Anda dapat mendefinisikannya sebagai bagian dari Arrayapa yang disarankan komentator lain, tetapi Anda harus menghindari pembagian integer atau hasil Anda akan salah. Juga, ini umumnya tidak berlaku untuk setiap jenis elemen yang mungkin (jelas, rata-rata hanya masuk akal untuk hal-hal yang dapat dirata-ratakan). Tetapi jika Anda ingin pergi ke rute itu, gunakan ini:

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

  def mean 
    sum / size
  end
end

Jika Anda belum pernah melihat injectsebelumnya, itu tidak ajaib seperti yang mungkin muncul. Itu berulang atas setiap elemen dan kemudian menerapkan nilai akumulator untuknya. Akumulator kemudian diserahkan ke elemen berikutnya. Dalam hal ini, akumulator kami hanyalah bilangan bulat yang mencerminkan jumlah semua elemen sebelumnya.

Sunting: Komentator Dave Ray mengusulkan perbaikan yang bagus.

Sunting: Proposal komentator Glenn Jackman, menggunakan arr.inject(:+).to_f, juga bagus tapi mungkin agak terlalu pintar jika Anda tidak tahu apa yang terjadi. Itu :+adalah simbol; ketika melewati untuk menyuntikkan, itu berlaku metode yang dinamai oleh simbol (dalam hal ini, operasi penambahan) untuk setiap elemen terhadap nilai akumulator.


6
Anda dapat menghilangkan to_f dan? Operator dengan melewati nilai awal untuk menyuntikkan: arr.inject(0.0) { |sum,el| sum + el } / arr.size.
Dave Ray

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

5
Saya tidak berpikir waran ini menambah kelas Array, karena tidak dapat digeneralisasikan untuk semua jenis yang bisa berisi Array.
Sarah Mei

8
@ John: Itu bukan konversi Simbol # to_proc - itu bagian dari injectantarmuka, yang disebutkan dalam dokumentasi. The to_procoperator adalah &.
Chuck

21
Jika Anda menggunakan Rails, Array#injectberlebihan di sini. Gunakan saja #sum. Misalnyaarr.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

Versi ini yang tidak digunakan instance_evaladalah:

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

4
Saya tidak berpikir itu terlalu pintar. Saya pikir itu memecahkan masalah secara idiomatis. Yaitu, ia menggunakan mengurangi, yang persis benar. Pemrogram harus didorong untuk memahami apa yang benar, mengapa itu benar, dan kemudian menyebar. Untuk operasi sepele seperti rata-rata, benar, orang tidak perlu "pintar". Tetapi dengan memahami apa yang dimaksud "mengurangi" untuk kasus sepele, seseorang kemudian dapat mulai menerapkannya pada masalah yang jauh lebih kompleks. Suara positif.
pduey

3
mengapa perlunya instance_eval di sini?
tybro0103

10
instance_evalmemungkinkan Anda menjalankan kode sementara hanya menentukan asekali, sehingga dapat dirantai dengan perintah lain. Yaitu random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f } alih-alihrandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
Benjamin Manns

2
Saya tidak tahu, menggunakan instance_eval dengan cara ini sepertinya aneh, dan memiliki banyak gotcha yang terkait dengannya yang menjadikan pendekatan ini sebagai ide yang buruk, IMO. (Misalnya, jika Anda mencoba mengakses dan membuat variabel atau metode di selfdalam blok itu, Anda akan mengalami masalah.) instance_evalLebih untuk metaprogramming atau DSL.
Ajedi32

1
@ Ajedi32 Saya setuju, jangan gunakan ini dalam kode aplikasi Anda. Namun itu sangat bagus untuk dapat menempel di repl saya (:
animatedgif

94

Saya percaya jawaban yang paling sederhana adalah

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

1
Butuh beberapa saat untuk menemukannya - reduceadalah metode Enumerablemixin yang digunakan oleh Array. Dan terlepas dari namanya, saya setuju dengan @ ShuWu ... kecuali jika Anda menggunakan Rails yang mengimplementasikan sum.
Tom Harrison

Saya melihat solusi di sini, bahwa saya tahu itu terlihat sangat rapi, tetapi saya khawatir jika saya membaca kode saya di masa depan mereka akan suka omong kosong. Terima kasih atas solusi bersihnya!
atmosx

Di sistem saya ini 3x lebih cepat dari jawaban yang diterima.
sergio

48

Saya berharap untuk Matematika. Rata-rata (nilai), tetapi tidak beruntung.

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

3
Saya tidak menyadari #sum ditambahkan oleh Rails! Terima kasih telah menunjukkannya.
Denny Abraham

11
Setelah Natal 2016 (Ruby 2.4), Array akan memiliki summetode, jadi ini tampaknya jawaban yang benar setelah 6 tahun, layak untuk penghargaan Nostradamus.
steenslag

38

Versi Ruby> = 2.4 memiliki metode # jumlah Enumerable .

Dan untuk mendapatkan floating point rata-rata, Anda dapat menggunakan Integer # fdiv

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

arr.sum.fdiv(arr.size)
# => 3.375

Untuk versi yang lebih lama:

arr.reduce(:+).fdiv(arr.size)
# => 3.375

9

Beberapa pembandingan solusi teratas (dalam urutan yang paling efisien):

Array Besar:

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 Kecil:

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)

Patokan Anda sedikit salah. benchmark / ips sebenarnya lebih baik untuk perbandingan semacam ini. Saya juga menyarankan untuk menggunakan Array yang dihuni secara acak dengan angka negatif dan positif serta mengapung, untuk mendapatkan hasil yang lebih realistis. Anda akan menemukan instance_eval lebih lambat dari array.sum.fdiv. Sekitar 8x untuk mengapung. dan tentang x1.12 untuk bilangan bulat. Juga, OS yang berbeda akan memberikan hasil yang berbeda pula. di mac saya beberapa metode ini 2 kali lebih lambat dari pada Linux Droplet saya
konung

Metode penjumlahan juga menggunakan rumus Gauss, pada rentang alih-alih menghitung penjumlahan.
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
Ini mengembalikan nilai yang salah karena pembagian integer. Cobalah, misalnya, [2,3] .mean, yang mengembalikan 2 bukannya 2,5.
John Feminella

1
Mengapa array kosong memiliki jumlah nildaripada 0?
Andrew Grimm

1
Karena Anda bisa mendapatkan perbedaan antara [] dan [0]. Dan saya pikir semua orang yang menginginkan nilai sebenarnya dapat menggunakan to_i atau mengganti nol di atas dengan 0
astropan

4

Biarkan saya membawa sesuatu ke dalam kompetisi yang memecahkan masalah divisi dengan nol:

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

Namun saya harus mengakui bahwa "coba" adalah penolong Rails. Tetapi Anda dapat dengan mudah menyelesaikan ini:

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

BTW: Saya pikir benar bahwa rata-rata daftar kosong adalah nol. Rata-rata tidak ada apa-apa, bukan 0. Jadi itulah perilaku yang diharapkan. Namun, jika Anda mengubah ke:

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

hasil untuk Array kosong tidak akan menjadi pengecualian seperti yang saya harapkan tetapi mengembalikan NaN ... Saya belum pernah melihat itu sebelumnya di Ruby. ;-) Tampaknya menjadi perilaku khusus kelas Float ...

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

4

apa yang saya tidak suka tentang solusi yang diterima

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

adalah bahwa itu tidak benar-benar berfungsi secara murni fungsional. kita membutuhkan variabel arr untuk menghitung arr.size di akhir.

untuk menyelesaikan ini murni secara fungsional kita perlu melacak dua nilai: jumlah semua elemen, dan jumlah elemen.

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

Santhosh meningkatkan solusi ini: alih-alih argumen r menjadi array, kita bisa menggunakan destrrukturisasi untuk segera mengambilnya menjadi dua variabel

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

jika Anda ingin melihat cara kerjanya, tambahkan beberapa put:

[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

Kita juga bisa menggunakan struct alih-alih array untuk mengandung jumlah dan penghitungan, tetapi kemudian kita harus mendeklarasikan struct terlebih dahulu:

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

Ini adalah pertama kalinya saya melihat end.methoddigunakan di ruby, terima kasih untuk ini!
Epigene

Array yang diteruskan ke metode injeksi dapat didispersikan. arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Santhosh

@Santhosh: ya, itu jauh lebih mudah dibaca! Saya tidak akan menyebut ini "bubar", saya akan menyebutnya "merusak" tony.pitluga.com/2011/08/08/destrukturisasi-with-ruby.html
bjelli

3

Untuk hiburan publik, solusi lain:

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
Jika ini lebih tinggi dalam pemungutan suara saya tidak akan memahaminya! Baik sekali.
Matt Stevens

Jelas lebih baik daripada pintar , kode ini tidak jelas.
Sebastian Palma

2

Tidak ada ruby ​​pada pc ini, tetapi sesuatu sejauh ini harus bekerja:

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

average = total/values.size

2

Menambahkan Array#average .

Saya melakukan hal yang sama cukup sering sehingga saya pikir lebih baik hanya memperpanjang Arraykelas dengan sederhanaaverage metode . Ini tidak berfungsi untuk apa pun selain array angka seperti Integer atau Mengapung atau Desimal tetapi berguna ketika Anda menggunakannya dengan benar.

Saya menggunakan Ruby on Rails jadi saya menempatkan ini di config/initializers/array.rbtetapi Anda dapat menempatkannya di mana saja yang termasuk dalam boot, dll.

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
Ini akan mengembalikan nilai yang salah karena pembagian integer. Misalnya, jika a adalah [2, 3], hasil yang diharapkan adalah 2,5, tetapi Anda akan kembali 2.
John Feminella

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

Memecahkan pembagian dengan nol, pembagian integer dan mudah dibaca. Dapat dengan mudah dimodifikasi jika Anda memilih untuk mengembalikan array kosong 0.

Saya suka varian ini juga, tetapi sedikit lebih bertele-tele.

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

Metode ini dapat membantu.

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
Selamat datang di stack overflow di sini. Poster Asli dari pertanyaan tersebut menginginkan jawaban sebagai 3.375 dan solusi Anda memberi 3. i, e
27/8

Terima kasih atas komentar anda Saya tahu Poster Asli pertanyaan menginginkan jawaban sebagai 3,375 dan itulah metode ini tidak seperti yang saya berikan variabel 'var' nilai float (yaitu; 0,0). Munim Munna Saya harus setuju dengan Anda bahwa memang ada yang serupa.
Kishor Budhathoki

0

Tanpa harus mengulang array (mis. Sempurna untuk satu-liner):

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

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

Pendek tapi menggunakan variabel instan


2
Saya akan melakukan a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_sizedaripada membuat variabel instan.
Andrew Grimm

-1

Anda dapat mencoba sesuatu seperti berikut ini:

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.