0% found this document useful (0 votes)
2 views125 pages

Lecture08 Handouts Proto

The lecture focuses on recursion, explaining its concept and applications, such as in sorting algorithms like Merge Sort. It emphasizes the importance of defining base cases and simplifying steps in recursive functions to ensure they terminate correctly. Additionally, it provides guidance on writing recursive functions and includes examples and exercises for practice.

Uploaded by

Abbas Sarfraz
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views125 pages

Lecture08 Handouts Proto

The lecture focuses on recursion, explaining its concept and applications, such as in sorting algorithms like Merge Sort. It emphasizes the importance of defining base cases and simplifying steps in recursive functions to ensure they terminate correctly. Additionally, it provides guidance on writing recursive functions and includes examples and exercises for practice.

Uploaded by

Abbas Sarfraz
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 125

1

Lecture #8
• Recursion
• How to design an Object Oriented program
(on your own study)
• Project 3 Design Tips
Recursion
What’s the big picture?
Normally functions call other functions...
But some problems are much easier solved by
having a function call itself! That’s recursion.
// n! = n * (n-1)! by definition
int factorial(int n)
{
if (n == 0) return 1;

int fact_of_n = n * factorial(n-1);


return fact_of_n;
} Uses:
AI chess players,
The key is that each time a recursive function
solving sudoku
calls itself, it must pass in a simpler sub-problem problems, cracking
than the one it was asked to solve. ciphers, optimally
laying out circuits on
Eventually it reaches the simplest subproblem, circuit boards, job
which it solves without needing to call itself. interviews!
Idea Behind Recursion
3

SolveAProblem(problem)

Just return
Is the problem trivially solved Yes
the answer
No
Break the problem into two
or more simpler sub-problems

SolveAProblem(sub-problem1)
Solve each sub-problem j
...
bybycalling
callingsome
ourself!
other SolveAProblem(sub-problemn)
function on the sub-problem j

Collect all the solution(s)


to the sub-problems

Use the sub-solutions to construct Return the


a solution to the complete problem solution
“The Lazy Person’s Sort”
4

Let’s design a new sorting algorithm,


called the “lazy person’s sort”…

The input to this sort


622
are a bunch of index 17395
14
cards with #s.

Lazy Person’s Sort:


Split the cards into two roughly-equal piles
Hand one pile to nerdy student A and ask them to sort it
Hand the other pile to nerdy student B and ask them to sort it
Take the two sorted piles and merge them into a single sorted pile

6 17 22 3 14 95
“The Lazy Person’s Sort”
5

99322
2774
61492
17

6 17 22 3 14 95

Lazy Person’s Sort:


Split the cards into two roughly-equal piles
Hand one pile to nerdy student A and ask them to sort it
Hand the other pile to nerdy student B and ask them to sort it
Take the two sorted piles and merge them into a single sorted pile
“The Lazy Person’s Sort”
6

99322
2774
61492
17

Lazy Person’s Sort:


Split the cards into two roughly-equal piles
Hand one pile to nerdy student A and say
ask “do
them toLazy
the sort Person’s
it Sort”
say them
cute student B and ask
Hand the other pile to nerdy “do the
to Lazy
sort Person’s
it Sort”
Take the two sorted piles and merge them into a single sorted pile
“The Lazy Person’s Sort”
7

99322
2774
61492
17

Lazy Person’s Sort:


If you’re handed just one card, then just give it right back.
Split the cards into two roughly-equal piles
Hand one pile to studly
nerdy student A and say
ask “do
them toLazy
the sort Person’s
it Sort”
Hand the other pile to nerdy
cute student B and ask
say them to Lazy
“do the sort Person’s
it Sort”
Take the two sorted piles and merge them into a single sorted pile
“The Lazy Person’s Sort”
8
void mergeSort(int arr[], int n) {
if (n <= 1) return;

Lazy
int Person’s Sort:
mid = n / 2;
If you’re handed mid);
mergeSort(arr, just one card, then just
// sort givehalf
first it right back.
of array
Split the cards into
mergeSort(arr two
+ mid, n -roughly-equal
mid); // sortpiles
second half of array
Hand one pile to person A and say “do the Lazy Person’s Sort”
Hand the other
merge(arr, pilen);
0, mid, to person B and say “do
// Merge the
the Lazy Person’s
sorted halves Sort”
} Take the two sorted piles and merge them into a single sorted pile

Our Lazy Person’s Sort is officially called Merge Sort!

MergeSort works by breaking up its input into two smaller parts


and calling itself to sort each sub-part.
Then merging the sorted sub-arrays back into a single sorted array.

mergeSort( 5 4 7 1 6 2 )

mergeSort(
4 5 7 ) mergeSort(
1 2 6 )
9
void mergeSort(int arr[], int n) {
if (n <= 1) return;

int mid = n / 2;
mergeSort(arr,
otherSort mid); // sort first half of array
mergeSort(arr
otherSort + mid, n - mid); // sort second half of array

merge(arr, 0, mid, n); // Merge the sorted halves


}

It’s hard to believe it works!

Ok... Would you agree the algorithm works if


we change it like this?

// Smallberg wrote it!


// It works perfectly!
Alright, well then let me void otherSort( array
otherSort(int ) int n)
arr[],
show you how otherSort {
works! ☺ ...
mergeSort(arr, n);
}}
10
void mergeSort(int arr[], int n) {
if (n <= 1) return; If you’re willing to assume that
MergeSort works on a full array…
int mid = n / 2;
mergeSort(arr, mid); // sort first half of array
mergeSort(arr + mid, n - mid); // sort second half of array

merge(arr, 0, mid, n); Then


// Merge theyou must halves
sorted have faith that
} it’ll work on a half array too!

When you write a recursive function…

Your job is to figure out how the function can use itself
(on a subset of the input) to solve the complete problem.

You need to have faith that that the


recursive call will work properly.
The Two Rules of Recursion
11

The recursion approach is governed by two rules.

Let's learn them by considering one of the simplest


recursive functions: factorial

int factorial(int n) {
if (n <= 0)
return 1;
else
return n * factorial( n – 1 );
}
Recursion Rule #1
12

Rule #1: Every recursive function must eventually stop


calling itself, or it'll never stop running.

To do so, the function identifies the simplest possible


input(s) and returns a result without calling itself.

This is called a
stopping condition
or base case.

int factorial(int n) {
if (n <= 0) We handle the most basic
return 1; problem (e.g., n = 0)
without using recursion!
else
return n * factorial( n – 1 );
}
Recursion Rule #2
13

Rule #2: Every time a recursive function calls itself,


it must pass in a smaller sub-problem.
factorial( 3 ) factorial( 2 ) factorial( 1 ) factorial( 0 )

This ensures the function eventually reaches its base case.

This is called the simplifying step.


This ensures We pass in a
that we smaller problem
eventually int factorial(int n) { each time our
reach the function calls
base case if (n <= 0) itself recursively.
and stop return 1;
recursing!
else
return n * factorial( n – 1 );
}
14
Factorial Trace-through
int fact(int n) n 0
{
if (n == 0)
return 1;
return n * fact(n-1);
}
int fact(int n) n 1
{
if (n == 0)
return 1;
0
return n * fact(n-1);
} result 2
int fact(int n) n 2 int main()
{
{
if (n == 0)
int result;
return 1;
result = fact(2);
1
return n * fact(n-1); cout << result;
} }
Recursion Rule #1
15

What would happen if we didn't have a base case


in our factorial function?

Right! Our function would never stop calling itself!

factorial( 2 ) factorial( 1 ) factorial( 0 ) factorial( -1 ) ...

int factorial(int n) {
if (n <= 0)
return 1;
else
return n * factorial( n – 1 );
}
Recursion Rule #2
16

What would happen if we didn't have a


simplifying step in our factorial function?

Right! Our function would never stop calling itself!

factorial( 3 ) factorial( 3 ) factorial( 3 ) factorial( 3 ) ...

int factorial(int n) {
if (n <= 0)
return 1;
else
return n * factorial( n – 1 );
}
17

