Как создать среднее из массива Ruby? - PullRequest
197 голосов
/ 27 августа 2009

Как бы найти среднее из массива?

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

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

Усреднение дало бы мне 3,375.

Спасибо!

Ответы [ 18 ]

246 голосов
/ 27 августа 2009

Попробуйте это:

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

106 голосов
/ 27 августа 2009
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
89 голосов
/ 25 февраля 2012

Я считаю, что самый простой ответ -

list.reduce(:+).to_f / list.size
45 голосов
/ 07 сентября 2010

Я надеялся на Math.average (значения), но не повезло.

values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
30 голосов
/ 29 декабря 2016

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 голосов
/ 23 февраля 2018

Сравнительный анализ лучших решений (в порядке наиболее эффективных):

Большой массив:

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)
4 голосов
/ 27 августа 2009
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
4 голосов
/ 21 мая 2014

Позвольте мне внести в конкуренцию что-то, что решает проблему деления на ноль:

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
4 голосов
/ 07 февраля 2014

Для общественного развлечения, еще одно решение:

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
3 голосов
/ 07 февраля 2014

что мне не нравится в принятом решении

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(:/)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...