David Sletten
4/15/2006 10:10:00 PM
JB wrote:
> Hi gang,
>
> I got pretty frustrated with trying to learn to program, that I just gave it
> up for a while. Then, I finally got the revised edition book of 'Learn to
> Program' by Chris Pine, instead of trying to work off the website with the
> old version of it.
> So, now I'm back at the '99 bottles of beer' thing, and this is what I came
> up with:
>
> beer = 100
> while beer >= 2
> beer = (beer - 1)
> puts beer.to_s + ' bottles of beer on the wall, ' + beer.to_s + ' bottles of
> beeer,'
> puts 'take one down, pass it around, ' + (beer - 1).to_s + ' bottles of beer
> on the wall!'
> if beer == 1
> puts beer.to_s + ' bottle of beer on the wall, ' + beer.to_s + ' bottle
> of beeer,'
> puts 'take one down, pass it around, ' + (beer - 1).to_s + ' bottles of
> beer on the wall!'
> elsif beer != 1 && (beer - 1) == 1
> puts beer.to_s + ' bottles of beer on the wall, ' + beer.to_s + '
> bottles of beeer,'
> puts 'take one down, pass it around, ' + (beer - 1).to_s + ' bottle of
> beer on the wall!'
> end
> end
>
> It's messy in this newsreader, but in an IDE, it looks fine.
> The problem I'm having is, it seems 'bloated' and, at the end, it prints
> this:
>
> 2 bottles of beer on the wall, 2 bottles of beeer,
> take one down, pass it around, 1 bottles of beer on the wall!
> 2 bottles of beer on the wall, 2 bottles of beeer,
> take one down, pass it around, 1 bottle of beer on the wall!
> 1 bottles of beer on the wall, 1 bottles of beeer,
> take one down, pass it around, 0 bottles of beer on the wall!
> 1 bottle of beer on the wall, 1 bottle of beeer,
> take one down, pass it around, 0 bottles of beer on the wall!
>
> Why does it do the '2 bottles' once correctly and once incorrectly, and also
> the '1 bottles' it does it the same way?
>
> Thanks again for putting up with this new learner to programming.
>
> JB
One very important idea in successful programming is the use of
abstraction. We see a number of similar, though not identical,
operations and recognize that there is an underlying sameness if we
ignore the specific details.
Let's say we find ourselves checking repeatedly whether certain numbers
are even. We can do this by checking the remainder after dividing by 2.
If a number is odd we end up with 1. If it's even we get 0. So our
program looks like this:
if x % 2 == 0 then ...
if y % 2 == 0 then ...
if a % 2 != 0 then ...
The salient aspect of all of these tests is to check the "eveness" of a
number. So we use abstraction to define this test in a general case.
Given any number x, x % 2 == 0 means x is even:
def even?(x)
if x % 2 == 0 then true
else false
end
end
Of course, this can be simplified:
def even?(x)
return x % 2 == 0 # return can be implicit here
end
And simplified to the extreme:
def even?(x)
(x % 2).zero?
end
Now our intent in the rest of our program is clearer:
if even?(x) then ...
if even?(y) then ...
if !even?(a) then ...
In your case, you have a situation where you need to repeatedly
determine the correct pluralization of the word "bottle" based on a
bottle count. It will greatly simplify your program if you separate out
this oft used pattern:
def pluralize(string, count)
if count == 1 then string
else string + "s"
end
end
Now we get:
pluralize("bottle", 2) => "bottles"
pluralize("bottle", 1) => "bottle"
pluralize("bottle", 0) => "bottles"
This simple function will not handle all of the complexity of English
spelling, but we can extend it easily:
def pluralize(base, count, suffix="s", suffix1="")
if count == 1 then base + suffix1
else base + suffix
end
end
Now we get:
pluralize("bottle", 0) => "bottles" # Easy case is still easy
pluralize("bottle", 1) => "bottle"
pluralize("pupp", 1, "ies", "y") => "puppy"
pluralize("pupp", 2, "ies", "y") => "puppies"
pluralize("child", 1, "ren") => "child"
pluralize("child", 2, "ren") => "children"
pluralize("ind", 1, "ices", "ex") => "index" # Complex cases now possible
pluralize("ind", 2, "ices", "ex") => "indices"
pluralize("medi", 1, "a", "um") => "medium"
pluralize("medi", 2, "a", "um") => "media"
This function probably uses some Ruby features which you haven't studied
yet. It's also probably not complete for all possible plurals, but it
gets us pretty far.
Now we can streamline your program:
100.downto(1) do |i|
puts("#{i} #{pluralize("bottle", i)} of beer on the wall.")
puts("#{i} #{pluralize("bottle", i)} of beer...")
puts("You take one down and pass it around--")
puts("#{i-1} #{pluralize("bottle", i-1)} of beer on the wall.")
puts
end
Here we take advantage of an abstraction built into Ruby--the common
pattern of counting down successive integers from a starting number to
an ending number. Then we utilize our own abstraction to handle the
logic of pluralizing. I hope this helps you see the effectiveness of
using abstractions to your advantage. Look for common patterns that can
be generalized when you work on your programs.
Aloha,
David Sletten