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.
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.
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
instance_eval
lets you run the code while only specifying a
once, so it can be chained with other commands. I.e. random_average = Array.new(10) { rand(10) }.instance_eval { reduce(:+) / size.to_f }
instead of random = Array.new(10) { rand(10) }; random_average = random.reduce(:+) / random.size
self
inside that block, you'd run into problems.) instance_eval
is more for metaprogramming or DSL.
I believe the simplest answer is
list.reduce(:+).to_f / list.size
reduce
is a method of the Enumerable
mixin used by Array
. And despite its name, I agree with the @ShuWu ... unless you're using Rails which implements sum
.
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
sum
method, so this appears to be a correct answer after 6 years, worthy of the Nostradamus award.
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
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)
Without having to repeat the array (e.g. perfect for one-liners):
[1, 2, 3, 4].then { |a| a.sum.to_f / a.size }
.then
!
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
nil
rather than 0?
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
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
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(:/)
end.method
used in ruby, thanks for this!
arr.inject([0.0,0]) { |(sum, size), el| [ sum + el, size + 1 ] }.inject(:/)
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
Another simple solution too
arr = [0,4,8,2,5,0,2,6]
arr.sum(0.0) / arr.size
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
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
[1, 2, 3, 4].average
. It is returning 2
instead of 2.5
. If somebody desires a fully working solution with Monkey Patch, it can be found right here: stackoverflow.com/a/65337711/7644846
sum
to a BigDecimal
before dividing by the size
. Now, [1, 2, 3, 4].average #=> 0.25e1
. Thanks again.
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
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
arr = [0,4,8,2,5,0,2,6]
average = arr.inject(&:+).to_f / arr.size
# => 3.375
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])
print array.sum / array.count is how i've done it
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 nil
s, just remove both the .compact
s.
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/
[1,2].tap { |a| @asize = a.size }.inject(:+).to_f/@asize
Short but using instance variable
a_size = nil; [1,2].tap { |a| a_size = a.size }.inject(:+).to_f/a_size
rather than create an instance variable.
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
a = [1, 2, 3, 4]
. The average should be 2.5
instead of 2.0
. It should be a.sum.to_f / a.length.to_f
instead.
Success story sharing
arr.inject(0.0) { |sum,el| sum + el } / arr.size
.inject
interface, mentioned in the documentation. Theto_proc
operator is&
.Array#inject
is overkill here. Just use#sum
. E.g.arr.sum.to_f / arr.size