How to Create an Average from a Ruby Array

How do I create an average from a Ruby array?

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.

Array Average Rails

 average = temp.sum / temp.size.to_f

How to calculate averages of items in array

You can use Enumerable#each_cons:

array = [ 1, 3, 5, 1, -10]
array.each_cons(2).map { |a| a.inject(:+) / 2.0 }
#=> [2.0, 4.0, 3.0, -4.5]

Summarizing it into a method:

def each_cost_avg(array, size = 2)
array.each_cons(size).map { |a| a.inject(:+) / size.to_f }
end

each_cost_avg(array, 2) #=> [2.0, 4.0, 3.0, -4.5]
each_cost_avg(array, 3) #=> [3.0, 3.0, -1.3333333333333333]
each_cost_avg(array, 4) #=> [2.5, -0.25]

Get the average of numbers in an array which is the values of an hash

hash_1 = {"Luke"=> [2,3,4], "Mark"=>[3,5], "Jack"=>[2]}

You don't need another hash for the given below code.

p hash_1.transform_values!{|x| x.sum/x.count}

Result

{"Luke"=>3, "Mark"=>4, "Jack"=>2}

Ruby: Average array of times

So you need do define a function that can calculate the average of times formatted as strings. Convert the data to minutes, avg the total minutes and then back to a time.

I would do it something like this:

a =  ['18:35', '19:07', '23:09']

def avg_of_times(array_of_time)
size = array_of_time.size
avg_minutes = array_of_time.map do |x|
hour, minute = x.split(':')
total_minutes = hour.to_i * 60 + minute.to_i
end.inject(:+)/size
"#{avg_minutes/60}:#{avg_minutes%60}"
end

p avg_of_times(a) # = > "20:17"

Then when you call you function you check if any/all items in your array is formatted as a time. Maybe using regexp.

Sum and Average Array of Array

output = [
{"name"=>"aaa", "job"=>"a", "pay"=> 2 },
{"name"=>"zzz", "job"=>"a", "pay"=> 4 },
{"name"=>"xxx", "job"=>"a", "pay"=> 6 },
{"name"=>"yyy", "job"=>"a", "pay"=> 8 },
{"name"=>"aaa", "job"=>"b", "pay"=> 2 },
{"name"=>"zzz", "job"=>"b", "pay"=> 4 },
{"name"=>"xxx", "job"=>"b", "pay"=> 6 },
{"name"=>"yyy", "job"=>"b", "pay"=> 10 },
]

output.group_by { |obj| obj['job'] }.map do |key, list|
[key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end

The group_by method will transform your list into a hash with the following structure:

{"a"=>[{"name"=>"aaa", "job"=>"a", "pay"=>2}, ...], "b"=>[{"name"=>"aaa", "job"=>"b", ...]}

After that, for each pair of that hash, we want to calculate the mean of its 'pay' values, and return a pair [key, mean]. We use a map for that, returning a pair with:

  1. They key itself ("a" or "b").
  2. The mean of the values. Note that the values list has the form of a list of hashes. To retrieve the values, we need to extract the last element of each pair; that's what list.map { |obj| obj['pay'] } is used for. Finally, calculate the mean by suming all elements with .reduce(:+) and dividing them by the list size as a float.

Not the most efficient solution, but it's practical.


Comparing the answer with @EricDuminil's, here's a benchmark with a list of size 8.000.000:

def Wikiti(output)
output.group_by { |obj| obj['job'] }.map do |key, list|
[key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end
end

def EricDuminil(output)
count_and_sum = output.each_with_object(Hash.new([0, 0])) do |hash, mem|
job = hash['job']
count, sum = mem[job]
mem[job] = count + 1, sum + hash['pay']
end
result = count_and_sum.map do |job, (count, sum)|
[job, sum / count.to_f]
end
end

require 'benchmark'

Benchmark.bm do |x|
x.report('Wikiti') { Wikiti(output) }
x.report('EricDuminil') { EricDuminil(output) }
end

user system total real
Wikiti 4.100000 0.020000 4.120000 ( 4.130373)
EricDuminil 4.250000 0.000000 4.250000 ( 4.272685)

How to calculate average in a multidimensional array conditionally if their first values are the same

With a bit of debugging, the following should help get much faster results:

Answer.
joins(:question, :feedbacks). # assuming that answer has_many feedbacks
group(["questions.id", "feedbacks.week"]). # assuming week is integer column
average("CAST(answers.name AS INT)"). # assuming that name is string-y column
each_with_object({}) do |(keys, average), hash|
question_id, week = keys
hash[question_id] ||= []
hash[question_id][week] = average
end

If you want to keep things the way they are (not advised), then one working (albeit hard-to-follow) solution is this:

arr = [ 
[0, [[22, 3], [23, 5]]],
[0, [[22, 1], [23, 2]]],
[1, [[22, 4], [23, 4]]],
[1, [[22, 2], [23, 4]]]
]

arr.each_with_object({}) do |(a, b), hash|
c, d, e, f = b.flatten
# for first row this will be c, d, e, f = 22, 3, 23, 5

hash[c] ||= []
hash[c][a] ||= []
hash[c][a] << d

hash[e] ||= []
hash[e][a] ||= []
hash[e][a] << f


end.each_with_object({}) do |(k, v), hash|
# k are your 'keys' like 22, 23
# v is an array of arrays that you want to find out the averages of

hash[k] = \
v.map do |array|
array.reduce(:+).fdiv(array.size)
end
end


Related Topics



Leave a reply



Submit