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:
3. Enjoy!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
This week's Ruby Quiz is to write a program that presents the user with Madlibs.
The script should ask the user for a series of words, then fill in the proper
places in the story using the user's answers.
We'll keep our story format very simple, using a ((...)) notation for
placeholders. Here's an example:
Our favorite language is ((a gemstone)).
If your program is fed that template, it should ask you to enter "a gemstone"
and then display your version of the story:
Our favorite language is Ruby.
That covers the simple cases, but in some instances we may want to reuse an
answer. For that, we'll introduce a way to name them:
Our favorite language is ((gem:a gemstone)). We think ((gem)) is better
than ((a gemstone)).
With the above story, your program should ask for two gemstones, then substitute
the one designated by ((gem:...)) at ((gem)). That would give results like:
Our favorite language is Ruby. We think Ruby is better than Emerald.
You can choose any interface you like, as long as person can interact with the
end result. You can play around with my solution here:
http://rubyquiz.com/cgi-bin/madlib.cgi
And here are the two Madlib files I'm using, to get you started:
http://rubyquiz.com/madlibs/Lunch_Hungers.madlib
http://rubyquiz.com/madlibs/Gift_Giving.madlib
> This week's Ruby Quiz is to write a program that presents the user with
> Madlibs.
> The script should ask the user for a series of words, then fill in the
> proper
> places in the story using the user's answers.
Here is my solution. It is a very simplistic command line interface
version, but it does the job :-)
Just give it the madlib filename as first argument.
The Code:
def ask_for(str)
puts "Give me #{str}:"
$stdin.gets.chomp
end
keys={}
puts "", ARGV[0].split(".")[0].gsub("_", " "),
IO.read(ARGV[0]).gsub(/\(\(([^)]+)\)\)/) {
if (t=$1) =~ /\A([^:]+):(.+)\z/
keys[$1]=ask_for($2)
else
keys[t] || ask_for(t)
end
}
=======================
And here is an even shorter version, that does the same:
keys=Hash.new { |h, k|
puts "Give me #{k.sub(/\A([^:]+):/, "")}:"
h[$1]=$stdin.gets.chomp
}
puts "", $*[0].split(".")[0].gsub("_", " "),
IO.read($*[0]).gsub(/\(\(([^)]+)\)\)/) { keys[$1] }
I output the result in either text or PDF if given the proper
command-line options. To use the PDF feature you will need to install
PDF::Writer, available from here: http://rubyforge.org/projects/ruby-pdf/
What a fun quiz this was, especially using the program to create some
Madlibs. I also decided to create an additional madlib file for
testing/having fun with. This is below following the code. The source
for this was
http://www.geocities.com/pezheadpaul/madlibs/prints/printprojects.htm
and all credit goes to the author of that site. This is a good test
because it has multiple paragraphs.
I hereby decree than an additional rule for this quiz would be that
every solution include a new madlib file :)
Ryan Leavengood
CODE (beware of wrapping):
def do_madlib(madlib_file, output_pdf, underline)
substitutions = {}
# Put paragraphs all on one line (there has got to be a better way!)
madlib =
IO.readlines(madlib_file).join('').split(/\n\n/).collect{|l|l.gsub(/\n/,'
')}.join("\n\n")
madlib_result = madlib.gsub(/\(\([^)]*\)\)/) do |match|
match.gsub!(/\(|\)/,'') # Bye bye parens
if substitutions[match]
substitution = substitutions[match]
else
if match =~ /(.*):(.*)/
key = $1
match = $2
end
print "Please enter #{match}: "
substitution = $stdin.gets.chomp
if key
substitutions[key] = substitution
end
end
if output_pdf and underline
"<u>#{substitution}</u>"
else
substitution
end
end
if output_pdf
filename = madlib_file+".pdf"
print "Outputting PDF file #{filename}..."
require 'pdf/ezwriter'
pdf = PDF::EZWriter.new
pdf.select_font("pdf/fonts/Helvetica")
pdf.ez_text(madlib_result, 14)
File.open(filename, File::RDWR|File::CREAT|File::TRUNC) do |file|
file.print(pdf.ez_output)
end
puts "done."
else
puts "\n Your MadLib:\n\n"
madlib_result.split("\n").each do |paragraph|
# Lazy man's wrapping
puts paragraph.gsub(/.{72}[^ ]* /){|l|"#{l}\n"}
end
end
end
if $0 == __FILE__
if ARGV.length < 1
puts "Usage: #$0 [-pdf] [-u] <madlib file>"
puts " -pdf Output a PDF file."
puts " -u Underline the replaced words in the PDF output
file."
exit(1)
end
output_pdf = false
underline = false
filename = nil
while ARGV.length > 0
arg = ARGV.shift
case arg
when '-pdf': output_pdf = true
when '-u': underline = true
else filename = arg
end
end
if filename
if test(?e, filename)
do_madlib(filename, output_pdf, underline)
else
puts "Provided madlib file does not exist!"
end
else
puts "No madlib filename provided!"
end
end
Science_Fair.madlib:
According to ((principal:a school principal's name)), the school science
fair this year was 'very educational.' At the same time, ((principal))
announced plans to quit the school system and become a ((a dangerous
job)). 'It sounds like a safer job,' the principal said.
Several ((an adjective)) projects were disqualified this year. The
experiment on 'Animal Magnetism' by ((a girl's name)) was canceled
before she could plug in her ((an animal)).
The project by ((a male bully's name)) on 'Gravity's Effect on First
Graders' was canceled when the custodians wouldn't let him borrow a ladder.
And the Nuclear Powered ((a noun)) built by ((name:a proper noun)) was
taken away by the police, who said ((name)) will be back in school 'any
day now.'
((your name)) won second prize with an experiment that asked, 'Can
((animals:a plural animal)) Learn Karate?' The answer was 'yes.' The
((animals)) tossed ((principal)) over a ((a noun)) and left the science
fair. Anyone who sees them should call the main office.
((a girl's name)) won first prize with her TNT ((veggies:a plural
vegetable)). By planting seeds in gunpowder and watering them with
nitroglycerin, ((veggies)) grew that explode when you drop them. 'What a
dynamite idea,' the principal joked ((an adverb)).
So far, nobody has figured out how the prize-winning ((veggies)) got
into the salad served to ((principal)) at lunchtime. Just to be safe,
though, the 'Vegetable Surprise' has been taken off tomorrow's lunch menu.
My implementation does two passes. The first one gets the questions and
changes the tags by a label (if it does not have a label it's created
with the name "anonymous<number>". After answering questions the second
pass just translates the tags by the answers. I had to add an array to
keeping the questions order as I was using a hash to store the
questions and answers. I had also to strip return characters, I know it
should be a more elegant solution. The problem was the tags with return
characters in it.
Here is the code
--8<------
class QAPair
attr_accessor :question, :answer
def initialize(q, a="empty")
@question=q
@answer=a
end
end
class MadLib
def initialize(text)
@original=text.tr("\r\n", " ") # take care of multiline
@anon_number=0
@qa=Hash.new # Questions and Answers
@qa_order=Array.new
process
end
def process
@processed=@original.gsub(/\(\((.*?)\)\)/) {|matched|
question=matched[2..-3] # strip parentheses
name="anonymous"+@anon_number.to_s
if res=question.match(/(.*?):(.*)/)
question=res[2]
name=res[1]
elsif @qa.include?(question)
name=question
else
@anon_number+=1
end
@qa[name]=QAPair.new(question)
@qa_order << name if !@qa_order.include? name
"(("+name+"))"
}
end
def make_questions
@qa_order.each {|name|
pair=@qa[name]
print "Give me a "+pair.question+": "
pair.answer=STDIN.gets.chop
}
end
def create_text
@processed.gsub(/\(\(.*?\)\)/) {|matched|
name=matched[2..-3]
@qa[name].answer
}
end
end
if ARGV[0]
begin
test=MadLib.new(File.read(ARGV[0]))
rescue
puts "File #{ARGV[0]} not found."
exit -1
end
else
puts "madlib file as parameter is needed"
exit -1
end
test.make_questions
puts test.create_text
------>8--
James Edward Gray II
#!/usr/local/bin/ruby
MADLIBS = "../public_html/madlibs"
REPLACE = /\(\(\s*((?:\w+\s*:\s*)?)(.+?)\s*\)\)/m
require "cgi"
require "erb"
query = CGI.new("html4")
files = Dir[File.join(MADLIBS, "*.madlib")].
map { |f| File.basename(f, ".madlib").tr("_", " ") }
title = nil
content = case query["mode"]
when "questions"
title = query["madlib"]
madlib = File.read( File.join( MADLIBS,
"#{query['madlib'].tr(' ',
'_')}.madlib" ) )
count = 0
seen = Hash.new(false)
query.form("post") do
query.hidden("mode", "display") +
query.hidden("madlib", query["madlib"]) +
query.dl do
madlib.scan(REPLACE).inject("") do |fields, (key, question)|
key = if key.length > 0
key[/\w+/]
else
next fields if seen[question]
(count += 1).to_s
end
seen[key] = true
fields += query.dt("style" => "font-weight: normal") do
"Give me #{query.b { question }}."
end
fields += query.dd { query.text_field(key) }
end
end +
query.submit("finish")
end
when "display"
title = query["madlib"]
madlib = File.read( File.join( MADLIBS,
"#{query['madlib'].tr(' ',
'_')}.madlib" ) )
count = 0
madlib.split(/\n(?:\s*\n)+/).inject("") do |result, para|
result += query.p do
para.gsub(REPLACE) do
if $1.length > 0
query[$1[/\w+/]]
elsif query.has_key?($2)
query[$2]
else
query[(count += 1).to_s]
end
end
end
end +
query.p { " " } * 7
else # choose
query.p { "Please choose a Madlib from the following list:" } +
query.form("get") do
query.hidden("mode", "questions") +
query.popup_menu("madlib", *files) + " " +
query.submit("choose")
end +
query.p { " " } * 10
end
include ERB::Util
page = ERB.new(DATA.read, nil, "%")
query.out { page.result(binding) }
__END__
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Ruby Quiz</title>
<link rel="stylesheet" type="text/css" href="../quiz.css" />
</head><body>
<div id="page">
<div id="header"><span class="ruby">Ruby</span>
<span class="quiz">Quiz</span></div>
<div id="content">
<span class="title"><%= title || "Ruby Quiz Madlibs"%></span>
<%= content %>
</div>
<div id="logo"><img src="../images/ruby_quiz_logo.jpg" alt=""
width="157" height="150" /></div>
<div id="links">
<span class="title">Madlibs</span>
<ol>
% files.each do |file|
<li><a href="madlib.cgi?mode=questions&madlib=<%= u file %>"><%=
file
%></a></li>
% end
</ol>
</div>
<div id="footer"> </div>
</div>
</body>
</html>
> I hereby decree than an additional rule for this quiz would be that
> every solution include a new madlib file :)
Awesome idea. Wish I had thought of that one, because I would have
used it!
I've added your Madlib to those served on rubyquiz.com.
Here's my run with it:
Science Fair
According to Mr. McFee, the school science fair this year was 'very
educational.' At the same time, Mr. McFee announced plans to quit the
school system and become a Plutonium Expert. 'It sounds like a safer
job,' the principal said.
Several fuzzy projects were disqualified this year. The experiment on
'Animal Magnetism' by Dana was canceled before she could plug in her
Shih Tzu.
The project by Ken on 'Gravity's Effect on First Graders' was canceled
when the custodians wouldn't let him borrow a ladder.
And the Nuclear Powered computer built by Coke Classic was taken away
by the police, who said Coke Classic will be back in school 'any day
now.'
James won second prize with an experiment that asked, 'Can fleas Learn
Karate?' The answer was 'yes.' The fleas tossed Mr. McFee over a
snowboard and left the science fair. Anyone who sees them should call
the main office.
Tina won first prize with her TNT tomatoes. By planting seeds in
gunpowder and watering them with nitroglycerin, tomatoes grew that
explode when you drop them. 'What a dynamite idea,' the principal joked
hesitantly.
So far, nobody has figured out how the prize-winning tomatoes got into
the salad served to Mr. McFee at lunchtime. Just to be safe, though,
the 'Vegetable Surprise' has been taken off tomorrow's lunch menu.
----
That was great! Thanks.
James Edward Gray II
class Madlib
# Given the madlib text as a string, builds a list of questions and
# a map of questions to "blanks"
def initialize(txt)
@questions = []
@story_parts = []
@answer_list = []
@answers = []
stored = {}
txt.split(/\((\([^)]*\))\)/).each do |item|
if item[0] == ?(
item = item[1..-2].gsub("\n", ' ')
if item.index(':')
name, question = item.split(':')
stored[name] = @questions.length
@questions << question
else
name, question = item, item
end
@answer_list << (stored[name] || @questions.length)
@questions << question unless stored[name]
else
@story_parts << item
end
end
end
# Calls a block with the index and text of each question
def list_questions(&block)
@questions.each_index do |i|
yield(i, @questions[i])
end
end
# Stores the answer for a given question index
def answer_question(i, answer)
@answers[i] = answer
end
# Returns a string with the answers filled-in to their respective blanks
def show_result
real_answers = @answer_list.collect {|i| @answers[i]}
@story_parts.zip(real_answers).flatten.compact.join
end
end
# Example that reads the madlib text from a file specified on the
# command line
madlib = Madlib.new(IO.read(ARGV.shift))
answers = []
madlib.list_questions do |i, q|
print "Give me " + q + ": "
answers[i] = gets.strip
end
answers.each_index {|i| madlib.answer_question(i, answers[i]) }
puts madlib.show_result
--Sean McCardell
+= 2 #i cheat :-)
#
#I've added your Madlib to those served on rubyquiz.com.
#
#Here's my run with it:
#
#Science Fair
#
#According to Mr. McFee, the school science fair this year was 'very
#educational.' At the same time, Mr. McFee announced plans to quit the
#school system and become a Plutonium Expert. 'It sounds like a safer
#job,' the principal said.
#
#Several fuzzy projects were disqualified this year. The experiment on
#'Animal Magnetism' by Dana was canceled before she could plug in her
#Shih Tzu.
#
#The project by Ken on 'Gravity's Effect on First Graders' was canceled
#when the custodians wouldn't let him borrow a ladder.
#
#And the Nuclear Powered computer built by Coke Classic was taken away
#by the police, who said Coke Classic will be back in school 'any day
#now.'
#
#James won second prize with an experiment that asked, 'Can fleas Learn
#Karate?' The answer was 'yes.' The fleas tossed Mr. McFee over a
#snowboard and left the science fair. Anyone who sees them should call
#the main office.
#
#Tina won first prize with her TNT tomatoes. By planting seeds in
#gunpowder and watering them with nitroglycerin, tomatoes grew that
#explode when you drop them. 'What a dynamite idea,' the
#principal joked
#hesitantly.
#
#So far, nobody has figured out how the prize-winning tomatoes got into
#the salad served to Mr. McFee at lunchtime. Just to be safe, though,
#the 'Vegetable Surprise' has been taken off tomorrow's lunch menu.
LOL!
#
#----
#
#That was great! Thanks.
#
#James Edward Gray II
#
#
Dan
Best Regards,
Steve Li.
--------------------------------------------------------------------------------------------------------------------
Madlibs.rb
class Story
attr_accessor :placeholders
def initialize(base)
@placeholders = []
story_parts = []
match = Placeholder.getPattern().match(base)
reuseMap = {}
while(match != nil)
story_parts << match.pre_match
placeholderString = match[1]
placeholder = Placeholder.new(placeholderString, story_parts.size)
# if name is reused
if reuseMap[placeholder.name] == nil
@placeholders << placeholder
# if the name is reusable, add it to the reuse table
if placeholder.reusable()
reuseMap[placeholder.name] = placeholder
end
# replace the placeholder with the system generated position
string
story_parts << get_position_string(story_parts.size.to_s)
else
# for reuse placeholder,
# replace the placeholder with the system generated position
string for the referenced placeholder
story_parts <<
get_position_string(reuseMap[placeholder.name].position.to_s)
end
remaind = match.post_match
match = Placeholder.getPattern().match(match.post_match)
if (match == nil)
story_parts << remaind
end
end
@base = story_parts.join("")
end
def to_s
result = @base
@placeholders.each do |placeholder|
result.gsub!(Regexp.new(get_position_string(placeholder.position.to_s)),
placeholder.value)
end
return result
end
def get_position_string(position)
"%%" + position.to_s + "%%"
end
end
class Placeholder
attr_accessor :name, :display_name, :position, :value
def initialize(placeholderString, position)
@value = ""
@position = position
if placeholderString.include?(":")
@name = placeholderString.split(":")[0]
@display_name = placeholderString.split(":")[1]
else
@name = placeholderString
@display_name = placeholderString
end
end
def getTemplate()
Regexp.new(
"\\(\\(\\s*(#{name}|#{name}\\s*:\\s*#{display_name})\\s*\\)\\)")
end
def Placeholder.getPattern()
/\(\(([^)]*)\)\)/
end
def getValueQuestion()
"Give me #{display_name}: "
end
def reusable()
name != display_name
end
end
if $0 == __FILE__
# read story from standard input
story_string = ""
ARGF.each_line do |line|
story_string += line
end
# create story
story = Story.new(story_string)
# request uesr to enter the corresponding value for each placeholder
print "There are #{story.placeholders.size} placeholders.\n"
story.placeholders.each do |placeholder|
print placeholder.getValueQuestion()
placeholder.value = gets().chop()
end
# display the story
print story.to_s, "\n"
end
--------------------------------------------------------------------------------------------------------------------
UnitTest;
MadlibsTest.rb
require 'runit/testcase'
require 'Madlibs'
class TestMadlibs < RUNIT::TestCase
def testStoryTemplate()
# parse simple story
# e.g. "Our favorite language is ((a gemstone))."
template = "Our favorite language is ((a gemstone))."
story = Story.new(template)
# should return a Story with a symbol name='a gemstome' and alias=nil
assert_equals(1, story.placeholders.size)
assert_not_nil(story.placeholders[0])
assert_equals("a gemstone", story.placeholders[0].display_name)
end
def testStoryTemplateWithAlias()
# parse story with name alias
# e.g. "Our favorite language is ((gem:a gemstone)). We think ((gem))
is
# better than ((a gemstone))."
template = "Our favorite language is ((gem:a gemstone)). "
template += "We think ((gem)) is better then ((a gemstone))."
story = Story.new(template)
# should return a Story with 2 symbole
# Symbol 1: name = 'gem' alias='a gemstome'
# Symbol 2: name = 'a gemstome'
assert_equals(2, story.placeholders.size)
assert_not_nil(story.placeholders[0])
assert_equals("gem", story.placeholders[0].name)
assert_equals("a gemstone", story.placeholders[0].display_name)
assert_not_nil(story.placeholders[1])
assert_equals("a gemstone", story.placeholders[1].name)
assert_equals("a gemstone", story.placeholders[1].display_name)
end
def testStoryGeneration()
# give: "Our favorite language is ((a gemstone))."
# input: gemstone = Ruby
# result: Our favorite language is Ruby."
String template = "Our favorite language is ((a gemstone))."
story = Story.new(template)
story.placeholders[0].value = "Ruby"
assert_equals("Our favorite language is Ruby.", story.to_s())
end
def testStoryGenerationWithAlias()
# given: "Our favorite language is ((gem:a gemstone)).
# We think ((gem)) is better than ((a gemstone))."
# input: a gemstone = Ruby, a genstone = Emerald
# given: "Our favorite language is Ruby.
# We think Ruby is better than Emerald."
template = "Our favorite language is ((gem:a gemstone)). "
template += "We think ((gem)) is better then ((a gemstone))."
story = Story.new(template)
story.placeholders[0].value = "Ruby"
story.placeholders[1].value = "Emerald"
assert_equals("Our favorite language is Ruby. We think Ruby is better
then Emerald.", story.to_s())
end
end
if $0 == __FILE__
require 'runit/cui/testrunner'
RUNIT::CUI::TestRunner.run(TestMadlibs.suite)
end
template="Our ((var1:a)) favorite ((var2:bb)) language ((var1)) is ((a
gemstone))."
vars={}
rep = template.gsub(/\(\((.*?)\)\)/) { |match|
toks = match.split(":")
if toks.length == 1
varName = nil
varPrompt=toks[0][2..(toks[0].length-3)]
else
varName=toks[0][2..(toks[0].length-1)]
varPrompt=toks[1][0..(toks[1].length-3)]
end
# Alternative but just a many lines
# ndx = match.index(":")
# if ndx == nil
# varName = nil
# varPrompt = match[2..(match.length-3)]
# else
# varName = match[2..(ndx-1)]
# varPrompt = match[(ndx+1)..(match.length-3)]
# end
if varName == nil && vars[varPrompt] != nil
userInp=vars[varPrompt]
else
print "#{varPrompt} "
userInp=gets.chomp
vars[varName]=userInp
end
userInp
}
puts rep
> UnitTest;
> MadlibsTest.rb
> require 'runit/testcase'
You might want to take a look at the standard "test/unit" library. I
believe "runit" might be the old system of Ruby unit testing.
Obviously, it works fine, but you might find "test/unit" a little more
refined. Just FYI.
James Edward Gray II
Cheers,
Dave
#
# This function is the solution to the quiz. It takes a madlib string, fills
# the placeholders using input() (defined below) and returns the result.
#
def madlib(string)
names = {}
string.gsub /\(\(.*?\)\)/ do |token|
a, b = *token[2...-2].split(':')
if names.has_key? a
names[a]
elsif b
names[a] = input(b)
else
input(a)
end
end
end
#
# Ask the user for thing, and return the user's response.
#
def input(thing)
print "Enter #{thing}: "
gets.chomp
end
#
# Here's another interface - you can run this ruby script from the command
line
# and pass it a madlib filename as an argument, or pass the text on STDIN.
#
if $0 == __FILE__
puts madlib(ARGF.read)
end
James Edward Gray II
#!/usr/local/bin/ruby -w
# use Ruby's standard template engine
require "erb"
# storage for keyed question reuse
$answers = Hash.new
# asks a madlib question and returns an answer
def q_to_a( question )
question.gsub!(/\s+/, " ") # noramlize spacing
if $answers.include? question # keyed question
$answers[question]
else # new question
key = if question.sub!(/^\s*(.+?)\s*:\s*/, "") then $1 else nil end
print "Give me #{question}: "
answer = $stdin.gets.chomp
$answers[key] = answer unless key.nil?
answer
end
end
# usage
unless ARGV.size == 1 and test(?e, ARGV[0])
puts "Usage: #{File.basename(__FILE__)} MADLIB_FILE"
exit
end
# load Madlib, with title
madlib = "\n#{File.basename(ARGV[0], '.madlib').tr('_', ' ')}\n\n" +
File.read(ARGV[0])
# convert ((...)) to <%= q_to_a('...') %>
madlib.gsub!(/\(\(\s*(.+?)\s*\)\)/, "<%= q_to_a('\\1') %>")
# run template
ERB.new(madlib).run
Looking at the problem that way got me to thinking, doesn't Ruby ship with a
templating engine? Yes, it does. We could use that to build our solution:
#!/usr/local/bin/ruby -w
The main principal here is to convert ((...)) to <%= ... %>, so we can use
Ruby's own template engine. Of course, <%= a noun %> isn't going to be valid
Ruby code, so a helper method is needed. That's where q_to_a() comes in. It
takes the Madlib replacements as an argument and returns the user's answer. To
use that we actually need to convert ((...)) to <%= q_to_a('...') %>. From
there, ERb does the rest of the work for us.
Now for simple Madlibs, you don't really need something as robust as ERb. It's
easy to roll your own solution and most people did just that. Let's examine
Sean E. McCardell's code:
class Madlib
stored = {}
The Madlib object handles the heavy lifting here. initialize() really does a
lot of the work. It breaks the story down into an internal format which is
primarily a list of @story_parts, @questions, and @answers. Since the answer to
a question may be used in more than one place, an @answer_list is also built as
a mapping between the actual answers and all their replacements.
You can see this chunking process in the bottom half of initialize(). It
basically split()s the story around ((...)) replacement sections. The split()
Regexp uses capturing parentheses to ensure that the replacements themselves are
returned, in addition to the story parts.
Inside the iterator, the outer if branches to handle either questions (starting
with a "(" character) or story parts. Each item is added to the correct list.
Questions are also examined for the extra label and the stored Hash resolves
these repeats as they occur.
The next method, list_questions(), provides iteration over the list of
questions. (Note that the &block parameter isn't used in the method and could
be removed.) The block is yielded an index and the current question. The index
can be used to feed an answer to the sister method, answer_question(), which
just stores answers.
The final method of the class, show_result(), uses the @answer_list map to
construct a list of real_answers. That list is zip()ed with @story_parts to
produce the final output.
The final chunk of code just puts the class to work. An object is constructed
from the file passed as a command-line argument. Next, the code walks the
questions, asking each one in turn and collecting answers. Those answers are
passed to answer_question(), and the final results are printed. I believe you
could do away with the extra Array in this section and simplify a little:
madlib = Madlib.new(IO.read(ARGV.shift))
madlib.list_questions do |i, q|
print "Give me " + q + ": "
madlib.answer_question(i, gets.strip)
end
puts madlib.show_result
Well, there's a look at a couple of the solutions. Other solutions involved
CGI, PDF output (very cool!), and even a little golf action. Don't miss looking
over them.
My thanks to all the spongy Madlibers who took the time to fill out my
fire-hose.
Tomorrow we'll use the quiz to start a new library for Ruby that will hopefully
ease the ins and outs of common coding...
Version 2 is a class, and handles random input, which means you can easily
use it to power your new graphical interface.
Cheers,
Dave
class MadLib
# You can enumerate the placeholders... not too useful, though.
include Enumerable
def initialize(string)
# the original madlib string is kept
@s = string
# a placeholder for each user entry required
# maps a tag name or number onto a [question, answer] array.
@placeholders = {}
# an index for each set of brackets in @s
# the key into @placeholders for each respective set of brackets
@index = []
# the initial scan gets tag names and assigns numbers for tags with no
name
i = '0'
@s.scan /\(\(([^:)]*):?(.*?)\)\)/ do |a, b|
if @placeholders.has_key? a
@index << a
elsif b.size > 0
@index << a
@placeholders.update a => [b, nil]
else
@index << i
@placeholders.update i => [a, nil]
i = i.succ
end
end
end
def []=(index, answer)
@placeholders[index][1] = answer
@placeholders[index]
end
def [](index)
@placeholders[index]
end
def outstanding
@index.select {|i| @placeholders[i][1].nil? }.uniq
end
def outstanding_questions
outstanding.map {|i| @placeholders[i][0] }
end
def each_outstanding
outstanding.each do |i|
yield @placeholders[i]
end
end
def all
@index.uniq
end
def all_questions
all.map {|i| @placeholders[i][0] }
end
def each
all.each do |i|
yield @placeholders[i]
end
end
def done?
@placeholders.all? {|k, v| v[1] }
end
def collect!
all.each do |i|
self[i] = yield @placeholders[i]
end
self
end
def to_s
if done?
story
else
""
end
end
private
def story
i = 0
@s.gsub /\(\(.*?\)\)/ do |token|
i = i.succ
@placeholders[@index[i - 1]][1]
end
end
end
if $0 == __FILE__
m = MadLib.new(ARGF.read)
m.collect! do |question, answer|
answer or begin
print "#{question}? "
gets.chomp
end
end
puts m
end