Как создать среднее значение из массива Ruby?

Как получить найти среднее значение из массива?

Если у меня есть массив:

[0,4,8,2,5,0,2,6]

усреднение даст мне 3.375.

спасибо!

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, тоже неплохо, но, возможно, слишком умно, если вы не знаете, что происходит. The :+ является символом; при передаче для ввода он применяет метод, названный символом (в данном случае операция добавления) к каждому элементу против значения аккумулятора.


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

Я надеялся на математику.среднее (значения), но не такая удача.

values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f

Ruby versions >= 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.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(:/)

на этом ПК нет 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]
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


вы можете попробовать что-то вроде следующего:

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

короткий, но с помощью переменной экземпляра