WONDERS WHY HE CAN’T GET A DATE


18

Writing Recursive Functions

Initially, most students find


it confusing to write
recursive functions.

So we're going to do a
series of drills to help you
learn how!

But first, let's do a little refresher.


19

Refresher: Arrays
Fill in the blanks so this prints Fill in the blanks so this prints
the first n-1 values of array x. the last n-1 values of array x.

void print(int arr[], int n) {


for (int i = 0;i < n; ++i) {
cout << arr[i];
}
}

int main() { int main() {


const int n = 4; const int n = 4;
int x[n] = {10, 20, 30, 40}; int x[n] = {10, 20, 30, 40};

print(________, ________); print(________, ________);


} }
Answer: x, n-1 Answer: x+1, n-1 or &x[1], n-1
20

Refresher: Linked Lists


Fill in the blanks so this prints the last n-1
values of the linked list pointed to by head.

struct Node { int main() {


int val; Node *head;
Node *next; head = create_linked_list();
};
print_list(_____________);
void print_list(Node *ptr) { }
while (ptr != nullptr) {
cout << ptr->val;
ptr = ptr->next;
}
}

Answer: head->next
21

3 Steps to Writing a Recursive Function

Convert your
Write your function into a
Figure out the function... but recursive
base cases without using version in just
recursion one step
22

Basic Recursion (on Arrays)

FORGOT
BASE CASE
CHECK JAR
23

Step #1: Figure Out The Base Case(s)


The "base case" is code that identifies the simplest possible
input(s) and returns a result without a recursive call.

Every recursive function must start with code to handle


all base cases and return a result immediately.

In practice, here are the common base case inputs:

An integer
input value of An empty An empty An empty
zero array linked list string

An array A linked list A string with a


An integer
holding a single holding a single single
input value of 1
item node character
24

Step #1: Figure Out The Base Case(s)


Fill in the blanks to properly handle each base case!

// computes n factorial
int fact(int n) {

if (n == ______)
return 1;

// we'll see this later

Answer: 0
25

Step #1: Figure Out The Base Case(s)


Fill in the blanks to properly handle each base case!

// prints all of the items in // prints all of the items in


// an array from first to last. // an array in reverse. It can
// It can hold 0 or more items // hold 0 or more items
int print(int arr[], int n) { int printr(int arr[], int n) {

if (n == _____) if (n == _____)
The n parameter
return; specifies the count return;
of items in the array.

// we'll see this later // we'll see this later

} }

Answer: 0 for both


26

Step #1: Figure Out The Base Case(s)


Fill in the blanks to properly handle each base case!

// computes the sum of all // determines if all the items


// items in an array which // in an array are in ascending
// could hold 0 or more items // order; array has 0 or more items
int sum(int arr[], int n) { bool inorder(int arr[], int n) {

if (n == _______) if (n == 0)
return _______; return _______;

if (n == _______)
// we'll see this later return true;

// we'll see this later


} }

Answer: 0 for both Answer: true and 1


27

Step #1: Figure Out The Base Case(s)


Fill in the blanks to properly handle each base case!

// returns the first odd number // returns the last odd number
// found in an array, returning -1 // found in an array, returning -1
// if there are no odd numbers // if there are no odd numbers
int firstodd(int arr[], int n) { int lastodd(int arr[], int n) {

if (n == _______) if (n == _______)
return _______; return ________;

// we'll see this later // we'll see this later

} }

Answer: 0 and -1 Answer: 0 and -1


28

Step #2: Solve It NON-RECURSIVELY


OK, now that we've figured out our base cases, let's
complete the code for each function!
But... I'm going to make it easier for you!
For every function I ask you to complete, I'll provide
you with a correct iterative solution that I wrote.
And you'll have to figure out how to use my
function to write your function.
I'll give you Use it to
this! solve this
// computes x! using iteration // compute factorial of n
int fact'(int x) { int fact(int n) {
int f = 1; if (n == 0) // base case from
return 1; // earlier
for (int i=1; i <= x; ++i)
f *= i; int f = n * fact'(_______);

return f; return _______;


} }
29

Step #2: Solve It NON-RECURSIVELY


But I'm going to place two restrictions on you:

#1: You must use my provided function, f', in your solution.

void f(int n) {
...
f'( ... );
}

#2: If your function f is passed an input of size n, you may


only use f' on an input of size n-1 or less.
void f(int n) { void f(int arr[], int n) {
... ...
f'(n/2); f'(arr, n-1); You can only use f' on
} } problems smaller than n
You can only use f' on like n-1 or n/2.
problems smaller than n
like n-1 or n/2.
30

Step #2: Solve It NON-RECURSIVELY


Hint: As you're solving the next set of challenges, your
function f will typically have the following structure:

f(x) {
Handle the base case
(we just saw this)

Process one item from x


(e.g., the 1st item an array/list x) The order
of these
Call f' to process remaining items steps may
(e.g., last n-1 items of array/list x) be swapped
Optionally return a result
(based on the previous two steps)
}
31

Step #2: Solve It NON-RECURSIVELY


Complete the factorial function by using fact'()

You must call the following


Complete the following function:
function in your solution:
// computes x! using iteration // compute factorial of n
int fact'(int x) { int fact(int n) {
int f = 1;
while (x > 1) { if (n == 0) // base case from
for
f *=
(int
x;i=1; i <= x; ++i) return 1; // earlier
f *= i;
--x;
} int f = n * fact'(_______);
return f;
} return f; return _______;
} }

Answer: n-1 and f


32

Step #2: Solve It NON-RECURSIVELY


Complete the function which prints
an array from first to last

You must call the following


Complete the following function:
function in your solution:
// prints all of the items in // prints all of the items in
// an array from first to last // an array from first to last
// using iteration int print(int arr[], int n) {
int print'(int arr[], int x) { if (n == 0) // empty array
for (int i=0; i < x; ++i) return;
cout << arr[i] << endl;
} cout << arr[0] << endl;

arr + 1 ________);
print'(________, n-1

}
Answer: arr + 1, n-1
33

Step #2: Solve It NON-RECURSIVELY


Complete the function which prints
an array in reverse (version #1)

You must call the following


Complete the following function:
function in your solution:
// prints all of the items in // prints all of the items in
// an array in reverse using // an array in reverse
// iteration int printr(int arr[], int n) {
int printr'(int arr[], int x) { if (n == 0) // base case
for (int i=x-1; i >= 0; --i) return; // from before
cout << arr[i] << endl;
}
Hint: We want to print printr'(________, n - 1 );
all but the first item of
the array in reverse. cout << _______ << endl;
}
Answer: arr+1, arr[0]
34

Step #2: Solve It NON-RECURSIVELY


Complete the function which prints
an array in reverse (version #2)

You must call the following


Complete the following function:
function in your solution:
// prints all of the First,
items we in
print the last// prints all of the items in
// an array in reverse item
usingin the array. // an array in reverse.
// iteration int printr(int arr[], int n) {
int printr'(int arr[], int x) { if (n == 0) // base case
for (int i=x-1; i >= 0; --i) return; // from before
cout << arr[i] << endl;
} cout << arr[_______] << endl;
Then, we print the
first n-1 items in the printr'(________, n - 1);
array in reverse.
}
Answer: n-1, arr
35

Step #2: Solve It NON-RECURSIVELY


Complete the function which sums the items in an array

You must call the following


Complete the following function:
function in your solution:
// computes the sum of all // sums all #s in array
// items in an array iteratively int sum(int arr[], int n) {
int sum'(int arr[], int x) { if (n == 0) // base case
int s = 0; return 0; // from before
for (int i=0; i < x; ++i)
s += arr[i]; int tot = _________ +
sum'(_________, n - 1);
return s;
} return tot;
}

OR arr[n-1] and arr


Answer: Either arr[0] and arr+1,
36

Step #2: Solve It NON-RECURSIVELY


Complete the function which determines if an array is in-order

You must call the following


