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

186

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

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

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

Усреднение дало бы мне 3.375.

Спасибо!

  • 11
    Если вы получаете 21,75 как среднее из этих чисел, что-то очень неправильно ...
  • 2
    Дотти, не уверен, как вы получили 21,75, но среднее / среднее для этого набора данных составляет 3,375, а сумма равна 27. Я не уверен, какая функция агрегации даст 21,75. Пожалуйста, проверьте еще раз и убедитесь, что среднее действительно то, что вы после!
Показать ещё 2 комментария
Теги:

18 ответов

226
Лучший ответ

Попробуйте следующее:

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

  • 5
    Вы можете исключить to_f и? оператор, передав начальное значение для ввода: arr.inject(0.0) { |sum,el| sum + el } / arr.size .
  • 97
    Или: arr.inject (: +). To_f / arr.size # => 3,375
Показать ещё 9 комментариев
105
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
  • 0
    Привет, Корбан. Я ищу некоторую документацию по ": +". Вписывается ли это в «класс» символов, которые может использовать функция, например, Reduce? Спасибо.
  • 0
    Это можно считать слишком умным, но очень полезным, когда вы находитесь внутри консольного сеанса и не хотите расширять класс Array или хранить Array в отдельной переменной, поэтому +1 определенно!
Показать ещё 5 комментариев
84

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

list.reduce(:+).to_f / list.size
  • 1
    Мне потребовалось время, чтобы найти его - метод reduce Enumerable используется Array . И, несмотря на его название, я согласен с @ShuWu ... если только вы не используете Rails, который реализует sum .
  • 0
    Я вижу здесь решения, которые, как я знаю, выглядят очень аккуратно, но я боюсь, что если я буду читать мой код в будущем, им понравится тарабарщина. Спасибо за чистое решение!
44

Я надеялся на Math.average(значения), но такой удачи не было.

values = [0,4,8,2,5,0,2,6]
average = values.sum / values.size.to_f
  • 8
    Я думаю, что это может быть расширение Rails, так что не общий ответ по рубиновому случаю. Все равно дал вам голос в любом случае.
  • 3
    Я не знал, что #sum был добавлен Rails! Спасибо что подметил это.
Показать ещё 1 комментарий
20

Версии 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
4

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

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

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)
  • 0
    Ваш тест немного ошибочен. benchmark / ips на самом деле лучше для такого рода сравнений. Также я бы предложил использовать массив, заполненный случайным образом отрицательными и положительными числами, а также числами с плавающей точкой, чтобы получить более реалистичный результат. Вы обнаружите, что instance_eval медленнее, чем array.sum.fdiv. Примерно в 8 раз для поплавков. и около x1.12 для целых чисел. Также разные ОС будут давать разные результаты. на моем Mac некоторые из этих методов работают в 2 раза медленнее, чем на моей Linux Droplet
  • 0
    Также метод суммы использует формулу Гаусса, на диапазонах вместо вычисления суммы.
4

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

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
4

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

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
  • 1
    Если бы это было выше при голосовании, я бы этого не понял! Отлично.
3

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

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(:/)
  • 0
    Это первый раз, когда я вижу end.method в ruby, спасибо за это!
  • 0
    Массив, переданный методу inject, может быть разогнан. arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
Показать ещё 1 комментарий
3
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
  • 2
    Это возвращает неправильные значения из-за целочисленного деления. Попробуйте это, например, с помощью [2,3] .mean, который возвращает 2 вместо 2,5.
  • 2
    Нужно больше плавучести.
Показать ещё 2 комментария
2

У вас нет рубина на этом компьютере, но что-то в этом роде должно работать:

values = [0,4,8,2,5,0,2,6]
total = 0.0
values.each do |val|
 total += val
end

average = total/values.size
1

arr = [0,4,8,2,5,0,2,6] average = arr.inject(&:+).to_f / arr.size => 3.375

1
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
1
a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length
  • 4
    Это вернет неправильные значения из-за целочисленного деления. Например, если a равно [2, 3], ожидаемый результат равен 2,5, но вы получите 2.
0

Этот метод может быть полезен.

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])
  • 1
    Добро пожаловать в переполнение стека. Оригинальный постер вопроса хочет получить ответ 3.375, а ваше решение дает 3. i, e 27/8 = 3.
  • 0
    Подобный ответ уже существует здесь, см. Ответ Сарета.
Показать ещё 1 комментарий
0
a = [5,3,6,1,8]
a.mean # => 4.6
0

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

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
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize

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

  • 2
    Я бы сделал a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size а не создавать переменную экземпляра.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню