On Aug 25, 2006, at 9:01 AM, Ruby Quiz wrote: > This is not intended to be a difficult quiz, but I think the > solutions would be > useful in many situations, especially in web applications. The > solution I have > come up with works and is relatively fast (fast enough for my > purposes anyway), > but isn't very elegant. I'm very interested in seeing how others > approach the > problem. It wasn't difficult because I could call on Array, Hash, Range, Regexp, and Enumerable to do the heavy lifting. It wouldn't be pleasant to write this in C using just the standard libraries. As for speed, I don't see that as much of an issue (and I didn't try to make my code fast) because I can't see myself using this in a situation where it would be evaluated at high frequency. As for elegance -- elegance is in the eye of the beholder :) The only bell (or is it a whistle?) I've added is a flag that controls whether or not day names are printed in long or short form by to_s. I've taken a fairly permissive approach on what arguments DayRange#initialize accepts. Arguments may be repeated or given in no particular order. <code> #! /usr/bin/ruby -w # Author: Morton Goldberg # # Date: August 27, 2006 # # Ruby Quiz #92 -- DayRange class DayRange DAY_DIGITS = { 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6, 'sun' => 7, 'monday' => 1, 'tuesday' => 2, 'wednesday' => 3, 'thursday' => 4, 'friday' => 5, 'saturday' => 6, 'sunday' => 7, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7 } SHORT_NAMES = [nil, 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] LONG_NAMES = [ nil, 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] # Return day range as nicely formatted string. # If @long is true, day names appear in long form; otherwise, they # appear in short form. def to_s names = @long ? LONG_NAMES : SHORT_NAMES result = [] @days.each do |d| case d when Integer result << names[d] when Range result << names[d.first] + "-" + names[d.last] end end result.join(", ") end # Return day range as array of integers. def to_a result = @days.collect do |d| case d when Integer then d when Range then d.to_a end end result.flatten end def initialize(*args) @days = [] @long = false @args = args @args.each do |arg| case arg when Integer bad_arg if arg < 1 || arg > 7 @days << arg when /^(.+)-(.+)$/ begin d1 = DAY_DIGITS[$1.downcase] d2 = DAY_DIGITS[$2.downcase] bad_arg unless d1 && d2 && d1 <= d2 d1.upto(d2) {|d| @days << d} rescue StandardError bad_arg end else d = DAY_DIGITS[arg.downcase] bad_arg unless d @days << d end end @days.uniq! @days.sort! normalize end # Use this change printing behavior from short day names to long day names # or vice-versa. attr_accessor :long private # Convert @days from an array of digits to normal form where runs of # three or more consecutive digits appear as ranges. def normalize runs = [] first = 0 for k in 1... / days.size unless @days[k] == @days[k - 1].succ runs << [first, k - 1] if k - first > 2 first = k end end runs << [first, k] if k - first > 1 runs.reverse_each do |r| @days[r[0]..r[1]] = @days[r[0]]..@days[r[1]] end end def bad_arg raise(ArgumentError, "Can't create a DayRange from #{@args.inspect}") end end if $0 == __FILE__ # The following should succeed. days = DayRange.new("mon-wed", "thursday", 7) puts days days.long = true puts days p days.to_a puts days = DayRange.new("friday-fri", "mon-monday") puts days days.long = true puts days p days.to_a puts days = DayRange.new("mon", 7, "thu-fri") puts days days.long = true puts days p days.to_a puts days = DayRange.new("2-7") puts days days.long = true puts days p days.to_a puts days = DayRange.new(1, 2, 1, 2, 3, 3) puts days days.long = true puts days p days.to_a puts args = (1..4).to_a.reverse days = DayRange.new(*args) puts days days.long = true puts days p days.to_a puts # The following should fail. begin DayRange.new("foo") rescue StandardError=>err puts err.message puts end begin DayRange.new("foo-bar") rescue StandardError=>err puts err.message puts end begin DayRange.new("sat-mon") rescue StandardError=>err puts err.message puts end begin args = (0..4).to_a.reverse DayRange.new(*args) rescue StandardError=>err puts err.message puts end end </code> <result> Mon-Thu, Sun Monday-Thursday, Sunday [1, 2, 3, 4, 7] Mon, Fri Monday, Friday [1, 5] Mon, Thu, Fri, Sun Monday, Thursday, Friday, Sunday [1, 4, 5, 7] Tue-Sun Tuesday-Sunday [2, 3, 4, 5, 6, 7] Mon-Wed Monday-Wednesday [1, 2, 3] Mon-Thu Monday-Thursday [1, 2, 3, 4] Can't create a DayRange from ["foo"] Can't create a DayRange from ["foo-bar"] Can't create a DayRange from ["sat-mon"] Can't create a DayRange from [4, 3, 2, 1, 0] </result> Regards, Morton