ฉันจะสร้างค่าเฉลี่ยจากอาร์เรย์ Ruby ได้อย่างไร


209

จะหาค่าเฉลี่ยจากอาร์เรย์ได้อย่างไร

ถ้าฉันมีอาร์เรย์:

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

ค่าเฉลี่ยจะให้ฉัน 3.375


11
หากคุณได้รับ 21.75 ขณะที่ค่าเฉลี่ยของตัวเลขเหล่านั้นบางสิ่งบางอย่างที่ผิดปกติมาก ...
ceejayoz

2
dotty ไม่แน่ใจว่าคุณมี 21.75 ได้อย่างไร แต่ค่าเฉลี่ย / ค่าเฉลี่ยของชุดข้อมูลนั้นคือ 3.375 และผลรวมคือ 27 ฉันไม่แน่ใจว่าฟังก์ชันการรวมประเภทใดที่จะให้ผล 21.75 โปรดตรวจสอบอีกครั้งและตรวจสอบให้แน่ใจว่าค่าเฉลี่ยเป็นสิ่งที่คุณต้องการจริง ๆ !
Paul Sasik

2
ฉันไม่มีความคิดที่ฉันได้ 21.75 จาก ต้องกดบางอย่างเช่น 0 + 48 + 2 + 5 + 0 + 2 + 6 บนเครื่องคิดเลข!
dotty

16
เนื่องจากนี่เป็นแท็ก ruby-on-rails การคำนวณเรกคอร์ดที่ใช้งานจึงคุ้มค่าที่จะดูว่าคุณกำลังหาค่าเฉลี่ยของเรกคอร์ด ActiveRecord หรือไม่ Person.average (: age,: country => 'Brazil') ส่งคืนอายุเฉลี่ยของผู้คนจากประเทศบราซิล สวยเท่ห์!
Kyle Heironimus

คำตอบ:


259

ลองสิ่งนี้:

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

สังเกตการ.to_fที่คุณต้องการหลีกเลี่ยงปัญหาใด ๆ จากการหารจำนวนเต็ม คุณยังสามารถทำสิ่งต่อไปนี้

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

คุณสามารถกำหนดมันเป็นส่วนหนึ่งของ Arrayความคิดเห็นอื่นได้แนะนำ แต่คุณต้องหลีกเลี่ยงการแบ่งจำนวนเต็มมิฉะนั้นผลลัพธ์ของคุณจะผิด นอกจากนี้โดยทั่วไปจะไม่สามารถใช้ได้กับองค์ประกอบทุกประเภทที่เป็นไปได้ (โดยชัดแจ้งว่าค่าเฉลี่ยจะเหมาะสมสำหรับสิ่งที่สามารถเฉลี่ยเท่านั้น) แต่ถ้าคุณต้องการที่จะไปเส้นทางนั้นใช้สิ่งนี้:

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

  def mean 
    sum / size
  end
end

หากคุณยังไม่เห็น injectมาก่อนมันไม่วิเศษเท่าที่ปรากฏ มันวนซ้ำทุกองค์ประกอบแล้วใช้ค่าตัวสะสมกับมัน ตัวสะสมจะถูกส่งไปยังองค์ประกอบถัดไป ในกรณีนี้ตัวสะสมของเราเป็นเพียงจำนวนเต็มที่สะท้อนผลรวมขององค์ประกอบก่อนหน้าทั้งหมด

แก้ไข: Commenter Dave Ray เสนอการปรับปรุงที่ดี

แก้ไข: ผู้เสนอข้อคิดเห็นของ Glenn Jackman ใช้arr.inject(:+).to_fเป็นคนดี แต่บางทีก็ฉลาดเกินไปถ้าคุณไม่รู้ว่าเกิดอะไรขึ้น :+เป็นสัญลักษณ์; เมื่อส่งผ่านไปยังการฉีดจะใช้วิธีการตั้งชื่อโดยสัญลักษณ์ (ในกรณีนี้การดำเนินการเพิ่มเติม) กับแต่ละองค์ประกอบเทียบกับค่าตัวสะสม


6
คุณสามารถกำจัด to_f และ? arr.inject(0.0) { |sum,el| sum + el } / arr.sizeผู้ประกอบการโดยการส่งผ่านค่าเริ่มต้นที่จะฉีด:
Dave Ray

