Ahh, it feels good to do a ruby quiz again :-). I've ignored the other solutions so far so someone else may have taken the same approach. I took the approach that there are actual 3 times involved, so as to hopefully avoid as little as possible the range limiting that Ara was talking about. And it just made it easier to think about the problem. Essentially, the time displayed to the user is all that must be increase. So long as that is continually moving forward, the times behind the scene don't really matter. That for me results in 3 different times. - Actual time - Fuzzed time : actual time fuzzed within the fuzzy range (+- 5 min default) - Display time : Fuzzed time 'floored' to a granularity (10 min default) As a result, my approach has the following algorithm: - Calculate the actual time (update/advance) - Calculate a fuzzy range for the new fuzzed time which has - lower bound = the maximum of the last display time or (actual - fuzz factor minutes) - upper bound = actual + range - randomly pick a new time in that range for the fuzzed time - calculate the new display time off of the fuzzed time As a result, the Fuzzed time can increase/decrease with respect to itself, but will always be greater than the displayed time. But overall the fuzzed time will continue to increase. And the display time is always increasing. I also threw in some Extra Credits: 24hour/12hour option, fuzz factor (changing the +- range) and display granularity(one minute, ten minute on hour). Great Quiz! enjoy, -jeremy -- ======================================================================== Jeremy Hinegardner jeremy / hinegardner.org #---------------------------------------------------------------------- # Ruby Quiz #99 - Fuzzy Time # # Jeremy Hinegardner #---------------------------------------------------------------------- class FuzzyTime HOUR_24_FORMAT = "%H:%M" HOUR_12_FORMAT = "%I:%M" HOUR_FORMAT_OPTIONS = Hash.new(HOUR_24_FORMAT) HOUR_FORMAT_OPTIONS[HOUR_12_FORMAT] = HOUR_12_FORMAT HOUR_FORMAT_OPTIONS[:twelve_hour] = HOUR_12_FORMAT HOUR_FORMAT_OPTIONS[HOUR_24_FORMAT] = HOUR_24_FORMAT HOUR_FORMAT_OPTIONS[:twentyfour_hour] = HOUR_24_FORMAT GRANULARITIES = [:one_minute, :ten_minute, :one_hour] attr_accessor :time_format attr_accessor :fuzz_factor attr_accessor :display_granularity attr_reader :actual attr_reader :fuzzed attr_reader :display def initialize(time = Time.now) @actual = time.dup @time_format = HOUR_24_FORMAT @fuzz_factor = 5 * 60 @display_granularity = :ten_minute # initialize a base history that is minimal to give maximum # range for calculating a fuzzy time @fuzzed = @actual - @fuzz_factor @fuzz_history = [ { :actual => @actual, :fuzzed => @fuzzed, :display => calculate_display_time } ] calculate_fuzz_time end def advance(seconds) @actual += seconds calculate_fuzz_time end def update @actual = Time.now calculate_fuzz_time end # return a string representation of the display time which is the time # with up to the @disiplay_granularity replaced by '~'. when # :one_minute is the granularity, nothing needs to be done def to_s s = @display.strftime(time_format) case @display_granularity when :ten_minute s.chop! s << "~" when :one_hour s.chop! s.chop! s << "~~" end return s end # allow the time format to be set to 24 hour or 12 hour time def time_format=(format) @time_format = HOUR_FORMAT_OPTIONS[format] end # all the fuzz factor to be set in a range of minutes, no limit def fuzz_factor=(minutes) @fuzz_factor = 60 * minutes.abs end def fuzz_factor (@fuzz_factor / 60).to_i end def display_granularity=(granularity) if GRANULARITIES.include?(granularity.to_sym) @display_granularity = granularity # need to recalculate this when the granularity is updated calculate_display_time else raise ArgumentError, "display_granularity must be one of #{GRANULARITIES.join(",")}" end @display_granularity end private # # the display time is the fuzzy time converted to a "floor" time # with a granularity base upon the @display_granularity. For # example, if the fuzzed time is 13:43 then the display time would # be: # # granularity display # ======================== # :one_minute 13:43 # :ten_minute 13:40 # :one_hour 13:00 # def calculate_display_time case @display_granularity when :one_minute min = @fuzzed.min when :ten_minute min = (@fuzzed.min / 10) * 10 when :one_hour min = 0 end @display = Time.mktime( @fuzzed.year, @fuzzed.month, @fuzzed.day, @fuzzed.hour, min, 0, 0) end # # calculate the new fuzzy time. # # Since : # 1) the displayed time must appear to be continually increasing # 2) we must always be within the fuzz factor of the actual time # # Therefore: # the lower bound of the fuzzy range is the maximum of the displayed # time or the lower bond of the fuzz factor around the actual time. # def calculate_fuzz_time last_display = @fuzz_history.last[:display] min_fuzz_factor = @actual - @fuzz_factor lower_bound = last_display > min_fuzz_factor ? last_display : min_fuzz_factor upper_bound = @actual + @fuzz_factor range = upper_bound - lower_bound @fuzzed = lower_bound + rand(range + 1) calculate_display_time @fuzz_history << { :actual => @actual, :fuzzed => @fuzzed, :display => @display } end end