Week 11: Introduction to recursion

Recursion

In common language, a “circular definition” is not useful. When you define something in terms of itself, it conveys no meaningful information: “Good music is music that’s good” tells you nothing.

But math (and computer science) often uses recursive definition to define concepts in terms of themselves. WTF?

For example:

and so on.

How do the above differ from our bad definition of good music? While they are defined in terms of themselves, the circularity is only partial – there’s a way out of the circle.

Recursion and fractals

(This particular exercise is directly inspired by a talk Dan Garcia gave at a recent SIGCSE about part of their CS 10 (CS Principles) class, The Beauty and Joy of Computing.)

Many things can be expressed “recursively” that is, partly or wholly in terms of themselves. This is usually where your CS teacher immediately brings up factorial. BUT I WILL MANFULLY RESTRAIN MYSELF! (or maybe you’ll see it in lab or later this week 😀)

I’ll point out two things to get us started:

First, that recursion is a beautiful and simple (though maybe not easy) way to think about computation. The dual of the Turing machine is the lambda calculus, and it’s elegantly powered by recursion.

Second, that recursion is all around us in nature, in the form of fractals, which when used colloquially means “things that are similar to themselves over extended, but finite, scales.” (See Wikipedia for some example of fractals: https://en.wikipedia.org/wiki/Fractal#Natural_phenomena_with_fractal_features)

We’re going to explore the concept of recursion graphically today, rather than with factorials and Fibonacci and the like. To be clear, we will also do a little bit of factorial stuff and the like, too. Well, more than a little bit.

Drawing trees

So, we’re going to learn to draw beautiful trees that look like this (on board / slides).

But we’ll start with something simpler.

The important thing to see is that this tree is self-similar; in other words, it’s fractal: each part contains smaller versions of itself. (on board)

We’re going to draw this in Processing. But first, some preliminaries.

Installing Processing

Processing is a Java-based tool for sketching, computer-based art, and learning to code in the context of the visual arts. There are also versions that run in Python and in JavaScript in your browser.

The Java-based Processing is available as both a stand-alone program, and a .jar you can integrate into your Java projects.

Unfortunately, the support for Java versions greater then Java8 is in alpha and still somewhat sketchy (see what I did there? dad jokes amirite?), so we’re going to use the standalone version. If you want to follow along, you can download and install it pretty easily from their website. Alternatively, you can download the “Processing4” .jar and include it in a Code project for Java11. You’ll also have to do a little bit more work to write full classes rather than just the relevant methods. But the ideas are the same (as are the syntax and method names).

Aside: setting up Code

If you do this in Code, you’ll need to do it in a project that has the Processing JAR set up. You’ll need to write a class that extend PApplet like so, with a very specific main method:

import processing.core.PApplet;

public class Trees extends PApplet {
	
	public static void main(String[] args) {
		PApplet.main("processing.Trees"); // or whatever package.Class is the name of this file
	}
}

Processing lets you skip this step and directly start coding methods and variables, without worrying about the class, which is what we’re going to do.

Turtle / pen graphics

One (old!) way to introduce programming to people is via physical computing. Some of you may have had a robotics club or the like in high school; this approach is a very specialized version of that idea.

In short, a “turtle” (which is really just a robot with a pen attached) could be programmed to draw things on paper. You can also do this virtually, which is what we’re going to do, but I thought you might appreciate the history.

(show slide)

A turtle (or “pen”) minimally has three pieces of state: its x and y coordinates, and the direction it’s facing. You could add others (like the pen color or thickness, etc.) but let’s just start there.

We’re going to implement a turtle pen, and then use it to explore tree-drawing and recursion.

Setting up processing

Processing lets you do pretty arbitrary things (it’s Java, after all). We’re going to tame the complexity somewhat by doing “turtle graphics” aka “pen graphics.” In other words, we’re going to draw everything in terms of pen. The pen has three pieces of state: X, Y, and direction. This is similar to a turtle with a pen attached crawling around on a piece of paper, or my hand on the board, sorta.

float penX;
float penY;
float direction;

Note that we’re using float instead of double because much of the Processing API uses float; using float prevents various warning messages.

OK. So far, so nothing. Let’s set up the sketching area:

void setup() {
  size(600, 600);
  penX = width / 2;
  penY = height / 2;
  background(255);
  noLoop();
}

(Note: because reasons, if you are doing this in Code, you’ll need to make setup public, and move size() to a public void settings() method.)

Line by line, what’s happening here?

size(600, 600); creates a viewable (x, y) rectangle to draw on – it’s the sketch, in Processing lingo. The x coordinates increase left to right, and the y coordinates increase top to bottom. This is the opposite of algebra y-axes! But it is common in graphics.

penX = width / 2; and the following line center the pen on the x/y coordinates.

background(255); sets the entire background to white. If you pass a value on the range 0…255 as a color, Processing interprets it as a shade of grey (0 = black, 255 = white).

noLoop(); tells Processing that once we start to draw, call draw() only one time; otherwise (by default) it is called repeatedly in an endless loop.

Now, let’s teach processing how to move the pen:

void move(float amount) {
  float newX = penX + sin(radians(direction)) * amount;
  float newY = penY - cos(radians(direction)) * amount;
  
  line(penX, penY, newX, newY);
  
  penX = newX;
  penY = newY;
}

This is a three-step process. First we have to compute where we’re going, on the basis of where we are. We use trig to figure out how far away in the x and y directions we need to go (remember, y is flipped, hence the minus). We draw a line from the pen’s current location to its new location. Then we update the pen’s location.

Let’s test it out in a draw() method:

void draw() {
    move(50);
}

OK, that works. What about if we want to turn?

void turn(float degrees) {
  direction += degrees;
}

void draw() {
    move(50);
    turn(90);
    move(50);
    turn(90);
    move(50);
    turn(90);
    move(50);
    turn(90);
}

Great. How about a random walk?

void draw() {
  turn(random(360));
  stroke(random(255), random(255), random(255)); // random pen color each time, RGB when you pass three values
  move(random(20));
}

And how about we use the mouse to turn it on and off?

void mousePressed() {
  loop();
}

void mouseReleased() {
  noLoop();
}

OK, now we’re cooking!

Drawing a tree

So, back to our drawing: notice that each tree is made up of smaller trees. We’re going to write a tree method that draws a tree. It’ll start with a move() call to draw the trunk, then it will turn left, draw the smaller tree, turn right, draw the smaller true, and go back to where it started.

In other words, it’ll start with a move() call to draw the trunk, then it will turn left, draw the smaller tree using tree(), and so on.

“Hey dawg, I heard you liked trees inside of your trees, so can you show me how I can use tree() inside of tree() to draw a tree?” That’s today’s magic show!

No recursion here

First write tree1. All it does is draw the trunk by moving forward size and then backward size:

void tree1(float size) {
  move(size);
  move(-size);
}

Make sure it works:

void draw() {
    tree1(100);
}

Maybe also move the root of the tree down a bit:

// in setup()
penY = height * 0.9

Now, let’s draw a two-level tree.

void tree2a(float size) {
  move(size);
  turn(-15);
  move(size * 0.75);
  move(-size * 0.75);
  turn(30);
  move(size * 0.75);
  move(-size * 0.75);
  turn(-15);
  move(-size);
}

At this rate, it’s gonna take a lot of code to draw a beautiful tree. Don’t worry, though, we’ll get better.

Does everyone understand what’s happening above? (Trace through on board.)

More levels

OK, let’s simplify a bit. Do you see that tree2a contains tree1 if you stop and squint at it a bit? Let’s rewrite it to use tree1:

void tree2(float size) {
  move(size);
  turn(-15);
  tree1(size * 0.75);
  turn(30);
  tree1(size * 0.75);
  turn(-15);
  move(-size);
}

Now, I want you to write a tree3 that uses tree2. It should not use tree1. (Hint: it looks almost exactly like tree2; copy/paste and change three characters).

void tree3(float size) {
  move(size);
  turn(-15);
  tree2(size * 0.75);
  turn(30);
  tree2(size * 0.75);
  turn(-15);
  move(-size);
}

How would tree4 look? Exactly the same, except it would call tree3. So, here’s the big idea: we can write a tree method that uses itself to draw a tree, so long as it knows how many levels it’s expected to draw.

A Recursive tree

tree will need a levels parameter in addition to size. This tells us what “level” we are on, where level 1 is the last level (the outermost branches):

void tree(int levels, float size) {
    move(size);
    turn(-15);
    tree(levels - 1, size * 0.75);
    turn(30);
    tree(levels - 1, size * 0.75);
    turn(-15);
    move(-size);
}

What happens when we run this in our draw() method?

As Processing helpfully explains, there’s a problem with our recursion. Let’s think about what’s going on. (on board, trace operation with levels = 2)

The problem is that the methods we wrote before aren’t all exactly the same: tree1 in particular just drew a trunk, and no branches. So, we need a special case to handle this:

void tree(int levels, float size) {
  if (levels == 1) {
    move(size);
    move(-size);
  }
  else {
    move(size);
    turn(-15);
    tree(levels - 1, size * 0.75);
    turn(30);
    tree(levels - 1, size * 0.75);
    turn(-15);
    move(-size);
  }

And now it works. Let’s try a larger levels for a prettier tree. (show 9)

This general code pattern, with a simple base case that doesn’t call the method itself, and a less simple case that does call itself, is typical of recursive code. Let’s break it down:

void tree(int levels, float size) {
  // the base case; a problem so simple we know how to handle it
  // completely
  if (levels == 1) {
    move(size);
    move(-size);
  }
  // the recursive case
  // do a small part here, and delegate the rest to somebody else
  else {
    // draw the trunk
    move(size);
    // position the pen for the left sub-tree
    turn(-15);
    // delegate drawing the left sub-tree to someone else
    tree(levels - 1, size * 0.75);
    // re-position for the right sub-tree
    turn(30);
    // delegate drawing the right sub-tree to someone else
    tree(levels - 1, size * 0.75);
    // re-position to go back to the start
    turn(-15);
    // retrace our steps back to where we started
    move(-size);
  }

Notice that in the recursive case, we are always making progress toward the base case – here, levels is guaranteed to be decremented by one each time the method calls itself, so we’re guaranteed (mostly) to hit levels == 1 and stop. If we don’t do this, the method calls itself indefinitely, and we end up with a StackOverflow (at least in Java).

What would happen if we started with levels < 1?

Sometimes a recursive program is more elegant if you have a smaller base case. What would a “zero level” tree mean? Can you rewrite the program to have a base case where levels == 0?

An exercise

Try to draw a more “beautiful” tree. Two things that might help:

strokeWeight(5);

makes the stroke (the ink left by the pen) five pixels wide instead of one.

And you can change the color by red green blue components. Google has a nice color picker to help.

stroke(75, 160, 15); // kinda greenish

Recursive algorithms: the mathy example

It turns out some algorithms are most naturally expressed recursively – usually they correspond to solving problems or implementing abstractions that are also defined recursively. A big chunk of COMPSCI 250 explores the relationship between recursion and inductive proofs.

For example, consider the factorial function over non-negative integers. n-factorial (usually written n!) is the product of the numbers 1 through n: (1 * 2 * 3 * … * n)

4! = 1 * 2 * 3 * 4 = 24.

What is 0!? 1. Why? The intuition is that you’re multiplying together “no numbers”, which is like not multiplying at all. What’s the same as not multiplying at all? Multiplying by 1. The real reason for this has to do with the algebra of integers (which differs from the algebra you learned in middle/high school); take an abstract algebra or number theory course if you want to learn more.

We can recursively define the factorial function.

0! = 1
n! = n * (n-1)!

If we use this definition to compute 4!, we get 4 * 3!. Applied again, we get 4 * 3 * 2!, finally, we get 4 * 3 * 2 * 1, which is the same as the non-recursive definition.

The non-recursive definition is relatively easy to transcribe into Java (we’ll not handle invalid inputs, like -1):

static int factorial(int n) {
  int x = 1;
  for (int i = 1; i <= n; i++) {
    x *= i;
  }
  return x;
}

What about the recursive one? Baby steps. Let’s define factorial0, then factorial1, and so on:

static int factorial0() }
  return 1;
}

static int factorial1() }
  return 1 * factorial0();
}

