Simplicus has asked for the wisdom of the Perl Monks concerning the following question:
I have a short script that implements "Mad Libs." The idea is to read in a file containing noun1, noun2 type tags, and replace the tags with particles supplied by the user at run time. here is the script:
#!/usr/bin/perl -w
@story = <>; #filename supplied in ARGV
print "give me a noun: "; chomp($noun1 = <STDIN>);
print "give me a noun: "; chomp($noun2 = <STDIN>);
print "give me a noun: "; chomp($noun3 = <STDIN>);
print "give me a verb: "; chomp($verb1 = <STDIN>);
print "give me a verb: "; chomp($verb2 = <STDIN>);
print "give me a verb: "; chomp($verb3 = <STDIN>);
print "give me an adjective: "; chomp($adj1 = <STDIN>);
print "give me an adjective: "; chomp($adj2 = <STDIN>);
print "give me an adjective: "; chomp($adj3 = <STDIN>);
foreach (@story) {
$_ =~ s/\[noun1\]/$noun1/g;
$_ =~ s/\[noun2\]/$noun2/g;
$_ =~ s/\[noun3\]/$noun3/g;
$_ =~ s/\[verb1\]/$verb1/g;
$_ =~ s/\[verb2\]/$verb2/g;
$_ =~ s/\[verb3\]/$verb3/g;
$_ =~ s/\[adj1\]/$adj1/g;
$_ =~ s/\[adj2\]/$adj2/g;
$_ =~ s/\[adj3\]/$adj3/g;
print $_;
}
As you can see, the script reads in a story from a file, and replaces up to three instances of nouns, verbs, and adjectives. The problem is that the method is grossly inelegant. I know there must be a less brutish solution, so I turn to you for guidance. Thanks
Re: An Elegance Question
by plaid (Chaplain) on Apr 13, 2000 at 23:02 UTC
|
Another interesting way. This will take anything in [],
and prompt for a word of that type. This eliminates the
need to hard-code adjectives, adverbs, nouns, etc.
Your file can look like
The [noun] [past-tense verb] over to [proper noun]
and soon [future-tense verb] to the [place].
This allows you more flexibility in what you want them to
input
!/usr/bin/perl -w
use strict;
my $story;
{
local $/ = undef; #Get everything
$story = <>;
}
while($story =~ /\[(.*?)\]/g) { #Find anything in []
print "Give me a $1: ";
my $val = <STDIN>; #Get a value for it
chomp $val;
$story =~ s/\[$1\]/$val/; #And sub it in
}
print $story;
| [reply] [d/l] [select] |
|
This is truly awe inspiring. I need to go be alone for a while. I am humbled, and my coffee cup is empty.
Simplicus.
| [reply] |
|
I just tried this, and it works (of course) . . .what impresses me about this solution is not only its brevity, but its clarity. Simplicus.
| [reply] |
Re: An Elegance Question
by btrott (Parson) on Apr 13, 2000 at 22:03 UTC
|
Sure, use Text::Template. You'll need to slightly change
the format of your madlib. Instead of something like
the [noun1] [verb2]
you need to use
the [$noun1] [$verb2]
But that's not a big change. So you may need to change
the script around a bit, but it should work pretty much
as is.
use Text::Template;
# read in the story
my @story = <>;
my $hash = { };
# these are the word types that you're
# going to prompt for and include in your madlib;
# the key is what you call the type in the madlib ("adj")
# and the value is what you want to prompt for
my %types = (
noun => 'noun',
verb => 'verb',
adj => 'adjective'
);
# read in the values from STDIN;
# you may want to change this around.
# 3 means read 3 words of each type (3 nouns, 3 verbs, etc.)
for my $type (keys %types) {
for my $i (1..3) {
print "give me a $types{$type}: ";
chomp($hash->{$type . $i} = <STDIN>);
}
}
# set up a new template object:
# get template from array @story where template
# variables are in [] (DELIMITERS)
my $t = new Text::Template(TYPE => 'ARRAY', SOURCE => [ @story ],
DELIMITERS => ['[', ']']);
# fill in the template with the values you read into $hash;
# so in the template, [$noun1] => $hash->{'noun1'}
my $story = $t->fill_in(HASH => $hash);
# then just print it out
print $story;
| [reply] [d/l] [select] |
|
Thanks. I'm new to Perl, and appreciate any help I can get. I knew someone here would come through.
S-
| [reply] |
Re: An Elegance Question
by chromatic (Archbishop) on Apr 13, 2000 at 23:12 UTC
|
On the ridiculous end of the scale, why not a hash of arrays? If you're going to use your WORDS in order:
my %words = ( NOUN => [], VERB => [], ADJ => []);
# use stephen's part_of_speech sub
push @{ $words{$part_of_speech} }, $word;
Then, when you do your substitution:
$story =~ s/[(NOUN|VERB|ADJ)]/shift @{ $words{$1} }/eg;
I wouldn't do it this way, but it's food for thought. | [reply] [d/l] [select] |
Re: An Elegance Question
by plaid (Chaplain) on Apr 13, 2000 at 22:30 UTC
|
Instead of using variables such as $noun1, $noun2, etc.,
you might want to consider encapsulating the values in a
hash ($hash{noun1}, $hash{noun2}, etc.) when reading them
in. This would first of all keep your namespace cleaner,
and secondly, would allow the foreach loop to be rewritten
as
foreach (@story) {
s/\[(.*?)\]/$hash{$1}/g;
print;
}
or, for more fun
map { s/\[(.*?)\]/$hash{$1}/g; print } (@story);
The same effect could be achieved with the variable names
you're reading into now, but would require the use of
symbolic references, the use of which would probably get
you dragged out into the street and shot here:) | [reply] [d/l] [select] |
|
| [reply] |
Re: An Elegance Question
by stephen (Priest) on Apr 13, 2000 at 22:35 UTC
|
First of all, you'll want to replace that 'give me a' with a subroutine, since you call it X times.
Like so:
sub give_me_a
{
my ($part_of_speech) = @_;
print STDOUT "Give me a $part_of_speech: ";
my $word = <STDIN>;
chomp($word);
$word;
}
More importantly, there're a number of ways you can make the
replace more efficient. First, there're a billion text templating
modules on CPAN. Text::Template is the most commonly used; that would
allow you to do variable substitution directly into the templates,
like so:
The quick $adj1 fox ${verb1}ed over the lazy $noun1.
Better would be to put all of your nouns and adjectives
into arrays or hash tables. Something like this would do:
my %word_list = ();
foreach my $part_of_speech ( 'noun', 'verb', 'adj' ) {
for (my $word_idx = 1; $word_idx <= 3; $word_idx++) {
$word_list{$part_of_speech}{$word_idx} = give_me_a($part_of_spe
+ech);
}
}
foreach my $line (@story) {
$line =~ s/\[(noun|verb|adj)([1-3])\]/$word_list{$1}{$2}/g;
print STDOUT $line;
}
I'm not sure at all if that's more elegant, but it's shorter.
The completed script is here: "Mad Libs Rewrite" .
stephen
| [reply] [d/l] [select] |
Re: An Elegance Question
by turnstep (Parson) on Apr 13, 2000 at 22:40 UTC
|
%madname = ( ## Uppercase easy to see in the story file
"NOUN" => "a noun",
"VERB" => "a verb",
"ADJ" => "an adjective");
$storyfile = shift or die "I need a filename!\n";
open(STORY, "$storyfile") || die "Could not open $storyfile: $!\n";
while (<STORY>) {
push(@lines, $_);
## Count total inserted words
while (m/\[(NOUN|VERB|ADJ)\]/g) {
$madlib{$1}++;
}
}
close(STORY);
for $x (sort keys %madlib) {
for $y (1..$madlib{$x}) {
print "Please give me $madname{$x}: ";
chomp($answer{$x.$y} = <STDIN>);
}
$madlib{$x}=1; ## reset this
}
## Tricky part...
for (@lines) {
s/\[(NOUN|VERB|ADJ)\]/$answer{$1.$madlib{$1}++}/ge;
print;
}
exit;
As a bonus, you can have more than one "madlib"
on a line, like this:
She dropped the [ADJ] [NOUN] and ran into the [ADJ] house.
You also don't have to worry about numbering them,
or keeping a total count - the program does that
for you.
| [reply] [d/l] [select] |
|
Wow! A plethora of elegant epistles! Thanks!
S-
| [reply] |
Re: An Elegance Question
by jbert (Priest) on Apr 14, 2000 at 18:55 UTC
|
Your spirit is pure but your mind untutored.
You feel the ugliness in similarly named variables and your inner self desires to know beauty.
Consider the petals on a lotus blossum.
This is many things in one, alike but different.
Fulfillment for this ache lies in the use of an array, Grasshoppper.
| [reply] |
Re: An Elegance Question
by Simplicus (Monk) on Apr 13, 2000 at 23:05 UTC
|
thanks to all of you, I've learned more in the last half hour than in the past half year.
S.-- | [reply] |
|
|