Complete the following function:
function in your solution:
// determines if all the items // determines if all items in an
// in an array are in order, // array are in ascending order
// using iteration bool inorder(int arr[], int n) {
bool inorder'(int arr[], int x) { if (n == 0) // base case
return true; // from before
if (x < 2) return true;First, we check if (n == 1) // base case
that the first two return true; // from before
for (int i=0; i < x - items are in order.
1; ++i)
if (arr[i] > arr[i+1]) arr[0] > _______)
if (_______ arr[1]
return false; return false;
Then we check the
return true; the last n-1 items. return inorder'(_______, n-1
arr + 1 ___);
} }
Answer: arr[0] > arr[1], arr+1 and n-1
37

Step #2: Solve It NON-RECURSIVELY


Complete the function which finds the
first odd number in an array

You must call the following


Complete the following function:
function in your solution:
// returns the first odd number // returns the first odd number
// in an array, or -1 if no odds, // in an array, or -1 if no odds
// using iteration int firstodd(int arr[], int n) {
int firstodd'(int arr[], int x) { if (n == 0) // base case
return -1; // from before
for (int i=0; i < x; ++i)
if (arr[i] % 2 == 1) if (_______ % 2 == 1)
return arr[i]; return _________;

return -1; return firstodd'(_______, n-1);


} }

Answer: arr[0]. arr[0] and arr + 1


38

Step #2: Solve It NON-RECURSIVELY


Complete the function which finds the
last odd number in an array

You must call the following


Complete the following function:
function in your solution:
// returns the last odd number // returns the last odd number
// in an array, or -1 if no odds, // in an array, or -1 if no odds
// using iteration int lastodd(int arr[], int n) {
First, we see if there
int lastodd'(int arr[], intnumber
was an odd x) {found if (n == 0) // base case
later in the array. return -1; // from before
for (int i=x-1; i >= 0; --i)
if (arr[i] % 2 == If so, we return it
1)immediately. int q = lastodd'(______, ____);
return arr[i]; if (q != ____) return q;
Otherwise, we check if
the first item is odd... if (______ % 2 == 1)
return -1;
} If so, we return it. return arr[0];
If not, it means there were no return -1; // no odd found!
odd numbers in the array! }
Answer: arr + 1, n-1, -1 and arr[0]
39

Step #3: Make it Recursive


We've written a bunch of non-recursive functions!

int firstodd'(int arr[], int x) { int firstodd(int arr[], int n) {


if (n == 0)
for (int i=0; i < x; ++i) return -1;
if (arr[i] % 2 == 1)
return arr[i]; if (arr[0] % 2 == 1)
return arr[0];
return -1;
} return firstodd'(arr+1, n-1);
}

Each function first handles the base case(s)...

Then the function processes a single value


(e.g., the first or last item of the array)

Finally, the function delegates processing of the


rest of the items to another function.
40

Step #3: Make it Recursive


#2: Well... our
function works
correctly!!!

We've written a bunch of non-recursive functions!

int firstodd'(int arr[], int x) { int firstodd(int arr[], int n) {


if (n == 0)
for (int i=0; i < x; ++i) return -1;
if (arr[i] % 2 == 1)
return arr[i]; if (arr[0] % 2 == 1)
return arr[0];
return -1;
} return firstodd(arr+1,
firstodd'(arr+1,n-1);
n-1);
}
#3: So why can't our #1: Before, we delegated
function delegate to to a separate, non-
itself??? recursive function.

Right now, we're delegating processing of all but


the first item to a function I provided...
But shouldn't we be able to delegate that
processing to any function that works correctly?
Yes!
41

Step #3: Make it Recursive


Let's prove that it works for arrays of size 0!

int firstodd(int arr[], int n) {


if (n == 0)
For an empty array, we
return -1;
correctly return -1 since
if (arr[0] % 2 == 1) it holds no odd numbers.
return arr[0];

return firstodd(arr+1, n-1);


}

If our array is empty, firstodd correctly returns -1,


indicating there are no odd numbers in the array.
42

Step #3: Make it Recursive


#1: Ok, so what if an array
Let's prove that it works for arrays of size 1!n = 1 is passed in?
of size

#5: And since we int firstodd(int arr[], int n) {


just proved firstodd if (n == 0) #2: If the first number
returns the correct return -1; in the array is odd...
result for an array of
size 0...
if (arr[0] % 2 == 1)
return arr[0]; #3: we return it, which is
the correct behavior.
return firstodd(arr+1, n-1);
}
#4: Otherwise
#6: that means our function also firstodd calls
returns a correct answer for itself on a problem
problems of size n = 1. of size n = 0.

So, if our array has one item, firstodd also works correctly.
It either returns the first number (if it's odd) or returns -1.
Ok let's just try this once more!
43

Step #3: Make it Recursive


Let's prove that it works for arrays of size 2!
#1: Ok, so what if an array
of size n = 2 is passed in?
int firstodd(int arr[], int n) {
if (n == 0) #2: If the first number in
return -1; the array is odd...

if (arr[0] % 2 == 1)
return arr[0];
#3: we return it, which
is the correct behavior.
return firstodd(arr+1, n-1);
#4: Otherwise firstodd calls
} itself on a problem of size n =
#5: And since we just
proved firstodd correctly 1.
returns the first odd
#6: that means our function
number (or -1) for an array
also returns a correct answer
of size 1...
for problems of size n = 2.

So, if our array has two items, firstodd also works correctly.
It either returns the first number (if it's odd) or uses itself to
return the first odd number in the rest of the array (or -1).
We can repeat this process over and over, and it'll always work!
44

Step #3: Make it Recursive


int fact(int n) { int print(int arr[], int n) {
if (n == 0) // base case from if (n == 0) // empty array
return 1; // earlier return;

int f = n * fact(n-1);
fact'(n-1); cout << arr[0] << endl;

return f; print(arr
print'(arr++1,
1,nn--1);
1);
} }

int printr(int arr[], int n) { int sum(int arr[], int n) {


if (n == 0) // base case if (n == 0) // base case
return; // from before return 0; // from before

printr'(arr++1,
printr(arr 1,nn--1);
1); int tot = arr[0] +
sum(arr
sum'(arr++1,
1,nn--1);
1);
cout << arr[0] << endl;
} return tot;
}
45

Step #3: Make it Recursive


bool inorder(int arr[], int n) { int firstodd(int arr[], int n) {
if (n == 0) // base case if (n == 0) // base case
return true; // from before return -1; // from before
if (n == 1) // base case
return true; // from before if (arr[0] % 2 == 1)
if (arr[0] > arr[1]) return arr[0];
return false;
return inorder(arr
inorder'(arr++1,1,n-1);
n-1); firstodd'(arr+1,n-1);
return firstodd(arr+1, n-1);
} }

int lastodd(int arr[], int n) {


if (n == 0) // base case
return -1; // from before
int q = lastodd'(arr+1, n-1);
lastodd(arr+1, n-1);
if (q != -1)
return q;
if (arr[0] % 2 == 1)
return arr[0];
return -1;
}
46

Let's See Some Trace-Thrus


Working Through Recursion
47
void printr(string arr[ ], int size)
{
if (size == 0) // an empty array arr 2040
return;
size 1 names
arr
else 2000
{ [0] Leslie
arr 2020
printr(arr + 1, size – 1 );
[1]
[0] Phyllis
cout << arr[0] << “\n”; arr
} 2040
void
} printr(string arr[ ], int size) [2]
[0]
[1] Nan
{
if (size == 0) // an empty array arr 2020
return;
size 2
else
{ 2020 + 20 1
printr(arr + 1, size – 1 );
cout << arr[0] << “\n”;
}
void
} printr(string arr[ ], int size) main()
{
if (size == 0) // an empty array arr 2000 {
return; size 3 string names[3];
else
{ 2000 + 20 ...
printr(arr + 1, size – 1 ); 2000 3
cout << arr[0] << “\n”; printr(names,3);
}
} }
Working Through Recursion
48

void printr(string arr[ ], int size)


{
if (size == 0) // an empty array arr 2060
names
arr
return; size 0 2000
[0] Leslie
else arr 2020
{
[1]
[0] Phyllis
printr(arr + 1, size – 1 ); arr 2040
cout << arr[0] << “\n”; [2]
[0]
[1] Nan
void }printr(string arr[ ], int size) arr
}
{ [0]
if (size == 0) // an empty array arr 2040
return;
size 1
else
{
printr(arr + 1, size – 1 );
cout << arr[0] << “\n”;
} main()
void
} printr(string arr[ ], int size)
{ {
if (size == 0) // an empty array arr 2020
string names[3];
return;
size 2
else ...
{
printr(arr + 1, size – 1 ); printr(names,3);
cout << arr[0] << “\n”; }
}
void printr(string arr[ ], int size)
Working Through Recursion
49
void printr(string arr[ ], int size)
{
if (size == 0) // an empty array arr 2040
return;arr [0] is
size 1 names
arr
else “ Nan ” 2000
{ [0] Leslie
arr 2020
printr(arr + 1, size – 1 );
[1]
[0] Phyllis
cout << arr[0] << “\n”; arr
} 2040
void
} printr(string arr[ ], int size) [2]
[0]
[1] Nan
{
if (size == 0) // an empty array arr 2020
return; arr [0] is
“ Phyllis ” size 2
else
{
printr(arr + 1, size – 1 );
cout << arr[0] << “\n”;
}
void
} printr(string arr[ ], int size) main()
{
if (size == 0) // an empty array arr 2000 {
return; arr [0] is size 3 string names[3];
else “ Leslie ”
{ ...
printr(arr + 1, size – 1 ); 2000 3
cout << arr[0] << “\n”; printr(names,3);
}
} }
50

Working Through
0 Recursion
int sum(int arr[], int n) { arr 2008
if (n == 0) // base case
n
return 0; // from before

int tot = arr[0] +


sum(arr + 1, n - 1);
return tot;
}
int sum(int arr[], int n) {
arr 2004
if (n == 0) // base case
return 0; // from before n 1
nums
arr
tot 100 2000
[0] 10
int tot = arr[0] + arr 2004
sum(arr + 1, n - 1); [1]
[0]
return tot; arr
100
100
2008
} [1]
[0] ...
...
int sum(int arr[], int n) {
arr 2000 [1]
if (n == 0) // base case
return 0; // from before n 2
tot 110 int main() {
int tot = arr[0] + int nums[2] = {10, 100};
sum(arr + 1, n - 1);
return tot; cout << sum(arr, 2);
} }
51

Recursion On Linked Lists

Using recursion on linked lists is similar to


using recursion on arrays!

The only difference is that we always recurse


from top-to-bottom (not bottom up).

Ok, let's use the same approach to learn


recursion on linked lists!
52

Step #1: Figure Out The Base Case(s)


For our linked list examples, we'll be using the typical node structure:

struct Node { void func(Node *p) {


int val; ...
Node *next;
}; }
When we first call our
functions, p will point
Given this structure, the two base cases when
at the head node.
dealing with linked lists are:

A linked list
An empty holding a single
linked list node

p nullptr p

val 10
Ok, let's do some base case challenges! next nullptr
53
struct Node {
Step #1: Figure Out The Base Case(s) int val;
Node *next;
};
Fill in the blanks to properly handle each base case!

// prints all of the items in // sums all of the items in


// a linked list from last to // a linked list. The list may
// first. The list may be empty // be empty
void printr(Node *p) { int sum(Node *p) {

if (p == ________) if (________ == nullptr)


return; return _______;

// we'll see this later // we'll see this later

} }

Answer: nullptr Answer: p and 0


54
struct Node {
Step #1: Figure Out The Base Case(s) int val;
Node *next;
};
Fill in the blanks to properly handle each base case!

// returns the last odd number // find the maximum item in


// in a potentially empty linked // a non-empty linked list.
// list, returns -1 if no odd #s int max(Node *p) {
int lastOdd(Node *p) {
if (__________ == nullptr)
if (p == ________) return __________;
return ________;
p
// we'll see this later // we'll see this later
val 10
next nullptr
} }

Answer: nullptr, -1 Answer: p->next, p->val


55
struct Node {
Step #1: Figure Out The Base Case(s) int val;
Node *next;
};
Fill in the blanks to properly handle each base case!

// determines if all the items


// in a linked list of 0 or more
An empty list is, by // nodes are in order
definition, in order. bool inorder(Node *p) {

if (________ == nullptr)
return ________;
if (________ == nullptr)
return true;
A list of just one
item is, by
definition, in order. // we'll see this later

Answer: p,. true, p->next


56

Step #2: Solve It NON-RECURSIVELY


OK, now for some problem solving!

Use my provided function to implement your function!


57

Step #2: Solve It NON-RECURSIVELY


Complete the function which prints a
linked list in reverse

You must call the following


Complete the following function:
function in your solution:
// prints list in reverse w/stack // prints all of the items in
void printr'(Node *p) { // a linked list from last to
stack<int> vals; // first. The list may be empty
// just trust
while(p me that
!= nullptr) { we have void printr(Node *p) {
//vals.push(p->val);
a function that works! if (p == nullptr)
p = p->next; return;
} }
while (!vals == "") { printr'(_________);
cout << vals.top() << endl;
vals.pop(); cout << _________ << endl;
} }
}
Answer: p->next, p->val
58

Step #2: Solve It NON-RECURSIVELY


Complete the function which sums all
of the numbers in a linked list

You must call the following


Complete the following function:
function in your solution:
// sums all values in a list // sums all of the items in
int sum'(Node *p) { // a linked list. The list may
// be empty
// just trust me that we have int sum(Node *p) {
// a function that works! if (p == nullptr)
return 0;
}
int s = _________ +
sum'(_________);

return s;
}
Answer: p->val, p->next
59

Step #2: Solve It NON-RECURSIVELY


Complete the function which finds
the last odd number in a linked list

You must call the following


Complete the following function:
function in your solution:
// finds las odd number in a list // returns the last odd number
int lastodd'(Node *p) { // in an array, or -1 if no odds
int lastodd(Node *p) {
First, we see if there
// just trust me that we have if (p == nullptr) // base case
was an odd number found
// a function that works!
later in the list. return -1; // from before

} If so, we return it. int q = lastodd'(________);


Otherwise, we check if if (q != ____) return q;
the first item is odd...
If so, we
if (________ % 2 == 1) return it
If not, it means there
return p->val; immediately.
were no odd numbers return -1; // no odd found!
in the list! }
Answer: p->next, -1, p->val
60

Step #2: Solve It NON-RECURSIVELY


Complete the function which returns
the maximum number in a linked list

You must call the following


Complete the following function:
function in your solution:
// sums all values in a list // find the maximum item in
int max'(Node *p) { // a non-empty linked list.
int max(Node *p) {
// just trust me that we have if (p->next == nullptr)
// a function that works! return p->val;

} int max_rest = max'(________);

if (________ > max_rest)


return ________;
else
return max_rest;
}
Answer: p->next, p->val, p->val
61

Step #2: Solve It NON-RECURSIVELY


Complete the function which
determines if a linked list is ordered

You must call the following


Complete the following function:
function in your solution:
// determines if all the items // determines if all the items
// in a linked list are in order // in a linked list of 0 or more
// using iteration // nodes are in order
bool inorder'(Node *p) { bool inorder(Node *p) {
if (p == nullptr)
// just trust me that we have return true;
// a function that works! if (p->next == nullptr)
return true;
} if (__________ > _____________)
return false;

return inorder'(________);
}
Answer: p->val, p->next->val, p->next
62

Step #3: Make it Recursive


OK, you know the drill! Let's make 'em recursive!

void printr(Node *p) { int sum(Node *p) {


if (p == nullptr) if (p == nullptr)
return; return 0;

printr(p->next);
printr'(p->next); int s = p->val +
sum'(p->next);
sum(p->next);
cout << p->val << endl; return s;
} }
int lastodd(Node *p) {
if (p == nullptr)
return -1;
int q = lastodd(p->next);
lastodd'(p->next);
if (q != -1) return q;
if (p->val % 2 == 1)
return p->val;
return -1; // no odd found!
}
63