103
หรือ: arr.inject (: +). to_f / arr.size # => 3.375
glenn jackman

5
ฉันไม่คิดว่าหมายจับนี้เพิ่มไปยังคลาส Array เนื่องจากมันไม่สามารถใช้กับทุกประเภทที่อาร์เรย์สามารถมีได้
Sarah Mei

8
@John: นั่นไม่ใช่ Symbol # to_proc conversion อย่างแน่นอน - เป็นส่วนหนึ่งของส่วนต่อinjectประสานที่กล่าวถึงในเอกสารประกอบ ผู้ประกอบการคือto_proc &
Chuck

21
หากคุณกำลังใช้ Rails Array#injectอยู่เกินความเป็นจริงที่นี่ #sumใช้เพียงแค่ ตัวอย่าง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

รุ่นที่ไม่ได้ใช้instance_evalจะเป็น:

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

4
ฉันไม่คิดว่ามันฉลาดเกินไป ฉันคิดว่ามันแก้ปัญหาที่เกิดขึ้นผิด ๆ คือมันใช้ลดซึ่งถูกต้องแน่นอน ผู้เขียนโปรแกรมควรได้รับการส่งเสริมให้เข้าใจสิ่งที่ถูกต้องสาเหตุที่ถูกต้องแล้วเผยแพร่ สำหรับการดำเนินการเล็กน้อยอย่างปานกลางจริงไม่จำเป็นต้อง "ฉลาด" แต่โดยการทำความเข้าใจว่า "การลด" สำหรับกรณีเล็กน้อยนั้นสามารถเริ่มนำไปใช้กับปัญหาที่ซับซ้อนมากขึ้นได้ upvote
pduey

3
ทำไมต้องใช้ instance_eval ที่นี่
tybro0103

10
instance_evalให้คุณเรียกใช้รหัสในขณะที่ระบุเพียงaครั้งเดียวเท่านั้นดังนั้นจึงสามารถถูกล่ามโซ่กับคำสั่งอื่น ๆ เช่นrandom_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f } แทนที่จะเป็นrandom = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
Benjamin Manns

2
ฉันไม่รู้ว่าการใช้ instance_eval ด้วยวิธีนี้ดูแปลกและมี gotchas จำนวนมากที่เกี่ยวข้องกับมันซึ่งทำให้วิธีการนี้เป็นแนวคิดที่ไม่ดี IMO (ตัวอย่างเช่นหากคุณพยายามเข้าถึงและตัวแปรอินสแตนซ์หรือวิธีการselfภายในบล็อกนั้นคุณจะพบปัญหา) instance_evalมีมากขึ้นสำหรับ metaprogramming หรือ DSL
Ajedi32

1
@ Ajedi32 ฉันเห็นด้วยอย่าใช้สิ่งนี้ในรหัสแอปพลิเคชันของคุณ อย่างไรก็ตามมันเป็นเรื่องดีมากที่จะสามารถวางลงในแบบจำลองของฉัน (:
animatedgif

94

ฉันเชื่อว่าคำตอบที่ง่ายที่สุดคือ

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

1
ผมใช้เวลาสักครู่เพื่อพบว่ามัน - reduceเป็นวิธีการที่Enumerablemixin Arrayใช้โดย และแม้จะมีชื่อของมันผมเห็นด้วยกับ @ShuWu ... sumถ้าคุณกำลังใช้ทางรถไฟซึ่งการดำเนินการ
ทอมแฮร์ริสัน

ฉันเห็นวิธีแก้ปัญหาที่นี่ฉันรู้ว่ามันดูเรียบร้อยมาก แต่ฉันกลัวว่าถ้าฉันอ่านรหัสของฉันในอนาคตพวกเขาจะชอบพูดพล่อยๆ ขอบคุณสำหรับการแก้ปัญหาที่สะอาด!
atmosx

ในระบบของฉันนี่คือ 3x เร็วกว่าคำตอบที่ยอมรับ
sergio

48

ฉันหวังว่า Math.average (ค่า) แต่ไม่มีโชคเช่นนั้น

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

3
ฉันไม่ได้รู้ว่า #sum ถูกเพิ่มโดย Rails! ขอบคุณสำหรับการชี้ให้เห็นว่า
Denny Abraham

11
หลังจากวันคริสต์มาสปี 2559 (ทับทิม 2.4), อาเรย์จะมีsumวิธีการดังนั้นสิ่งนี้ดูเหมือนจะเป็นคำตอบที่ถูกต้องหลังจาก 6 ปีซึ่งสมควรได้รับรางวัลนอสตราดามุส
steenslag

38

รุ่น Ruby> = 2.4 มีเมธอดsum # ที่นับได้

และเพื่อให้ได้ค่าเฉลี่ยของจำนวนจุดลอยตัวคุณสามารถใช้Integer # fdiv

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

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

สำหรับรุ่นเก่า:

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

9

การเปรียบเทียบประสิทธิภาพของโซลูชันชั้นนำบางส่วน (เรียงตามลำดับประสิทธิภาพสูงสุด):

อาร์เรย์ขนาดใหญ่:

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 = 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)

