Skip to content

Instantly share code, notes, and snippets.

@s4parke
Last active February 8, 2017 02:03
Show Gist options
  • Save s4parke/6a00eae1bf163b749af7bc01a1fcd205 to your computer and use it in GitHub Desktop.
Save s4parke/6a00eae1bf163b749af7bc01a1fcd205 to your computer and use it in GitHub Desktop.
Calculates mean time of day using circular vector average
class AverageTimes
# Accepts an array of objects (ActiveRecord collection probably)
# Computes the average time of day for a given :time_attr
# by converting times to degrees, calculating mean angle
#
# @sleep_diaries = Diary.all
# @average_times = AverageTimes( @sleep_diaries )
# @average_bedtime = @average_times.of( :bed_at )
include Enumerable
require 'complex' #not needed in ruby >= 2.0
#require 'active_support' #not needed in rails >= 2
#require 'active_support/core_ext/time' #not needed in rails >= 3
SECONDS_IN_DAY = 86400
SECONDS_IN_HOUR = 3600
SECONDS_IN_MIN = 60
PI = Math::PI
DEGREES = 360
VALID_TIME = /(\d?\d):(\d\d)/
def initialize( data )
@collection = data
end
def of( time_attr )
@times = get_times(time_attr)
raise "Invalid time attribute: #{time_attr}" unless times
average_time @times
end
def average_time( times )
deg2time( mean_angle(times.map {|t| time2deg t}) )
end
def get_times( time_attr = 'strip' )
conditions = lambda {|x|
x.respond_to?(time_attr) &&
is_valid_time.call(x.send(time_attr) )
}
@collection.select(&conditions).map{|x| parse_time.call x.send(time_attr) }
end
private
def is_valid_time
proc { |t| !(t.to_s.match(VALID_TIME).blank?) }
end
def parse_time
proc { |t|
if t.class == Time
t
elsif is_valid_time.call(t)
Time.parse t.to_s
else
false
end
}
end
def deg2rad(d)
d * PI / 180
end
def rad2deg(r)
r * 180 / PI
end
def mean_angle(deg)
rad2deg((deg.inject(0) {|z, d| z + Complex.polar(1, deg2rad(d))} / deg.length).arg)
end
def time2deg(t)
t.seconds_since_midnight * DEGREES.to_f / SECONDS_IN_DAY
end
def deg2time(d)
sec = (d % DEGREES) * SECONDS_IN_DAY / DEGREES
"%02d:%02d:%02d" % [sec/SECONDS_IN_HOUR, (sec%SECONDS_IN_HOUR)/SECONDS_IN_MIN, sec%SECONDS_IN_MIN]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment