Chad Perrin: SOB

28 March 2008

10 steps to 99 bottles: simple program dev 101

Filed under: Geek — Tags: , , , — apotheon @ 12:07

Thinking back, one of the most difficult things for me to really get the hang of at first when I initially started learning to program was how to get off to a good, orderly start when writing a simple program. I’m reminded of this by someone who popped up on the ruby-talk mailing list asking for help with a programming problem presented in Chris Pine’s Learn to Program online tutorial. (Note that page also gives you somewhere to go to buy the book Pine wrote as an “improved, expanded” version of the same idea.)

I figured I should share some of what I’ve learned since then about how to do exactly that — how to start writing a simple program. I’ll use the same programming problem that came up on ruby-talk: something that prints out the lyrics for 99 Bottles of Beer. It probably won’t be the most elegant implementation possible, but it should make the point about how to start writing software, and it’ll stay away from concepts that might be considered “advanced” or are idiomatic of a particular programming language. It will, however, assume that you know some very basic programming syntax.

So, here it is, a step-by-step example of how to start writing a simple program, using Ruby as the example language.

  1. Create the framework of the file. It should look something like this:

      #!/usr/bin/env ruby
    

  2. Come up with your flow control structure. Idiomatic Ruby would probably involve an iterator like each or downto, but we’ll stick with something common to most languages used by beginners — the while loop. There’ll also probably have to be a variable involved:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 0
      end
    

  3. Next, make sure you haven’t broken anything already. Of course, you need some way to indicate that things are working, if only very vaguely. As such, we’ll just make sure that the while loop terminates based on use of the variable, so you know you haven’t written an infinite loop. The following program should just run and end quickly without any output, indicating that your loop is behaving as expected:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 0
        bottles -= 1
      end
    

  4. Now you need to make it actually do something useful. Stick a puts statement in there to print out the first line of each stanza of the song. It should print out “n bottles of beer on the wall,” over and over, where n is a number counting down from 99 to 1.

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 0
        puts "#{bottles} bottles of beer on the wall,"
        bottles -= 1
      end
    

  5. Of course, there’s something wrong with saying “1 bottles of beer on the wall,” since “1” doesn’t really conjure up an idea of plurality. We’ll solve the problem of how to generate the correct behavior later, but for now we can at least eliminate the incorrect behavior by having it stop at 2 instead of 1.

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 1
        puts "#{bottles} bottles of beer on the wall,"
        bottles -= 1
      end
    

  6. Let’s finish off that while loop so that it does everything we need it to do — print out the rest of each stanza of the song:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 1
        puts "#{bottles} bottles of beer on the wall,"
        puts "#{bottles} bottles of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottles of beer on the wall!"
      end
    

  7. The last edit to the program reintroduced the singular/plural mismatch, because the last line generated says “1 bottles of beer on the wall!” Let’s just take that last stanza out of the mix, and deal with it separately. Increment your test value by one, again:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 2
        puts "#{bottles} bottles of beer on the wall,"
        puts "#{bottles} bottles of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottles of beer on the wall!"
      end
    

  8. Unfortunately, the song isn’t quite done. We still need to deal with the special cases we cut out of the while loop. Write something to deal with the last two stanzas:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 2
        puts "#{bottles} bottles of beer on the wall,"
        puts "#{bottles} bottles of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottles of beer on the wall!"
      end
    
      puts "2 bottles of beer on the wall,"
      puts "2 bottles of beer,"
      puts "Take one down, pass it around,"
      puts "1 more bottle of beer on the wall!"
    
      puts "1 bottle of beer on the awall,"
      puts "Only 1 bottle of beer,"
      puts "Take it down, pass it around,"
      puts "No more bottles of beer on the wall!"
    

  9. Looking at the output, it could be prettier. Add some empty puts statements in there to space out the stanzas:

      #!/usr/bin/env ruby
    
      bottles = 99
    
      while bottles > 2
        puts "#{bottles} bottles of beer on the wall,"
        puts "#{bottles} bottles of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottles of beer on the wall!"
        puts
      end
    
      puts "2 bottles of beer on the wall,"
      puts "2 bottles of beer,"
      puts "Take one down, pass it around,"
      puts "1 more bottle of beer on the wall!"
    
      puts
    
      puts "1 bottle of beer on the awall,"
      puts "Only 1 bottle of beer,"
      puts "Take it down, pass it around,"
      puts "No more bottles of beer on the wall!"
    

  10. Now that everything works, all we need to do is make a better program out of it. If you learn some programming techniques that can help make the program more easily understood and maintained, or somehow more efficient, they might be useful. Some idiomatic Ruby techniques such as the aforementioned downto iteration method, conditional handling of singularity vs. plurality, and other changes might make for a more elegant solution.
  11. Refactoring your program isn’t necessarily a big deal for a trivial programming problem like this, but this is a great time to practice those techniques so you’ll have a handle on them when you start needing them for more complex problems.

15 Comments

  1. A simple approach to the ‘s’ problem is to create a dynamically scoped code block for it:

    bottles = 99
    s = proc {'s' unless bottles == 1}
    
      while bottles > 0
        puts "#{bottles} bottle#{s.call} of beer on the wall,"
        puts "#{bottles} bottle#{s.call} of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottle#{s.call} of beer on the wall!"
        puts
      end
    

    Comment by SterlingCamden — 28 March 2008 @ 03:30

  2. Good answer, Sterling. I slapped a downto block into that to get the following:

      #!/usr/bin/env ruby
    
      s = proc {|bottles| 's' unless bottles == 1}
    
      99.downto(1) do |bottles|
        puts "#{bottles} bottle#{s.call(bottles)} of beer on the wall,"
        puts "#{bottles} bottle#{s.call(bottles)} of beer,"
        puts "Take one down, pass it around,"
        puts "#{bottles -= 1} bottle#{s.call(bottles)} of beer on the wall!"
        puts unless (bottles < 1)
      end
    

    I think it’s a little more idiomatically Rubyish, though I probably haven’t thought it through enough to come up with a best-possible version. For instance, I imagine there’s something simple to be done about all those repetitions of text in the puts statements. Hopefully any readers who need the beginner help aren’t scared off by our use of procs and blocks.

    Back to work-related stuff now, though.

    Comment by apotheon — 28 March 2008 @ 03:58

  3. A much longer and more humorous version can be found here: http://99-bottles-of-beer.net/language-ruby-1272.html

    Comment by SterlingCamden — 28 March 2008 @ 03:58

  4. Well, if you’re going to pass bottles into the code block, then you could use ‘def’ instead of ‘proc’. By using proc, the scope of bottles is defined when the code is called.

    Comment by SterlingCamden — 28 March 2008 @ 04:00

  5. Good link. I like that example.

    I guess that’s what I get for writing from scratch rather than doing some more research first — my little howto isn’t nearly as amusing as it could have been.

    Comment by apotheon — 28 March 2008 @ 04:04

  6. Well, if you’re going to pass bottles into the code block, then you could use ‘def’ instead of ‘proc’. By using proc, the scope of bottles is defined when the code is called.

    Good point. I was just too lazy to think it through.

    Comment by apotheon — 28 March 2008 @ 04:06

  7. And if you use def, then you can eliminate the .call (in fact, you must). So you get this:

    bottles = 99
    
    def s(bottles)
        's' unless bottles == 1
    end
    
      while bottles > 0
        puts "#{bottles} bottle#{s(bottles)} of beer on the wall,"
        puts "#{bottles} bottle#{s(bottles)} of beer,"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{bottles} bottle#{s(bottles)} of beer on the wall!"
        puts
      end
    

    Comment by SterlingCamden — 28 March 2008 @ 04:07

  8. Here’s a little DRYer example:

    bottles = 99
    
    def sing_about(bottles)
        "#{bottles} bottle#{'s' unless bottles == 1} of beer"
    end
    
      99.downto(1) do |bottles|
        puts "#{sing_about(bottles)} on the wall,"
        puts "#{sing_about(bottles)},"
        puts "Take one down, pass it around,"
        bottles -= 1
        puts "#{sing_about(bottles)} on the wall!"
        puts
      end
    

    Comment by SterlingCamden — 28 March 2008 @ 04:13

  9. Here’s your one-liner: http://99-bottles-of-beer.net/language-ruby-673.html

    Comment by SterlingCamden — 28 March 2008 @ 04:28

  10. I just realized that in my last example I didn’t need to initialize a ‘bottles’ variable at the top level.

    Comment by SterlingCamden — 28 March 2008 @ 04:40

  11. Heh, just for fun I got it down to a 183-character one-liner:

    def s(b) “#{b} bottle#{‘s’ if b!=1} of beer” end;99.downto(1){|b| puts “#{s(b)} on the wall,”;puts “#{s(b)},”;puts “Take one down, pass it around,”;puts “#{s(b-1)} on the wall!”;puts}

    Comment by SterlingCamden — 28 March 2008 @ 04:46

  12. Now with three fewer characters!:

    def s(b) “#{b} bottle#{‘s’ if b!=1} of beer” end;w=” on the wall”;99.downto(1){|b| puts “#{s(b)+w},”;puts “#{s(b)},”;puts “Take one down, pass it around,”;puts “#{s(b-1)+w}!”;puts}

    Comment by SterlingCamden — 28 March 2008 @ 04:53

  13. You’re obsessed, Sterling. Get help.

    . . . but seriously, this is fun. I’ll keep watching as long as you keep doing it.

    Comment by apotheon — 28 March 2008 @ 05:00

  14. It’s amazing how many characters I can save just by eliminating punctuation from the ends of the lines (allowing a minor refactor):

    def b(n)"#{n} bottle#{'s'if n!=1} of beer"end;def w()puts" on the wall"end;99.downto(1){|n|print b(n);w;puts b(n);puts"Take one down, pass it around";print b(n-=1);w;puts}

    172

    Could make it five shorter just by eliminating the newline between stanzas.

    Comment by apotheon — 29 March 2008 @ 10:42

  15. […] by a post from Chad Perrin, I became a little bit obsessed with the 99 bottles of beer problem again.  Specifically, how […]

    Pingback by 99 bottles of Ruby -- Chip’s Tips for Developers — 16 May 2008 @ 03:46

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

All original content Copyright Chad Perrin: Distributed under the terms of the Open Works License