# More recursion; public/private pairs

## Table of contents

## Problem solving

Now that we’ve seen a number of templates and examples of recursive programs, let’s practice writing our own recursive programs.

- Outline for solving recursion problems
- Identify one or more base cases.
- Decompose the problem into one or more smaller, independent subproblems.
- Combine the results of the smaller subproblem(s) into a final solution.

Base cases are often the simplest valid inputs to the problem. For problems involving integers, base cases are typically 0, 1, single-digit values, or negative numbers. For problems involving arrays, one base case is typically the empty array (though with a caveat that we’ll point out later).

Let’s implement a method, `skipAdd`

, that takes a non-negative integer `n`

and returns the sum of every other integer between 0 and `n`

. For example, `skipAdd(5)`

should return 5 + 3 + 1 + 0 = 9 while `skipAdd(4)`

should return 4 + 2 + 0 = 6.

## Outline how to solve skipAdd following the steps above.

- The simplest valid input to the problem is 0, the smallest non-negative integer. We know
`skipAdd(0)`

should return 0. - A larger problem like
`skipAdd(4)`

relies on calling`skipAdd(2)`

which then calls`skipAdd(0)`

. Each recursive call is passed the input`n - 2`

. - If we want to solve
`skipAdd(4)`

, the recursive call to`skipAdd(2)`

returns 2. In order to return the final solution, 6, we need to add`4`

. In general, we’ll want to add`n`

.

Turning the outline into code might look something like the following.

```
// pre : n >= 0
// post: Returns the sum of every other integer between 0 and n
public static int skipAdd(int n) {
if (n == 0) {
return 0;
} else {
return n + skipAdd(n - 2);
}
}
```

We can check our work by examining the domain of the method and its recursive subproblems. The **domain** of a function is all of the possible values of its inputs (parameters). `skipAdd(n)`

depends on `skipAdd(n - 2)`

to work correctly. Our overall recursive approach seems reasonable based on the mathematical formula from our outline, but we’ll want to double check the complete domain. For this problem, since we’re subtracting by 2, even and odd input integers will matter. For example, `skipAdd(5)`

will call `skipAdd(3)`

, `skipAdd(1)`

, `skipAdd(-1)`

, … forever! (In reality, Java will throw a `StackOverflowException`

once it detects that there are too many recursive calls.)

There are multiple ways of addressing this problem. One way is to introduce a second base case.

```
// pre : n >= 0
// post: Returns the sum of every other integer between 0 and n
public static int skipAdd(int n) {
if (n == 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return n + skipAdd(n - 2);
}
}
```

It would also work to combine these two base cases.

```
// pre : n >= 0
// post: Returns the sum of every other integer between 0 and n
public static int skipAdd(int n) {
if (n == 0 || n == 1) {
return n;
} else {
return n + skipAdd(n - 2);
}
}
```

Real world problems can involve multiple variables that interact in different ways, so identifying the simplest base cases won’t always be straightforward. It can be helpful in these scenarios to instead focus first on identifying how larger inputs should be decomposed, and then using the relationships between the original problem and the recursive subproblems to help identify more complicated base cases.

## Array sum

Let’s implement a method `sum`

that takes an `int[]`

and returns the sum of the values in the array. We’ve seen a couple ways to do this iteratively in this course using either regular loops or the for-each loop (iterators).

```
public static int sum(int[] data) {
int total = 0;
for (int i = 0; i < data.length; i++) {
total += data[i];
}
return total;
}
```

## Outline how to solve sum.

- The simplest valid input to the problem is an empty
`data`

array. - A subproblem for
`[1, 2]`

could be the rest of the`data`

:`[2]`

. Since we’re breaking down the problem by looking at one less element, we only need to include one base case for the empty`data`

array. - Since the
`sum([2])`

is just 2, we can add the value at`data[0]`

to arrive at the final solution.

It turns out that there are some interesting hiccups with turning this outline into code. The following code does not work!

```
public static int sum(int[] data) {
if (data.length == 0) {
return 0;
} else {
return data[0] + sum(data);
}
}
```

## Why doesn't this recursive method work?

In our outline’s recursive case, we wanted to break down the problem by reducing the `data`

by one element. Here, we’re not changing the `data`

array when we pass it to the next recursive call. So the program will continue recursing on the original `data`

array!

One way to address this is to make a new array with all but the zeroth element of `data`

. However, this is very inefficient since each recursive call needs to make a new array of length `data.length - 1`

.

Comparing the iterative implementation to the recursive implementation can point us towards a solution. The iterative implementation keeps track of an integer index `i`

