Apologies if this appears as a repost, but the mailing list complained
it
was too large. Posting now in two parts:
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
The three rules of Ruby Q. 2:
-
Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have passed from the time on this message. -
Support Ruby Q. 2 by submitting ideas as often as you can! (A
permanent, new website is in the works for Ruby Q. 2. Until then,
please visit the temporary website at -
Enjoy!
Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby T. follow the discussion. Please reply to
the original quiz message, if you can.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
This week’s quiz might look a little scary, but it’s not as bad as it
seems.
Give it a shot, and ask for help if you get stuck!
Statistician II
Last week’s quiz started the creation of a line-based pattern-matching
system: our statistician. This week, your task is to further develop a
solution from last week: organize the code and provide a more
interesting
interface.
The first thing is organization. This little library should be reusable
and
not tied to any particular parsing need. So we want to separate out the
“Statistician” from the client. To do this means moving the appropriate
code
into a separate file called statistician.rb
, containing:
# statistician.rb
module Statistician
# This module is your task! Your code goes here...
end
Meanwhile, the client code will now begin with:
# client.rb
require 'statistician'
Simple, eh?
Next, we will move the rules from their own data file and bring them
into
the code. Admittedly, moving data into code usually is not a wise thing
to
do, but as the primary data is that which the rules parse, we’re going
to do
it anyway. Besides, this is Ruby Q., so why not?
Simultaneously, we’re going to group rules together: rules that while
may
differ somewhat in appearance, essentially represent the same kind or
category of data. As the rules and category are client data, they will
go
into the client’s code. Here’s an example to begin, borrowing the LotRO
rules used last week.
# client.rb
class Offense < Statistician::Reportable
rule "You wound[ the] <name>[ with <attack>] for <amount> point[s]
of
[ damage]."
rule “You reflect point[s] of damage to[ the]
.”
end
class Victory < Statistician::Reportable
rule "Your mighty blow defeated[ the] <name>."
end
Next, we need a parser (or Reporter, as I like to call it) that can
manage
these rules and classes, read the input data and process it all line by
line. Such client code looks like this:
# client.rb
lotro = Statistician::Reporter.new(Offense, Victory)
lotro.parse(File.read(ARGV[0]))
Finally, we need to begin getting useful information out of all the
records
that have been read and parsed by the Reporter. After the data is
parsed,
the final bit will be to support code such as this:
# client.rb
num = Offense.records.size
dmg = Offense.records.inject(0) { |s, x| s + x.amount.to_i }
puts "Average damage inflicted: #{dmg.to_f / num}"
puts Offense.records[0].class # outputs "Offense"
What is going on here? The class Offense
serves three purposes.
- Its declaration contains the rules for offensive related records.
- After parsing, the class method
records
returns an array of
records
that matched those rules. - Those records are instances of the class, and instance methods that
match the field names (extracted from the rules) provide access to a
record’s data.
Hopefully this isn’t too confusing. I could have broken up some of these
responsibilities into other classes or sections of code, but since the
three
tasks are rather related, I thought it convenient and pleasing to group
them
all into the client’s declared class.
Below I’ll give the full, sample client file I’m using, as well as the
output it generates when run over the [hunter.txt][1] file we used last
week. A few hints, first…
-
You are welcome to make
statistician.rb
depend on other Ruby
modules.
I personally foundOpenStruct
to be quite useful here. -
Personally, I found making
Offense
inherit fromReportable
to
be
the cleanest method. At least, it is in my own code. There may be other
ways
to accomplish this goal: byinclude
orextend
methods. If you find
those
techniques more appealing, please go ahead, but make a note of it in
your
submission, since it does require changing how client code is written. -
Metaprogramming can get a bit tricky to explain in a couple
sentences,
so I’ll leave such hints and discussion for the mailing list. Aside from
that, there are some good examples of metaprogramming looking back
through
past Ruby Q.zes. Of particular interest would be the [metakoans.rb
quiz][2]. -
Finally, my own solution for this week’s quiz is just under 80
lines
long, so it need not be overly complex to support the client file below.
((( Remainder of this Ruby Q. to come as a reply to this message. )))