Morton Goldberg
8/27/2006 3:15:00 PM
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