static int factorial2() }
  return 2 * factorial1();
}

static int factorial3() }
  return 3 * factorial2();
}

static int factorial4() }
  return 4 * factorial3();
}

factorial4 calls factorial3; once factorial3 returns an answer, factorial4 returns its answer. How does factorial3 work? The same way, using factorial2. In fact, everything except factorial0 works the same way. So why not capture this pattern in a single method:

static int factorial(int n) }
  return n * factorial(n - 1);
}

That’s not quite right, because it won’t “stop” when it gets to zero. We could check for the zero case and call factorial0:

static int factorial(int n) }
  if (n == 0) {
      return factorial0();
  } else {
    return n * factorial(n - 1);
  }
}

or just get rid of it entirely:

static int factorial(int n) }
  if (n == 0) {
      return 1;
  } else {
    return n * factorial(n - 1);
  }
}

What actually happens when we call the recursive factorial with an argument of 4?

That call makes a call to factorial(3), which calls factorial(2), which calls factorial(1), which calls factorial(0), which returns 1 to factorial(1), which returns 1 to factorial(2), which returns 2 to factorial(3), which return 6 to factorial(4), which returns 24, which is the right answer, which is the same as what happened when we had different (factorial1, factorial2, etc.) methods. But we do it all from one method!

The values are passed along on the call stack, which grows to a height of five while this recursive method operates. Why doesn’t it just blow up? Because eventually the recursion terminates. How do we know it will terminate, and how do we know the answer is right? Glad you asked.

