Як би знайти середнє значення з масиву?
Якщо у мене є масив:
[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
, це не так магічно, як може здатися. Він повторюється над кожним елементом, а потім застосовує до нього значення акумулятора. Потім акумулятор передається наступному елементу. У цьому випадку наш акумулятор - це ціле число, яке відображає суму всіх попередніх елементів.
Редагувати: Коментатор Дейв Рей запропонував приємне поліпшення.
Редагувати: Пропозиція коментатора Глена Джекмана 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
Більше для метапрограмування або DSL.
Я вважаю, що найпростіша відповідь
list.reduce(:+).to_f / list.size
reduce
це метод Enumerable
міксину, який використовує Array
. І незважаючи на його назву, я погоджуюся з @ShuWu ... якщо ви не використовуєте Rails, який реалізує sum
.
Я сподівався на Math.average (значення), але такої удачі немає.
values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
sum
метод, так це , здається, правильна відповідь після 6 років, гідні нагороди Nostradamus.
У версіях Ruby> = 2.4 використовується метод " Сума" # .
А щоб отримати середнє значення з плаваючою комою, ви можете використовувати 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
Але я мушу визнати, що "спробувати" - це помічник Рейла. Але ви можете легко вирішити це:
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
Сантош покращив це рішення: замість того, щоб аргумент 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
Ми також могли б використовувати структуру замість масиву, щоб містити суму та кількість, але тоді спочатку слід оголосити структуру:
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
Array#average
.Я робив те саме, що часто, тому я вважав за доцільне просто розширити Array
клас простимaverage
методом. Він не працює ні для чого, крім масиву чисел, таких як Integers або Floats або Decimals, але це зручно при правильному використанні.
Я використовую 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].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