99 Bottles of Beer (Perl)

From LiteratePrograms

Jump to: navigation, search
Other implementations: Alice ML | Erlang | Haskell | Inform 7 | Java | OCaml | Perl | Python | Ruby

The following is a perl program to write the lyrics to the song "99 bottles of beer"

The song's first verse goes:

   99 bottles of beer on the wall, 99 bottles of beer.
   Take one down, pass it around, 98 bottles of beer on the wall.

Each verse the decrements the number of bottles by one and repeats all the way to the end, or until the singing children are informed that they will be walking home if they do not stop.

From this we can see that a variable will be required to store the number of bottles left.

<<bottles of beer>>=
my $bottles_of_beer = 99;

The verse is then constructed by inserting the $bottles_of_beer variable into the correct points in the verse and printing the result. The $bottles_of_beer variable has to be decremented after the first line to display the correct value in the second line.

print $bottles_of_beer, " bottles of beer on the wall, ", $bottles_of_beer, ".\n";
$bottles_of_beer = $bottles_of_beer - 1;
print "Take one down, pass it around, ", $bottles_of_beer, " bottles of beer on the wall.\n"

This is then wrapped up in a loop to print almost all of the required verses.

<<verse loop>>=
while ($bottles_of_beer > 2) {

The last three verses of the song are not handled in the loop because they are special cases. Once a single bottle of beer is referred to, we have to use the singular "bottle" instead of "bottles".

<<two bottles left>>=
if ($bottles_of_beer == 2) {
  print "2 bottles of beer on the wall, 2 bottles of beer\n";
  $bottles_of_beer = $bottles_of_beer - 1;
  print "take one down, pass it around, 1 bottle of beer on the wall\n";

In this example the if statement is not strictly required because by the time the thread of execution reaches this part of the code $bottles_of_beer will be two. It is included for completeness in case the original value of $bottles_of_beer is set to one or less.

<<one bottle left>>=
if ($bottles_of_beer == 1) {
  print "1 bottle of beer on the wall, 1 bottle of beer\n";
  $bottles_of_beer = $bottles_of_beer - 1;
  print "take one down, pass it around, no bottles of beer on the wall.\n";

The final verse is quite different to the rest.

<<no bottles left>>=
if ($bottles_of_beer == 0) {
  print "No bottles of beer on the wall, no bottles of beer.\n";
  print "Go to the store, buy some more, 99 bottles of beer on the wall.\n";

Restting the $bottles_of_beer variable and introducing the extra code that results in an infinte loop is left to the reader. To conclude this article we simply need to dust some standard perl around our existing definitions to produce a working program.

#!/usr/bin/perl -w
use strict;
bottles of beer
verse loop
two bottles left
one bottle left
no bottles left


First we set how many bottles to start with.

<<how many bottles of beer>>=
  my $max = 99; 

Next Lets create a function that will correctly pluralize the word bottles based on what round we are on.

<<function to pluralize bottles>>=
  sub container { (shift == 1) ? 'bottle' : 'bottles' }

Along the same lines it would be handy to have a function that will allow us to return 'no' for the 0 round.

<<function to return no for zero>>=
  sub count { ($_[0]) ? $_[0] : 'no' }

With these steps out of the way we can now assemble the first part of our repeating verse. This is accomplished by leveraging sprintf's placement operator to better manage duplication, while also allowing us to insert the correct values in to the verse. We are also using ucfirst so that when we get to the 0/no verse that the first letter is capatilized.

<<first half of verse>>=
  printf qq{%1\$s %3\$s of beer on the wall, %2\$s %3\$s of beer.\n}, 

For the second half of the verse there are a few things going on, we need to check to see if this is the 0/no verse as it is completely diffrent. The rest of the verses just need to decrement the round by one and print. The round does not need to be stored as the next time thru the verse loop, the round will already have been decremented if it needs to be.

<<second half of verse>>=
   if ( $round > 0 ) {
      printf qq{  Take one down, pass it around, %s %s of beer on the wall.\n}, 
        container($round-1) ;
   else {
      printf qq{  Go to the store, buy some more, %d bottles of beer on the wall.\n}, 
        $max ;

To tie the two verse parts together we will wrap them up in to one function.

<<function to print out verse>>=
  sub verse { 
     my $round = shift; #what round are we on?
     first half of verse
     second half of verse

Finally to wrap it all together we create a for loop that will itterate along a range. Though note, in perl the range opperator only runs positively, thus for what we need we reverse the list. Then for every instance in that list we pass that to the verse function as the round. And now we have our song.

<<loop over all the rounds>>=
  for (reverse(0..$max)) { verse($_)} 

Final code would look like:

  use strict;
  use warnings;
how many bottles of beer
function to pluralize bottles
function to return no for zero
function to print out verse
loop over all the rounds