Step #3: Make it Recursive


OK, you know the drill! Let's make 'em recursive!

int max(Node *p) { bool inorder(Node *p) {


if (p->next == nullptr) if (p == nullptr)
return p->val; return true;
if (p->next == nullptr)
int max_rest = max(p->next);
max'(p->next); return true;
if (p->val > p->next->val)
if (p->val > max_rest) return false;
return p->val;
else return inorder(p->next);
inorder'(p->next);
return max_rest; }
}
64
Linked List Trace-
int biggest(Node *cur) through
{
if (cur->next == nullptr)
return( cur->val);
int rest = biggest( cur->next ); cur→ 1200
return max( rest, cur->val );
}

1300
int biggest(Node *cur)
{
if (cur->next == nullptr)
return(cur->val); 1400
1400
int rest = biggest( cur->next );
return max( rest, cur->val );
}

int biggest(Node *cur)


{
int main() {
if (cur->next == nullptr) Node *head;
return(cur->val);
1300 ... // create linked list
int rest = biggest( cur->next ); 1200
return max( rest, cur->val ); cout << biggest(head);
}
}
65

More Advanced Recursion


66

Finding The Position in an Array/List


Sometimes we want to write a recursive function that
finds the position of an item in an array or list.

Before we do that, let's think about how we'd do this IRL.

Ok let's do some challenges now!


67

Position of the First Odd #: Base Case(s)


Fill in the blanks to properly handle each base case!

// returns the position of the


// first odd number in an array,
// or -1 if there are no odd #s
int pfirstodd(int arr[], int n) {

if (n == 0) // smallest array
return _______;

// we'll see this later

Answer: -1
68

Position of the First Odd #: Non-recursive Solution


Complete the function which finds the
position of the first odd number in an array

You must call the following


Complete the following function:
function in your solution:
// returns the position of the // returns the position of the
// first odd number in an array, // first odd number in an array,
// or -1 if there are no odd #s, // or -1 if there are no odd #s
// using iteration int pfirstodd(int arr[], int n) {
int pfirstodd'(int arr[], int x) { if (n == 0) // base case
return -1; // from before
// just trust us that this works
// for all arrays! if (arr[0] % 2 == 1)
return _______;
} int p = pfirstodd'(arr+1, n-1);
if (p != ______) return ______;
return _______;
}
Answer: 0. -1, p+1, -1
69

Position of the First Odd #: Make it Recursive

OK, you know the drill! Let's make it recursive!

int pfirstodd(int arr[], int n) {


if (n == 0) // base case
return -1; // from before

if (arr[0] % 2 == 1)
return 0;
-1;
int p = pfirstodd(arr+1,
pfirstodd'(arr+1,n-1);
n-1);
if (p != -1) return p+1;
return -1;
}
70

Position of Biggest # in Array: Base Case


Fill in the blanks to properly handle each base case!

// returns the position of the


// biggest number in an array
// with 1 or more items
int posmax(int arr[], int n) {

if (n == 1) // smallest array
return _______;

// we'll see this later

Answer: 0
71

Position of Biggest # in Array: Non-recursive Solution


Complete the function which finds the
position of the biggest number in an array

You must call the following


Complete the following function:
function in your solution:
// returns the position of the // returns the position of the
// biggest number in an array, // biggest number in an array
// using iterative solution // with 1 or more items
int posmax'(int arr[], int x) { int posmax(int arr[], int n) {
if (n == 1) // base case
// just trust us that this works return 0; // from before
// for all arrays!
int p = posmax'(arr+1, n-1);
} if (arr[0] > _________)
return ______;
return _______;
}

Answer: arr[p+1], 0, p+1


72

Position of Biggest # in Array: Make it Recursive

OK, you know the drill! Let's make it recursive!

int posmax(int arr[], int n) {


if (n == 1) // base case
return 0; // from before

int p = posmax(arr+1,
posmax'(arr+1,n-1);
n-1);
if (arr[0] > arr[p+1])
return 0;
return p+1;
}
73

Finding The Position of The Biggest Item in a List


We just learned how to find the position of the biggest
number in an array.

Challenge: Convert your array


code to work on a linked list!

int posmax(int arr[], int n) { int posmax(Node *n) {


if (n == 1) // base case if (__________ == nullptr)
return 0; // from before return 0;

int p = posmax(arr+1, n-1); int p = posmax(_________);


if (arr[0] > arr[p+1]) if (n->val > _________)
return 0; return 0;
return p+1; return p+1;
} }

Answer: n->next and n->next, but wait... Oh no! We have no way of finding the biggest value in the rest of the list!
74

Finding The Position of The Biggest Item in a List


In addition to returning the position of the largest value, somehow
we also have to communicate back the largest value itself.

Let's see how we might do it IRL

5 19 8 15

x
75

Finding The Position of The Biggest Item in a List


Challenge: Complete this code to
find the position of the biggest item

If our list has only one


int posmax(Node *n, int &x) { value, then by definition
if (n->next == nullptr) { it's the biggest.

x = __________; Store it away in x.


return 0;
Notice that x's value is set
} by this call to posmax! int main() {
Node *head = create_list(...);
int p = posmax(________, _____);
if (n->val > ______) { int temp; // our "box"
x = __________; int pos = posmax(head, temp);
cout << "Biggest # in slot: "
return 0;
<< pos << endl;
} }
return p+1; We can then see if the current
} node's value is larger than the one
stored in x by the recursive call.

Answer: n->val, n->next, x, x, n->val


76

Finding The Position of The Biggest Item in a List

Helper Wrapper
function function

int posmax(Node *n, int &x) { int posmax(Node *n) {


if (n->next == nullptr) { int temp;
n->val
x = __________; return posmax(n, temp);
return 0; }
} int main() {
Node *head = create_list(...);
n->next _____);
int p = posmax(________, x
x
if (n->val > ______) {
n->val
x = __________; int temp; // our "box"
return 0; int pos = posmax(head);
posmax(head, temp);
} cout << "Biggest # in slot: "
return p+1; << pos << endl;
} }
Sometimes our recursive functions will have parameters like x which are only used internally and would be confusing for a programmer
who's using your function. In these cases, it helps to write a "wrapper" function that hides these parameters from the user of your code.
The wrapper then calls your main recursive function, called the "helper" function, to do the real computation.
Programming Dingleberries
77

Thoughts to cling on to from Carey

Most recursive functions need to only access the contents of the


current node and maybe the next node for proper operation.

bool is_in_order(Node *ptr) {


if (ptr == nullptr || ptr->next == nullptr)
return true;
if (ptr->value > ptr->next->value)
return false;
return is_in_order(ptr->next);
}

Dingleberry: If your recursive function ever reaches more than


one node down, you probably have a bug! Be careful!
bool is_in_order(Node *ptr) {
...
if (ptr->next->value > ptr->next->next->value)
return false;
...
}
Programming Dingleberries
78

Thoughts to cling on to from Carey

The same holds true when processing arrays!

bool is_in_order(int arr[], int n) {


if (n == 0 || n == 1)
return true;
if (arr[0] > arr[1])
return false;
return is_in_order(arr+1, n-1);
}

Dingleberry: If your recursive function ever accesses more than


one array cell away, you probably have a bug! Be careful!

bool is_in_order(int arr[], int n) {


...
if (arr[0] > arr[1] || arr[1] > arr[2])
return false;
return is_in_order(arr+2, n-2);
}
Using Multiple Recursive Calls
79

In all our examples so far, our recursive functions made a


single recursive call to themselves.
But sometimes we need to perform multiple recursive calls. Let's see!
int posmax(int arr[], int n) { int firstodd(int arr[], int n) {
if (n == 1) // base case if (n == 0) // base case
return 0; // from before return -1; // from before

int p = posmax(arr+1, n-1); if (arr[0] % 2 == 1)


if (arr[0] > arr[p+1]) return arr[0];
return 0;
return p+1; return firstodd(arr+1, n-1);
} }
int sum(Node *p) { void printr(Node *p) {
if (p == nullptr) if (p == nullptr)
return 0; return;

int s = p->val + printr(p->next);


sum(p->next);
return s; cout << p->val << endl;
} }
Using Multiple Recursive Calls
80

