Как найти среднее из массива?
Если у меня есть массив:
[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 имеют метод Enumerable # 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
Однако я должен признать, что «try» является помощником Rails. Но вы можете легко решить это:
class Object;def try(*options);self&&send(*options);end;end
class Array;def avg;reduce(:+).try(:to_f).try(:/,size);end;end
Кстати: я думаю, что это правильно, что среднее значение пустого списка равно нулю. Среднее ничего - ничто, а не 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
Мы могли бы также использовать структуру вместо массива, чтобы содержать сумму и количество, но затем мы должны сначала объявить структуру:
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
в ruby, спасибо за это!
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
методом. Он не работает ни для чего, кроме массива чисел, таких как целые числа или числа с плавающей запятой или десятичные числа, но это удобно, когда вы используете его правильно.
Я использую 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]
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