Как создать среднее значение из массива Ruby?
Как получить среднее значение из массива?
Если у меня есть массив:
[0,4,8,2,5,0,2,6]
Усреднение дало бы мне 3.375.
Спасибо!
Ответы
Ответ 1
Попробуйте следующее:
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
, это не так волшебно, как могло бы показаться. Он выполняет итерацию по каждому элементу, а затем применяет к нему значение аккумулятора. Аккумулятор затем передается следующему элементу. В этом случае наш аккумулятор представляет собой просто целое число, которое отражает сумму всех предыдущих элементов.
Изменить: Комментарий Dave Ray предложил хорошее улучшение.
Изменить: Предложение комментатора Glenn Jackman, используя arr.inject(:+).to_f
, тоже хорошо, но, возможно, слишком умно, если вы не знаете, что происходит. :+
является символом; когда он передается для инъекции, он применяет метод, названный символом (в данном случае операции добавления) к каждому элементу, против значения аккумулятора.
Ответ 2
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
Ответ 3
Я считаю, что самый простой ответ -
list.reduce(:+).to_f / list.size
Ответ 4
Я надеялся на Math.average(значения), но такой удачи не было.
values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
Ответ 5
Версии 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
Ответ 6
Некоторые сравнительные тесты лучших решений (в порядке наиболее эффективных):
Большой массив:
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)
Ответ 7
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
Ответ 8
Позвольте мне привести что-то в соревнование, которое решает задачу деления на нуль:
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
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
Ответ 9
что мне не нравится в принятом решении
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(:/)
Ответ 10
Для общественного развлечения еще одно решение:
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
Ответ 11
У вас нет рубина на этом компьютере, но что-то в этом роде должно работать:
values = [0,4,8,2,5,0,2,6]
total = 0.0
values.each do |val|
total += val
end
average = total/values.size
Ответ 12
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
Ответ 13
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
Ответ 14
arr = [0,4,8,2,5,0,2,6]
average = arr.inject(&:+).to_f / arr.size
# => 3.375
Ответ 15
Этот метод может быть полезен.
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])
Ответ 16
Добавьте 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
Ответ 17
Вы можете попробовать что-то вроде следующего:
a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0
Ответ 18
a = [5,3,6,1,8]
a.mean # => 4.6
Ответ 19
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize
Короткая, но использующая переменную экземпляра