. Each iteration of the loop processes the element at `data[i]`

. How might we convert this idea to recursion, such that each recursive call processes the element at `data[i]`

?

An initial (but incorrect) approach could be to introduce an index `i`

as a local variable.

```
public static int sum(int[] data) {
if (data.length == 0) {
return 0;
} else {
int i = 0;
int value = data[i];
i++;
return value + sum(data);
}
}
```

## Public/private pairs

In a recursive implementation of `sum`

, each recursive call to `sum`

gets its own parameters and local variables. These parameters aren’t shared between calls!

In Java, we share information between recursive calls by passing the data in as parameters. To maintain a simple `public`

interface, we’ll keep the `public`

method header the same but move all of the recursive code into a `private`

“helper” method with the variables that we want to change between recursive calls.

```
public static int sum(int[] data) {
return sum(data, 0);
}
private static int sum(int[] data, int i) {
if (data.length == i) {
return 0;
} else {
return data[i] + sum(data, i + 1);
}
}
```

The role of the `public`

method is to start off the recursive process by calling the recursive `private`

helper method. Each recursive call to the helper method processes the element at `data[i]`

before making a recursive call for the sum of the remaining elements. Note that we also changed the base case to correspond with `i`

reaching the end of `data.length`

.

The advantage of introducing this **public/private pair** is that the client does not need to know any of the implementation details or setup any of the recursive variables. This also lets the implementer change their implementation however they want by introducing new variables without breaking client code.

Alternatively, it’s also possible to share information by storing the variable as a field in the instance. However, this can lead to buggy code since fields are permanent parts of an object and might need to be reset, i.e. each new call to `sum`

needs to start `i`

at 0.

## Tree recursion

So far, all of our recursive programs haven’t really benefited from recursion since simple iterative solutions exist. However, there are some problems that are much more well-suited for recursion. We’ll turn our attention to how Java evaluates **tree recursion**, recursive programs that make more than one recursive call, resulting in a tree-like series of calls. We’ll learn how to write our own tree recursive methods in later lessons. Today, our focus is on understanding how Java evaluates them.

Let’s see how tree recursion works in the context of a subtraction game. In game theory, a **subtraction game** is a simple game with two players. At the beginning, there is a pile of `n`

cookies. The players alternate turns. Each turn, a player can take either 1 or 2 cookies. The player who takes the last cookie wins. We’ll assume that each player takes the optimal number of cookies to win the game. For example, if there are three cookies in the pile and it’s our turn, we can’t win the game.

- If we take 1 cookie, the other player can take 2 cookies and win the game.
- If we take 2 cookies, the other player can take 1 cookie and win the game.

Let’s consider the `canWin`

method, which returns true when it is possible to win if it’s our turn and there are `n`

cookies in the pile. The method works by simulating each player taking successive turns, so we win if we put the other player in a situation where they’re guaranteed to lose `!canWin`

.

```
public static boolean canWin(int n) {
if (n == 0) {
return false;
} else if (n == 1) {
// Take 1 cookie to win the game
return true;
} else {
// Is the other player forced to lose?
boolean takeOne = !canWin(n - 1);
boolean takeTwo = !canWin(n - 2);
return takeOne || takeTwo;
}
}
```

Simulating the code, we get the following trace for `canWin(3)`

. (Our human prediction is outlined above.) **We** start with 3 cookies on the pile by playing `canWin(3)`

.

**We**take 1, so they will be playing`canWin(2)`

.**They**take 1, so we will be playing`canWin(1)`

. We win!**They**take 2, so we will be playing`canWin(0)`

. We lose!`return`

**they**can win if we take 1 since there’s a position where they win (and we lose).

**We**take 2, so they will be playing`canWin(1)`

. They win!`return`

**they**can win if we take 2 since there’s a position where they win (and we lose).

`return`

**we**lose since there are no positions where we can win.

In this simple game, there are many winning positions so long as we avoid leaving either 0 or 3 cookies on the pile. However, this same idea applies to many more complicated problems, such as artificial intelligence for playing chess and simulation of protein folding for biomedical applications. These real-world problems are much harder for even supercomputers to solve for two reasons.

- The number of options to explore is
**exponential**with respect to the size of`n`

. Even simulating all of the possible playthroughs for`canWin(1000)`

can take a long time. Imagine the time it takes to compute`n`

in the millions or billions! - Rather than only two options (take 1 or 2 cookies), there are dozens, hundreds, or even more options to consider at each turn.