How do I create an average from a Ruby array?

Ruby on-RailsRuby

Ruby on-Rails Problem Overview


How would get find an average from an array?

If I have the array:

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

Averaging would give me 3.375.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Try this:

arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5

Note the .to_f, which you'll want for avoiding any problems from integer division. You can also do:

arr = [5, 6, 7, 8]
arr.inject(0.0) { |sum, el| sum + el } / arr.size
=> 6.5

You can define it as part of Array as another commenter has suggested, but you need to avoid integer division or your results will be wrong. Also, this isn't generally applicable to every possible element type (obviously, an average only makes sense for things that can be averaged). But if you want to go that route, use this:

class Array
  def sum
    inject(0.0) { |result, el| result + el }
  end

  def mean 
    sum / size
  end
end

If you haven't seen inject before, it's not as magical as it might appear. It iterates over each element and then applies an accumulator value to it. The accumulator is then handed to the next element. In this case, our accumulator is simply an integer that reflects the sum of all the previous elements.

Edit: Commenter Dave Ray proposed a nice improvement.

Edit: Commenter Glenn Jackman's proposal, using arr.inject(:+).to_f, is nice too but perhaps a bit too clever if you don't know what's going on. The :+ is a symbol; when passed to inject, it applies the method named by the symbol (in this case, the addition operation) to each element against the accumulator value.

Solution 2 - Ruby on-Rails

a = [0,4,8,2,5,0,2,6]
a.instance_eval { reduce(:+) / size.to_f } #=> 3.375

A version of this that does not use instance_eval would be:

a = [0,4,8,2,5,0,2,6]
a.reduce(:+) / a.size.to_f #=> 3.375

Solution 3 - Ruby on-Rails

I believe the simplest answer is

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

Solution 4 - Ruby on-Rails

I was hoping for Math.average(values), but no such luck.

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

Solution 5 - Ruby on-Rails

Ruby versions >= 2.4 has an Enumerable#sum method.

And to get floating point average, you can use Integer#fdiv

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

arr.sum.fdiv(arr.size)
# => 3.375

For older versions:

arr.reduce(:+).fdiv(arr.size)
# => 3.375

Solution 6 - Ruby on-Rails

Some benchmarking of top solutions (in order of most efficient):

Large Array:
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)
Small Arrays:
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)

Solution 7 - Ruby on-Rails

Without having to repeat the array (e.g. perfect for one-liners):

[1, 2, 3, 4].then { |a| a.sum.to_f / a.size }

Solution 8 - Ruby on-Rails

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

Solution 9 - Ruby on-Rails

Let me bring something into competition which solves the division by zero problem:

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

I must admit, however, that "try" is a Rails helper. But you can easily solve this:

class Object;def try(*options);self&&send(*options);end;end
class Array;def avg;reduce(:+).try(:to_f).try(:/,size);end;end

BTW: I think it is correct that the average of an empty list is nil. The average of nothing is nothing, not 0. So that is expected behavior. However, if you change to:

class Array;def avg;reduce(0.0,:+).try(:/,size);end;end

the result for empty Arrays won't be an exception as I had expected but instead it returns NaN... I've never seen that before in Ruby. ;-) Seems to be a special behavior of the Float class...

0.0/0 #==> NaN
0.1/0 #==> Infinity
0.0.class #==> Float

Solution 10 - Ruby on-Rails

what I don't like about the accepted solution

arr = [5, 6, 7, 8]
arr.inject{ |sum, el| sum + el }.to_f / arr.size
=> 6.5

is that it does not really work in a purely functional way. we need a variable arr to compute arr.size at the end.

to solve this purely functionally we need to keep track of two values: the sum of all elements, and the number of elements.

[5, 6, 7, 8].inject([0.0,0]) do |r,ele|
    [ r[0]+ele, r[1]+1 ]
end.inject(:/)
=> 6.5   

Santhosh improved on this solution: instead of the argument r being an array, we could use destructuring to immediatly pick it apart into two variables

[5, 6, 7, 8].inject([0.0,0]) do |(sum, size), ele| 
   [ sum + ele, size + 1 ]
end.inject(:/)

if you want to see how it works, add some puts:

