Lecture 4/27: Procedural Recursion


April 27, 2020

đź“‚Associated files

Procedural Recursion

CS 106B: Programming Abstractions

Spring 2020, Stanford University Computer Science Department

Lecturers: Chris Gregg and Julie Zelenski

A diagram showing all permutations of a length four generated by different algorithms


Slide 2

Announcements


Slide 3

Today's Goals:


Slide 4

Why recursion?


Slide 5

Templates for recursive functions


Slide 6

Generating sequences

Let's say you flip a coin twice. What are the possible sequences that might result? A little human brainstorming can come up with the four possibilities:

H H 
H T
T H
T T

What about a sequence of three coin flips or four? If we try to manually enumerate these longer sequences, how can we be sure we got them all and didn't repeat any? It will help to take a systematic approach.

Consider: how does a length N sequence build upon the length N-1 sequences? If we take each of the above length 2 sequences and paste an H or T on the front, we will generate all the length 3 sequences. We can do something similar to extend from length 3 to length 4 and so on. This indicates a self-similarity in the problem that should lend itself nicely to being solved recursively.

H H H 
H H T
H T H
H T T
T H H 
T H T
T T H
T T T

Slide 7

Decision trees

Let's create a visualization of the search space for coin flips. We model the choices and where each leads using a diagram called a decision tree.

Each decision is one flip, and the possibilities are heads or tails. A sequence of N flips has N such choices.

The top of the tree is the first choice with its possibilities underneath:

                                 |
                        +--------+--------+
                        |                 |   
                        H                 T 

Each choice we make leads to a different part of the search space. From there, we consider the remaining choices. Below is a decision tree for the first two flips:

                                 |
                        +--------+---------+
                        |                  |   
                        H                  T 
                        |                  |
                 +------+------+     +-----+------+
                 |             |     |            | 
                HH            HT    TH           TT

The height of the tree corresponds to the number of decisions we have to make. The width at each decision point corresponds to the number of options. To exhaustively explore the entire search space, we must try every possible option for every possible decision. That can be a lot of paths to walk!

Next let's write a function to generate these sequences. This code traverses the entire decision tree. By using recursion, we are able to capitalize on the self-similar structure.

void generateSequences(int length, string sofar)
{
    if (length == 0) {
        cout << sofar << endl;
    } else {
        generateSequences(length - 1, sofar + " H");
        generateSequences(length - 1, sofar + " T");
    }
}

Note that the recursive case makes two recursive calls. The first call recursively explores the left arm of the decision tree (having chosen "H"), the second explores the right arm (having chosen "T").

This function prints all possible 2^N sequences. If you extend the sequence length from N to N + 1, there are now twice as many sequences printed. It first prints the complete set of the sequences of length N each with an "H" pasted in front, followed by a repeat of the sequences of length N now with an "T" pasted in front.

Next we changed the search space. In place of flipping a coin, we will choose letter from the alphabet. The code changes only slightly (though it is ugly!):

void generateLetterSequencesUgly(int length, string sofar)
{
    if (length == 0) {
        cout << sofar << endl;
    } else {
        generateLetterSequencesUgly(length - 1, sofar + 'A');
        generateLetterSequencesUgly(length - 1, sofar + 'B');
        generateLetterSequencesUgly(length - 1, sofar + 'C');
        generateLetterSequencesUgly(length - 1, sofar + 'D');
        generateLetterSequencesUgly(length - 1, sofar + 'E');
        generateLetterSequencesUgly(length - 1, sofar + 'F');
        generateLetterSequencesUgly(length - 1, sofar + 'G');
        generateLetterSequencesUgly(length - 1, sofar + 'H');
        generateLetterSequencesUgly(length - 1, sofar + 'I');
        generateLetterSequencesUgly(length - 1, sofar + 'J');
        generateLetterSequencesUgly(length - 1, sofar + 'K');
        generateLetterSequencesUgly(length - 1, sofar + 'L');
        generateLetterSequencesUgly(length - 1, sofar + 'M');
        generateLetterSequencesUgly(length - 1, sofar + 'N');
        generateLetterSequencesUgly(length - 1, sofar + 'O');
        generateLetterSequencesUgly(length - 1, sofar + 'P');
        generateLetterSequencesUgly(length - 1, sofar + 'Q');
        generateLetterSequencesUgly(length - 1, sofar + 'R');
        generateLetterSequencesUgly(length - 1, sofar + 'S');
        generateLetterSequencesUgly(length - 1, sofar + 'T');
        generateLetterSequencesUgly(length - 1, sofar + 'U');
        generateLetterSequencesUgly(length - 1, sofar + 'V');
        generateLetterSequencesUgly(length - 1, sofar + 'W');
        generateLetterSequencesUgly(length - 1, sofar + 'X');
        generateLetterSequencesUgly(length - 1, sofar + 'Y');
        generateLetterSequencesUgly(length - 1, sofar + 'Z');
    }
}

