16 ответов:
попробуйте это:
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раньше, это не так волшебно, как может показаться. Он перебирает каждый элемент, а затем применяет к нему значение аккумулятора. Аккумулятора передается следующему элементу. В этом случае, наш аккумулятор-это просто целое число, которое отражает сумму всех предыдущих элементов.Edit: комментатор Дэйв Рэй предложил хорошее улучшение.
Edit: Комментатор Гленн Предложение Джекмана, используя
arr.inject(:+).to_f, это тоже хорошо, но, возможно, слишком умно, если вы не знаете, что происходит. Элемент:+является символом; при передаче для инъекции он применяет метод, названный символом (в данном случае операция сложения) к каждому элементу против значения аккумулятора.
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
Я надеялся на математику.средний(значения), но не такая удача.
values = [0,4,8,2,5,0,2,6] average = values.sum / values.size.to_f
версии Ruby >= 2.4 имеет перечисляемый#sum метод.
и чтобы получить среднее значение с плавающей запятой, вы можете использовать целое число#fdiv
arr = [0,4,8,2,5,0,2,6] arr.sum.fdiv(arr.size) # => 3.375
для общественного развлечения, еще одно решение:
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
позвольте мне внести что-то в конкуренцию, которая решает проблему деления на ноль:
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... Я никогда не видел этого раньше в Руби. ;- ) Кажется, особое поведение класса Float...
0.0/0 #==> NaN 0.1/0 #==> Infinity 0.0.class #==> Float
некоторые бенчмаркинг топовых решений (в порядке наибольшей эффективности):
Большой Массив:
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
что мне не нравится в принятом решении
arr = [5, 6, 7, 8] arr.inject{ |sum, el| sum + el }.to_f / arr.size => 6.5это то, что он на самом деле не работает чисто функционально. нам нужна переменная arr для вычисления arr.размер в конце.
чтобы решить это чисто функционально, нам нужно отслеживать два значения: сумма всех элементов, и количество элементов.
[5, 6, 7, 8].inject([0.0,0]) do |r,ele| [ r[0]+ele, r[1]+1 ] end.inject(:/) => 6.5Santhosh улучшил это решение: вместо того, чтобы аргумент 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(:/)
на этом ПК нет ruby, но что-то в этой степени должно работать:
values = [0,4,8,2,5,0,2,6] total = 0.0 values.each do |val| total += val end average = total/values.size
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
вы можете попробовать что-то вроде следующего:
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короткий, но с помощью переменной экземпляра
Comments