Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine

An Elegance Question

by Simplicus (Monk)
on Apr 13, 2000 at 21:55 UTC ( #7496=perlquestion: print w/replies, xml ) Need Help??

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

Replies are listed 'Best First'.
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;
      This is truly awe inspiring. I need to go be alone for a while. I am humbled, and my coffee cup is empty. Simplicus.
      I just tried this, and it works (of course) . . .what impresses me about this solution is not only its brevity, but its clarity. Simplicus.
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;
      Thanks. I'm new to Perl, and appreciate any help I can get. I knew someone here would come through. S-
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.
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:)
      Very cool. Thank you! S-
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" .


Re: An Elegance Question
by turnstep (Parson) on Apr 13, 2000 at 22:40 UTC

    How about this:

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

      Wow! A plethora of elegant epistles! Thanks! S-
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.

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

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://7496]
Approved by root
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (6)
As of 2023-03-23 09:01 GMT
Find Nodes?
    Voting Booth?
    Which type of climate do you prefer to live in?

    Results (60 votes). Check out past polls.