[SUMMARY] VCR Program Manager (#101)

R

Ruby Quiz

When I used the term VCR in my latest book, my editor complained that it "dates
me." (Ouch. I'm only 30!) I'm told we all use Tivo to record our shows now.
I'm not on that bandwagon yet, so the rest of you will need to tell me if we
need a Tivo Program Manager quiz or if this same code will work.

First, let's examine a super straight forward solution to the task to see what's
involved. When we've done that, I'll make note of a few variations that caught
my eye.

We will begin with some code by Peter Severin. Here's a helper method Peter
added to the Time class:

class Time
def seconds
(hour * 60 + min) * 60 + sec
end
end

This just calculates the number of seconds since midnight. That measurement is
used by weekly programs in the quiz, to specify when to record on a given day.

Let's take our first steps into the Program objects now:

class Program
attr_reader :channel

def initialize(program_details)
@program_start = program_details[:start]
@program_end = program_details[:end]
@channel = program_details[:channel]
end
end

I think Peter has a very clean and correct OO design in these Program classes.

Here we see the base class handling only the initialization that applies to all
subclasses. Also note that only channel() is exposed to the outside world,
since that's all the ProgramManager really needs.

Here's the subclass for one-shot recording:

class SpecificProgram < Program
def record?(time)
time.between?(@program_start, @program_end)
end
end

All Peter adds here is the ability for the Program to determine if it is
scheduled for the passed time. This keeps the Program logic in the Program
classes where it belongs.

The other subclass is for repeat programming:

class RepeatingProgram < Program
WEEKDAYS = %w(mon tue wed thu fri sat sun)

def initialize(program_details)
super
@days = program_details[:days].map {|day| WEEKDAYS.index(day) + 1}
end

def record?(time)
@days.include?(time.wday) &&
time.seconds.between?(@program_start, @program_end)
end
end

Here initialization is modified to handle the :days parameter that only applies
to this type of Program. From there, another record?() method is created to
examine both the day and time.

I don't see as much of this traditional OO design in solutions to the quizzes,
but here I found it quite elegant. With each Program able to answer the right
questions about itself, the ProgramManager is almost trivial to construct:

class ProgramManager
def initialize()
@programs = []
end

def add(program_details)
case program_details[:start]
when Numeric
@programs << RepeatingProgram.new(program_details)
when Time
@programs[0, 0] = SpecificProgram.new(program_details)
end

self
end

def record?(time)
program = @programs.find {|program| program.record?(time)}
program ? program.channel : nil
end
end

Peter begins by constructing an Array to hold the @programs in initialize().
Conflict management was pretty easy for this quiz in that the last defined
Program wins out. You can deal with that by keeping them all in one list and
making sure you order them correctly at insertion time.

The insertions are handled by the add() method. It determines the type of
Program object to create and adds it to our list of @programs. The second
addition is the tricky one, if you're not familiar with how Array.[]=() works.
Let's see what that does in IRb:
programs = Array.new => []
programs << "repeating 1" << "repeating 2" => ["repeating 1", "repeating 2"]
programs[0, 0] = "specific 1" => "specific 1"
programs
=> ["specific 1", "repeating 1", "repeating 2"]

Put another way, the assignment to index zero, length zero adds the element to
the front of the Array. It's equivalent to the more common:

@programs.unshift(SpecificProgram.new(program_details))

The final method of Peter's ProgramManager, record?(), just forwards the
record?() calls to the Array of Program objects via the find() iterator. The
first one to claim the time is selected and that Program's channel() is
returned.

There was an interesting element in Dema's Program class that's probably worth a
quick look:

class Program

# ...

def initialize(program)
@start = program[:start]
@end = program[:end]
@channel = program[:channel]
@days = program[:days]

raise "Missing start or end" \
if @start.nil? || @end.nil?
raise "Wrong start or end types" \
unless (@start.is_a?(Time) && @end.is_a?(Time)) ||
(@start.is_a?(Integer) && @end.is_a?(Integer))
raise "Invalid program" \
if weekly? && (@start.is_a?(Time) || @end.is_a?(Time))
raise "End must come after Start" \
if !weekly? && @start > @end
raise "Missing channel" \
if [email protected]_a?(Integer)
raise "Invalid weekday" \
if @days.is_a?(Array) && @days.any? { |day| WEEKDAYS[day] == nil }
end

# ...

end

The only difference here is that Dema does a fair amount of error checking when
Programs are constructed. Ordinarily, I'm not a big fan of this kind of
lock-down coding, but this seems like a good case where it might just be worth
the effort. Programs are going to come from users and of course users are going
to make mistakes. Isolating these at Program construction time would allow the
machine to respond to those errors intelligently at the time when it matters,
instead of running into trouble down the road and recording at the wrong times.

The focus of this quiz was not to implement a complete VCR, of course, and most
solvers just assumed they would receive correct input. I just thought it worth
mentioning that the safeguards employed by Dema do need to be in place somewhere
in the system.

I won't show the code here, but as a final point of interest I want to recommend
everyone take a peek at Gordon Thiesfeld's solution. It uses a library called
Runt to handle the majority of the scheduling. I wasn't aware of this resource
and Gordon's code got me to look into it.

Runt is Temporal Expression library designed with things like recurring events
and scheduling in mind. The documentation is pretty good and the project worth
a look:

http://runt.rubyforge.org/

My thanks to all the solvers who always manage to teach me things, even when we
do these simple problems. I love that aspect of the quiz.

Tomorrow we will take a shot at bringing Literate Programming to our fair
Ruby...
 
L

Louis J Scoras

Tomorrow we will take a shot at bringing Literate Programming to our fair
Ruby...

Awesome. I've actually been working on something to do this since it
was mentioned during the last quiz. It will be interesting to see
what kind of solutions everyone comes up with. Afterwards if anybody
is interested I'd definitely be for starting a project for working on
this.

I think ruby is really well suited for literate programming, but I'll
hold off on some thoughts until after the quiz.

Looking forward to it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top