Làm cách nào để tạo trung bình từ mảng Ruby?


209

Làm thế nào có thể tìm thấy một trung bình từ một mảng?

Nếu tôi có mảng:

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

Tính trung bình sẽ cho tôi 3,375.


11
Nếu bạn nhận được 21,75 là trung bình của những con số đó, thì có gì đó rất sai ...
ceejayoz

2
dotty, không chắc chắn làm thế nào bạn có 21,75 nhưng trung bình / trung bình cho tập dữ liệu đó là 3,375 và tổng là 27. Tôi không chắc loại hàm tổng hợp nào sẽ mang lại 21,75. Vui lòng kiểm tra lại và đảm bảo rằng trung bình thực sự là những gì bạn đang theo đuổi!
Paul Sasik

2
Tôi không biết tôi đã lấy 21,75 từ đâu. Phải nhấn một cái gì đó như 0 + 48 + 2 + 5 + 0 + 2 + 6 trên máy tính!
chấm

16
Vì đây cũng được gắn thẻ ruby-on-rails, nên các tính toán bản ghi hoạt động rất đáng để xem xét nếu bạn lấy trung bình một mảng ActiveRecord. Person.a hiểm (: tuổi ,: quốc gia => 'Brazil') trả về độ tuổi trung bình của người từ Brazil. Tuyệt đấy!
Kyle Heironimus

Câu trả lời:


259

Thử cái này:

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

Lưu ý .to_f, điều mà bạn sẽ muốn tránh bất kỳ vấn đề nào từ phép chia số nguyên. Bạn cũng có thể làm:

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

Bạn có thể định nghĩa nó là một phần của Arraymột bình luận viên khác đã đề xuất, nhưng bạn cần tránh phân chia số nguyên nếu không kết quả của bạn sẽ sai. Ngoài ra, điều này thường không áp dụng cho mọi loại yếu tố có thể (rõ ràng, trung bình chỉ có ý nghĩa đối với những thứ có thể được tính trung bình). Nhưng nếu bạn muốn đi theo con đường đó, hãy sử dụng:

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

  def mean 
    sum / size
  end
end

Nếu bạn chưa từng thấy injecttrước đây, nó không kỳ diệu như nó có thể xuất hiện. Nó lặp lại qua từng phần tử và sau đó áp dụng giá trị tích lũy cho nó. Bộ tích lũy sau đó được trao cho phần tử tiếp theo. Trong trường hợp này, bộ tích lũy của chúng tôi chỉ đơn giản là một số nguyên phản ánh tổng của tất cả các phần tử trước đó.

Chỉnh sửa: Bình luận viên Dave Ray đề xuất một cải tiến tốt đẹp.

Chỉnh sửa: Bình luận, đề xuất của Glenn Jackman arr.inject(:+).to_f, cũng rất hay nhưng có lẽ hơi quá thông minh nếu bạn không biết chuyện gì đang xảy ra. Đây :+là một biểu tượng; khi được truyền vào để tiêm, nó áp dụng phương thức được đặt tên bằng ký hiệu (trong trường hợp này là phép toán cộng) cho mỗi phần tử so với giá trị của bộ tích lũy.


6
Bạn có thể loại bỏ to_f và? toán tử bằng cách chuyển một giá trị ban đầu để tiêm : arr.inject(0.0) { |sum,el| sum + el } / arr.size.
Dave Ray

103
. Hoặc: arr.inject (: +) to_f / arr.size # => 3,375
glenn jackman

5
Tôi không nghĩ điều này đảm bảo thêm vào lớp Array, vì nó không thể khái quát hóa cho tất cả các loại mà Mảng có thể chứa.
Sarah Mei

8
@ John: Đó không chính xác là chuyển đổi Biểu tượng # to_proc - đó là một phần của injectgiao diện, được đề cập trong tài liệu. Các to_procnhà điều hành là &.
Chuck

21
Nếu bạn đang sử dụng Rails, Array#injectthì quá mức cần thiết ở đây. Chỉ cần sử dụng #sum. Ví dụ:arr.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

Một phiên bản không sử dụng instance_evalsẽ là:

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

4
Tôi không nghĩ nó quá thông minh. Tôi nghĩ rằng nó giải quyết vấn đề một cách thành ngữ. Tức là, nó sử dụng giảm, đó là chính xác. Các lập trình viên nên được khuyến khích để hiểu điều gì là đúng, tại sao nó đúng và sau đó tuyên truyền. Đối với một hoạt động tầm thường như trung bình, đúng, người ta không cần phải "thông minh". Nhưng bằng cách hiểu "giảm" là gì đối với một trường hợp tầm thường, người ta có thể bắt đầu áp dụng nó cho các vấn đề phức tạp hơn nhiều. nâng cao.
pduey

