RU beehive logo ITEC Department logo
ITEC 120
2007spring
ibarland,
jpittges

homeinfoexamslectureslabshws
RecipeLawsliessyntaxjava.lang docsjava.util docs

lab13a
practicing loops
lab13a

bottles of beverages

99 bottles of rootbeer on the wall,
99 bottles of rootbeer;
   take one down,
   pass it around,
98 bottles of rootbeer on the wall!

98 bottles of rootbeer on the wall,
98 bottles of rootbeer;
   take one down,
   pass it around,
97 bottles of rootbeer on the wall!

[etc., ad nauseum]

— anonymous1
  1. Write a function oneVerse, which takes in a number-of-bottles, and returns exactly one verse (exactly 5 lines) of that eternal classic, “99 bottles of rootbeer on the wall”.

    1. Write the signature for the method, and a dummy stub version which compiles.
    2. Write a unit test or two. You can cut-and-paste:
      • oneVerse(50) should return "50 bottles of rootbeer on the wall,\n50 bottles of rootbeer;\n  take one down,\n  pass it around,\n49 bottles of rootbeer on the wall!\n",
      • Of course, we also want to test 1.
        Heads up: Make the following its own unit case, separate from the above test case. (That is, end-recording test cases after the above two, and then Create Test Method… just for this one additional call.) The reason is that our code may not pass these test cases, and we'll live with it, but we want to still be sure we're passing the other test cases.
        Any five-year-old can tell you that oneVerse(1) should return "1 bottle of rootbeer on the wall,\n1 bottle of rootbeer;\n  take one down,\n  pass it around,\n0 bottles of rootbeer on the wall!\n", and oneVerse(2) should return "2 bottles of rootbeer on the wall,\n2 bottles of rootbeer;\n  take one down,\n  pass it around,\n1 bottle of rootbeer on the wall!\n"
        If your code doesn't pass these test cases, it's okay for this lab, as long as you include in your documentation:
        Known bug: Does not quite give grammatically correct answers for 2 and 1.
        (You certainly know how to write correct code, but we'll leave that as an optional exercise; for this lab, it's more important to get to the next method.)
      • Why does 0 not make sense as an input, for this problem? Be sure to note this in the documentation for your function.
    3. Write javadoc comments (cut-and-paste from above, if it helps)
    4. As usual -- only after the above steps, do we start writing any actual code (as per The Design Recipe).
      The implementation is straightforward -- it can even be done with a single return statement.
    You may use a different drink than rootbeer, if you like (as long as you're willing to drink 99 bottles of it :-).

    If you want to see your result formatted in a screen-specific way on the console, you can go to BlueJ's code pad and call the static method System.out.println yourself, passing it the result of calling your function. For example, call System.out.println( oneVerse(17) ). from the Code Pad.

    BlueJ Terminal Window tip: It's helpful to select the menu options Options > Unlimited buffering as well as Options > Clear screen at method call. These options are only in the menu when BlueJ's terminal window has focus2

    You might wonder why we don't have our function not bother returning a String, but just print directly to the console window. That's fine, if all you ever want to do is turn console-window pixels on and off. However, by returning a String instead, our code is much more flexible: People can call our method and then use the resulting string for whatever it wants (sending it to a speech-synthesizer, writing it to System.out, writing it to a file or a web-page, or even email a single verse of the song to your parents each minute for 100 minutes). If our method hadn't returned a String, we'd be pre-emptively crippling future uses of our method.

  2. After you finish testing and debugging oneVerse, Write a function versesFromTo which takes two numbers, start and stop (with startstop), and returns the verses from start bottles down to but not including stop bottles. (What is the signature for this method?) Here are some test cases:
    1. versesFromTo(50,47) should return "50 bottles of rootbeer on the wall,\n50 bottles of rootbeer;\n  take one down,\n  pass it around,\n49 bottles of rootbeer on the wall!\n\n49 bottles of rootbeer on the wall,\n49 bottles of rootbeer;\n  take one down,\n  pass it around,\n48 bottles of rootbeer on the wall!\n\n48 bottles of rootbeer on the wall,\n48 bottles of rootbeer;\n  take one down,\n  pass it around,\n47 bottles of rootbeer on the wall!\n\n" How many calls to oneVerse does this entail? (How many numbers are 50 or less, and greater than 47?)
    2. versesFromTo(50,49) should return "50 bottles of rootbeer on the wall,\n50 bottles of rootbeer;\n  take one down,\n  pass it around,\n49 bottles of rootbeer on the wall!\n\n" (How many numbers are 50 or less, and greater than 49?)
    3. What should versesFromTo(50,50) return? (What are all the numbers which are 50 or less, and greater than 50?)
    4. Note that there is a blank line after each verse, even the last.3
  3. After making your stub function and unit tests, write the body of versesFromTo. We realize that our code needs to do something over-and-over, so we'll want to use a loop. Which type -- for-each, or while? Well, we don't already have a list-of-somethings to work with, so a for-each loop won't do. That leaves trying a while loop…
    1. Loop initialization: When you are halfway through the problem, what result will you have accumulated so far? (Declare a “so-far” variable of the appropriate type, to hold this value.) (What value should this “so-far” variable hold initially, before starting?)
    2. Loop initialization: What loop variable do we need, to keep track of where we are in the loop? That is, when we are halfway through the problem, we need to know where we are currently at. Declare an int verseNum, which will hold the number of the next verse we'll sing. (What value should verseNum it initially, before starting?)
    3. Loop body: Given verseNum, how do we get the one current verse? (Hint: Don't repeat code; just call an existing function whenever possible.)
    4. Loop body: How do we use combine this current verse with the “so-far” variable, to reflect the fact that we've now seen that one additional verse?
    5. Loop control logic: How do we update verseNum, so that the next time through we'll be considering the next verse? (We do this inside the loop.)
    6. Loop control logic: What is a boolean condition which tells use when to continue the loop?

    Note: if BlueJ's progress-bar is churning away and you think your program is an infinite loop, you can right-click on the progress bar to stop it.

  4. Write a function entireSong which takes no inputs, and returns the entire lyrics to the song. Don't repeat any code!; instead just call a method which already computes sub-answer this method wants.

    Use a named-constant to represent where the song starts. For testing, change the named-constant from 99 to (say) 4. No unit tests are needed; just call your method and give the result to System.out.println.

  5. ITEC120 does not condone underage drinking, nor in peer pressure to drink rootbeer. It would be nice to provide users with a version of our function which allows the caller to specify what sort of drink they'd like 99 bottles of. For example,

    oneVerse_v2( 17, "ginger ale" ) =
    "17 bottles of ginger ale on the wall,
    17 bottles of ginger ale;
      take one down,
      pass it around,
    16 bottles of ginger ale on the wall!
    "
    Here's something you can cut/paste for your test method:
    "17 bottles of ginger ale on the wall,\n17 bottles of ginger ale;\n  take one down,\n  pass it around,\n16 bottles of ginger ale on the wall!\n"

    Your task Make “_v2” versions all each of your three previous methods so that it they each take a second argument (the beverage to drink).
    (You'll temporarily have repeated code, as each method will have a lot of code in common with each _v2 method. Don't worry; we'll fix that in the following steps.)

  6. We now have lots of repeated code. Let's fix this, so that we have hardly any more code that a moment earlier:
    How can we delete most of oneVerse, and replace it with a single line of code?
    Put more concretely: If you were oneVerse(int), and you were handed the input 17 (say), your job is to compute the verse starting with “17 bottles of rootbeer on …”. How can you be lazy and get the other method, oneVerse_v2(int,String), to compute the answer for you? Remember, you definitely need to hand that other method two inputs, as per its signature.)
  7. Similarly, get rid of repeated code by gutting the old versesFromTo and completeSong, replacing their body with a single call their “_v2” cousins.

  8. Make a named-constant to hold the default-drink (“rootbeer”, or whatever you want the default to be.) The goal: If you want to change the default drink to “lemonade”, you need only have to change one line of your program, and suddenly all three methods involving the default drink will automatically use that new value.


Overloading

For each of the above functions, we have two parallel versions. A week later, it might be hard to remember which-is-which, between (say) oneVerse and oneVerse_v2.

Java actually allows us to use the same name, for these two methods! that is, instead of signatures

String oneVerse(int);
String oneVerse_v2(int, String);
we could actually have
String oneVerse(int);
String oneVerse(int, String);
But if both methods have the same name, and my code calls oneVerse, how does Java know which of the two functions to call? No problem: Java checks how many arguments you are passing (and what type they are), and it selects the version with the corresponding signature. So if you call oneVerse( n+3, "ginger ale" ) it knows to call oneVerse(int,String); and if instead you call oneVerse( n+3 ), it knows to call oneVerse(int). Having two methods with the same name but different signatures is called overloading.

Note that overloading is a shallow concept: If your language didn't have overloading (like Java does), you wouldn't really mind; you'd just call the methods oneVerse_v1(int) and oneVerse_v2(int,String). When other people call your method, they are fully aware of whether they are passing in one argument or two, so they are fully able to decide whether to call _v1 or _v2. However, overloading is convenient: it means people need to remember fewer method names, especially for closely-related functions.

You probably noticed this in java documentation already, that there were multiple methods with the same name. For example, class Scanner has three different methods all named hasNext, and eight different constructors (all with different signatures).

extra-credit

Extra-credit is individual work only. You can turn this in (or get it checked off) up through lab on Apr.18 (Wed).

  1. Make sure your output is always grammatical (no “1 bottles”), and yet you don't repeat data. (That is, if the song were modified to use carafes instead of bottles, you should only have to change one word of your program.)
    Hint: I have functions verseShortLine(int), verseLongLine(int), and pluralSuffixFor(int).

    Your code should now finally the test cases in Step 1.

  2. Your program currently prints half of the lyrics of a song which goes on forever: “…go to the store, buy one more, 1 bottle of rootbeer on the wall” (etc.)!
    Write a program which returns a given number of down-and-up “mega-verses” of this longer song. (That is, one mega-verse is nearly 1000 lines of text; you can omit unit tests, and just print the result to the System.out.) You still do not want any repeated code, so you'll want to factor out the common part of the verse structure into its own function, and pass in the varying part as an argument.

1Anonymous, and extremely annoying      

2You can give the terminal window focus by either (a) calling System.out.println, or (b) selecting View > Terminal.      

3If you really wanted no blank line after the last verse, it's a bit annoying. Approaches include: (a) Have your loop handle the first verse (if it exists) specially; then have a loop which handles all the rest (prepending a newline each time). (b) including an 'if' statement inside the loop, to check for the last verse specially; (c) adding a newline after every single verse, and then remove the last character (retroactively un-doing part of the last iteration). All three of these are aesthetically distasteful, but (a) is the least annoying. You'll have some repeated code; you can factor that out into its own method to regain a single-point-of-control.      

homeinfoexamslectureslabshws
RecipeLawsliessyntaxjava.lang docsjava.util docs


©2007, Ian Barland, Radford University
Last modified 2007.Aug.27 (Mon)
Please mail any suggestions
(incl. typos, broken links)
to iba�rlandrad�ford.edu
Powered by PLT Scheme