Three questions (about recursion)

Stop! Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.
— Bridgekeeper, Monty Python and the Holy Grail

There are three questions we must answer affirmatively about a recursive algorithm if we expect it to (a) terminate and (b) produce the correct answer.

  1. Does it have a base case?
  2. Does every recursive call (more generally: every bounded sequence of recursive calls) make progress toward (or arrive at) the base case?
  3. Does the algorithm get the right answer if we assume the recursive calls get the right answer?

A base case is the end of the line, when there is no more recursion. factorial’s base case is when n=0.

Guaranteeing progress is trickier. If our parameter is numerical, and each recursive call brings us a constant amount closer toward the base case (as is the case of factorial) then we can be certain we’ll eventually get there. But sometimes you need two or more recursive calls to make progress (see foo below).

Justifying correctness is something you’ll do more in COMPSCI 250, but in short, if you are proceeding from a correct recursive definition (which factorial is) then your recursive algorithm will be correct.

Recursion for control flow

Recursion is a mechanism for control flow, similar to loops in its behavior but quite dissimilar in terms of its appearance.

We talked about how, if you can define the solution to some problem recursively, you can generally rewrite that recursive definition into a recursive method in Java.

Generally, any time we can write a recursive definition for a problem, we can then write the recursive method pretty easily. But how do we write these recursive definitions? It’s pretty easy if you’re a Math major and you’re used to this style of thinking, or if you’re a CS major who has passed through the purifying flame of COMPSCI 250, or you’re just a nerdy-nerd.