3
Tại sao cần ví dụ_eval ở đây?
tybro0103

10
instance_evalcho phép bạn chạy mã trong khi chỉ định amột lần, vì vậy nó có thể được nối với các lệnh khác. Tức là random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f } thay vìrandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
Benjamin Manns

2
Tôi không biết, sử dụng instance_eval theo cách này có vẻ kỳ lạ, và nó có rất nhiều vấn đề liên quan đến nó khiến cho cách tiếp cận này trở thành một ý tưởng tồi, IMO. (Ví dụ: nếu bạn đã cố truy cập và biến thể hiện hoặc một phương thức selfbên trong khối đó, bạn sẽ gặp vấn đề.) instance_evalSẽ nhiều hơn cho siêu lập trình hoặc DSL.
Ajedi32

1
@ Ajedi32 Tôi đồng ý, không sử dụng mã này trong mã ứng dụng của bạn. Tuy nhiên, thật tuyệt khi có thể dán vào thay thế của tôi (:
hoạt hình

94

Tôi tin rằng câu trả lời đơn giản nhất là

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

1
Phải mất một lúc tôi mới tìm thấy nó - reducelà một phương pháp của Enumerablemixin được sử dụng bởi Array. Và mặc dù tên của nó, tôi đồng ý với @ShuWu ... trừ khi bạn sử dụng Rails thực hiện sum.
Tom Harrison

Tôi thấy các giải pháp ở đây, rằng tôi biết rằng nó trông cực kỳ gọn gàng, nhưng tôi sợ nếu tôi đọc mã của mình trong tương lai thì chúng sẽ thích vô nghĩa. Cảm ơn các giải pháp sạch!
atmosx

Trên hệ thống của tôi, điều này nhanh hơn gấp 3 lần so với câu trả lời được chấp nhận.
sergio

48

Tôi đã hy vọng cho Math.alusive (giá trị), nhưng không có may mắn như vậy.

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

3
Tôi không nhận ra #sum đã được thêm vào bởi Rails! Cảm ơn đã chỉ ra rằng.
Denny Abraham

11
Sau Giáng sinh 2016 (Ruby 2.4), Array sẽ có một sumphương pháp, vì vậy đây dường như là một câu trả lời đúng sau 6 năm, xứng đáng với giải thưởng Nostradamus.
steenslag

38

Các phiên bản Ruby> = 2.4 có phương thức # tổng .

Và để lấy điểm trung bình nổi, bạn có thể sử dụng Integer # fdiv

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

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

Đối với phiên bản cũ hơn:

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

9

Một số điểm chuẩn của các giải pháp hàng đầu (theo thứ tự hiệu quả nhất):

Mảng lớn:

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)

Mảng nhỏ:

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)

Điểm chuẩn của bạn là một chút sai. điểm chuẩn / ips thực sự tốt hơn cho các loại so sánh này. Ngoài ra, tôi sẽ đề nghị sử dụng một Array được điền ngẫu nhiên với các số âm và dương cũng như số float, để có được kết quả thực tế hơn. Bạn sẽ thấy instance_eval chậm hơn mảng.sum.fdiv. Khoảng 8 lần cho phao. và khoảng x1.12 cho số nguyên. Ngoài ra, các hệ điều hành khác nhau sẽ cho kết quả khác nhau. trên máy mac của tôi, một số phương thức này chậm hơn 2 lần so với trên Giọt Linux của tôi
konung

Ngoài ra phương pháp sum sử dụng công thức của Gauss, trên các phạm vi thay vì tính tổng.
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
Điều này trả về giá trị không chính xác vì phân chia số nguyên. Hãy thử với, ví dụ, [2,3] .mean, trả về 2 thay vì 2,5.
John Women'sella

1
Tại sao một mảng trống có tổng bằng nil0?
Andrew Grimm

1
Bởi vì bạn có thể nhận được sự khác biệt giữa [] và [0]. Và tôi nghĩ rằng tất cả những ai muốn có ý nghĩa thực sự đều có thể sử dụng to_i hoặc thay thế số 0 ở trên bằng 0
astropanic

4

Hãy để tôi mang một cái gì đó vào cạnh tranh để giải quyết sự phân chia bằng không vấn đề:

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

Tôi phải thừa nhận, tuy nhiên, "thử" là một người trợ giúp Rails. Nhưng bạn có thể dễ dàng giải quyết điều này:

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

