From: Tom Pollard Date: 2006-10-31T03:30:38+09:00 Subject: Re: [QUIZ] Fuzzy Time (#99) Hi, Here's my entry. I haven't peeked at any of the other responses yet, but I imagine most people have gone about this in roughly the same way. Any comments are welcome, I'm a relative nuby, and have only done toy projects so far. The place where there's probably the most latitude for variation is in the algorithm for deciding how the offset between the reported time and the actual time varies. The first thing I tried was to figure out, whenever the time was updated, what the range of possible offsets was (given the current actual and reported times and need to make sure the reported time doesn't retrogress) and then select a new offset uniformly within that range. The problem there is that, if you update more often than every five minutes, the clock will tend to advance 4-5 minutes ahead of the actual time and stay there. To address that, I now select an offset uniformly within the range of allowable errors (+5 minutes to -5 minutes) and, if the new offset would cause the time to retrogress, I just leave the reported time unchanged. The offset will still tend to be positive, however, and more positive the shorter the interval between updates. I'm interested to see if anyone came up with a good way to get a balanced distribution of errors over a long run. Cheers, Tom Here are my sources for the FuzzyTime module and a driver program. To run the driver, just say ruby fuzzyclock.rb You can set the number of digits to fuzz out with the option '-f '. You can set the "wobble" (maximum allowable error) with '-w '. You can have it display the actual time and cuurrent error with '-d'. You can have it run in an accelerated test mode with the option '-t'. - - - - - - - - - - - - - - - - - - - - fuzzytime.rb - - - - - - - - - - - - - - - - - - - #!/usr/bin/env ruby # Fuzzy time module # Author: Tom Pollard # class FuzzyTime attr_reader :actual, :current, :updated attr_accessor :am_pm, :fuzz, :wobble, :method # Return a new FuzzyTime clock, intialized to the given time. # If no intial time is specified, the current time is used. # The default fuzz is 1 digit; the default wobble is 5 minutes; # times are represented in 12-hour style by default. # def initialize ( actual=nil ) @actual = actual || Time.new() # the actual time (Time) @current = nil # the time that we report (Time) @updated = Time.new # when @current was last updated (Time) @wobble = 5 # the maximum error in @current (minutes) @fuzz = 1 # the number of digits to fuzz out (int) @method = 2 # the update algorithm to use (int) @am_pm = true # report 12-hour time? (boolean) @current = @actual + offset end # Advance the actual time by the given number of seconds # (The reported time may or may not change.) def advance ( delta=0 ) @actual += delta @current = @actual + offset @updated = Time.new self end # Advance the actual time to account for the time since it was last changed. # (The reported time may or may not change.) def update advance( (Time.new - @updated).to_i ) end # Calculate a new offset (in minutes) between the actual and reported times. # (This is called whenever the actual time changes.) def offset max_range = 2*@wobble + 1 min_offset = @current.to_i/60 - @actual.to_i/60 if @current.nil? || min_offset < -@wobble range = max_range else range = @wobble - min_offset + 1 end range = max_range if range > max_range if range == 0 delta = 0 else if @method == 1 # pick a new offset within the legal range of offsets. delta = @wobble - rand(range) else # pick a new offset within the range of allowable errors. # if it would require the time to regress, don't change the reported time. delta = @wobble - rand(max_range) delta = min_offset if delta < min_offset end end return 60 * delta end # Report the difference (in minutes) between the reported and actual times. def error (current - actual).to_i/60 end # Return a string representation of the fuzzy time. # The number of digits obscured by tildes is controlled by the 'fuzz' attribute. # Whether the time is in 12- or 24-hour style is controlled by 'am_pm'. def to_s if @am_pm display = @current.strftime("%I:%M") else display = @current.strftime("%H:%M") end @fuzz.times { display.sub!(/\d(\D*)$/, '~\1') } if @fuzz > 0 display end end - - - - - - - - - - - - - - - - - - - - fuzzyclock.rb - - - - - - - - - - - - - - - - - - - #!/usr/bin/env ruby # # Report the current time on the console, using the FuzzyTime module. # require 'fuzzytime.rb' require 'getoptlong.rb' opts = GetoptLong.new( [ '--fuzz', '-f', GetoptLong::REQUIRED_ARGUMENT], [ '--wobble', '-w', GetoptLong::REQUIRED_ARGUMENT], [ '--24hour', '-m', GetoptLong::NO_ARGUMENT], [ '--test', '-t', GetoptLong::NO_ARGUMENT], [ '--debug', '-d', GetoptLong::NO_ARGUMENT] ) ft = FuzzyTime.new test = false debug = false opts.each do |opt, arg| ft.fuzz = arg.to_i if opt == '--fuzz' ft.wobble = arg.to_i if opt == '--wobble' ft.am_pm = false if opt == '--24hour' debug = true if opt == '--debug' test = true if opt == '--test' end puts "Fuzzy Clock" while true printf "\r%s", ft.to_s printf " (%s %3d)", ft.actual.strftime("%H:%M"), ft.error if debug STDOUT.flush if test sleep 1 ft.advance 60 else sleep 60 ft.update end end - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -