Notes for July 11, 2005

[If you go to 7-11 today, you can get a free Slurpee!]

(courtesy of Stuart Reges)

We have seen how to write code using loops, which is a technique called iteration. Recursion is an alternative to iteration that is equally powerful. It might seem odd to study something equally powerful if it doesn't allow us to solve a new set of problems. It's important to study recursion because some problems are naturally easier to express recursively.

Consider the following iterative method (a method written using iteration). The method produces a line of output with n stars on it (for some n greater than or equal to 0):

    public static void writeStars(int n) {
        for (int i = 0; i < n; i++) {
            System.out.print("*");
	}
        System.out.println();
    }

This is a classic iterative solution. There is some initialization before the loop begins (int i = 0), a test for the loop (i < n), some code that is executed each time through the loop (print and i++) and some concluding code that is executed after the loop finishes (the println).

To write this recursively, we want to think in terms of cases. Recursion always involves case analysis. There is always at least one base case and at least one recursive case. Some people find it easier to think about the recursive case first, while others find it easier to think about the base case first. To each their own.

To write the base case, we have to think about the range of possible tasks we might be asked to perform and of those, which is the simplest? Which writeStars task is the easiest of all? One would think that writing one star is easy and it is, but there's something even easier: writing 0 stars! So we can begin by handling that case:

    if (n == 0) {
        System.out.println();
    } else {
        ...
    }
It is common to see if/else statements in recursive definitions because we are so often distinguishing cases. So what do we do in the else part? This is where your programming instincts are going to lead you astray. You're going to want to solve the whole problem (a for loop starting at 0...). With recursive solutions, we want to figure out how to solve just a little bit of the problem. How can we get a little closer? We could write out one star:
    if (n == 0) {
        System.out.println();
    } else {
        System.out.print("*");
        ...
    }

This is where the second problem comes in. You have to believe that the method you are writing actually works. This is called the "leap of faith" that you have to make to be able to write recursive code. So after we've written one star, what we have left to do is to write a line of (n-1) stars. How can we do that? If only we had a method that would write out a line of stars, then we could call it. But we do have such a method! It's the one we're writing:

    public static void writeStars2(int n) {
        if (n == 0) {
            System.out.println();
        } else {
            System.out.print("*");
            writeStars2(n - 1);
        }
    }

This is the entire definition. It doesn't seem right to us because we're used to solving the entire problem ourselves. And it doesn't seem like we should be able to call writeStars2 inside of writeStars2. But that's what recursion is all about.

To understand this, think of writeStars2 as an entire army of robots all programmed with the writeStars2 code. We can have a series of robots solve one of these tasks. Consider what happens when we call writeStars2(3). We bring out the first robot of the writeStars2 army and we tell it to write a line of 3 stars. It notices that 3 is not 0, so it writes out a star. Then it calls writeStars2(2). At that point, we bring out a second robot and we tell it to write a line of 2 stars. It sees that 2 isn't 0, so it writes one star and calls up a third robot to write a line of 1 star. That robot sees that 1 isn't 0, so it writes out one star and calls up a fourth robot to write a line of 0 stars. That robot is the one that ends up with the easy task, the base case. It sees that it has been asked to write a line of 0 stars and 0 equals 0, so it does the println and finishes executing. So the first three robots each printed one star and the fourth did a println to produce our line of 3 stars. It is useful to draw diagrams of the series of calls, using indentation to keep track of the fact that one version called another:

    writeStars2(3);
        writeStars2(2);
            writeStars2(1);
                writeStars2(0);
Look at how these two methods differ. The iterative version has a familiar loop structure. The recursive version has an if/else that distinguishes between a base case and a recursive case. The recursive case includes a call on the method we're writing, but with a different value than the original (a smaller n than in the original call).

The writeStars example is a good one for seeing how recursion works, but it doesn't help you to see the power of recursion. So for a second example, consider the task of reversing the lines of text in a file. Suppose that we have a Scanner variable tied to a file that contains these three lines of text:

    fun
    for
    everyone
We want to write a method called reverse that takes the Scanner as a parameter and that reads these lines and writes them in reverse order:
    everyone
    for
    fun
This isn't hard to do with a loop as long as you have some kind of data structure to store the lines. In fact, a stack would work out very nicely to solve this problem. But with recursion, we can solve it without defining a data structure.

Again, we begin with the base case. What is the easiest file to reverse? A one-line file is pretty easy to reverse, but even easier would be a 0-line or empty file. Assume that the Scanner variable is called input. With a Scanner, we know that there are no more lines to read if a call on hasNextLine() returns false. In other words, we could write code like the following for the base case:

    if (!input.hasNextLine()) {
        // handle base case
    }
But in this case, there isn't anything to do. An empty file has no lines to reverse. So in this case, it makes more sense to turn around the if/else so that we test for the recursive case. That way we can make it a simple if that has an implied "else there is nothing to do":
    if (input.hasNextLine()) {
        ...
    }
Again the challenge is to do a little bit. How do we get a bit closer to being done? We can start by reading one line of text:
    if (input.hasNextLine()) {
        String line = input.nextLine();
        ...
    }
So in our example, this would read the line "fun" into the variable line and would leave us with two lines of text in the Scanner. What next? The output has to begin with the other two lines of text in reverse order. If only we had a method that would read two lines of text and print them in reverse order. But we do have such a method, the one we're writing:
    if (input.hasNextLine()) {
        String line = input.nextLine();
        reverse(input);
        ...
    }
We have to again make the leap of faith to believe that the call on reverse actually works. If it does, it would read the lines "for" and "everyone" and write them in reverse order. What would be left to do? All we'd have to do is write out the first line ("fun") to complete the output. This completes the method:
    public static void reverse(Scanner input) {
        if (input.hasNextLine()) {
            String line = input.nextLine();
            reverse(input);
            System.out.println(line);
        }
    }
How would this execute? Imagine cards that each has the definition for the reverse method and a place for storing the value of the variable line. In other words, they would each look like this:
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |      if (input.hasNextLine()) {               |
        |          String line = input.nextLine();      |
        |          reverse(input);                      |
        |          System.out.println(line);            |
        |      }                                        |
        |  }                                            |
        |      +------------------+                     |
        | line |                  |                     |
        |      +------------------+                     |
        +-----------------------------------------------+
By stacking them one on top of the other, this is a visual representation of what is referred to as the call stack. As methods are called, Java uses a stack to keep track of the next method invocation. The first time we call the method, it reads the first line of text into its variable line and then it reaches the recursive call on reverse:
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |      if (input.hasNextLine()) {               |
        |          String line = input.nextLine();      |
        |     -->  reverse(input);                      |
        |          System.out.println(line);            |
        |      }                                        |
        |  }                                            |
        |      +------------------+                     |
        | line | "fun"            |                     |
        |      +------------------+                     |
        +-----------------------------------------------+
Then what happens? What happens whenever a method is called? Java sets aside this method and turns its attention to the new method. So we take a second card and put it on top of the stack.
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |  +-----------------------------------------------+
        |  |  public static void reverse(Scanner input) {  |
        |  |      if (input.hasNextLine()) {               |
        |  |          String line = input.nextLine();      |
        |  |          reverse(input);                      |
        |  |          System.out.println(line);            |
        |  |      }                                        |
        |  |  }                                            |
        |  |      +------------------+                     |
        +--| line |                  |                     |
           |      +------------------+                     |
           +-----------------------------------------------+
This new version of the method has its own variable called line in which it can store a line of text. And even though the previous version (the one underneath this one) is in the middle of its execution, this new one is at the beginning of its execution. This one finds that there is a line of text to be read and it reads it in (the second line that contains "for"). Then it finds itself executing a recursive call on reverse:
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |  +-----------------------------------------------+
        |  |  public static void reverse(Scanner input) {  |
        |  |      if (input.hasNextLine()) {               |
        |  |          String line = input.nextLine();      |
        |  |     -->  reverse(input);                      |
        |  |          System.out.println(line);            |
        |  |      }                                        |
        |  |  }                                            |
        |  |      +------------------+                     |
        +--| line | "for"            |                     |
           |      +------------------+                     |
           +-----------------------------------------------+
So Java sets aside this version of the method as well and brings up a third version.
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |  +-----------------------------------------------+
        |  |  public static void reverse(Scanner input) {  |
        |  |  +-----------------------------------------------+
        |  |  |  public static void reverse(Scanner input) {  |
        |  |  |      if (input.hasNextLine()) {               |
        |  |  |          String line = input.nextLine();      |
        |  |  |          reverse(input);                      |
        |  |  |          System.out.println(line);            |
        |  |  |      }                                        |
        +--|  |  }                                            |
           |  |      +------------------+                     |
           +--| line |                  |                     |
              |      +------------------+                     |
              +-----------------------------------------------+
This is like the third robot of the reverse army that we've called up to work on this problem. It finds that there is a line to reverse (the third line, "everyone"), so it reads it in and reaches a recursive call on reverse:
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |  +-----------------------------------------------+
        |  |  public static void reverse(Scanner input) {  |
        |  |  +-----------------------------------------------+
        |  |  |  public static void reverse(Scanner input) {  |
        |  |  |      if (input.hasNextLine()) {               |
        |  |  |          String line = input.nextLine();      |
        |  |  |     -->  reverse(input);                      |
        |  |  |          System.out.println(line);            |
        |  |  |      }                                        |
        +--|  |  }                                            |
           |  |      +------------------+                     |
           +--| line | "everyone"       |                     |
              |      +------------------+                     |
              +-----------------------------------------------+
This brings up a fourth version of the method:
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |  +-----------------------------------------------+
        |  |  public static void reverse(Scanner input) {  |
        |  |  +-----------------------------------------------+
        |  |  |  public static void reverse(Scanner input) {  |
        |  |  |  +-----------------------------------------------+
        |  |  |  |  public static void reverse(Scanner input) {  |
        |  |  |  |      if (input.hasNextLine()) {               |
        |  |  |  |          String line = input.nextLine();      |
        |  |  |  |          reverse(input);                      |
        +--|  |  |          System.out.println(line);            |
           |  |  |      }                                        |
           +--|  |  }                                            |
              |  |      +------------------+                     |
              +--| line |                  |                     |
                 |      +------------------+                     |
                 +-----------------------------------------------+
This one turns out to have the easy task, like the call on writeStars2 asking for a line of 0 stars. This time around the Scanner is empty (input.hasNextLine() returns false). This is our very important base case that stops this process from going on indefinitely. This version of the method recognizes that there are no lines to reverse, so it simply terminates.

Then what? What happens in general when a method terminates? We're done with it, so we throw it away and go back to where we were just before this call. In other words, we pop the call stack and return to the line of code right after the method call:

        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |  +-----------------------------------------------+
        |  |  public static void reverse(Scanner input) {  |
        |  |  +-----------------------------------------------+
        |  |  |  public static void reverse(Scanner input) {  |
        |  |  |      if (input.hasNextLine()) {               |
        |  |  |          String line = input.nextLine();      |
        |  |  |          reverse(input);                      |
        |  |  |     -->  System.out.println(line);            |
        |  |  |      }                                        |
        +--|  |  }                                            |
           |  |      +------------------+                     |
           +--| line | "everyone"       |                     |
              |      +------------------+                     |
              +-----------------------------------------------+
We've finished the call on reverse, so now we're positioned at the println right after it. So we print the text in our variable line (we print "everyone") and terminate. And where does that leave us? This method goes away and we return to where we were just before (we pop the call stack and go to the line of code right after the recursive call):
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |  +-----------------------------------------------+
        |  |  public static void reverse(Scanner input) {  |
        |  |      if (input.hasNextLine()) {               |
        |  |          String line = input.nextLine();      |
        |  |          reverse(input);                      |
        |  |     -->  System.out.println(line);            |
        |  |      }                                        |
        |  |  }                                            |
        |  |      +------------------+                     |
        +--| line | "for"            |                     |
           |      +------------------+                     |
           +-----------------------------------------------+
So we print our line of text, which is "for" and this version goes away (we pop the stack again):
        +-----------------------------------------------+
        |  public static void reverse(Scanner input) {  |
        |      if (input.hasNextLine()) {               |
        |          String line = input.nextLine();      |
        |          reverse(input);                      |
        |     -->  System.out.println(line);            |
        |      }                                        |
        |  }                                            |
        |      +------------------+                     |
        | line | "fun"            |                     |
        |      +------------------+                     |
        +-----------------------------------------------+
Notice that we've written out two lines of text so far:
    everyone
    for
So our leap of faith was justified. The recursive call on reverse read in the two lines of text that came after the first and printed them in reverse order. We complete the task by printing our line of text, which leads to this overall output:
    everyone
    for
    fun
Then this version of the method terminates and we're done.

Now, let's look at a different problem. We want to write a method called stutter that will take an integer value like 342 and return the integer 334422 (the value you get by replacing each digit with two of that digit). How do we write this? We again start with simple cases. What are easy numbers to stutter? One would consider 0, but that's actually a hard number to stutter. We can't really return 00, so we'll consider it acceptable to return 0. So what are some easy numbers to stutter? 1, 2, any one digit number.

So how do we stutter one-digit numbers? To turn a digit n into nn, we'd need to do something like this:

    n0  10 of n
     n  plus 1 of n
    --
    nn
So we need a total of 11 times n. It's pretty obvious when you think about it that 1 times 11 is 11, 2 times 11 is 22, 3 times 11 is 33, and so on. Assume for now a precondition that n is greater than or equal to 0. So we have our base case:
    //pre : n >= 0
    public static int stutter(int n) {
        if (n < 10) {
            return 11 * n;
        } else {
            return ...;
	}
    }
So what do we do with numbers like 342? We need some way to break it up into smaller chunks, a way to split off one or more digits. Division by 10 will help:
    n = 342

         34 | 2
     -------+-------
     n / 10 | n % 10
What next? Here you have to think recursively. The original number was 342. We've split it up into 34 and 2. What if we stuttered those values? You have to make the leap of faith and believe that the method actually works. If it does, then it turns 34 into 3344 and 2 into 22. How do we combine them to get 334422? We can't do simple addition:
    3344
      22
    ----
    wrong
We need to shift the 22 over to the right relative to the 3344. We do this by multiplying 3344 by 100:
    334400
        22
    ------
    334422
But that means we've solved the problem:
    //pre : n >= 0
    public static int stutter(int n) {
        if (n < 10) {
            return 11 * n;
        } else {
            return 100 * stutter(n / 10) + stutter(n % 10);
	}
    }
The final version of the method in the handout includes an extra case for negative numbers. We remove the precondition and add a case for negative n. For a number like -342, we return -334422 by stuttering the positive number (stutter 342) and then negating the result.

The back of the handout has traces of the various recursive methods executing for various input values. These are crude pictures of the recursive process, but they're at least an attempt to show what's happening.