BTW: Tôi nghĩ đúng là trung bình của một danh sách trống là không. Trung bình của không có gì là không có gì, không phải 0. Vì vậy, đó là hành vi dự kiến. Tuy nhiên, nếu bạn đổi thành:

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

kết quả cho Mảng trống sẽ không phải là một ngoại lệ như tôi mong đợi mà thay vào đó, nó trả về NaN ... Tôi chưa bao giờ thấy điều đó trước đây trong Ruby. ;-) Có vẻ là một hành vi đặc biệt của lớp Float ...

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

4

những gì tôi không thích về giải pháp được chấp nhận

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

là nó không thực sự hoạt động theo cách hoàn toàn chức năng. chúng ta cần một mảng biến để tính toán Array.size ở cuối.

để giải quyết vấn đề này hoàn toàn theo chức năng, chúng ta cần theo dõi hai giá trị: tổng của tất cả các phần tử và số phần tử.

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

Santhosh đã cải thiện giải pháp này: thay vì đối số r là một mảng, chúng ta có thể sử dụng hàm hủy để lập tức tách nó thành hai biến

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

nếu bạn muốn xem nó hoạt động như thế nào, hãy thêm một số đặt:

[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

Chúng ta cũng có thể sử dụng một cấu trúc thay vì một mảng để chứa tổng và số đếm, nhưng sau đó chúng ta phải khai báo cấu trúc trước:

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

Đây là lần đầu tiên tôi thấy end.methodđược sử dụng trong ruby, cảm ơn vì điều này!
Epigene

Các mảng được truyền cho phương thức tiêm có thể được phân tán. arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Santhosh

@Santhosh: vâng, điều đó dễ đọc hơn nhiều! Mặc dù vậy, tôi sẽ không gọi đây là "phân tán", tôi sẽ gọi nó là "phá hủy" tony.pitluga.com/2011/08/08/desturationuring-with-ruby.html
bjelli

3

Đối với giải trí công cộng, một giải pháp khác:

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
Nếu điều này cao hơn trong cuộc bỏ phiếu, tôi sẽ không hiểu nó! Rất tốt.
Matt Stevens

Rõ ràng là tốt hơn thông minh , đoạn mã này không rõ ràng.
Sebastian Palma

2

Đừng có ruby ​​trên máy tính này, nhưng một cái gì đó đến mức này sẽ hoạt động:

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

average = total/values.size

2

Thêm Array#average.

Tôi đã làm điều tương tự khá thường xuyên vì vậy tôi nghĩ thật thận trọng khi chỉ mở rộng Arraylớp học bằng một averagephương pháp đơn giản . Nó không hoạt động cho bất cứ thứ gì ngoài Mảng số như Số nguyên hoặc Số nổi hoặc Số thập phân nhưng nó rất tiện lợi khi bạn sử dụng đúng.

Tôi đang sử dụng Ruby on Rails vì vậy tôi đã đặt nó vào config/initializers/array.rbnhưng bạn có thể đặt nó ở bất cứ đâu có trong boot, v.v.

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
Điều này sẽ trả về giá trị không chính xác vì phân chia số nguyên. Ví dụ: nếu a là [2, 3], kết quả mong đợi là 2,5, nhưng bạn sẽ quay lại 2.
John Women'sella

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

Giải các phép chia cho số 0, phép chia số nguyên và dễ đọc. Có thể dễ dàng sửa đổi nếu bạn chọn có một mảng trống trả về 0.

Tôi cũng thích biến thể này, nhưng nó dài dòng hơn một chút.

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

Phương pháp này có thể hữu ích.

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
Chào mừng bạn đến với ngăn xếp tràn ở đây Poster gốc của câu hỏi muốn có câu trả lời là 3.375 và giải pháp của bạn đưa ra 3. i, e 27/8 = 3.
Ajay Barot

Cảm ơn bạn đã bình luận của bạn. Tôi biết Poster gốc của câu hỏi muốn có câu trả lời là 3.375 và đó là những gì phương pháp này thực hiện khi tôi đã đưa ra biến 'var' một giá trị float (ví dụ: 0,0). Munim Munna Tôi phải đồng ý với bạn, thực sự có một ans tương tự.
Kishor Budhathoki

0

Không cần phải lặp lại mảng (ví dụ: hoàn hảo cho một lớp):

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

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

Ngắn nhưng sử dụng biến thể hiện


2
Tôi sẽ làm a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_sizehơn là tạo một biến thể hiện.
Andrew Grimm

-1

Bạn có thể thử một cái gì đó như sau:

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.