มาตรฐานของคุณผิดเล็กน้อย มาตรฐาน / ips ดีกว่าจริง ๆ สำหรับการเปรียบเทียบแบบนี้ นอกจากนี้ฉันขอแนะนำให้ใช้ Array ที่มีประชากรแบบสุ่มกับจำนวนลบและบวกรวมทั้งลอยเพื่อให้ได้ผลลัพธ์ที่สมจริงยิ่งขึ้น คุณจะพบว่า instance_eval ช้ากว่า array.sum.fdiv ประมาณ 8x สำหรับลอย และประมาณ x1.12 สำหรับจำนวนเต็ม นอกจากนี้ระบบปฏิบัติการต่าง ๆ จะให้ผลลัพธ์ที่แตกต่างกัน บน mac ของฉันวิธีการเหล่านี้บางครั้งช้ากว่า Linux Droplet ของฉัน 2 เท่า
konung

นอกจากนี้วิธีการรวมใช้สูตรของเกาส์ในช่วงแทนการคำนวณผลรวม
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
ส่งคืนค่าที่ไม่ถูกต้องเนื่องจากการหารจำนวนเต็ม ตัวอย่างเช่นลอง [2,3] .mean ซึ่งคืนค่า 2 แทน 2.5
John Feminella

1
ทำไมอาร์เรย์ที่ว่างเปล่าควรมีผลรวมnilมากกว่า 0
Andrew Grimm

1
เพราะคุณสามารถรับความแตกต่างระหว่าง [] และ [0] และฉันคิดว่าทุกคนที่ต้องการค่าเฉลี่ยที่แท้จริงสามารถใช้ประโยชน์จาก to_i หรือแทนที่ค่าศูนย์ข้างต้นด้วย 0
astropanic

4

ให้ฉันนำบางสิ่งเข้าสู่การแข่งขันซึ่งแก้ปัญหาการหารด้วยศูนย์:

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

ฉันต้องยอมรับว่า "ลอง" เป็นตัวช่วยของ Rails แต่คุณสามารถแก้ปัญหานี้ได้อย่างง่ายดาย:

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

BTW: ฉันคิดว่ามันถูกต้องว่าค่าเฉลี่ยของรายการว่างเปล่าเป็นศูนย์ ค่าเฉลี่ยของความว่างเปล่าคืออะไรไม่ใช่ 0 ดังนั้นพฤติกรรมที่คาดหวัง อย่างไรก็ตามหากคุณเปลี่ยนเป็น:

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

ผลลัพธ์ของอะเรย์ที่ว่างเปล่าจะไม่เป็นข้อยกเว้นอย่างที่ฉันคาดไว้ แต่มันกลับมาที่ NaN ... ฉันไม่เคยเห็นมาก่อนใน Ruby ;-) ดูเหมือนว่าจะเป็นพฤติกรรมพิเศษของคลาส Float ...

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

4

สิ่งที่ฉันไม่ชอบเกี่ยวกับโซลูชันที่ได้รับการยอมรับ

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

คือมันไม่ได้ทำงานอย่างแท้จริงในลักษณะการทำงานอย่างหมดจด เราต้องการตัวแปร arr ในการคำนวณ arr.size ในตอนท้าย

ในการแก้ปัญหาอย่างหมดจดในทางปฏิบัติเราจำเป็นต้องติดตามค่าสองค่า: ผลรวมขององค์ประกอบทั้งหมดและจำนวนองค์ประกอบ

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