Can we fix this code so that it isn't so tedious? This is a CS 106A question! A loop is just the thing to compactly express iterating over the alphabet:

void generateLetterSequencesNice(int length, string sofar)
{
    if (length == 0) {
        cout << sofar << endl;
    } else {
        for (char ch = 'A'; ch <= 'Z'; ch++) {
            generateLetterSequencesNice(length - 1, sofar + ch);
        }
    }
}

Using both iteration and recursion in combo may be a bit puzzling. In some of our previous examples, recursion was used as an alternative to iteration, but in this case, both are working together. The iteration is used to enumerate the options for a given single choice. Once an option is picked in the loop, recursion is used to explore subsequent choices from there. In terms of the decision tree, iteration is traversing horizontally and recursion is traversing vertically.


Slide 8

Jumble

The 'Jumble' word puzzle, which is a daily feature in many newspapers. The jumble has four or five scrambled words and a comic with a fill-in-the-blank joke. Some of the letters in the scrambled words are used in the final punchline to the joke, which is often a pun. The comic for this Jumble has a man and a woman standing in front of some blue prints. The man says, 'I've also included more closet space', and the woman says, 'I can't wait to have more room.' The punchline reads, 'The math teacher hired an architect because she wanted a new ---' and the '---' will be answered by the final pun. This puzzle has four scrambled words: 'knidy', 'legia', 'cronee', and 'tuvedo'. The first word's first two letters (when unscrambled) will be used in the punchline, and the first and third of the second word will be used in the punchline. The second and fourth letters of the third word, and the first and last letters of the last word will be used in the punchline. The punchline is a single word with eight letters.




Slide 9

Permutations – the wrong way


Slide 10

Permutations: the correct way


Slide 11

The search tree

A permutation 'tree' -- at the top there is a single box, with 'soFar: <blank>' and 'rest: abcd' In four branches below that, there are four boxes, with 'a' and 'bcd' in one box, 'b' and 'acd' in the next box, 'c' and 'abd' in the third box, and 'd' and 'abc' in the fourth box. Each of those boxes hs three further boxes. The 'a' and 'bcd' box has an 'ab' and 'cd' branch, an 'ac' and 'bc' branch, and an 'ad' and 'bc' branch. The 'b' and 'acd' box has a 'ba' and 'cd' branch, a 'bc' and 'ad' branch, and a 'bd' and ac' branch. The 'c' and 'abd' box has a 'ca' and 'bd' branch, a 'cb' and 'ad' branch, and a 'cd' and 'ab' branch. Finally, the 'd' and 'abc' box has a 'da' and 'bc' branch, a 'db' and 'ac' branch, and a 'dc' and 'ab' branch. The rest of tree is similar, with the 'ab' and 'cd' box having two branches, 'abc' and 'd' in one branch, and 'abd' and 'c' in the other branch. This is the same pattern for all other branches. The bottom of the tree (the 'leaves') has all of the permutations. The 'abc' and 'd' branch becomes 'abcd', the 'abd' and 'c' branch becomes 'abdc', etc.


Slide 12

Helper functions for recursive functions


Slide 13

More Decision Trees

startling -> starling -> staring -> string -> sting -> sing -> sin -> in -> i


A decision tree for `cart`. The tree shows cart at the top (root), and it has four branches, `art`, `crt`, `cat`, and `car`. `art` has three branches, `rt`, `at`, and `ar`. `rt` has a two branches, `t` and `r`. `at` has two branches, `t`, and `a`. This continues for the entire tree. Notice that we can find a path through real words: cart -> art -> at -> a


Slide 14

Decision tree search template

bool search(currentState) {
    if (isSolution(currentState)) {
        return true;
    } else {
        for (option : moves from currentState) {
            nextState = takeOption(curr, option);
            if (search(nextState)) {
                return true;
            }
        }
        return false;
    }
}

Slide 15

Reducible word


Slide 16

Reducible code

bool reducible(Lexicon & lex, string word) {
    // base case
    if(word.length() == 1 && lex.contains(word)) {
        return true;
    }

    // recursive case
    for(int i = 0; i < (int)word.length(); i++) {
        string copy = word;
        copy.erase(i, 1);

        if(lex.contains(copy)) {
            if(reducible(lex, copy)) {
                return true;
            }
        }
    }
    return false;
}