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



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



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



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


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



спасибо!

596   16  

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

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

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

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

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.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

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

Comments

    Ничего не найдено.