Santhosh ได้รับการปรับปรุงในการแก้ปัญหานี้: แทนที่จะใช้อาร์กิวเมนต์ r เป็นอาร์เรย์เราสามารถใช้การทำลายเพื่อแยกมันออกเป็นสองตัวแปรทันที

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

หากคุณต้องการดูวิธีการทำงานให้เพิ่มบางส่วน:

[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

เราสามารถใช้ struct แทน array เพื่อเก็บผลรวมและจำนวน แต่จากนั้นเราต้องประกาศ struct ก่อน:

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.methodใช้ในทับทิมขอบคุณสำหรับสิ่งนี้!
Epigene

อาร์เรย์ที่ส่งผ่านไปยังวิธีการฉีดสามารถแยกย้ายกันไป arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Santhosh

@ แซนโทช: ใช่มันอ่านง่ายกว่าเยอะ! ฉันจะไม่เรียกสิ่งนี้ว่า "การสลาย" แต่ฉันจะเรียกมันว่า "การทำลาย" tony.pitluga.com/2011/08/08/destructuring-with-ruby.html
bjelli

3

เพื่อความบันเทิงสาธารณะยังเป็นทางออกอื่น:

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
หากสิ่งนี้สูงขึ้นในการลงคะแนนฉันจะไม่เข้าใจเลย! ดีมาก.
Matt Stevens

ล้างดีกว่าฉลาดรหัสชิ้นนี้ไม่ชัดเจน
Sebastian Palma

2

ไม่มีทับทิมในพีซีเครื่องนี้ แต่มีบางอย่างที่ควรใช้:

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

average = total/values.size

2

Array#averageเพิ่ม

ฉันทำสิ่งเดียวกันบ่อยครั้งดังนั้นฉันคิดว่ามันรอบคอบที่จะขยายArrayชั้นเรียนด้วยaverageวิธีการง่ายๆ มันไม่ได้ทำงานอะไรเลยนอกจากอาร์เรย์ของตัวเลขเช่นจำนวนเต็มหรือจำนวนลอยหรือทศนิยม แต่จะมีประโยชน์เมื่อคุณใช้งานถูกต้อง

ฉันใช้ Ruby on Rails ดังนั้นฉันจึงวางมันไว้config/initializers/array.rbแต่คุณสามารถวางไว้ที่ใดก็ได้ที่รวมอยู่ในการบู๊ต ฯลฯ

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
สิ่งนี้จะคืนค่าที่ไม่ถูกต้องเนื่องจากการหารจำนวนเต็ม ตัวอย่างเช่นถ้า a คือ [2, 3] ผลลัพธ์ที่คาดหวังคือ 2.5 แต่คุณจะกลับมาที่ 2
John Feminella

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

แก้ปัญหาหารด้วยศูนย์หารจำนวนเต็มและอ่านง่าย สามารถแก้ไขได้อย่างง่ายดายหากคุณเลือกที่จะให้อาเรย์คืนค่าว่าง 0

ฉันชอบตัวแปรนี้เหมือนกัน แต่มันก็พูดมาก

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


1

วิธีนี้มีประโยชน์

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
ยินดีต้อนรับสู่ stack overflow here โปสเตอร์ต้นฉบับของคำถามต้องการคำตอบเป็น 3.375 และวิธีแก้ปัญหาของคุณให้ 3 i, e
Ajay Barot

ขอบคุณสำหรับความคิดเห็นของคุณ ฉันรู้ว่าผู้โพสต์ดั้งเดิมของคำถามต้องการคำตอบเป็น 3.375 และนั่นคือสิ่งที่วิธีการนี้ทำตามที่ฉันได้ให้ตัวแปร 'var' ค่าลอยตัว (เช่น; 0.0) Munim Munna ฉันต้องเห็นด้วยกับคุณแน่นอนมีคำตอบที่คล้ายกัน
Kishor Budhathoki

0

โดยไม่ต้องทำซ้ำอาร์เรย์ (เช่นสมบูรณ์แบบสำหรับหนึ่งสมุทร):

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

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

สั้น ๆ แต่ใช้ตัวแปรอินสแตนซ์


2
ฉันจะทำa_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_sizeมากกว่าสร้างตัวแปรอินสแตนซ์
Andrew Grimm

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.