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.
Calculate average time from array of hashes
require 'time'
array = [
{"startTime"=>"2015-12-05T07:49:30.000Z", "endTime"=>"2015-12-05T15:56:30.000Z"},
{"startTime"=>"2015-12-04T07:02:30.000Z", "endTime"=>"2015-12-04T14:59:30.000Z"},
{"id"=>"5660614e8a3b090700bad845", "userId"=>"55cd59542d98600100397c12", "day"=>"2015-12-03", "startTime"=>"2015-12-03T06:02:32.000Z", "endTime"=>"2015-12-03T13:38:32.000Z"},
{"startTime"=>"2015-12-02T09:17:33.000Z", "endTime"=>"2015-12-02T15:27:33.000Z"},
{"startTime"=>"2015-12-01T05:45:36.000Z", "endTime"=>"2015-12-01T13:50:36.000Z"}
]
times = array.map{|el| el['startTime']}.map{|el| Time.parse(el) }
#=> [2015-12-05 07:49:30 UTC, 2015-12-04 07:02:30 UTC, 2015-12-03 06:02:32 UTC, 2015-12-02 09:17:33 UTC, 2015-12-01 05:45:36 UTC]
average_time = Time.at(times.map(&:to_f).inject(:+) / times.size)
#=> 2015-12-03 08:11:32 +0100
Average array of times as strings in Ruby
(sz = array.reject {|t| t == '0:00:00' }).
map {|t| Time.parse t }.
reduce(0) {|a, t| a += t.to_i }.to_f / sz.size
You want to group these things into functional operations. Reject the stuff you don't need, then act on the rest.
Here, reduce
is a viable way to get an array average, and has been answered before.
here is an alternative that is slightly more concise, slightly more cryptic
(sz = array.reject {|t| t == '0:00:00' }).
map {|t| Time.parse(t).to_i }.
reduce(:+).to_f / sz.size
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.
Ruby average value of array of time values (fixnums)
def average_days(a)
seconds = a.inject(:+) / a.size
minutes = seconds / 60
days = (minutes / 1440).round
"#{days} days"
end
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:
- They key itself (
"a"
or"b"
). - 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)
Group by key in array and get max and average
So, when you do "data": { ... }
, the data
actually becomes a symbol, not a string so you would need to do something like:
@data[:data].group_by { |data| data[:datetime].split('-')[0] }
in order to group by the :datetime
key, ignoring the time portion (I assume, the time portion is just everything after the -
). Then you end up with a hash looking like:
{"20160815"=>[{:temp=>22, :wind=>351.0, :datetime=>"20160815-0330"}, {:temp=>21, :wind=>321.0, :datetime=>"20160815-0345"}]}
and to find the max :temp
and average of the :wind
you can do:
results = @data[:data].group_by { |data| data[:datetime].split('-')[0] }.map do |date, values|
[date, {
maximum_temp: values.max_by { |value| value[:temp] }[:temp],
average_wind: values.sum { |value| value[:wind] }.to_f / values.length
}]
end.to_h
# => {"20160815"=>{:maximum_temp=>22, :average_wind=>336.0}}
Array Average Rails
average = temp.sum / temp.size.to_f
Related Topics
Any Ruby Library to Inspect What Are the Arguments That a Certain Methods Take
In Ruby How to Create a Local Variable Explicitly
Rails 5.2 Activestorage Save and Then Read Exif Data
Ruby 1.9 Ramaze App Failing with "Illegal Instruction"
Can't Start Rails Server - Could Not Find a JavaScript Runtime
Reverse a String Each Two Characters with Ruby
Array#Uniq with Block Equivalent in Ruby 1.8.7
Set Site/User Fields in Activeresource
Symbol#To_Proc Shorthand with the Stabby Lambda Syntax
Why Do I Get a Encoding::Compatibilityerror with #Inspect
Static Variables in Ruby, Like in C Functions
Make User-Inputted Url into External Link in Rails
Rails 3 - How to Handle Pg Error Incomplete Multibyte Character