In all our examples so far, our recursive functions made a


single recursive call to themselves.
But sometimes we need to perform multiple recursive calls. Let's see!
function recursivefunc(data) {
... // handle base cases

// now use multiple recursive calls to process the


// the rest of the data and combine with current value
result1 = recursivefunc(subset1 of data);
result2 = recursivefunc(subset2 of data);
combined_result
function =recursivefunc(data)
combine(data[0], result1,
{ result2);
return combined_result;
... // handle base cases
}
// process the current data value and use multiple
// recursive calls to process the rest of the data
if (process(data[0])) return result1;
if (recursivefunc(subset1 of data)) return result2;
if (recursivefunc(subset2 of data)) return result3;
return result4;
}
81

Using Multiple Recursive Calls: Subsequence


Let's create a recursive function to determine if a word X's
letters can be found in a word Y in the same order.

Here's how we'll use it:


int main() {
cout << isXinY("cat","craft"); // true
cout << isXinY("LA","UCLA"); // true

cout << isXinY("dog", "do"); // false


cout << isXinY("cat","tack"); // false
}

To solve this, we'll need to use the string class's substr method:
int main() {
string s = "abcd";
cout << s.substr(1); // "bcd"
cout << s.substr(2); // "cd"
}
82

Subsequence

'
isXinY

isXinY
bool isXinY(string x, string y) {

}
83

Subsequence

X = ""
Y = "another string" '
isXinY

isXinY
bool isXinY(string x, string y) {
if (x == "") return true;

}
84

Subsequence

X = "some string"
Y = "" '
isXinY

isXinY
bool isXinY(string x, string y) {
if (x == "") return true;
if (y == "") return false;

}
85

Subsequence

X = ""
Y = "" '
isXinY

isXinY
bool isXinY(string x, string y) {
if (x == "") return true;
if (y == "") return false;

}
86

Subsequence

X = "cap"
Y = "crap" '
isXinY

isXinY
bool isXinY(string x, string y) {
if (x == "") return true;
if (y == "") return false;
if (x[0] == y[0])
return isXinY'(x.substr(1),
y.substr(1));

}
87

Subsequence

X = "ap"
Y = "rap" '
isXinY

isXinY
bool isXinY(string x, string y) {
if (x == "") return true;
if (y == "") return false;
if (x[0] == y[0])
return isXinY'(x.substr(1),
y.substr(1));
return isXinY'(x,
y.substr(1));
}
88

Subsequence

'
isXinY

isXinY
bool isXinY(string x, string y) {
if (x == "") return true;
if (y == "") return false;
if (x[0] == y[0])
return isXinY'(x.substr(1),
y.substr(1));
return isXinY'(x,
y.substr(1));
}
89

Subsequence

'
isXinY

isXinY
bool isXinY(string x, string y) {
if (x == "") return true;
if (y == "") return false;
if (x[0] == y[0])
return isXinY(x.substr(1),
isXinY'(x.substr(1),
y.substr(1));
y.substr(1));
return isXinY(x,
isXinY'(x,
y.substr(1));
y.substr(1));
}
90

Subsequence
This is our complete isXinY function!

Now let's see several other recursive


functions that uses multiple recursive calls!
isXinY

bool isXinY(string x, string y) {


if (x == "") return true;
if (y == "") return false;
if (x[0] == y[0])
return isXinY(x.substr(1),
isXinY'(x.substr(1),
y.substr(1));
y.substr(1));
return isXinY(x,
isXinY'(x,
y.substr(1));
y.substr(1));
}
91

Recursion: Binary Search


Notice how Binary
Search code
recurses on either
Goal:
the Search
first a sorted array of data for a particular item.
half *or*
the second half of
Idea: Use recursion
the array… But and a divide-and-conquer approach!
never both. This is
for efficiency.
Pseudocode: bool Search(sortedArray, findMe)
{
if (sortedArray.size == 0) // empty array
What is return false;
the base
middle_word = sortedArray.size / 2;
case? if (findMe == sortedArray[middle_word])
return true;
Where is the
simplification if (findMe < sortedArray[middle_word])
return Search( first ½ of sortedArray );
code? else // findWord > middle word
return Search( second ½ of sortedArray );
}
92

Binary Search: C++ Code


Here’s a real binary search implementation in C++. Let’s see how it works!

int BS(string A[], int top, int bot, string f)


{
if (numItemsBetween(top, bot) == 0)
return (-1); // Value not found
else
{
int Mid = (top + bot) / 2;
if (f == A[Mid])
return Mid; // found – return where!
else if (f < A[Mid])
return BS(A, top, Mid - 1,f);
else if (f > A[Mid])
return BS(A, Mid + 1,bot,f);
}
}
Recursion: Binary Search
93
top 0 Albert
1 Brandy
2 Carol
3 David
4 Eugene
Mid 5 Frank
int BS(string A[], int top, int bot, string f)
{
6 Gordon
if (numItemsBetween(top, bot) == 0) 7 Grendel
return (-1); // Value not found 8 Hank
else
{ 9 Wayne
int Mid = (top + bot) / 2; bot 10 Yentle
if (f == A[Mid])
return Mid; // found – return where!
else if (f < A[Mid]) main()
return BS(A,top,Mid - {1,f);
else if (f > A[Mid]) string names[11] = {“Albert”,…};
return BS(A, Mid + 1,bot,f);
} if (BS(names,0,10,”David”) != -1)
cout << “Found it!”;
}
}
94

Recursion Helper Functions


So we just saw a recursive version of Binary Search:

int BS(string A[], int top, int bot, string f)


{
...
}

Notice how many crazy parameters it takes?


What is top? What’s bot? That’s going to be really confusing for the user!

Wouldn’t it be nicer if we just provided our user with a simple


function (with a few, obvious params) and then hid the complexity?

int SimpleBinarySearch(string A[], int size, string findMe)


{
return BS(A , 0 , size-1 , findMe);
}

This simple function can then call the complex recursive


“helper function” to do the dirty work, without confusing the user.
95

Solving a Maze
We can also use recursion to find a solution to a maze.
In fact, the recursive solution works in the same basic way
as the stack-based solution we saw earlier.

The algorithm uses recursion to keep moving down paths


until it hits a dead end.

Once it hits a dead end, the function returns until it finds


another path to try.

This approach is called “backtracking” or


