Dondo Land

Bryan Donovan's Weblog

All | General | Music | Rails

20060827 Sunday August 27, 2006

 Day Range Rubyquiz

Rubyquiz has accepted my submission, asking readers to write a class that returns a range of days in a human-readable format. The quiz can be found here. Here's an excerpt explaining the idea..
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.
[...]
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
To my surprise, there have already been several submissions with varying ways of solving the problem. This is a good way to learn some Ruby for sure..

(2006-08-27 12:10:46.0/2006-08-27 10:15:06.0) Permalink Comments [6]
Trackback: http://blogs.sun.com/bdonovan/entry/day_range_rubyquiz

Trackback URL: http://blogs.sun.com/bdonovan/entry/day_range_rubyquiz
Comments:

class DateRange

@@WEEK_DAYS = (1..7)
@@DAY_TO_S = %w{zero Mon Tue Wed Thu Fri Sat Sun}
@@DAY_TO_I = @@DAY_TO_S.inject(Hash.new) { |h, day| h[day] = h.size; h }

def initialize(*days)
  @day_nums = []
  days.each do |day|
    day = @@DAY_TO_I[day[0..2].capitalize] if day.kind_of?(String)
    raise ArgumentError unless @@WEEK_DAYS.include?(day)
    @day_nums.push(day)
  end
  @day_nums.sort!
end

def to_s
  s = ''
  days = @day_nums.clone
  1.upto(@day_nums.size-1) do |i|
    today = @day_nums[i]
    days[i] = nil if @day_nums[i-1] == today-1 and @day_nums[i+1] == today+1
  end
  days.each do |day|
    s += (day ? ', ' : '-') if s.size > 0 and s[-1].chr != '-'
    s += @@DAY_TO_S[day] if day
  end
  s
end

Posted by Kevin Hutchinson on August 27, 2006 at 01:42 PM PDT #

oops, that should be "size-2"

Posted by Kevin Hutchinson on August 27, 2006 at 01:45 PM PDT #

...and maybe rename the class "DayRange".

Posted by Kevin Hutchinson on August 28, 2006 at 11:02 AM PDT #

Thanks Kevin. Maybe you should submit your answer to the RubyQuiz guys.. My initial solution is:
class DayRange

  def day_hash
    { '1'=>'Mon',
      '2'=>'Tue',
      '3'=>'Wed',
      '4'=>'Thu',
      '5'=>'Fri',
      '6'=>'Sat',
      '7'=>'Sun' }
  end

  def initialize(day_ids)
    @day_ids = day_ids.uniq.sort
    unless @day_ids.all? { |d| d.between?(1,7) }
      raise ArgumentError "Days must be between 1 and 7"
    end
  end

  #Returns formatted string representing the day array
  def to_s
    day_abbrs = day_hash
    i = 0
    #Array of consed_days arrays
    consec_day_groups = []
    #Array of consecutive days.  This gets
    #reset when we find a day that is not
    #part of a consecutive set of days.
    consec_days = []

    @day_ids.each do |day_id|

      if i == 0
        #add the day abbr for this day if this is the first
        #element of @day_ids.
        consec_days << day_abbrs[day_id.to_s]

      elsif @day_ids[i-1] == (day_id - 1)
        #if this is not the first day in @day_ids, check
        #if the previous day in the array is the real life
        #previous day of the week.  If so, add it to the
        #consec_days array
        consec_days << day_abbrs[day_id.to_s]
      else
        #otherwise start a new consec_days array and
        #add the current consec_days array to the
        #consec_day_groups array.
        consec_day_groups << consec_days
        consec_days = []
        consec_days << day_abbrs[day_id.to_s]
      end
      #Always add the consec_days array when this is the
      #last day of the @day_ids array
      if day_id == @day_ids.last
        consec_day_groups << consec_days
      end
      i += 1
    end
day_strings = []
    consec_day_groups.each do |c|
      if c.length > 2
        day_strings <<  c.first.to_s + "-" + c.last.to_s
      else
        day_strings << c.join(", ")
      end
    end
    return day_strings.join(", ")
  end
end

Posted by BryanDonovan on August 28, 2006 at 07:02 PM PDT #

As an "old school" Perl programmer, I've been trying to get into Ruby for the past year but I never found a "bite at a time" approach like this. I found it really interesting, and plan to use your Ruby Quiz link to get into Ruby some more. I really hope that scripting in general becomes more popular at Sun because it can be so powerful compared to Java. I know that Mustang will include script support and that's good. But imagine if you could completely script Aquarium (JEE) using Groovy or JRuby? Wow, now you'd be competing with Rails - which for all its sins, gets the job done, and will a huge amount of default usability built in.

What I like about your solution is:

  • Great comments (I can be so cryptic!)
  • days.uniq.sort (I forgot that!)
  • Using << instead of "push"

Posted by Kevin Hutchinson on August 29, 2006 at 01:11 PM PDT #

I shamelessly use Rails for side projects and even at work (Sun). I tried some J2EE for a while, but didn't really like the complexity. I do a lot in PHP too, but I get tired of using -> for OO stuff.

Posted by Bryan on August 29, 2006 at 01:22 PM PDT #

Post a Comment:

Name:
E-Mail:
URL:

Your Comment:

HTML Syntax: NOT allowed

« November 2009
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     
       
Today


XML







Today's Page Hits: 5