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

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

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

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

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

Спасибо!

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

list.reduce(:+).to_f / list.size

Ответ 4

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

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

Ответ 5

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

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

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

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)

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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(:/)

Ответ 10

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

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

Ответ 11

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

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

average = total/values.size

Ответ 12

a = [0,4,8,2,5,0,2,6]
sum = 0
a.each { |b| sum += b }
average = sum / a.length

Ответ 13

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

Ответ 14

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

Ответ 15

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

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])

Ответ 16

Добавьте Array#average.

Я делал то же самое довольно часто, поэтому подумал, что было бы разумно просто расширить класс Array простым методом average. Он не работает ни для чего, кроме массива чисел, таких как целые числа или числа с плавающей запятой или десятичные числа, но это удобно, когда вы используете его правильно.

Я использую Ruby on Rails, поэтому я поместил это в config/initializers/array.rb но вы можете разместить его в любом месте, которое включено при загрузке и т.д.

config/initializers/array.rb

class Array

  # Will only work for an Array of numbers like Integers, Floats or Decimals.
  #
  # Throws various errors when trying to call it on an Array of other types, like Strings.
  # Returns nil for an empty Array.
  #
  def average
    return nil if self.empty?

    self.sum / self.size
  end

end

Ответ 17

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

a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
(a.sum/a.length).to_f
# => 3.0

Ответ 18

a = [5,3,6,1,8]
a.mean # => 4.6

Ответ 19

[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize

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