[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

[QUIZ] DayRange (#92

James Gray

8/25/2006 1:01:00 PM

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.rub...

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 Bryan Donovan

If you've ever created a web application that deals with scheduling recurring
events, you may have found yourself creating a method to convert a list of days
into a more human-readable string.

For example, suppose a musician plays at a certain venue on Monday, Tuesday,
Wednesday, and Saturday. You could pass a list of associated day numbers to your
object or method, which might return "Mon-Wed, Sat".

The purpose of this quiz is to find the best "Ruby way" to generate this
sentence-like string.

Basically, the rules are:

* The class's constructor should accept a list of arguments that can be day
numbers (see day number hash below), day abbreviations ('Mon', 'Tue', etc.),
or the full names of the days ('Monday', 'Tuesday', etc.).
* If an invalid day id is included in the argument list, the constructor
should raise an ArgumentError.
* The days should be sorted starting with Monday.
* Three or more consecutive days should be represented by listing the first
day followed by a hyphen (-), followed by the last day of the range.
* Individual days and the above day ranges should be separated by commas.
* The class should number days (accepting Integers or Strings) as follows:
1: Mon
2: Tue
3: Wed
4: Thu
5: Fri
6: Sat
7: Sun
* The class needs a method named #to_s that returns the day range string.
Here are some example lists of days and their expected returned strings:
1,2,3,4,5,6,7: Mon-Sun
1,2,3,6,7: Mon-Wed, Sat, Sun
1,3,4,5,6: Mon, Wed-Sat
2,3,4,6,7: Tue-Thu, Sat, Sun
1,3,4,6,7: Mon, Wed, Thu, Sat, Sun
7: Sun
1,7: Mon, Sun
1,8: ArgumentError

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.

48 Answers

Morton Goldberg

8/25/2006 10:48:00 PM

0

Request for clarification.

Are you asking us to define a class or a top-level conversion method?

If it's a class you want, beside initialize, what methods are you
asking for?

My guess is that you expect a 'to_s' that returns the human-readable
form and a 'to_a' that returns an array of day numbers. Is this
correct? Also, should 'initialize' accept mixed argument sequences?
For example, which of following do you consider valid argument lists
for 'initialize'?

('Mon-Wednesday', 5)
('1-Wed', 5)
([1, 2, 3], 'Fri')
(1, 2, 3, 'Fri')
('1-3', 'Fri')

Regards, Morton

On Aug 25, 2006, at 9:01 AM, Ruby Quiz wrote:

> by Bryan Donovan
>
> If you've ever created a web application that deals with scheduling
> recurring
> events, you may have found yourself creating a method to convert a
> list of days
> into a more human-readable string.
>
> For example, suppose a musician plays at a certain venue on Monday,
> Tuesday,
> Wednesday, and Saturday. You could pass a list of associated day
> numbers to your
> object or method, which might return "Mon-Wed, Sat".
>
> The purpose of this quiz is to find the best "Ruby way" to generate
> this
> sentence-like string.
>
> Basically, the rules are:
>
> * The class's constructor should accept a list of arguments that
> can be day
> numbers (see day number hash below), day abbreviations ('Mon',
> 'Tue', etc.),
> or the full names of the days ('Monday', 'Tuesday', etc.).
> * If an invalid day id is included in the argument list, the
> constructor
> should raise an ArgumentError.
> * The days should be sorted starting with Monday.
> * Three or more consecutive days should be represented by listing
> the first
> day followed by a hyphen (-), followed by the last day of the
> range.
> * Individual days and the above day ranges should be separated by
> commas.
> * The class should number days (accepting Integers or Strings) as
> follows:
> 1: Mon
> 2: Tue
> 3: Wed
> 4: Thu
> 5: Fri
> 6: Sat
> 7: Sun
> * The class needs a method named #to_s that returns the day range
> string.
> Here are some example lists of days and their expected returned
> strings:
> 1,2,3,4,5,6,7: Mon-Sun
> 1,2,3,6,7: Mon-Wed, Sat, Sun
> 1,3,4,5,6: Mon, Wed-Sat
> 2,3,4,6,7: Tue-Thu, Sat, Sun
> 1,3,4,6,7: Mon, Wed, Thu, Sat, Sun
> 7: Sun
> 1,7: Mon, Sun
> 1,8: ArgumentError
>
> 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.
>


James Gray

8/26/2006 12:50:00 AM

0

On Aug 25, 2006, at 5:47 PM, Morton Goldberg wrote:

> Request for clarification.
>
> Are you asking us to define a class or a top-level conversion method?

The quiz asks for a class, yes.

> If it's a class you want, beside initialize, what methods are you
> asking for?

#initialize and #to_s plus anything else you deem cool.

> Also, should 'initialize' accept mixed argument sequences?

I'll leave that to your judgement.

James Edward Gray II

Morton Goldberg

8/26/2006 2:33:00 AM

0

On Aug 25, 2006, at 8:49 PM, James Edward Gray II wrote:

> On Aug 25, 2006, at 5:47 PM, Morton Goldberg wrote:
>
>> Request for clarification.
>>
>> Are you asking us to define a class or a top-level conversion method?
>
> The quiz asks for a class, yes.

Thanks for the clarification. Rereading the OP more carefully, I see
this is explicitly specified in the bulleted rules. I got thrown off
course by following sentences, which precede the rules:

> For example, suppose a musician plays at a certain venue on Monday,
> Tuesday,
> Wednesday, and Saturday. You could pass a list of associated day
> numbers to your
> object or method, which might return "Mon-Wed, Sat".

To me, the second sentence doesn't make a lot of sense in the context
of a DayRange class. Something that takes a list of numbers and
returns a string, suggests a top-level conversion function.

Regards, Morton



Robert Retzbach

8/26/2006 3:03:00 PM

0

Thanks I was about to ask too.

For the time being I made it this way:

dayrange = DayRange.new [1,3,4,5,6,7], %w{Mo Di Mi Do Fr Sa So}
p dayrange.to_s #=> "Mo, Mi-So"

I think that's the way it should work. If not, please tell me.

Morton Goldberg schrieb:
> Request for clarification.
>
> Are you asking us to define a class or a top-level conversion method?
>
> If it's a class you want, beside initialize, what methods are you asking
> for?
>
> My guess is that you expect a 'to_s' that returns the human-readable
> form and a 'to_a' that returns an array of day numbers. Is this correct?
> Also, should 'initialize' accept mixed argument sequences? For example,
> which of following do you consider valid argument lists for 'initialize'?
>
> ('Mon-Wednesday', 5)
> ('1-Wed', 5)
> ([1, 2, 3], 'Fri')
> (1, 2, 3, 'Fri')
> ('1-3', 'Fri')
>
> Regards, Morton
>
> On Aug 25, 2006, at 9:01 AM, Ruby Quiz wrote:
>
>> by Bryan Donovan
>>
>> If you've ever created a web application that deals with scheduling
>> recurring
>> events, you may have found yourself creating a method to convert a
>> list of days
>> into a more human-readable string.
>>
>> For example, suppose a musician plays at a certain venue on Monday,
>> Tuesday,
>> Wednesday, and Saturday. You could pass a list of associated day
>> numbers to your
>> object or method, which might return "Mon-Wed, Sat".
>>
>> The purpose of this quiz is to find the best "Ruby way" to generate this
>> sentence-like string.
>>
>> Basically, the rules are:
>>
>> * The class's constructor should accept a list of arguments that
>> can be day
>> numbers (see day number hash below), day abbreviations ('Mon',
>> 'Tue', etc.),
>> or the full names of the days ('Monday', 'Tuesday', etc.).
>> * If an invalid day id is included in the argument list, the
>> constructor
>> should raise an ArgumentError.
>> * The days should be sorted starting with Monday.
>> * Three or more consecutive days should be represented by listing
>> the first
>> day followed by a hyphen (-), followed by the last day of the
>> range.
>> * Individual days and the above day ranges should be separated by
>> commas.
>> * The class should number days (accepting Integers or Strings) as
>> follows:
>> 1: Mon
>> 2: Tue
>> 3: Wed
>> 4: Thu
>> 5: Fri
>> 6: Sat
>> 7: Sun
>> * The class needs a method named #to_s that returns the day range
>> string.
>> Here are some example lists of days and their expected returned
>> strings:
>> 1,2,3,4,5,6,7: Mon-Sun
>> 1,2,3,6,7: Mon-Wed, Sat, Sun
>> 1,3,4,5,6: Mon, Wed-Sat
>> 2,3,4,6,7: Tue-Thu, Sat, Sun
>> 1,3,4,6,7: Mon, Wed, Thu, Sat, Sun
>> 7: Sun
>> 1,7: Mon, Sun
>> 1,8: ArgumentError
>>
>> 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.
>>
>
>
>

Michael W. Ryder

8/26/2006 11:49:00 PM

0

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.rub...
>
> 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 Bryan Donovan
>
> If you've ever created a web application that deals with scheduling recurring
> events, you may have found yourself creating a method to convert a list of days
> into a more human-readable string.
>
> For example, suppose a musician plays at a certain venue on Monday, Tuesday,
> Wednesday, and Saturday. You could pass a list of associated day numbers to your
> object or method, which might return "Mon-Wed, Sat".
>
> The purpose of this quiz is to find the best "Ruby way" to generate this
> sentence-like string.
>
> Basically, the rules are:
>
> * The class's constructor should accept a list of arguments that can be day
> numbers (see day number hash below), day abbreviations ('Mon', 'Tue', etc.),
> or the full names of the days ('Monday', 'Tuesday', etc.).
> * If an invalid day id is included in the argument list, the constructor
> should raise an ArgumentError.
> * The days should be sorted starting with Monday.
> * Three or more consecutive days should be represented by listing the first
> day followed by a hyphen (-), followed by the last day of the range.
> * Individual days and the above day ranges should be separated by commas.
> * The class should number days (accepting Integers or Strings) as follows:
> 1: Mon
> 2: Tue
> 3: Wed
> 4: Thu
> 5: Fri
> 6: Sat
> 7: Sun
> * The class needs a method named #to_s that returns the day range string.
> Here are some example lists of days and their expected returned strings:
> 1,2,3,4,5,6,7: Mon-Sun
> 1,2,3,6,7: Mon-Wed, Sat, Sun
> 1,3,4,5,6: Mon, Wed-Sat
> 2,3,4,6,7: Tue-Thu, Sat, Sun
> 1,3,4,6,7: Mon, Wed, Thu, Sat, Sun
> 7: Sun
> 1,7: Mon, Sun
> 1,8: ArgumentError
>
> 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.
>

What about the handling of day ranges that wrap, such as 1, 5, 6, and 7?
Do you want Monday, Friday-Sunday, or the more logical Friday-Monday?

James Gray

8/27/2006 2:28:00 AM

0

On Aug 26, 2006, at 10:02 AM, Robert Retzbach wrote:

> Thanks I was about to ask too.
>
> For the time being I made it this way:
>
> dayrange = DayRange.new [1,3,4,5,6,7], %w{Mo Di Mi Do Fr Sa So}
> p dayrange.to_s #=> "Mo, Mi-So"
>
> I think that's the way it should work. If not, please tell me.

There's no right or wrong answers here. I see you introduces a
feature not called for in the quiz and I like it. Clever.

James Edward Gray II

Morton Goldberg

8/27/2006 2:31:00 AM

0

It certainly looks good to me. I believe the quiz master has
indicated we have considerable latitude in what we admit as a valid
argument sequence for DayRange#initialize.

Allowing a set of day names to be specified when an instance of
DayRange is created is a neat idea. I didn't think of that. Also, I
decided not to allow lists such as [1,3,4,5,6,7] although my
DayRange#initialize is pretty permissive in other ways. The closest I
can get to what you show is:

<code>
days = DayRange.new(1,3,4,5,6,7)
puts days
puts days.to_s(true)
</code>

<result>
Mon, Wed-Sun
Monday, Wednesday-Sunday
</result>

As you can see, I do allow optional long-form English names for days
to be returned from to_s. But your idea of allowing the programmer to
specify the names at instance creation time is better.

Regards, Morton

On Aug 26, 2006, at 11:02 AM, Robert Retzbach wrote:

> Thanks I was about to ask too.
>
> For the time being I made it this way:
>
> dayrange = DayRange.new [1,3,4,5,6,7], %w{Mo Di Mi Do Fr Sa So}
> p dayrange.to_s #=> "Mo, Mi-So"
>
> I think that's the way it should work. If not, please tell me.


Simon Kröger

8/27/2006 1:48:00 PM

0

This is one of the problems that looks easier than it is
(at least to me). My solution handles input in form of day
abbreviations, full day names and integers as described in
the quiz. It raises an ArgumentError for all other inputs.

Further more my solution wraps around:

puts DayRange.new('Monday', 'Sun', 5, 2, 6)

results in "Fri-Tue" instead of "Mon, Tue, Fri-Sun"
(this can be easily switched of by deleting the last two
lines of the initialize method).

I also included Enumerable and provided an each method, but
i'm not sure if this is really helpful because each iterates
over each day not each range (would that be more helpful?).

Well, here it is:
--------------------------------------------------------------
require 'date'

class DayRange
include Enumerable
def initialize *days
@days = []
days.map do |d|
day = Date::DAYNAMES.index(d) || Date::ABBR_DAYNAMES.index(d)
raise ArgumentError, d.to_s unless day || (1..7).include?(d.to_i)
day ? day.nonzero? || 7 : d.to_i
end.uniq.sort.each do |d|
next @days << [d] if @days.empty? || d != @days.last.last + 1
@days.last << d
end
p @days
return unless @days.first.first == 1 && @days.last.last == 7
@days.last.concat(@days.shift) if @days.size > 1
end

def each
@days.flatten.each{|d| yield d}
end

def to_s
@days.map do |r|
first = Date::ABBR_DAYNAMES[r.first % 7]
last = Date::ABBR_DAYNAMES[r.last % 7]
next "#{first}, #{last}" if r.size == 2
r.size > 2 ? "#{first}-#{last}" : first
end * ', '
end
end

puts DayRange.new(1, 2, 3, 4, 5, 6, 7) #=> Mon-Sun
puts DayRange.new('Monday', 'Sun', 5, 2, 6) #=> Fri-Tue
puts DayRange.new(2, 6, 'Friday', 'Sun') #=> Tue, Fri-Sun

dr = DayRange.new(2, 6, 'Friday', 'Sun')
puts dr.map{|d| Date::DAYNAMES[d % 7]} * ', '
#=> Tuesday, Friday, Saturday, Sunday
-----------------------------------------------------------------

cheers

Simon

Morton Goldberg

8/27/2006 3:15:00 PM

0

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


James Gray

8/27/2006 3:37:00 PM

0

On Aug 26, 2006, at 10:50 PM, Michael W. Ryder wrote:

> What about the handling of day ranges that wrap, such as 1, 5, 6,
> and 7? Do you want Monday, Friday-Sunday, or the more logical
> Friday-Monday?

That's a great question. You decide what is best an implement that.

James Edward Gray II