Please Forward: Ruby Quiz Submission (92)

From: Eric Torreborre <etorreborre@yahoo.com>
Date: August 31, 2006 4:20:10 PM CDT
To: submission@rubyquiz.com
Subject: Please Forward: Ruby Quiz Submission (92)
Reply-To: Eric Torreborre <etorreborre@yahoo.com>

Hi all,

This is my first submission to the quiz and I am very glad to do it! I used this quiz to introduce Ruby to my fellow workers for today's "Dojo" (see http://bossavit.com/dojo/and http://wiki.agilefinland.com/?CodingDojo).

They found it a very gentle introduction to the language.

As for myself, I found a great interest in writing properly things down and then having a look at all other submissions, searching for better ways to do things.

Anyway, here is my submission, that goes around the following lines:

-use of rspec (behavior driven development) to write acceptance tests
-use of the Set class to know if days identifiers are correct
-extension of the Range class to split an array into an array of ranges for consecutive values

Here's the code:

===========================
#day_range_spec.rb

require "spec"
require "day_range"

context "A new DayRange" do
  specify "should accept a list of day numbers" do
    DayRange.new([1, 2, 3])
  end

  specify "should accept a list of abbreviated day names" do
    DayRange.new(%w{Mon Tue Fri})
  end

  specify "should accept a list of whole day names" do
    DayRange.new(%w{Monday Tuesday Friday})
  end

  specify "should raise an argument error an invalid day number is inputed in the list" do
    lambda {DayRange.new([1, 8])}.should_raise(ArgumentError)
  end

  specify "should raise an argument error an invalid abbreviated day is inputed in the list" do
    lambda {DayRange.new(["bla"])}.should_raise(ArgumentError)
  end

  specify "should raise an argument error an invalid day name is inputed in the list" do
    lambda {DayRange.new(["lunedi"])}.should_raise(ArgumentError)
  end
end

context "A week with a single day" do
  specify "should return the day short name only" do
    DayRange.new([7]).to_s.should_equal('Sun')
  end
end

context "An initialized DayRange" do
  specify "should sort days starting with Monday" do
    DayRange.new([3, 1]).to_s.should_equal('Mon, Wed')
  end
end

context "A week DayRange" do
  specify "should represent three or more consecutive days by listing the first day followed by a hyphen (-), followed by the last day of the range" do
    DayRange.new([1, 2, 3, 4, 5, 6, 7]).to_s.should_equal('Mon-Sun')
  end
end
context "A week without Thursday and Friday" do
  specify "should return 'Mon-Wed, Sat, Sun' (added hyphen)" do
    DayRange.new([1, 2, 3, 6, 7]).to_s.should_equal('Mon-Wed, Sat, Sun')
  end
end

===========================
# day_range.rb

require 'set'
require 'range'

class DayRange
  def initialize(daylist)
    raise ArgumentError, "#{daylist.to_s} is not valid" unless Week.are_valid_identifiers?(daylist)
    @days = Week.day_indexes(daylist)
  end

  def to_s
    ranges = Range.split(@days)
    ranges.collect{|r| Week.short_names_from_range(r)}.join(', ')
  end

end

class Week
  DAYS = [[1, 'Mon', 'Monday'],
          [2, 'Tue', 'Tuesday'],
          [3, 'Wed', 'Wednesday'],
          [4, 'Thu', 'Thursday'],
          [5, 'Fri', 'Friday'],
          [6, 'Sat', 'Saturday'],
          [7, 'Sun', 'Sunday']]

  def self.are_valid_identifiers?(days)
    return DAYS.flatten.to_set.superset?(days.to_set)
  end

  def self.day_indexes(daylist)
    return DAYS.select{|d| !d.to_set.intersection(daylist.to_set).empty?}.collect{|d| d[0]}.sort!
  end

  def self.short_names_from_range(range)
    if range.to_a.size == 1
      return DAYS[range.first - 1][1]
    elsif range.to_a.size == 2
      return DAYS[range.first - 1][1] + ', ' + DAYS[range.last - 1][1]
    else
      return DAYS[range.first - 1][1] + '-' + DAYS[range.last - 1][1]
    end
  end
end

===========================
#range_spec.rb

require "spec"
require "range"

context "A Range" do
  specify "should split a [1] array in a 1..1 range" do
    Range.split([1]).should_equal([1..1])
  end

  specify "should split a [1, 2] array in a 1..2 range" do
    Range.split([1, 2]).should_equal([1..2])
  end

  specify "should split a [1, 2, 3] array in a 1..3 range" do
    Range.split([1, 2, 3]).should_equal([1..3])
  end

  specify "should split a [1, 3] array in 2 ranges: [1..1, 3..3]" do
    Range.split([1, 3]).should_equal([1..1, 3..3])
  end

  specify "should split a [1, 2, 4] array in 2 ranges: [1..2, 4..4]" do
    Range.split([1, 2, 4]).should_equal([1..2, 4..4])
  end

end

#range.rb

class Range
  def self.split(list)
    list.inject([]) do |ranges, i|
      last_range = ranges.last
      if (last_range.nil? || i > last_range.last.next)
        ranges << [i, i]
      else
        last_range[1] = i
      end
      ranges
    end.collect{|r| r.first..r.last} # transform to Range objects
  end

end

--------------------------------------------------
Eric Torreborre
LTG - Product Manager
LEIRIOS
tel: 33(0)6.61.48.57.65/33(0)3.81.88.62.02
e-mail: etorreborre@yahoo.com
blog: http://etorreborre.blogspot.com
--------------------------------------------------

···

Begin forwarded message: