Как получить среднее значение из массива?
Если у меня есть массив:
[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
, это не так волшебно, как могло бы показаться. Он выполняет итерацию по каждому элементу, а затем применяет к нему значение аккумулятора. Аккумулятор затем передается следующему элементу. В этом случае наш аккумулятор представляет собой просто целое число, которое отражает сумму всех предыдущих элементов.
Изменить: Комментарий Dave Ray предложил хорошее улучшение.
Изменить: Предложение комментатора Glenn Jackman, используя arr.inject(:+).to_f
, тоже хорошо, но, возможно, слишком умно, если вы не знаете, что происходит. :+
является символом; когда он передается для инъекции, он применяет метод, названный символом (в данном случае операции добавления) к каждому элементу, против значения аккумулятора.
arr.inject(0.0) { |sum,el| sum + el } / 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
Я считаю, что самый простой ответ -
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
Версии 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)
Позвольте мне привести что-то в соревнование, которое решает задачу деления на нуль:
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
Для общественного развлечения еще одно решение:
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
что мне не нравится в принятом решении
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(:/)
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
У вас нет рубина на этом компьютере, но что-то в этом роде должно работать:
values = [0,4,8,2,5,0,2,6]
total = 0.0
values.each do |val|
total += val
end
average = total/values.size
arr = [0,4,8,2,5,0,2,6]
average = arr.inject(&:+).to_f / arr.size
=> 3.375
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
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
Этот метод может быть полезен.
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])
a = [5,3,6,1,8]
a.mean # => 4.6
Вы можете попробовать что-то вроде следующего:
2.0.0-p648 :009 > a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
2.0.0-p648 :010 > (a.sum/a.length).to_f
=> 3.0
[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
а не создавать переменную экземпляра.