But what if you’re more comfortable thinking about things iteratively? And why wouldn’t you be, since you’ve been conditioned by AP CS and/or 121 and/or this class to always think in terms of for (and sometimes, while) loops?

That’s OK. Next we’re going to spend some time practicing translating loops from their iterative to their recursive form, so that you can see (some of) the symmetry between these two forms of control flow.

So, first things first: it turns out that while for is generally very useful for the iteratively-minded, it obscures some of the connection between iteration and recursion in a way that while does not. But good news: we can trivially rewrite any (non-enhanced) for loop as a while loop. To refresh your memory, here’s an example:

int sumToNFor(int n) {
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += i;
    }
    return sum;
}

int sumToNWhile(int n) {
    int sum = 0;
    
    int i = 1;
    while (true) {
        if (i > n) {
            break;
        }
        else {
            sum += i;
        }
        i++;
    }
    return sum;
}

Notice I separated out the break to make the cases clear.

More generally:

for (init; condition; increment)
    doStuff

is equivalent to

init
while (true):
    if !condition:
        break
    else:
        doStuff
    increment

There’s still not an exact correspondence to the recursive form, though:

int sumToN(int n) {
    if (n == 0) {
        return 0;
    }
    else {
        return n + (sumToN(n - 1));
    }
}

Why not? Because our loop control variable (i) is still being tested against an input to the method (a variable), rather than an end point that’s independent of the input. If we rewrite our for loop to terminate based upon a constant rather than a variable:

for (int i = n ; i >= 0; i--)

and rewrite our while loop the same way:

int sumToNWhile(int n) {
    int sum = 0;
    
    int i = 1;
    while (true) {
        if (i == 0) {
            break;
        }
        else {
            sum += i;
        }
        i--;
    }
    return sum;
}

It’s maybe now a little easier to see how the base case and the recursive case of the recursive solution relate to the branches within the while loop of the iterative solution.

There’s a general observation here about translating iterative solutions to recursive ones to make: Recursive methods must “make progress toward the base case.” For methods where “the thing that’s making progress” is a counter, that means the most natural way to write them is usually counting down rather than up, even though that’s the opposite way we typically write our for loops.

Why? Because we need to know “when to stop” our recursion. It’s easy to know when you’ve hit a constant (in other words, when n == 0), since there’s no external state to keep track of. But it’s less clear “how to stop” if your recursion starts at 0 and counts up to n. It’s certainly possible (we’ll get there next week).