จะหาค่าเฉลี่ยจากอาร์เรย์ได้อย่างไร
ถ้าฉันมีอาร์เรย์:
[0,4,8,2,5,0,2,6]
ค่าเฉลี่ยจะให้ฉัน 3.375
จะหาค่าเฉลี่ยจากอาร์เรย์ได้อย่างไร
ถ้าฉันมีอาร์เรย์:
[0,4,8,2,5,0,2,6]
ค่าเฉลี่ยจะให้ฉัน 3.375
คำตอบ:
ลองสิ่งนี้:
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
เป็นคนดี แต่บางทีก็ฉลาดเกินไปถ้าคุณไม่รู้ว่าเกิดอะไรขึ้น :+
เป็นสัญลักษณ์; เมื่อส่งผ่านไปยังการฉีดจะใช้วิธีการตั้งชื่อโดยสัญลักษณ์ (ในกรณีนี้การดำเนินการเพิ่มเติม) กับแต่ละองค์ประกอบเทียบกับค่าตัวสะสม
arr.inject(0.0) { |sum,el| sum + el } / arr.size
ผู้ประกอบการโดยการส่งผ่านค่าเริ่มต้นที่จะฉีด:
inject
ประสานที่กล่าวถึงในเอกสารประกอบ ผู้ประกอบการคือto_proc
&
Array#inject
อยู่เกินความเป็นจริงที่นี่ #sum
ใช้เพียงแค่ ตัวอย่างarr.sum.to_f / arr.size
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
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
self
ภายในบล็อกนั้นคุณจะพบปัญหา) instance_eval
มีมากขึ้นสำหรับ metaprogramming หรือ DSL
ฉันเชื่อว่าคำตอบที่ง่ายที่สุดคือ
list.reduce(:+).to_f / list.size
reduce
เป็นวิธีการที่Enumerable
mixin Array
ใช้โดย และแม้จะมีชื่อของมันผมเห็นด้วยกับ @ShuWu ... sum
ถ้าคุณกำลังใช้ทางรถไฟซึ่งการดำเนินการ
ฉันหวังว่า Math.average (ค่า) แต่ไม่มีโชคเช่นนั้น
values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
sum
วิธีการดังนั้นสิ่งนี้ดูเหมือนจะเป็นคำตอบที่ถูกต้องหลังจาก 6 ปีซึ่งสมควรได้รับรางวัลนอสตราดามุส
รุ่น 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
การเปรียบเทียบประสิทธิภาพของโซลูชันชั้นนำบางส่วน (เรียงตามลำดับประสิทธิภาพสูงสุด):
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)
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
nil
มากกว่า 0
ให้ฉันนำบางสิ่งเข้าสู่การแข่งขันซึ่งแก้ปัญหาการหารด้วยศูนย์:
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
สิ่งที่ฉันไม่ชอบเกี่ยวกับโซลูชันที่ได้รับการยอมรับ
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
ใช้ในทับทิมขอบคุณสำหรับสิ่งนี้!
arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
เพื่อความบันเทิงสาธารณะยังเป็นทางออกอื่น:
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
ไม่มีทับทิมในพีซีเครื่องนี้ แต่มีบางอย่างที่ควรใช้:
values = [0,4,8,2,5,0,2,6]
total = 0.0
values.each do |val|
total += val
end
average = total/values.size
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
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
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
arr = [0,4,8,2,5,0,2,6]
average = arr.inject(&:+).to_f / arr.size
# => 3.375
วิธีนี้มีประโยชน์
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, 2, 3, 4].then { |a| a.sum.to_f / a.size }
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize
สั้น ๆ แต่ใช้ตัวแปรอินสแตนซ์
a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size
มากกว่าสร้างตัวแปรอินสแตนซ์
คุณสามารถลองทำสิ่งต่อไปนี้:
a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0