[QUIZ] Fuzzy Time (#99)

Discussion in 'Ruby' started by Ruby Quiz, Oct 27, 2006.

  1. Ruby Quiz

    Ruby Quiz Guest

    The three rules of Ruby Quiz:

    1. Please do not post any solutions or spoiler discussion for this quiz until
    48 hours have passed from the time on this message.

    2. Support Ruby Quiz by submitting ideas as often as you can:

    http://www.rubyquiz.com/

    3. Enjoy!

    Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
    on Ruby Talk follow the discussion. Please reply to the original quiz message,
    if you can.

    -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

    by Gavin Kistner

    The Background
    ---------------------------------------------------------------------

    Last night I was having trouble falling asleep, and was staring at my digital
    alarm clock (as I do so often under that circumstance). Something on the bedside
    table was occluding the minutes digit, however, so all I could tell is that it
    was "10 4". (Oddly, my alarm clock has no ":" between the hours and minutes.)

    "How nice!" I thought. "An imposed vagueness to keep me from obsessing on
    exactly what time it is! Should I really be worried about the exact time?
    Shouldn't I be more relaxed? Shouldn't a 10-minute precision in life be enough
    to keep me roughly on time, without obsessing on exacting promptitude?"

    I realized that if I kept staring at the clock (as I did), and I were to observe
    it changing from "10 4" to "10 5", that I would, at that moment, know exactly
    what time it is.

    "Bah" I thought, "so much for that idea."

    And then I thought some more. I thought of bad ideas: analog watches where the
    hand erratically swings forward and backward, digital clocks that showed random
    times near the correct time. And then I dreamed of the watch I wanted to own...

    The Challenge
    ---------------------------------------------------------------------

    Requirement #1: Write a Ruby program that shows the current time, but only the
    'tens' part of the minutes. For example, if the time is 10:37, then the program
    might output "10:3~"

    Requirement #2: The time shown by the clock must randomly vary +/- 5 minutes
    from reality. For example, if the time is actually 10:37, the program might
    output "10:3~" or "10:4~" (but not "10:2~" or "10:5~").

    Requirement #3: The time on the clock should continuously increase. If the time
    shows "10:4~" it must continue to show "10:4~" until it shows "10:5~". (It can't
    show "10:4~", then "10:3~" for a bit and then come back to "10:4~".)

    Putting the three requirements together, the left column below shows the actual
    time and the next three columns show the possible outputs from three different
    runs of the same program:

    10:35 10:3~ 10:4~ 10:3~
    10:36 10:3~ 10:4~ 10:3~
    10:37 10:3~ 10:4~ 10:3~
    10:38 10:3~ 10:4~ 10:3~
    10:39 10:4~ 10:4~ 10:3~
    10:40 10:4~ 10:4~ 10:3~
    10:41 10:4~ 10:4~ 10:3~
    10:42 10:4~ 10:4~ 10:3~
    10:43 10:4~ 10:4~ 10:3~
    10:44 10:4~ 10:4~ 10:3~
    10:45 10:4~ 10:4~ 10:4~
    10:46 10:4~ 10:4~ 10:5~
    10:47 10:4~ 10:4~ 10:5~
    10:48 10:4~ 10:4~ 10:5~
    10:49 10:4~ 10:4~ 10:5~
    10:50 10:4~ 10:4~ 10:5~
    10:51 10:4~ 10:4~ 10:5~
    10:52 10:5~ 10:4~ 10:5~
    10:53 10:5~ 10:4~ 10:5~
    10:54 10:5~ 10:4~ 10:5~
    10:55 10:5~ 10:5~ 10:5~
    10:56 10:5~ 10:5~ 11:0~
    10:57 10:5~ 10:5~ 11:0~
    10:58 10:5~ 10:5~ 11:0~
    10:59 10:5~ 10:5~ 11:0~
    11:00 10:5~ 10:5~ 11:0~
    11:01 10:5~ 10:5~ 11:0~

    Testing your Output
    ---------------------------------------------------------------------

    You should supply a FuzzyTime class that supports the following:

    ft = FuzzyTime.new # Start at the current time
    ft = FuzzyTime.new(Time.at(1161104503)) # Start at a specific time

    p ft.to_s # to_s format
    #=> "10:5~"

    p ft.actual, ft.actual.class # Reports real time as Time
    #=> Tue Oct 17 11:01:36 -0600 2006
    #=> Time

    ft.advance( 60 * 10 ) # Manually advance time
    puts ft # by a specified number of
    #=> 11:0~ # seconds.

    sleep( 60 * 10 )

    ft.update # Automatically update the time based on the
    puts ft # time that has passed since the last call
    #=> 11:1~ # to #initialize, #advance or #update

    Your class and output will be tested with code like the following:

    def test_output
    # Initialize with a well-known time
    ft = FuzzyTime.new( Time.at( ... ) )

    60.times do
    @legal = ... # Find the array of legal output strings
    @output = ft.to_s

    assert_block "#@output not one of #@legal.inspect" do
    @legal.include?( @output )
    end

    sleep( rand( 30 ) )
    ft.update
    end

    60.times do
    @legal = ... # Find the array of legal output strings
    @output = ft.to_s

    assert_block "#@output not one of #@legal.inspect" do
    @legal.include?( @output )
    end

    ft.advance( rand( 30 ) )
    end
    end

    Extra Credit
    ---------------------------------------------------------------------

    * Provide a self-running application that shows the time somehow.
    (ASCII in the terminal, some GUI window, etc.)

    * Allow your class to be customized to display 12- or 24-hour time.

    * Allow your class to be customized to change how close to reality
    it must display. (+/- 3 minutes, +/- 12 minutes, etc.)

    * Allow your class to be customized to change how roughly it displays
    the time (e.g. 1 minute, 10 minute, 1 hour intervals).

    * Ensure that the transition from one digit to the next occurs
    randomly across the range of -5 to +5. (So, while the digit might
    change 5 minutes before or 5 minutes after the real transition, on
    average the change should occur around the transition itself.)
    You might need to assume that your update/advance method will be
    called with a certain regularity (e.g. once per second, once every
    7 seconds, once every 30 seconds, etc.)

    * Come up with your own technique of displaying time that
    (a) is always 'close' to right, but (b) never allows a
    watchful person to ever know exactly what time it is.

    Things to Keep in Mind
    ---------------------------------------------------------------------

    * You need to be able to handle the transition across hour/day
    boundaries. (10:5~ might change to 11:0~ when the real time is still
    10:58, or might continue to display 10:5~ when the real time is
    11:04. On a 24-hour click, you also need to be able to wrap from
    23:5~ to 00:0~)

    * For testing purposes of the real-time #update advancing, you might
    find it easier to work with minutes and seconds instead of hours and
    minutes.

    * Requirement #3 is, well, a requirement. Repeated #update/#to_s
    calls to a FuzzyTime instance should never show an earlier time
    (unless 24 hours occurred between #update calls ;).
     
    Ruby Quiz, Oct 27, 2006
    #1
    1. Advertising

  2. Ruby Quiz

    Guest

    On Fri, 27 Oct 2006, Ruby Quiz wrote:

    > The Challenge
    > ---------------------------------------------------------------------
    >
    > Requirement #1: Write a Ruby program that shows the current time, but only the
    > 'tens' part of the minutes. For example, if the time is 10:37, then the program
    > might output "10:3~"
    >
    > Requirement #2: The time shown by the clock must randomly vary +/- 5 minutes
    > from reality. For example, if the time is actually 10:37, the program might
    > output "10:3~" or "10:4~" (but not "10:2~" or "10:5~").
    >
    > Requirement #3: The time on the clock should continuously increase. If the time
    > shows "10:4~" it must continue to show "10:4~" until it shows "10:5~". (It can't
    > show "10:4~", then "10:3~" for a bit and then come back to "10:4~".)



    it seems like #2 and #3 contradict one another. imagine it's 10:45 and,
    through randomness, you choose to vary the clock by +5, therefore displaying
    10:5~, you will not be able to change the output again until 10:55, and then
    only because the upper bould will have rolled over into the next hour. here it
    is in table form

    ----- ------ ----- ---------
    lower actual upper selection
    ----- ------ ----- ---------
    10:40 10:45 10:50 10:50/5~
    10:41 10:46 10:51 10:51/5~
    10:42 10:47 10:52 10:52/5~
    10:43 10:48 10:53 10:53/5~
    10:44 10:49 10:54 10:54/5~
    10:45 10:50 10:55 10:55/5~
    10:46 10:51 10:56 10:56/5~
    10:47 10:52 10:57 10:57/5~
    10:48 10:53 10:58 10:58/5~
    10:49 10:54 10:59 10:59/5~
    10:50 10:55 11:00 11:00/0~

    an initial high selection eats into subsequent ranges - in otherwords a
    selection of t+ means that #2 will not by able to hold: the variance will be
    required to b smaller for the next choice: a high selection eats the variance
    range of the next choice...

    it's possibly even worse when we not near the top of an hour and start of with
    maximum variance:

    ----- ------ ----- ---------
    lower actual upper selection
    ----- ------ ----- ---------
    10:20 10:25 10:30 10:30/3~
    10:21 10:26 10:31 10:31/3~
    10:22 10:27 10:32 10:32/3~
    10:23 10:28 10:33 10:33/3~
    10:24 10:29 10:34 10:34/3~
    10:25 10:30 10:35 10:35/3~
    10:26 10:31 10:36 10:36/3~
    10:27 10:32 10:37 10:37/3~
    10:28 10:33 10:38 10:38/3~
    10:29 10:34 10:39 10:39/3~
    10:30 10:35 10:40 10:30/3~
    10:31 10:36 10:41 10:31/3~
    10:32 10:37 10:42 10:32/3~
    10:33 10:38 10:43 10:33/3~
    10:34 10:39 10:44 10:34/3~
    10:35 10:40 10:45 10:35/3~
    10:36 10:41 10:46 10:36/3~
    10:37 10:42 10:47 10:37/3~
    10:38 10:43 10:48 10:38/3~
    10:39 10:44 10:49 10:39/3~
    10:40 10:45 10:50 10:50/5~

    so the combined effect means that it's acceptable to display the same time~
    for twenty straight minutes - is that really the a desired potential effect?



    -a
    --
    my religion is very simple. my religion is kindness. -- the dalai lama
     
    , Oct 27, 2006
    #2
    1. Advertising

  3. On 10/27/06, <> wrote:
    >
    > it seems like #2 and #3 contradict one another.


    Indeed. I take it that the challenge of the quiz is to come up with a
    way to balance out the two goals. The purpose for requirement 2 is
    that you don't want the user to be able to deduce the actual time
    based on when you switch from one output to the next. The problem
    with doing that as a simple random selection is that you get wildly
    differing ranges.

    > +/- 5 minutes from reality.


    Is problematic, as you point out Ara. The example

    > For example, if the time is actually 10:37, the program might
    > output "10:3~" or "10:4~" (but not "10:2~" or "10:5~").


    however, is much more lenient. You don't have to round down, you just
    can't pick a number from a different section. (10:3~ is not +/-5 of
    10:37 obviously).


    I gather that there would be some pretty simple ways you can dampen
    the latter effect while still preventing the user from easily guessing
    the actual time.


    --
    Lou.
     
    Louis J Scoras, Oct 27, 2006
    #3
  4. #!/usr/bin/env ruby
    ################################################################################
    # quiz99.rb: Implementation of RubyQuiz #99 - Fuzzy Time
    #
    # Lou Scoras <>
    # Sunday, October 29th, around 11 o-clock (*smirks*).
    #
    # I decided to approximate time to the quarter hour. When people on the street
    # bother you for the time, that's probably what they are expecting. It's been
    # my experience that they give you nasty looks when you make them do any
    # amount of trivial math =)
    #
    # This one was a lot of fun. Thanks again to James and Gavin for another great
    # quiz.
    #
    ################################################################################

    class Time

    # A string representation for times on the quarter hour

    def to_quarter_s
    case min
    when 0
    "#{hour_12} o-clock"
    when 15
    "quarter past #{hour_12}"
    when 30
    "half past #{hour_12}"
    when 45
    n = self + 3600
    "quarter 'till #{n.hour_12}"
    end
    end

    # Let the Time library to the hard work for getting 12 time.

    def hour_12
    (strftime "%I").sub(/\A0/,'')
    end
    end

    class FuzzyTime

    # Quarter hours fall every 900 seconds

    TimeScale = 60 * 15

    def initialize(time = Time.now)
    @real = @time = time
    @emitted = nil
    end

    # Use a simple linear scaling to even out the intervals. This seems to work
    # out okay after a little testing, but it could probably be improved quite a
    # bit.
    #
    # One variable that effects this is the sampling rate. We're approximating to
    # the nearest quarter hour; however, if you call to_s say once a second, you
    # have a greater chance of bumping up to the next interval than if you
    # increase the time between calls to one minute.

    def to_s
    pick = rand(TimeScale)
    threshold = TimeScale - @time.to_i % TimeScale - 1
    p [pick, threshold, last_valid, next_valid] if $DEBUG

    @emitted = if (!@emitted.nil? && @emitted > last_valid) \
    || pick >= threshold
    next_valid
    else
    last_valid
    end

    @emitted.to_quarter_s
    end

    def inspect
    t_obj = if @emitted.nil?
    self.class.new(@time).next_valid
    else
    @emitted
    end
    %!FuzzyTime["#{t_obj.to_quarter_s}"]!
    end

    def actual
    @time
    end

    def last_valid
    Time.at(@time.to_i / TimeScale * TimeScale)
    end

    def next_valid
    last_valid + TimeScale
    end

    def advance(offset)
    @real = Time.now
    @time += offset
    offset
    end

    def update
    now = Time.now
    delta = now - @real
    @time += delta
    @real = now
    end

    end

    # Err, I forgot what the incantation is on windows. I think it is 'cls', but
    # I'll leave it as an exercise to the reader. *winks*

    ClearString = `clear`
    def clear
    print ClearString
    end

    def get_sample_rate
    default = 60
    return default unless ARGV[0]
    begin
    return ARGV[0].to_i
    rescue
    return default
    end
    end

    if caller.empty?
    ft = FuzzyTime.new

    while true do
    clear
    puts ft
    sleep get_sample_rate
    ft.update
    end
    end
     
    Louis J Scoras, Oct 29, 2006
    #4
  5. Ruby Quiz

    Marcel Ward Guest

    Thanks for this week's interesting problem. My solution is below and
    I look forward to any feedback and seeing other techniques used.

    There are two files pasted below; the second is the unit test, which
    takes about 5-10 minutes to run to completion. My first unit test
    ever in any language :)

    This is also my first greeting to the Ruby community... Hello!

    Cheers,
    Marcel


    #!/usr/bin/env ruby
    #
    # Marcel Ward <wardies ^a-t^ gmaildotcom>
    # Sunday, 29 October 2006
    # Solution for Ruby Quiz number 99
    #
    ################################################
    # fuzzy_time.rb

    class FuzzyTime
    attr_reader :actual, :timed_observation_period

    # If the time passed is nil, then keep track of the time now.
    def initialize(tm=nil, range_secs=600, disp_accuracy_secs=range_secs,
    fmt="%H:%M", obs_period=nil)
    @actual = @last_update = @next_diff = tm || Time.now
    @realtime = tm.nil?
    @maxrange = range_secs
    @display_accuracy = @best_accuracy = disp_accuracy_secs
    @tformat = fmt
    @last_observed = @max_disptime = Time.at(0)
    @timed_observation_period = obs_period
    end

    def to_s
    @last_update = Time.now
    @actual = @last_update if @realtime

    check_observation_period unless @timed_observation_period.nil?

    # We only calculate a new offset each time the last offset times out.
    if @next_diff <= @actual
    # Calculate a new time offset
    @diff = rand(@maxrange) - @maxrange/2
    # Decide when to calculate the next time offset
    @next_diff = @actual + rand(@maxrange)
    end
    @last_observed = @actual

    # Don't display a time less than the time already displayed
    @max_disptime = [@max_disptime, @actual + @diff].max

    # Take care to preserve any specific locale (time zone / dst) information
    # stored in @actual - for example, we cannot use Time::at(Time::to_i).
    disptime = @max_disptime.strftime(@tformat)

    # Lop off characters from the right of the display string until the
    # remaining string matches one of the extreme values; then fuzz out the
    # rightmost digits
    (0..disptime.size).to_a.reverse.each do
    |w|
    [@display_accuracy.div(2), - @display_accuracy.div(2)].map{
    |offs|
    (@max_disptime + offs).strftime(@tformat)
    }.each do
    |testtime|
    return disptime[0,w] + disptime[w..-1].tr("0123456789", "~") if \
    disptime[0,w] == testtime[0,w]
    end
    end
    end

    def advance(secs)
    if @realtime
    @actual = Time.now + secs
    # Once a real-time FuzzyTime is advanced, it can never again be
    # real-time.
    @realtime = false
    else
    @actual += secs
    end
    @last_update = Time.now
    end

    def update
    diff = Time.now - @last_update
    @actual += diff
    @last_update += diff
    # By calling update, you are effectively saying "set a fixed time"
    # so we must disable the real-time flag.
    @realtime = false
    end

    def accuracy
    "+/- #{@maxrange/2}s"
    end

    def dump
    "actual: #{@actual.strftime("%Y-%m-%d %H:%M:%S")}, " \
    "diff: #{@diff}, " \
    "next_diff: #{@next_diff.strftime("%Y-%m-%d %H:%M:%S")}, " \
    "accuracy: #{@display_accuracy}"
    end

    private
    def check_observation_period
    # Is the clock being displayed too often?

    # Although this method seems to work, it may be a bit simplistic.
    # Proper statistical / mathematical analysis and a proper understanding
    # of the human ability to count seconds may be necessary to determine
    # whether this still gives away too much info for the average observer.

    patience = @actual - @last_observed

    if patience < @timed_observation_period / 2
    # Worsen display accuracy according to how impatient the observer is.
    @display_accuracy += (2 * @best_accuracy *
    (@timed_observation_period - patience)) /
    @timed_observation_period
    elsif patience < @timed_observation_period
    # Immediately punish impatience by enforcing a minumum accuracy
    # twice as bad as the best possible.
    # Don't give too much away but allow the accuracy to get slowly better
    # if the observer is a bit more patient and waits over half the
    # observation period
    @display_accuracy = [
    2 * @best_accuracy,
    @display_accuracy - ((@best_accuracy * patience) /
    @timed_observation_period)
    ].max
    else
    # The observer has waited long enough.
    # Reset to the best possible accuracy.
    @display_accuracy = @best_accuracy
    end
    end
    end

    def wardies_clock
    # Get us a real-time clock by initializing Time with first parameter==nil
    # Make the seconds harder to guess by expanding the range to +/- 15s whilst
    # keeping the default display accuracy to +/- 5 secs. The user will have
    # to wait 30s between observations to see the clock with best accuracy.
    ft = FuzzyTime.new(nil, 30, 10, "%H:%M:%S", 30)

    # This simpler instantiation does not check the observation period and
    # shows "HH:M~". (This is the default when no parameters are provided)
    #ft = FuzzyTime.new(nil, 600, 600, "%H:%M")

    puts "** Wardies Clock\n"
    puts "**\n** Observing more often than every " \
    "#{ft.timed_observation_period} seconds reduces accuracy" \
    unless ft.timed_observation_period.nil?
    puts "**\n\n"

    loop do
    puts "\n\nTime Now: #{ft.to_s} (#{ft.accuracy})\n\n" \
    "-- Press Enter to observe the clock again or " \
    "q then Enter to quit --\n\n"

    # Flush the output text so that we can scan for character input.
    STDOUT.flush

    break if STDIN.getc == ?q
    end
    end


    def clocks_go_back_in_uk
    # Clocks go back in the UK on Sun Oct 29. (+0100 => +0000)
    # Start at Sun Oct 29 01:58:38 +0100 2006
    ft = FuzzyTime.new(Time.at(Time.at(1162083518)))

    # In the UK locale, we see time advancing as follows:
    # 01:5~
    # 01:5~
    # 01:0~ (clocks gone back one hour)
    # 01:0~
    # ...
    # 01:0~
    # 01:1~

    60.times do
    puts ft.to_s
    ft.advance(rand(30))
    end
    end

    def full_date_example
    # Accuracy can be set very high to fuzz out hours, days, etc.
    # E.g. accuracy of 2419200 (28 days) fuzzes out the day of the month
    # Note the fuzz factoring does not work so well with hours and
    # non-30-day months because these are not divisble exactly by 10.

    tm = FuzzyTime.new(nil, 2419200, 2419200, "%Y-%m-%d %H:%M:%S")
    300.times do
    puts "#{tm.to_s} (#{tm.dump})"
    # advance by about 23 days
    tm.advance(rand(2000000))
    #sleep 0.2
    end
    end

    # Note, all the examples given in the quiz are for time zone -0600.
    # If you are in a different timezone, you should see other values.
    def quiz_example
    ft = FuzzyTime.new # Start at the current time
    ft = FuzzyTime.new(Time.at(1161104503)) # Start at a specific time

    p ft.to_s # to_s format

    p ft.actual, ft.actual.class # Reports real time as Time
    #=> Tue Oct 17 11:01:36 -0600 2006
    #=> Time

    ft.advance( 60 * 10 ) # Manually advance time
    puts ft # by a specified number of
    #=> 11:0~ # seconds.

    sleep( 60 * 10 )

    ft.update # Automatically update the time based on the
    puts ft # time that has passed since the last call
    #=> 11:1~ # to #initialize, #advance or #update
    end

    if __FILE__ == $0
    wardies_clock
    #clocks_go_back_in_uk
    #full_date_example
    #quiz_example
    end


    ################################################
    # fuzzy_time_test.rb

    require 'test/unit'
    require 'fuzzy_time'

    class FuzzyTime_Test < Test::Unit::TestCase
    #def setup
    #end

    #def teardown
    #end

    def test_advance

    # Initialize with a known UTC time (Tue Jun 10 03:14:52 UTC 1975)
    ft = FuzzyTime.new(Time.at(171602092).getgm, 60, 60, "%H:%M:%S")

    # Add 6 hours 45 minutes 30 secs to give us
    # (Tue Jun 10 09:59:22 UTC 1975)
    ft.advance(3600*6 + 60*45 + 30)
    @last_output = ""

    60.times do
    # Initial displayed time sourced from between 09:58:52 and 09:59:52
    # Time will be advanced by between 0 and 600 seconds.
    # So final displayed time source ranges from 10:08:52 to 10:09:52

    # The array of legal output strings:
    @legal = ["09:58:~~", "09:59:~~", "10:00:~~", "10:01:~~",
    "10:02:~~", "10:03:~~", "10:04:~~", "10:05:~~",
    "10:06:~~", "10:07:~~", "10:08:~~", "10:09:~~"]

    @output = ft.to_s

    assert_block "#@output not one of #{@legal.inspect}" do
    @legal.include?( @output )
    end

    assert_block "#@output must be greater than or equal to " \
    "last value, #@last_output" \
    do
    @output >= @last_output
    end
    @last_output = @output

    ft.advance( rand( 11 ) )
    end
    end

    def test_advance_rollover
    # Initialize with a known UTC time (Fri Dec 31 23:58:25 UTC 1999)
    # Test rollover at midnight
    # Note, we have an accuracy of +/- 5 secs now and enabled the
    # observations timer
    ft = FuzzyTime.new(Time.at(946684705).getgm, 10, 10, "%H:%M:%S", 10)

    30.times do
    # Initial displayed time sourced from between 23:58:20 and 23:58:30
    # Time will be advanced by between 0 and 150 seconds.
    # So final displayed time source ranges from 00:00:50 to 00:01:00

    # Note, if we watch too often over a short period of time,
    # our displayed accuracy will decrease. Then we will lose
    # the 10's digit of the seconds and occasionally the 1's minute.

    # The array of legal output strings:
    @legal = ["23:58:1~", "23:58:2~", "23:58:3~",
    "23:58:4~", "23:58:5~", "23:58:6~",
    "23:58:~~", "23:59:~~", "23:5~:~~",
    "23:59:0~", "23:59:1~", "23:59:2~",
    "23:59:3~", "23:59:4~", "23:59:5~",
    "00:00:0~", "00:00:1~", "00:00:2~",
    "00:00:3~", "00:00:4~", "00:00:5~", "00:00:~~",
    "00:01:0~", "00:01:~~", "00:0~:~~"]

    @output = ft.to_s

    assert_block "#@output not one of #{@legal.inspect}" do
    @legal.include?( @output )
    end

    # We cannot easily check that the current output is greater or equal to
    # the last because with timed observations, a valid output sequence is:
    # 23:59:0~
    # 23:59:~~ (looking too often, accuracy has been reduced)
    # 23:59:0~ (waited long enough before observing for accuracy to return)

    ft.advance( rand(6) )
    end
    end

    def test_update
    # NOTE - this test takes 5-10 minutes to complete

    # Initialize with a known UTC time (Tue Jun 10 03:14:52 UTC 1975)
    ft = FuzzyTime.new(Time.at(171602092).getgm, 60, 60, "%H:%M:%S")
    @last_output = ""

    60.times do
    # Initial displayed time sourced from between 03:14:22 and 03:15:22
    # Duration of loop will be between 0 and ~600 seconds.
    # So final displayed time source ranges from 03:14:22 to 03:25:22

    # The array of legal output strings:
    @legal = ["03:14:~~", "03:15:~~", "03:16:~~", "03:17:~~",
    "03:18:~~", "03:19:~~", "03:20:~~", "03:21:~~",
    "03:22:~~", "03:23:~~", "03:24:~~", "03:25:~~"]

    @output = ft.to_s

    assert_block "#@output not one of #{@legal.inspect}" do
    @legal.include?( @output )
    end

    assert_block "#@output must be greater than or equal to " \
    "last value, #@last_output" \
    do
    @output >= @last_output
    end
    @last_output = @output

    sleep( rand( 11 ) ) # wait between 0..10 secs
    ft.update
    end
    end
    end
     
    Marcel Ward, Oct 29, 2006
    #5
  6. On Oct 29, 2006, at 10:28 AM, Marcel Ward wrote:

    > This is also my first greeting to the Ruby community... Hello!


    Then let me be one of the first to welcome you to Ruby and the Ruby
    Quiz!

    James Edward Gray II
     
    James Edward Gray II, Oct 29, 2006
    #6
  7. Ruby Quiz

    Paolo Negri Guest

    Hi all

    Here's is my script. It was fun working at it, just please no more
    time based quiz when there's the switching of daylight time saving :p
    The scripts defines two class one to manage how to move within a given
    range and one for actual FuzzyTime implementation.
    At the end of the email there's a really ugly extra file you may want
    to use to play with the FuzzyTime class.

    Paolo

    #!/bin/ruby
    class WanderingWalker
    SIGN = [1, -1]
    CHECK_LIMIT = %w(min max)
    DEFAULT_GENERATOR = lambda { |limit| rand(limit) * SIGN[rand(2)] }
    attr_reader :position, :limit, :target
    alias :level :position
    def initialize(limit, &block)
    @limit = limit
    @generator = block_given? ? block : DEFAULT_GENERATOR
    @position = 0
    generate_target
    end
    def walk(steps)
    generate_target while @position == @target
    new_pos = [(@position + direction*steps), @target].send \
    CHECK_LIMIT[SIGN.index(direction)]
    @position = new_pos
    end
    def distance
    @target - @position
    end
    def direction
    if distance == 0 : 1 else distance/distance.abs end
    end
    private
    def generate_target
    @target = @generator.call limit
    end
    end

    class FuzzyTime
    FORMATS = %w(%H:%M %I:%M)
    attr_reader :fuzziness
    #two params, both optional time and an hash
    #time: a time object used as the starting point
    #hash params
    #:hidden_digits - hidden_digits number, from right
    #:precision - maximum distance from real time in seconds
    def initialize(time=Time.new, opts={})
    @internaltime = time
    @last_call = @printed_time = Time.new
    @precision = opts[:precision] || 300
    @fuzziness = WanderingWalker.new(@precision)
    @format = FORMATS[0]
    @hidden_digits = opts[:hidden_digits] || 1
    @sub_args = case @hidden_digits
    when 0 : [//, '']
    when 1 : [/\d$/, '~']
    when 2 : [/\d{2}$/, '~~']
    when 3 : [/\d:\d{2}$/, '~:~~']
    else
    raise "nothing to see!"
    end
    end
    def advance(secs)
    tic(secs)
    end
    def update
    tic Time.new - @last_call
    end
    def actual
    @internaltime
    end
    #switch 12 / 24h format
    def toggle_format
    @format = FORMATS[FORMATS.index(@format) == 0 ? 1 : 0]
    end
    def to_s
    @printed_time = [@printed_time, (@internaltime + fuzziness.level)].max
    @printed_time.strftime(@format).sub(*@sub_args)
    end
    private
    def tic(secs=1)
    @internaltime += secs
    @last_call = Time.new
    @fuzziness.walk secs
    end
    end

    Ad here's the super ugly runner script

    #!/bin/ruby
    require 'fuzzy_time'
    # accepts a FuzzyTime object as argument + an hash of options
    #possible options
    # :time_warp if true instead of working as a clock just prints out
    #in an instant all the clock output, if false, works as a clock
    # :step_size how often the clock will be updated in seconds
    # :print_actual prints the actual time
    # :print_fuzziness, prints the error size
    # :toggle switch at every step from 24 to 12 hour format
    # :duration how many step_size the clock should run
    class FuzzyExec
    def initialize(ft = FuzzyTime.new ,opts = {})
    opt_def = {:duration => 100, :step_size => 60, :print_actual => true}
    @opts = opt_def.update opts
    @ft = ft
    end
    def show_fuzzy_clock
    @opts[:duration].times do
    @ft.toggle_format if @opts[:toggle]
    out = []
    out << @ft.to_s
    out << @ft.actual.strftime('%H:%M') if @opts[:print_actual]
    out << @ft.fuzziness.level if @opts[:print_fuzziness]
    out << Time.new.strftime('%H:%M') if @opts[:print_current]
    puts out.join ' '
    if @opts[:time_warp]
    @ft.advance(@opts[:step_size])
    else
    sleep @opts[:step_size]
    @ft.update
    end
    end
    end
    end
     
    Paolo Negri, Oct 29, 2006
    #7
  8. Ruby Quiz

    Paolo Negri Guest

    Hi

    I've posted an old version the second line of initialize should be changed from

    @last_call = @printed_time = Time.new

    to

    @last_call = @printed_time = time

    sorry

    Paolo
     
    Paolo Negri, Oct 29, 2006
    #8
  9. Ruby Quiz

    Ben Giddings Guest

    Here's my effort. I didn't get around to testing it, but the concept
    seems sound. I decided to interpret the goal a little more
    conservatively and instead of just making sure the displayed time
    never goes forward, I decided to make sure the offset never goes
    backwards. To do this, I made the max delta from the current offset
    no more than the amount of time that passed since the last offset
    update:



    class FuzzyTime
    MAX_OFFSET = 5*60

    def initialize(*args)
    now = Time.new
    @internal_time = args[0] || now
    @time_offset = now - @internal_time
    @fuzzy_secs = 0
    @last_calc = Time.new
    end

    def actual
    @internal_time
    end

    def update
    @internal_time = Time.new + @time_offset
    end

    def advance(amount)
    @time_offset += amount.to_i
    end

    def calc_offset
    # Choose a new offset that's between +/- 5 mins. If it has been
    # less than 5 mins since the last offset calc, choose that time as
    # a max delta (this makes sure time is always going forward)

    time_from_last_calc = (Time.new - @last_calc).to_i

    if time_from_last_calc > 0
    begin
    max_delta = [MAX_OFFSET, time_from_last_calc].min

    delta = rand((2*max_delta) + 1) - max_delta
    end until (delta + @fuzzy_secs).abs < MAX_OFFSET
    @fuzzy_secs += delta

    puts "Fuzzy secs now: #{@fuzzy_secs}"

    @last_calc = Time.new
    end
    @fuzzy_secs
    end

    def get_time
    fuzzy_hour = @internal_time.hour
    fuzzy_min = @internal_time.min
    fuzzy_sec = @internal_time.sec + calc_offset

    if fuzzy_sec > 60
    fuzzy_sec -= 60
    fuzzy_min += 1
    end

    if fuzzy_sec < 0
    fuzzy_sec += 60
    fuzzy_min -= 1
    end


    if fuzzy_min > 60
    fuzzy_min -= 60
    fuzzy_hour = (fuzzy_hour + 1) % 24
    end

    if fuzzy_min < 0
    fuzzy_min += 60
    fuzzy_hour = (fuzzy_hour + 23) % 24
    end

    [fuzzy_hour, fuzzy_min, fuzzy_sec]
    end

    def to_s
    fuzzy_hour, fuzzy_min, fuzzy_sec = get_time
    "#{fuzzy_hour}:#{fuzzy_min / 10}~"
    # "#{fuzzy_hour}:#{fuzzy_min / 10}~ (#{fuzzy_hour}:#{"%02d" %
    fuzzy_min}:#{"%02d" % fuzzy_sec})"
    end

    end

    if $0 == __FILE__
    t = FuzzyTime.new
    10.times do
    puts t, Time.new
    sleep 10
    t.update
    end
    end
     
    Ben Giddings, Oct 29, 2006
    #9
  10. --envbJBWh7q8WU6mo
    Content-Type: text/plain; charset=us-ascii
    Content-Disposition: inline

    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


    --envbJBWh7q8WU6mo
    Content-Type: text/plain; charset=us-ascii
    Content-Disposition: attachment; filename="fuzzytime.rb"

    #----------------------------------------------------------------------
    # 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 = [:eek:ne_minute, :ten_minute, :eek:ne_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
    # :eek:ne_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 :eek:ne_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
    # ========================
    # :eek:ne_minute 13:43
    # :ten_minute 13:40
    # :eek:ne_hour 13:00
    #
    def calculate_display_time
    case @display_granularity
    when :eek:ne_minute
    min = @fuzzed.min
    when :ten_minute
    min = (@fuzzed.min / 10) * 10
    when :eek:ne_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

    --envbJBWh7q8WU6mo--
     
    Jeremy Hinegardner, Oct 29, 2006
    #10
  11. Ruby Quiz

    Robert Conn Guest

    Hi all,

    This is my first submission to the list and I'm new to Ruby too. I'm
    finding the quiz excellent for learning. Comments and advice would
    really be appreciated.

    I haven't had time to do any "extra credit" stuff, or even test this
    fully, but here goes -

    class FuzzyTime

    attr_reader :actual, :display

    def initialize(*start_time)
    @current_systime = Time.new
    @actual = start_time[0] || Time.new
    @last_displayed = @actual
    end

    def to_s
    # Decide whether to go forward or back 5 mins
    if rand(2) == 1
    @display = @actual + (5 * 60)
    else
    @display = @actual - (5 * 60)
    end

    # If the time we are going to display is before what was last
    displayed, don't do it
    if @display < @last_displayed
    @display = @last_displayed
    end

    @last_displayed = @display

    "#{"%02d" % @display.hour}:#{("%02d" % @display.min.to_s)[0..0]}~"
    end

    # Advance the actual time by a number of seconds, reset the system
    time record so that
    # update will work
    def advance(secs)
    @actual += secs
    @current_systime = Time.new
    end

    # Work out the relative time difference since the last initialize,
    advance or update
    # and apply this to the actual time
    def update
    diff = Time.new - @current_systime
    @actual += diff.to_i
    @current_systime = Time.new
    end

    end


    Thanks everyone

    Robert
     
    Robert Conn, Oct 30, 2006
    #11
  12. Hi all,

    I'm new to Ruby and this is the first quiz I'm doing. Not sure if I'm
    missing something, but this seems pretty easy to me. Here's what I
    have:

    class FuzzyTime
    # if 24 then show in 24 hour format, else 12 hour format
    attr_accessor :mode

    def initialize(startAt = Time.now, variance = 5*60)
    @time = Time.at(startAt)
    @offset = Time.now - @time
    @variance = variance
    @mintime = Time.at(@time.to_i - @variance).to_i
    @mode = 24
    end

    def to_s
    t = @time.to_i - @variance + rand(@variance * 2)
    @mintime = @mintime > t ? @mintime : t
    now = Time.at(@mintime)
    sprintf('%02d:%d~ %s',
    @mode == 24 ? now.hour : now.hour % 12,
    now.min / 10,
    @mode != 24 ? now.hour / 12 == 1 ? 'pm' : 'am' : ''
    )
    end

    def update
    @time = Time.now + @offset
    end

    def actual
    @time
    end

    # def advance(amt)
    def +(amt)
    @time = @time + amt
    self
    end

    def -(amt)
    @time = @time + (-amt)
    # reset the minimum displayed time
    @mintime = Time.at(@time.to_i - @variance).to_i
    self
    end
    end

    if __FILE__ == $0 then
    t = FuzzyTime.new
    t.mode = 24

    30.times {
    t += 60
    puts "#{t.to_s} (#{t.actual.strftime('%H:%M')})"
    }
    end

    Any comments are welcome.

    Cheers,
    Thomas

    2006/10/30, Caleb Powell <>:
    > Hi,
    >
    > attached is my FuzzyTime.rb solution and a corresponding Unit Test
    > file. Thanks for the quiz, I look forward to any feedback. I didn't
    > have time to do the extra credit tasks :-(
    >
    > On 10/27/06, Ruby Quiz <> wrote:
    > > The three rules of Ruby Quiz:
    > >
    > > 1. Please do not post any solutions or spoiler discussion for this quiz until
    > > 48 hours have passed from the time on this message.
    > >
    > > 2. Support Ruby Quiz by submitting ideas as often as you can:
    > >
    > > http://www.rubyquiz.com/
    > >
    > > 3. Enjoy!
    > >
    > > Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
    > > on Ruby Talk follow the discussion. Please reply to the original quiz message,
    > > if you can.
    > >
    > > -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    > >
    > > by Gavin Kistner
    > >
    > > The Background
    > > ---------------------------------------------------------------------
    > >
    > > Last night I was having trouble falling asleep, and was staring at my digital
    > > alarm clock (as I do so often under that circumstance). Something on the bedside
    > > table was occluding the minutes digit, however, so all I could tell is that it
    > > was "10 4". (Oddly, my alarm clock has no ":" between the hours and minutes.)
    > >
    > > "How nice!" I thought. "An imposed vagueness to keep me from obsessing on
    > > exactly what time it is! Should I really be worried about the exact time?
    > > Shouldn't I be more relaxed? Shouldn't a 10-minute precision in life be enough
    > > to keep me roughly on time, without obsessing on exacting promptitude?"
    > >
    > > I realized that if I kept staring at the clock (as I did), and I were to observe
    > > it changing from "10 4" to "10 5", that I would, at that moment, know exactly
    > > what time it is.
    > >
    > > "Bah" I thought, "so much for that idea."
    > >
    > > And then I thought some more. I thought of bad ideas: analog watches where the
    > > hand erratically swings forward and backward, digital clocks that showed random
    > > times near the correct time. And then I dreamed of the watch I wanted to own...
    > >
    > > The Challenge
    > > ---------------------------------------------------------------------
    > >
    > > Requirement #1: Write a Ruby program that shows the current time, but only the
    > > 'tens' part of the minutes. For example, if the time is 10:37, then the program
    > > might output "10:3~"
    > >
    > > Requirement #2: The time shown by the clock must randomly vary +/- 5 minutes
    > > from reality. For example, if the time is actually 10:37, the program might
    > > output "10:3~" or "10:4~" (but not "10:2~" or "10:5~").
    > >
    > > Requirement #3: The time on the clock should continuously increase. If the time
    > > shows "10:4~" it must continue to show "10:4~" until it shows "10:5~". (It can't
    > > show "10:4~", then "10:3~" for a bit and then come back to "10:4~".)
    > >
    > > Putting the three requirements together, the left column below shows the actual
    > > time and the next three columns show the possible outputs from three different
    > > runs of the same program:
    > >
    > > 10:35 10:3~ 10:4~ 10:3~
    > > 10:36 10:3~ 10:4~ 10:3~
    > > 10:37 10:3~ 10:4~ 10:3~
    > > 10:38 10:3~ 10:4~ 10:3~
    > > 10:39 10:4~ 10:4~ 10:3~
    > > 10:40 10:4~ 10:4~ 10:3~
    > > 10:41 10:4~ 10:4~ 10:3~
    > > 10:42 10:4~ 10:4~ 10:3~
    > > 10:43 10:4~ 10:4~ 10:3~
    > > 10:44 10:4~ 10:4~ 10:3~
    > > 10:45 10:4~ 10:4~ 10:4~
    > > 10:46 10:4~ 10:4~ 10:5~
    > > 10:47 10:4~ 10:4~ 10:5~
    > > 10:48 10:4~ 10:4~ 10:5~
    > > 10:49 10:4~ 10:4~ 10:5~
    > > 10:50 10:4~ 10:4~ 10:5~
    > > 10:51 10:4~ 10:4~ 10:5~
    > > 10:52 10:5~ 10:4~ 10:5~
    > > 10:53 10:5~ 10:4~ 10:5~
    > > 10:54 10:5~ 10:4~ 10:5~
    > > 10:55 10:5~ 10:5~ 10:5~
    > > 10:56 10:5~ 10:5~ 11:0~
    > > 10:57 10:5~ 10:5~ 11:0~
    > > 10:58 10:5~ 10:5~ 11:0~
    > > 10:59 10:5~ 10:5~ 11:0~
    > > 11:00 10:5~ 10:5~ 11:0~
    > > 11:01 10:5~ 10:5~ 11:0~
    > >
    > > Testing your Output
    > > ---------------------------------------------------------------------
    > >
    > > You should supply a FuzzyTime class that supports the following:
    > >
    > > ft = FuzzyTime.new # Start at the current time
    > > ft = FuzzyTime.new(Time.at(1161104503)) # Start at a specific time
    > >
    > > p ft.to_s # to_s format
    > > #=> "10:5~"
    > >
    > > p ft.actual, ft.actual.class # Reports real time as Time
    > > #=> Tue Oct 17 11:01:36 -0600 2006
    > > #=> Time
    > >
    > > ft.advance( 60 * 10 ) # Manually advance time
    > > puts ft # by a specified number of
    > > #=> 11:0~ # seconds.
    > >
    > > sleep( 60 * 10 )
    > >
    > > ft.update # Automatically update the time based on the
    > > puts ft # time that has passed since the last call
    > > #=> 11:1~ # to #initialize, #advance or #update
    > >
    > > Your class and output will be tested with code like the following:
    > >
    > > def test_output
    > > # Initialize with a well-known time
    > > ft = FuzzyTime.new( Time.at( ... ) )
    > >
    > > 60.times do
    > > @legal = ... # Find the array of legal output strings
    > > @output = ft.to_s
    > >
    > > assert_block "#@output not one of #@legal.inspect" do
    > > @legal.include?( @output )
    > > end
    > >
    > > sleep( rand( 30 ) )
    > > ft.update
    > > end
    > >
    > > 60.times do
    > > @legal = ... # Find the array of legal output strings
    > > @output = ft.to_s
    > >
    > > assert_block "#@output not one of #@legal.inspect" do
    > > @legal.include?( @output )
    > > end
    > >
    > > ft.advance( rand( 30 ) )
    > > end
    > > end
    > >
    > > Extra Credit
    > > ---------------------------------------------------------------------
    > >
    > > * Provide a self-running application that shows the time somehow.
    > > (ASCII in the terminal, some GUI window, etc.)
    > >
    > > * Allow your class to be customized to display 12- or 24-hour time.
    > >
    > > * Allow your class to be customized to change how close to reality
    > > it must display. (+/- 3 minutes, +/- 12 minutes, etc.)
    > >
    > > * Allow your class to be customized to change how roughly it displays
    > > the time (e.g. 1 minute, 10 minute, 1 hour intervals).
    > >
    > > * Ensure that the transition from one digit to the next occurs
    > > randomly across the range of -5 to +5. (So, while the digit might
    > > change 5 minutes before or 5 minutes after the real transition, on
    > > average the change should occur around the transition itself.)
    > > You might need to assume that your update/advance method will be
    > > called with a certain regularity (e.g. once per second, once every
    > > 7 seconds, once every 30 seconds, etc.)
    > >
    > > * Come up with your own technique of displaying time that
    > > (a) is always 'close' to right, but (b) never allows a
    > > watchful person to ever know exactly what time it is.
    > >
    > > Things to Keep in Mind
    > > ---------------------------------------------------------------------
    > >
    > > * You need to be able to handle the transition across hour/day
    > > boundaries. (10:5~ might change to 11:0~ when the real time is still
    > > 10:58, or might continue to display 10:5~ when the real time is
    > > 11:04. On a 24-hour click, you also need to be able to wrap from
    > > 23:5~ to 00:0~)
    > >
    > > * For testing purposes of the real-time #update advancing, you might
    > > find it easier to work with minutes and seconds instead of hours and
    > > minutes.
    > >
    > > * Requirement #3 is, well, a requirement. Repeated #update/#to_s
    > > calls to a FuzzyTime instance should never show an earlier time
    > > (unless 24 hours occurred between #update calls ;).
    > >
    > >

    >
    >
    > --
    > Caleb
    >
    > "There is no theory of evolution, just a list of creatures Chuck
    > Norris allows to live."
    >
    >
    >
     
    Thomas Mueller, Oct 30, 2006
    #12
  13. On Oct 29, 2006, at 6:34 PM, Robert Conn wrote:

    > Hi all,


    Hello.

    > This is my first submission to the list and I'm new to Ruby too.


    Welcome!

    > Comments and advice would really be appreciated.


    My comment is that I wish I wrote Ruby that pretty when I was new. :)

    I'll give a couple super minor suggestions...

    > def initialize(*start_time)
    > @current_systime = Time.new
    > @actual = start_time[0] || Time.new
    > @last_displayed = @actual
    > end


    Why take an Array of arguments but only use the first one? I think
    you should drop the slurping operator:

    def initialize(start_time)
    @current_systime = Time.new
    @actual = start_time || Time.new
    @last_displayed = @actual
    end

    > # Decide whether to go forward or back 5 mins
    > if rand(2) == 1
    > @display = @actual + (5 * 60)
    > else
    > @display = @actual - (5 * 60)
    > end



    You could shorten that up a bit, though I'm not sure this is as
    readable:

    @display = @actual.send(%w[+ -][rand(2)], 5 * 60)

    > "#{"%02d" % @display.hour}:#{("%02d" % @display.min.to_s)[0..0]}~"


    Perhaps the this is more straight forward:

    @display.strftime("%H:%M").sub(/\d$/, "~")

    Hope that gives you some new ideas.

    James Edward Gray II
     
    James Edward Gray II, Oct 30, 2006
    #13
  14. Ruby Quiz

    Cameron Pope Guest

    Re: Fuzzy Time (#99)

    Thanks for the test and the quiz. Writing a solution wasn't very hard,
    but coming up with a good way to keep the time from drifting ahead and
    had good 'fuzziness', quantifying what made a good fuzzy time and then
    testing different approaches was the interesting part.

    An approach I took to test the fuzziness of my FuzzyTime class was
    track the number of minutes the fuzzy time remained the same, advancing
    one minute at a time, and then calculate the mean and variance of that
    set of numbers, with the idea that a good fuzzy time would have a mean
    change interval close to 10 minutes but with a large variance. That
    would also snag solutions that always displayed the correct time plus
    solutions that were always ahead or behind by a fixed amount: there
    would be no variance to the intervals. (It would also be good to see
    different results each time it's run) A unit test for that is after my
    solution.

    In my approach, when a time is asked for, it computes a time that is
    +/- 5 minutes from the actual time, keeping a low water mark that is
    the last displayed time or 5 mintes from the actual time, whatever is
    greater. There is a strong bias not to advance the time, correcting for
    the tendancy for the time to drift ahead. This is what my solution got
    running your test:

    Variation: 10.37% ahead, 20.75% behind
    Variation: 13.73% ahead, 8.39% behind
    Variation: 13.46% ahead, 10.64% behind
    Variation: 13.16% ahead, 10.72% behind
    Variation: 13.27% ahead, 11.64% behind
    Variation: 13.96% ahead, 9.06% behind
    Variation: 13.76% ahead, 9.87% behind
    Variation: 13.43% ahead, 10.57% behind
    Variation: 13.69% ahead, 8.77% behind
    Variation: 13.54% ahead, 9.18% behind
    Variation: 13.18% ahead, 9.73% behind
    Variation: 13.11% ahead, 10.15% behind
    Variation: 13.00% ahead, 10.67% behind
    Variation: 13.00% ahead, 10.30% behind
    Variation: 12.92% ahead, 10.57% behind
    Variation: 12.64% ahead, 10.80% behind
    Variation: 12.64% ahead, 11.07% behind
    Variation: 12.52% ahead, 10.94% behind
    Variation: 12.39% ahead, 11.28% behind
    Variation: 12.33% ahead, 11.49% behind

    And means and variances:

    Mean interval: 9.02446115288221
    Variance: 3.07336219244858

    As for the solution:

    ---- FuzzyTime.rb

    # Fuzzy Time class that reports a time that is always within a given
    # slop factor to the real time. The user specifies the amount of
    # fuzzyiness to the time and the display obscures the minutes field.
    #
    # This class works by maintaining the actual time it represents, and
    # calculating a new fuzzy time whenever it is asked to display the
    # fuzzy time. It keeps a low water mark so an earlier time is never
    # displayed.
    #
    class FuzzyTime
    # Initialize this object with a known time and a number of minutes
    # to randomly vary
    #
    # time -> initial time
    # fuzzy_minutes -> number of minutes to randomly vary time
    def initialize(time=Time.now, fuzzy_minutes=5)
    @fuzzy_factor = fuzzy_minutes
    @current_time = time
    set_low_water_mark
    set_updated
    end

    # Print the fuzzy time in a format obscuring the last number of the
    # time.
    def to_s
    ft = fuzzy_time
    s = ft.strftime("%H:%M")
    s[4] = '~'
    s
    end

    # Manually advance time by a certain number of seconds. Seconds
    # cannot be negative
    #
    # seconds -> number of seconds to advance. Will throw exception
    # if negative
    def advance(seconds)
    raise "advance: seconds cannot be negative" if seconds < 0
    @current_time = @current_time + seconds
    set_updated
    end

    # Update the current time with the number of seconds that has
    # elapsed since the last time this method was called
    def update
    @current_time = @current_time + update_interval
    set_updated
    end

    # Reports real time as Time
    def actual
    @current_time
    end

    private
    # sets the current low water mark. This is so the fuzzy time never
    # goes backwards
    def set_low_water_mark
    @low_water_mark = @current_time - (@fuzzy_factor*60)
    end

    # Updates the last time initialize, advance or update was called
    def set_updated
    @last_updated = Time.now
    end

    # Gets the number of seconds since the last update
    def update_interval
    Time.now - @last_updated
    end

    # Sets fuzzy time to be +/- the fuzzy factor from the current time,
    # while ensuring that we never return an earlier time than the one
    # returned last time we were called.
    def fuzzy_time
    fuzzy_seconds = @fuzzy_factor * 60

    # Raise the low watermark if it is lower than allowed, if we
    # advanced by a huge degree, etc.
    @low_water_mark =
    [@low_water_mark, @current_time - fuzzy_seconds].max

    # Compute a new random time, and set it to be the fuzzy time if
    # it is higher than the low water mark. The algorithm is biased
    # to return a negative time. This is to compensate for the low
    # water mark. We want the time to be behind as much as it is
    # ahead. At least 60 percent of the time the time will not
    # advance here.
    random_time = @current_time +
    (rand(fuzzy_seconds*5) - fuzzy_seconds*4)
    fuzzy_time = [@low_water_mark, random_time].max

    # Update the low water mark if necessary
    @low_water_mark = [@low_water_mark, fuzzy_time].max

    fuzzy_time
    end
    end

    -----

    The unit test to calculate means and variances:

    ----- FuzzyTimeTest.rb

    require 'FuzzyTime'

    class FuzzyTimeTest < Test::Unit::TestCase
    # Advances the fuzzy time by one minute repeatedly, keeping track
    # of how many minutes it took to go to the next time on the clock.
    # Calculates mean and variance to see if the intervals center
    # around 10 minutes and how much they vary from the mean. We want
    # the average to hover close to 10 minutes and the variance to be
    # as large as possible.
    def test_intervals
    ft = FuzzyTime.new

    last_time = nil
    fuzzy_interval = 0
    intervals = Array.new
    100000.times do
    ft.advance 60
    fuzzy_time = ft.to_s

    if last_time.nil? || last_time == fuzzy_time
    fuzzy_interval += 1
    else
    intervals.push fuzzy_interval
    fuzzy_interval = 0
    end
    last_time = fuzzy_time
    end

    average = intervals.inject {|sum,val| sum + val }.to_f /
    intervals.length
    puts "Mean interval: #{average}"

    variance = intervals.inject {|sum, val| sum+(val-average).abs}.to_f
    /
    intervals.length
    puts "Variance: #{variance}"
    end
    end
     
    Cameron Pope, Oct 30, 2006
    #14
  15. Ruby Quiz

    Tom Pollard Guest

    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
    <ndigits>'.
    You can set the "wobble" (maximum allowable error) with '-w <minutes>'.
    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
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - -
     
    Tom Pollard, Oct 30, 2006
    #15
  16. Re: Fuzzy Time (#99)

    Here is my script. This is my first entry to Ruby Quiz.

    I wanted this FuzzyTime to support random access so you can do this:

    ft = FuzzyTime.new; puts ft
    => 17:5~
    ft = FuzzyTime.set(Time.now+4.hours); puts ft
    => 22:0~
    ft = FuzzyTime.set(Time.now+10.minutes); puts ft
    => 18:1~
    ft = FuzzyTime.set(Time.now); puts ft
    => 17:5~

    Also, I added a 'rewind' method for symmetry with 'advance' (although
    you cannot rewind to before the time FuzzyTime was initialized with).
    Now 'update' adds to the internal time based on how long since you last
    called one of 'initialize', 'advance', 'rewind', 'update' or 'set'.

    Other than ensuring that the transition occurs randomly for each ten
    minute block, I didn't implement any of the extra credit options.

    -Dan Lucraft
    ___________________________________________________

    # handy stuff...
    require
    '/home/dan/gems/activesupport-1.3.1/lib/active_support/core_ext/numeric'
    require
    '/home/dan/gems/activesupport-1.3.1/lib/active_support/core_ext/time'

    class FuzzyTime
    def initialize(time=Time.now)
    # we record the start time as it's used to reset the random number
    generator
    # every time we fuzzify a time
    @start_time = time

    time_mins = (time - time.beginning_of_day).to_i/60
    hours = time_mins/60
    mins = time_mins-(hours*60)
    tens = mins/10
    @time_of_nearest_ten_before_start = time_mins - mins + (tens*10) -5

    @internal_time = time
    @last_called = time
    end

    def actual
    @internal_time
    end

    def advance(secs)
    @internal_time += secs
    @last_called = Time.now
    end

    def rewind(secs)
    @internal_time -= secs
    check_valid
    @last_called = Time.now
    end

    def update
    @internal_time += Time.now - @last_called
    check_valid
    @last_called = Time.now
    end


    def set(time=Time.now)
    @internal_time = time
    check_valid
    @last_called = Time.now
    end

    # this fuzzifies the current internal time.
    def to_s
    # run through random numbers for the number of 10 minute blocks
    since the
    # start time, and keep the last one (switch_val)
    time_mins = ((@internal_time -
    @start_time.beginning_of_day).to_i)/60
    distance = time_mins - @time_of_nearest_ten_before_start
    Kernel.srand(@start_time.to_i)
    switch_val = nil
    ((distance/10)+1).to_i.times do
    switch_val = rand(10)
    end

    # the kept random number is where in the 10 minute interval around
    # the current time that the fuzzy time switches from low to high
    # E.g.
    # current time 9:12
    # interval around current time: [9:05, 9:15]
    # a switch_val of 3 means that we go from 9:0~ to 9:1~ at 9:08
    # NB we always get the same switch_val for this ten-minute block,
    # since we are resetting the generator and counting the correct
    number
    # of random numbers in. This is what allows random access.
    # marginal_mins is 7 in this case. (9:12-9:05)
    marginal_mins = distance - (10*(distance/10))
    if marginal_mins >= switch_val and marginal_mins >= 5
    near_time(@internal_time, :below)
    elsif marginal_mins >= switch_val and marginal_mins < 5
    near_time(@internal_time, :above)
    elsif marginal_mins < switch_val and marginal_mins >= 5
    near_time(@internal_time-10.minutes, :below)
    elsif marginal_mins < switch_val and marginal_mins < 5
    near_time(@internal_time, :below)
    end
    end

    def near_time(time, type)
    time = (time - time.beginning_of_day).to_i
    hour = time/3600
    fuzzy_min = ((time - (hour*3600))/60)/10
    fuzzy_min += 1 if type == :above
    (fuzzy_min = 0 and hour += 1) if fuzzy_min == 6
    hour = 0 if hour == 24
    if hour >= 10
    "#{hour}:#{fuzzy_min}~"
    else
    "0#{hour}:#{fuzzy_min}~"
    end
    end

    def check_valid
    if @internal_time < @start_time
    raise Exception, "Rewound past start of FuzzyTime."
    end
    end
    end

    if __FILE__ == $0
    ft = FuzzyTime.new
    puts ft
    step = ARGV[0].to_i || 60
    while(true)
    sleep step
    ft.advance 60
    puts ft
    end
    end

    --
    Posted via http://www.ruby-forum.com/.
     
    Daniel Lucraft, Oct 30, 2006
    #16
  17. Ruby Quiz

    Robert Conn Guest

    James

    Thanks for the comments. This -

    > @display.strftime("%H:%M").sub(/\d$/, "~")


    is certainly more elegant than mine.

    Gavin - I was able to test the code with your test class. I don't
    claim to fully understand it yet though. It's good to finally do
    some test driven development.

    As a sidenote, I assume Ruby doesn't support overloaded methods? I
    initially tried to implement initialize with 2 methods, one with no
    parameters, one with the time parameter but it didn't work.

    Thanks

    Robert


    On 30 Oct 2006, at 14:08, James Edward Gray II wrote:

    > On Oct 29, 2006, at 6:34 PM, Robert Conn wrote:
    >
    >> Hi all,

    >
    > Hello.
    >
    >> This is my first submission to the list and I'm new to Ruby too.

    >
    > Welcome!
    >
    >> Comments and advice would really be appreciated.

    >
    > My comment is that I wish I wrote Ruby that pretty when I was new. :)
    >
    > I'll give a couple super minor suggestions...
    >
    >> def initialize(*start_time)
    >> @current_systime = Time.new
    >> @actual = start_time[0] || Time.new
    >> @last_displayed = @actual
    >> end

    >
    > Why take an Array of arguments but only use the first one? I think
    > you should drop the slurping operator:
    >
    > def initialize(start_time)
    > @current_systime = Time.new
    > @actual = start_time || Time.new
    > @last_displayed = @actual
    > end
    >
    >> # Decide whether to go forward or back 5 mins
    >> if rand(2) == 1
    >> @display = @actual + (5 * 60)
    >> else
    >> @display = @actual - (5 * 60)
    >> end

    >
    >
    > You could shorten that up a bit, though I'm not sure this is as
    > readable:
    >
    > @display = @actual.send(%w[+ -][rand(2)], 5 * 60)
    >
    >> "#{"%02d" % @display.hour}:#{("%02d" % @display.min.to_s)
    >> [0..0]}~"

    >
    > Perhaps the this is more straight forward:
    >
    > @display.strftime("%H:%M").sub(/\d$/, "~")
    >
    > Hope that gives you some new ideas.
    >
    > James Edward Gray II
    >
     
    Robert Conn, Oct 30, 2006
    #17
  18. Robert Conn wrote:
    > As a sidenote, I assume Ruby doesn't support overloaded methods? I
    > initially tried to implement initialize with 2 methods, one with no
    > parameters, one with the time parameter but it didn't work.


    No: any time you find
    def something
    end

    it redefines the previous meaning. But you can use optional arguments

    def func(a,b = :special)
    if b == :special
    # code for one arg
    else
    # code for two
    end
    end

    I agree it is quite cumbersome, but I think when you get more used to
    Ruby, you won't need overloaded methods anymore.

    Vince

    --
    Vincent Fourmond, PhD student
    http://vincent.fourmond.neuf.fr/
     
    Vincent Fourmond, Oct 30, 2006
    #18
  19. Re: Fuzzy Time (#99)

    On Oct 30, 2006, at 12:43 PM, Daniel Lucraft wrote:

    > This is my first entry to Ruby Quiz.


    Welcome!

    James Edward Gray II
     
    James Edward Gray II, Oct 31, 2006
    #19
  20. Ruby Quiz

    Cameron Pope Guest

    Re: Fuzzy Time (#99)

    Tom Pollard wrote:
    > On Oct 31, 2006, at 1:05 AM, Tom Pollard wrote:
    >
    > Out of curiosity, I tried translating Cameron's method into my code.
    > (This mainly involved working in units of minutes, rather than
    > seconds). I found it produces the following distribution,
    >
    > Cameron's method
    > * Choose offset uniformly from the set [-5..1], , but don't change
    > the reported time if the chosen offset would cause it to retrogress.
    > -5 -4 -3 -2 -1 0 1 2 3 4 5
    > 0.0 1.2 7.2 18.1 28.4 28.3 16.7 0.0 0.0 0.0 0.0


    That part of my code is kind of obsfuscated, but what it actually does
    is choose an offset uniformly from the set [-20..5], but don't change
    the reported time if the chosen offset would cause it to retrogress.
    This would be roughly equivalent to saying: do not choose a new offset
    60% of the time. Otherwise, choose from the set [-5..+5] but don't
    retrogress below the low-water-mark.

    > The results are reasonably balanced, centered more-or-less
    > symmetrically around minus a half-minute.
    > Also, in this scheme, the reported time can /never/ be more than one
    > minute ahead of the actual time.


    I got the following if I keep track of the difference between fuzzy
    time and actual time:

    -5 -> 2.122
    -4 -> 5.981
    -3 -> 9.366
    -2 -> 11.655
    -1 -> 12.912
    0 -> 12.757
    1 -> 11.935
    2 -> 10.175
    3 -> 7.987
    4 -> 5.873
    5 -> 9.237

    > > Method 3
    > > * Changes offset randomly by +/- one minute on each update, but
    > > don't change the reported time if the chosen offset would cause it
    > > to retrogress.
    > >
    > > -5 -4 -3 -2 -1 0 1 2 3 4 5
    > > 9.2 9.4 9.6 9.3 9.1 8.8 9.1 9.0 8.8 8.8 9.0


    I like that. Simple, with good results.

    Cheers!
    -Cameron
     
    Cameron Pope, Nov 1, 2006
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Ruby Quiz

    [QUIZ] Animal Quiz (#15)

    Ruby Quiz, Jan 14, 2005, in forum: Ruby
    Replies:
    11
    Views:
    416
    James Edward Gray II
    Jan 18, 2005
  2. David Tran
    Replies:
    9
    Views:
    233
    David Tran
    Jan 21, 2005
  3. Ruby Quiz

    [QUIZ] 1-800-THE-QUIZ (#20)

    Ruby Quiz, Feb 18, 2005, in forum: Ruby
    Replies:
    15
    Views:
    347
    gabriele renzi
    Feb 24, 2005
  4. Gavin Kistner

    [QUIZ] Fuzzy Time (#99) - Results

    Gavin Kistner, Nov 2, 2006, in forum: Ruby
    Replies:
    1
    Views:
    126
    Marcel Ward
    Nov 2, 2006
  5. Gavin Kistner

    [SUMMARY] Fuzzy Time (#99)

    Gavin Kistner, Nov 2, 2006, in forum: Ruby
    Replies:
    0
    Views:
    155
    Gavin Kistner
    Nov 2, 2006
Loading...

Share This Page