“depth first search.”
Solving a Maze
96
bool solvable; // globals
int dcol, drow;
• Below is a recursive version of the maze solver. It’s very similar to char m[11][11] = {
the stack-based version we learned about several weeks ago.
The algorithm first drops a breadcrumb # in the current spot (to Start

"**********",
prevent itself from re-exploring the current spot), then calls itself "*# *",
recursively to explore neighboring slots: up, down, left and right
assuming they haven’t been explored yet (they’re a space ‘ ‘)
"* * * ** *",
• You might wonder where our base case check is, since we don’t seem "*** * * *",
to have an explicit base case check in the code.
• Well, since we add bread crumbs, we are actually making the
"* * ** * *",
unexplored part of the maze smaller and smaller during every call. "* *** *",
• Eventually, our function will find that there will be no where to go,
so it’ll just return – that’s the base case… it’s implicit!
"* * * *",
"* ***** *",
void solve(int row, int col) "* * *",
{ "**********“
m[row][col] = ‘#’; // drop crumb }; Finish
if (row == drow && col == dcol)
solvable = true; // done! main()
if (m[row-1][col] == ' ‘)
{
solve(row-1,col);
if (m[row+1][col] == ' ‘) solvable = false;
solve(row+1,col); drow = dcol = 10;
if (m[row][col-1] == ' ‘)
solve(row,col-1); solve(1,1);
if (m[row][col+1] == ' ‘) if (solvable == true)
solve(row,col+1); cout << “possible!”;
} };
97
Writing a TicTacToe Player
bool gameIsOver() Have you ever wondered
{ how to build an
if (X has three in a row) // X wins intelligent chess player?
return true;
if (O has three in a row) // O wins Let’s learn how – but for
return true; simplicity, we’ll look at
if (all squares are filled) // tie game
TicTacToe!
return true;
return false;
}

GameBoard b;
while (!gameIsOver())
{
move = getBestMoveForX(); // Get X move from AI
applyMove(‘X’, move);

move = GetHumanMove(); // Get O move from human


applyMove(‘O’, move);
}
98
Writing a TicTacToe Player XOX
OO
X

• Here’s the high-level logic for a tic-tac-toe player.


• We’ve basically just got a loop that keeps running until
someone wins or we have a tie
• We alternate getting the next move from the
computer AI and the human, over and over

GameBoard b;
while (!gameIsOver())
{
move = getBestMoveForX(); // Get X move from AI
applyMove(‘X’, move);

move = GetHumanMove(); // Get O move from human


applyMove(‘O’, move);
}
99
Well, it could try playing out all of the So how might a computer
possible moves and responses virtually
and then pick the move that best AI go about picking the
optimizes its chances of winning! best move?

Win for O
Win for O

Tie Win for X Win for X Tie


100
So how can we teach a computer
to perform this deep evaluation?
getBestMoveForX():
Using recursion, of course! 1. Try each X move
a. If that ends the game, log the result
b. Otherwise, see how O would respond
2. Return the best move we found for X
This is called
getBestMoveForO(): SIMULATED
The function
co-recursion!
1. Try each O move
for X calls the
a. If that ends the game, log the result
b. Otherwise, see how X would respond
function for O…
2. Return the best move we found for O

And the function


getBestMoveForX(): for O calls the
1. Try each X move
function for X…
a. If that ends the game, log the result
b. Otherwise, see how O would respond
2. Return the best move we found for X

getBestMoveForO(): SIMULATED And the function


1. Try each O move for X calls the
a. If that ends the game, log the result function for O…
b. Otherwise, see how X would respond
2. Return the best move we found for O Until we hit
Tie Win for X Win for X Tie
the bottom…
101

Chess Is The Same… But Much Deeper!

If each side has 8 possible


So chess AIs tend to only
moves, a game of 50 total
evaluate 5-10 levels deep…
moves would have 850 possible
resulting in imperfect play.
outcomes to evaluate!

In contrast,
Tic Tac Toe
has less than
9! possible boards
to evaluate, so an
AI can evaluate
all of them, all the
way to the bottom
to make the
perfect move!
Object Oriented Design
102

(for on-your-own study)


Recursion
What’s the big picture?
Good software design can dramatically
reduce bugs, reduce development time,
and simplify team programming.

So far, you’ve learned the basics of OOP.

But you haven’t learned how properly


design OOP programs.

It’s like the difference between Uses:


writing simple sentences and writing Every type of
a great novel. software application
from search engines
to autonomous
So go learn this stuff! vehicles.
104

Object-Oriented Design
So, how does a computer scientist
go about designing a program?

How do you figure out all of the


classes, methods, algorithms, etc.
that you need for a program?

At a high level, it’s best to tackle (Well, it’s not easy! Many senior
Mad EE
engineers scientist
are horrible at it!)
a design in two phases:

First, determine the classes


Second, determine each
you need, what data they
class’s data structures
hold, and how they interact
and algorithms.
with one another.
105

Class Design Steps


1. Determine the classes and objects
required to solve your problem.

2. Determine the outward-facing


functionality of each class. How
do you interact with a class?

3. Determine the data each of your


classes holds and…

4. How they interact with each other.


106

An Example
Often, we start with a textual
specification of the problem.

For instance, let’s consider a


spec for an electronic calendar.

Each user’s calendar should contain appointments for


that user. There are two different types of
appointments, one-time appts and recurring appts.
Users of the calendar can get a list of appointments
for the day, add new appointments, remove existing
appointments, and check other users’ calendars to see
if a time-slot is empty. The user of the calendar
must supply a password before accessing the calendar.
Each appointment has a start-time and an end-time, a
list of participants, and a location.
107

Step #1: Identify Objects


Start by identifying
potential classes.
The easiest way to do this is
identify all of the nouns in the
specification!

Each user
user’s calendar should contain appointments for
that user. There are two different types of
appointments, one-time appts and recurring appts
appts.
Users of the calendar can get a list of appointments
for the day, add new appointments, remove existing
appointments, and check other users’ calendars to see
if a time-slot is empty. The user of the calendar
must supply a password before accessing the calendar.
Each appointment has a start-time and an end-time
end-time, a
list of participants
participants, and a location
location.
108

Step #1b: Identify Objects


109

Step #2a: Identify Operations


Next we have to
determine what actions
To do this, we identify all of
need to be performed
the verb phrases in the
by the rowstem.
specification!

Each user’s calendar should contain appointments for


that user. There are two different types of
appointments, one-time appts and recurring appts.
Users of the calendar can get a list of appointments
for the day, add new appointments
appointments, remove existing
appointments, and check other users’ calendars to see
appointments
if a time-slot is empty. The user of the calendar
must supply a password before accessing the calendar.
Each appointment has a start-time and an end-time, a
list of participants, and a location.
110

Step #2b: Associate Operations w/Classes


Next we have to determine what actions go with
which classes. Take each verb like “get a list of
Calendar appointments” and see if you can identify an
associated class it might go with, e.g. “Calendar”.
Of course, don’t forget constructors & destructors!
list getListOfAppts(void)
bool addAppt(Appointment *addme) Verbs
bool removeAppt(string &apptName) get a list of appointments
bool checkCalendars(Time &slot,
Calendar others[]) add new appointments
bool login(string &pass) remove existing
bool logout(void)
check other users’ calendars
Appointment supply a password
has a start-time
bool setStartTime(Time &st) has an end-time
bool setEndTime(Time &st)
bool addParticipant(string &user) has a list of participants
bool setLocation(string &location) has a location
111

Step #2b: Associate Operations w/Classes


We may not need all of the classes we initially come up with. For instance, a one-time appointment is just
a constrained version of a regular appointment. So you now delete classes that are superfluous.

Calendar
Calendar() and ~Calendar()
list getListOfAppts(void) OneTimeAppointment
Of course, our classes need
bool addAppt(Appointment *addme) constructors and destructors!
OneTimeAppointment()
bool removeAppt(string &apptName) ~OneTimeAppointment()
bool checkCalendars(Time &slot, AndsetStartTime(Time
bool now let’s consider &st)our
Calendar others[]) bool setEndTime(Time
other two classes.&st)
bool login(string &pass) bool addParticipant(string &user)
bool logout(void) bool setLocation(string &location)

Appointment RecurringAppointment
Appointment() and ~Appointment() RecurringAppointment()
bool setStartTime(Time &st) ~RecurringAppointment()
bool setEndTime(Time &st) bool setStartTime(Time &st)
bool addParticipant(string &user) bool setEndTime(Time &st)
bool setLocation(string &location) bool addParticipant(string &user)
bool setLocation(string &location)
bool setRecurRate(int numDays)
112

Step 3: Determine Relationships & Data


Now you need to figure out
how the classes relate to each
other and what data they hold.
There are three relationships
to consider:
1. Uses: Class X uses objects of class Y, but may not
actually hold objects of class Y.
2. Has-A: Class X contains one or more instances of
class Y (composition).
3. Is-A: Class X is a specialized version of class Y.

This will help you figure out what private data each class
needs, and will also help determine inheritance.
113

Step 3: Determine Relationships & Data


Calendar A Calendar contains appointments
Calendar() and ~Calendar()
list getListOfAppts(void) A Calendar must have a password
bool addAppt(Appointment *addme)
bool removeAppt(string &apptName) A Calendar uses other calendars,
bool checkCalendars(Time &slot, but it doesn’t need to hold them.
Calendar others[])
bool login(string &pass)
bool logout(void) In general, if a class naturally
holds a piece of data, your
private:
design should place the data in
Appointment m_app[100]; that class.
String m_password;
Of course, you might not get
it right the first time.
In this case, it helps to
“re-factor” your classes.
(i.e. iterate till you get it right)
114

Step 3: Determine Relationships & Data


Appointment
Appointment() An Appointment has a start time
virtual ~Appointment()
~Appointment()
bool setStartTime(Time &st) An Appointment has an end time
bool setEndTime(Time &st)
bool addParticipant(string &user) An Appointment is associated
bool setLocation(string &location) with a set of participants.
private: An Appointment is held at a
Time m_startTime;
Time m_endTime; location.
string m_participants[10];
string m_location; RecurringAppointment
: public Appointment
Now, how about our RecurringAppointment()
RecurringAppointment? ~RecurringAppointment()
bool setStartTime(Time &st)
It shares all of the attributes private:
bool setEndTime(Time &st)
of an Appointment. So should a bool addParticipant(string &user)
int m_numDays;
Recurring Appointment contain an bool setLocation(string &location)
Appointment or use inheritance? bool setRecurRate(int numDays)
115

Step 4: Determine Interactions


Here, we want to determine
how each class interacts with
the others.
The best way to determine the
interactions is by coming up
with use cases…

Use Case Examples


1. The user wants to add an appointment to their calendar.
2. The user wants to determine if they have an
appointment at 5pm with Joe.
3. The user wants to locate the appointment at 5pm and
update it to 6pm.
116

Use Case #1
1. The user wants to add an appointment to their calendar.
A. The user creates a new Appointment object
and sets
its values:

Appointment *app = new Appointment;


app->setStartTime(“10am”);
app->setEndTime(“11am”);

Appointment
Or, had we wanted to, we could pass in the Appointment( Time &start, Time &end,
start and end times to the Appointment
constructor (see right). string loc, string parts[])

B. The user adds the Appointment object to the ~Appointment()


Calendar:
bool setStartTime(Time &st)
Calendar c; bool setEndTime(Time &st)
c.addAppointment(app);
bool addParticipant(string &user)
bool setLocation(string &location)
private:
Time m_startTime;
Time m_endTime;
string m_participants[10];
string m_location;
117

Use Case #2
2. The user wants to determine if they have an appointment
at 5pm with Joe.
Calendar
Hmm… Can we do this with Calendar() and ~Calendar()
list getListOfAppts()
our Calendar class? bool addAppt(Appointment *addme)
bool removeAppt(string &apptName)
Nope.
ItSo
doesn’t
far,
We’ll
solook
good.
need
liketo
Now,
we
add
cancan
this
find
we
toif
bool checkCalendars(Time &slot,
ourwe
Appointment
determine
have an appointment
who’s
class –atlet’s
anatadd
a Calendar others[])
a new
particular
method
appointment?
time…
calledLet’s
Hmmm…
checkTime().
add this! bool login(string &pass)
bool logout(void)
Calendar c;
Appointment *checkTime(Time &t)
... bool isAttendee(string &person)
Appointment *appt; private:
appt = c.checkTime(“5pm”); Appointment m_app[100];
if (appt == nullptr) String m_password;
cout << “No appt at 5pm”;
else if (appt->isAttendee(“Joe”))
cout << “Joe is attending!”;
118

Class Design Conclusions


First and foremost, class design is an iterative process.

Before you ever start to program your class


implementations, it helps to determine your classes, their
interfaces, their data, and their interactions.

It’s important to go through all of the use cases in order


to make sure you haven’t forgotten anything.

This is something that you only get better at with


experience, so don’t feel bad if it’s difficult at first!
119

Class Design Tips

Helpful tips for Project #3!


120

Tip #1
Avoid using dynamic cast to identify common types of objects. Instead add methods to
check for various classes of behaviors:

Don’t do this:

void decideWhetherToAddOil(Actor *p)


{
if (dynamic_cast<BadRobot *>(p) != nullptr ||
dynamic_cast<GoodRobot *>(p) != nullptr ||
dynamic_cast<ReallyBadRobot *>(p) != nullptr ||
dynamic_cast<StinkyRobot *>(p) != nullptr)
p->addOil();
}

Do this instead:

void decideWhetherToAddOil (Actor *p)


{
// define a common method, have all Robots return true, all biological
// organisms return false
if (p->requiresOilToOperate())
p->addOil();
}
121

Tip #2
Always avoid defining specific isParticularClass() methods for each type of
object. Instead add methods to check for various common behaviors that span
multiple classes:

Don’t do this:

void decideWhetherToAddOil (Actor *p)


{
if (p->isGoodRobot() || p->isBadRobot() || p->isStinkyRobot())
p->addOil();
}

Do this instead:

void decideWhetherToAddOil (Actor *p)


{
// define a common method, have all Robots return true, all biological
// organisms return false
if (p-> requiresOilToOperate())
p->addOil();
}
122

Tip #3
If two related subclasses (e.g., BadRobot and GoodRobot) each directly define a member
variable that serves the same purpose in both classes (e.g., m_amountOfOil), then move that
member variable to the common base class and add accessor and mutator methods for it to
the base class. So the Robot base class should have the m_amountOfOil member variable
defined once, with getOil() and addOil()functions, rather than defining this variable directly in
both BadRobot and GoodRobot.

Don’t do this: Do this instead:

class SmellyRobot: public Robot class Robot


{ {
… public:
private: void addOil(int oil)
int m_oilLeft; { m_oilLeft += oil; }
}; int getOil() const
{ return m_oilLeft; }
class GoofyRobot: public Robot private:
{ int m_oilLeft;
… };
private:
int m_oilLeft;
};
123

Tip #4
Never make any class’s data members public or protected. You may make class
constants public, protected or private.

Tip #5
Never make a method public if it is only used directly by other methods within
the same class that holds it. Make it private or protected instead.
124

Tip #6
Your StudentWorld methods should never return a vector, list or iterator to StudentWorld’s private game objects or pointers to
those objects. Only StudentWorld should know about all of its game objects and where they are. Instead StudentWorld should do
all of the processing itself if an action needs to be taken on one or more game objects that it tracks.

Don’t do this: class NastyRobot


{
class StudentWorld public:
{ virtual void doSomething()
public: {
vector<Actor *> getZappableActors (int x, int y) …
{ vector<Actor *> v;
… // creates a vector with vector<Actor *>::iterator p;
// actor pointers and return it
} v = studentWorldPtr-> getZappableActors(getX(), getY());
}; for (p = actors.begin(); p != actors.end(); p++)
p->zap();
}
};
Do this instead:

class StudentWorld class NastyRobot


{ {
public: public:
void zapAllZappableActors(int x, int y) virtual void doSomething()
{ {
for (p = actors.begin(); p != actors.end(); p++) …
if (p->isAt(x,y) == true && p->isZappable()) studentWorldPtr-> zapAllZappableActors (getX(), getY());
p->zap();
} }
}; };
125

Tip #7
Do this instead:

class Robot
{
public:
virtual void doSomething()
{
If two subclasses have a method that shares some common
// first do all the common things that all robots do:
functionality, but also has some differing functionality, use
doCommonThingA();
an auxiliary method to factor out the differences:
doCommonThingB();
Don’t do this: // then call out to a virtual function to do the differentiated stuff
doDifferentiatedStuff();
class StinkyRobot: public Robot }
{
… protected:
protected: virtual void doDifferentiatedStuff() = 0;
virtual void doDifferentiatedStuff() };
{
doCommonThingA(); class StinkyRobot: public Robot
doCommonThingB(); {

passStinkyGas(); protected:
pickNose(); // define StinkyRobot’s version of the differentiated function
} virtual void doDifferentiatedStuff()
}; {
// only Stinky robots do these things
passStinkyGas();
class ShinyRobot: public Robot
pickNose();
{
}

};
protected:
virtual void doDifferentiatedStuff() class ShinyRobot: public Robot
{ {
doCommonThingA(); …
doCommonThingB(); protected:
// define ShinyRobot’s version of the differentiated function
polishMyChrome(); virtual void doDifferentiatedStuff()
wipeMyDisplayPanel(); {
} // only Shiny robots do these things
}; polishMyChrome();
wipeMyDisplayPanel();
}
};

You might also like