[SOLUTION] Madlibs (#28)

R

Ryan Leavengood

Here is my solution to the current quiz. While implementing this I
learned more about the power of gsub, which I don't think I've ever used
so much in one program as small as this one ;)

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

Javier Fontan

Hello,

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
@[email protected](/\(\((.*?)\)\)/) {|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--
 
J

James Edward Gray II

Here's the code running on rubyquiz.com.

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 { "&nbsp;" } * 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 { "&nbsp;" } * 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">&nbsp;</div>
</div>
</body>
</html>
 
J

James Edward Gray II

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

Sean E. McCardell

Here is my solution for the Madlibs Ruby Quiz. I tried to keep the
process of asking the questions separate from assembling the completed
madlib with the answers.

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)
end
end

# Stores the answer for a given question index
def answer_question(i, answer)
@answers = answer
end

# Returns a string with the answers filled-in to their respective blanks
def show_result
real_answers = @answer_list.collect {|i| @answers}
@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 = gets.strip
end
answers.each_index {|i| madlib.answer_question(i, answers) }
puts madlib.show_result

--Sean McCardell
 
L

Lyndon Samson

Here's mine, its probably a bit javaish compared to most, but I'm
getting there :)

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
 
D

Dave Burt

Version 1 is a function, and requires serial input. It looks a lot like
Dominik's solution, though I didn't see his before completing this. I think
mine is prettier, though Dominik seems to have terseness down pat :)

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
 
J

James Edward Gray II

Here's a different approach that came to me while writing the summary.

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
 
D

Dave Burt

I wrote earlier:
# Version 1 is a function, and requires serial input.

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[1].nil? }.uniq
end
def outstanding_questions
outstanding.map {|i| @placeholders[0] }
end
def each_outstanding
outstanding.each do |i|
yield @placeholders
end
end

def all
@index.uniq
end
def all_questions
all.map {|i| @placeholders[0] }
end
def each
all.each do |i|
yield @placeholders
end
end

def done?
@placeholders.all? {|k, v| v[1] }
end

def collect!
all.each do |i|
self = yield @placeholders
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
 

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

Forum statistics

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

Latest Threads

Top