[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

We could also use a struct instead of an array to contain the sum and the count, but then we have to declare the struct first:

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

Solution 11 - Ruby on-Rails

For public amusement, yet another solution:

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

Solution 12 - Ruby on-Rails

Don't have ruby on this pc, but something to this extent should work:

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

average = total/values.size

Solution 13 - Ruby on-Rails

Add Array#average.

I was doing the same thing quite often so I thought it was prudent to just extend the Array class with a simple average method. It doesn't work for anything besides an Array of numbers like Integers or Floats or Decimals but it's handy when you use it right.

I'm using Ruby on Rails so I've placed this in config/initializers/array.rb but you can place it anywhere that's included on boot, etc.

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.to_d / self.size
  end

end

Solution 14 - Ruby on-Rails

Another simple solution too

arr = [0,4,8,2,5,0,2,6]
arr.sum(0.0) / arr.size

Solution 15 - Ruby on-Rails

You can choose one of the below solutions as you wish.

Bruteforce

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

=> 3.375

Method

def avg(array)
  array.sum.to_f / array.size.to_f
end  

avg([0,4,8,2,5,0,2,6])
=> 3.375

Monkey Patching

class Array
  def avg
    sum.to_f / size.to_f
  end
end

[0,4,8,2,5,0,2,6].avg
=> 3.375

But I don't recommend to monkey patch the Array class, this practice is dangerous and can potentially lead to undesirable effects on your system.

For our good, ruby language provides a nice feature to overcome this problem, the Refinements, which is a safe way for monkey patching on ruby.

To simplify, with the Refinements you can monkey patch the Array class and the changes will only be available inside the scope of the class that is using the refinement! :)

You can use the refinement inside the class you are working on and you are ready to go.

Refinements
module ArrayRefinements
  refine Array do
    def avg
      sum.to_f / size.to_f
    end
  end
end

class MyClass
  using ArrayRefinements

  def test(array)
    array.avg
  end
end

MyClass.new.test([0,4,8,2,5,0,2,6])
=> 3.375

Solution 16 - Ruby on-Rails

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

Solution 17 - Ruby on-Rails

a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : a.reduce(:+)/a.size.to_f
=> 3.375

Solves divide by zero, integer division and is easy to read. Can be easily modified if you choose to have an empty array return 0.

I like this variant too, but it's a little more wordy.

a = [0,4,8,2,5,0,2,6]
a.empty? ? nil : [a.reduce(:+), a.size.to_f].reduce(:/)
=> 3.375

Solution 18 - Ruby on-Rails

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

Solution 19 - Ruby on-Rails

This method can be helpful.

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

Solution 20 - Ruby on-Rails

print array.sum / array.count is how i've done it

Solution 21 - Ruby on-Rails

I really like to define a mean() method so my code is more expressive.

I usually want to ignore nil by default, so here's what I define

def mean(arr)
  arr.compact.inject{ |sum, el| sum + el }.to_f / arr.compact.size
end

mean([1, nil, 5])
=> 3.0

If you want to keep the nils, just remove both the .compacts.

Solution 22 - Ruby on-Rails

A much faster solution than .inject is :

> arr.sum(0.0) / arr.size

See this article for ref: https://andycroll.com/ruby/calculate-a-mean-average-from-a-ruby-array/

Solution 23 - Ruby on-Rails

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

Short but using instance variable

Solution 24 - Ruby on-Rails

You could try something like the following:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondottyView Question on Stackoverflow
Solution 1 - Ruby on-RailsJohn FeminellaView Answer on Stackoverflow
Solution 2 - Ruby on-RailsCorban BrookView Answer on Stackoverflow
Solution 3 - Ruby on-RailsShu WuView Answer on Stackoverflow
Solution 4 - Ruby on-RailsDenny AbrahamView Answer on Stackoverflow
Solution 5 - Ruby on-RailsSanthoshView Answer on Stackoverflow
Solution 6 - Ruby on-RailsstevenspielView Answer on Stackoverflow
Solution 7 - Ruby on-RailsDorianView Answer on Stackoverflow
Solution 8 - Ruby on-RailsastropanicView Answer on Stackoverflow
Solution 9 - Ruby on-Railshurikhan77View Answer on Stackoverflow
Solution 10 - Ruby on-RailsbjelliView Answer on Stackoverflow
Solution 11 - Ruby on-RailsBoris StitnickyView Answer on Stackoverflow
Solution 12 - Ruby on-RailssaretView Answer on Stackoverflow
Solution 13 - Ruby on-RailsJoshua PinterView Answer on Stackoverflow
Solution 14 - Ruby on-RailsyvzlkrktView Answer on Stackoverflow
Solution 15 - Ruby on-RailsVictorView Answer on Stackoverflow
Solution 16 - Ruby on-RailserikView Answer on Stackoverflow
Solution 17 - Ruby on-RailsMatt StevensView Answer on Stackoverflow
Solution 18 - Ruby on-RailsRahul PatelView Answer on Stackoverflow
Solution 19 - Ruby on-RailsKishor BudhathokiView Answer on Stackoverflow
Solution 20 - Ruby on-RailsseasonalzView Answer on Stackoverflow
Solution 21 - Ruby on-RailsstevecView Answer on Stackoverflow
Solution 22 - Ruby on-RailsMMafiewsView Answer on Stackoverflow
Solution 23 - Ruby on-RailsAlex LeschenkoView Answer on Stackoverflow
Solution 24 - Ruby on-RailsPaul MarclayView Answer on Stackoverflow