Lecture08 Handouts Proto
Lecture08 Handouts Proto
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;
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
6 17 22 3 14 95
“The Lazy Person’s Sort”
5
99322
2774
61492
17
6 17 22 3 14 95
99322
2774
61492
17
99322
2774
61492
17
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
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
Your job is to figure out how the function can use itself
(on a subset of the input) to solve the complete problem.
int factorial(int n) {
if (n <= 0)
return 1;
else
return n * factorial( n – 1 );
}
Recursion Rule #1
12
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
int factorial(int n) {
if (n <= 0)
return 1;
else
return n * factorial( n – 1 );
}
Recursion Rule #2
16
int factorial(int n) {
if (n <= 0)
return 1;
else
return n * factorial( n – 1 );
}
17
So we're going to do a
series of drills to help you
learn how!
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.
Answer: head->next
21
Convert your
Write your function into a
Figure out the function... but recursive
base cases without using version in just
recursion one step
22
FORGOT
BASE CASE
CHECK JAR
23
An integer
input value of An empty An empty An empty
zero array linked list string
// computes n factorial
int fact(int n) {
if (n == ______)
return 1;
Answer: 0
25
if (n == _____) if (n == _____)
The n parameter
return; specifies the count return;
of items in the array.
} }
if (n == _______) if (n == 0)
return _______; return _______;
if (n == _______)
// we'll see this later return true;
// 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 ________;
} }
void f(int n) {
...
f'( ... );
}
f(x) {
Handle the base case
(we just saw this)
arr + 1 ________);
print'(________, n-1
}
Answer: arr + 1, n-1
33
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
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
int f = n * fact(n-1);
fact'(n-1); cout << arr[0] << endl;
return f; print(arr
print'(arr++1,
1,nn--1);
1);
} }
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
Working Through
0 Recursion
int sum(int arr[], int n) { arr 2008
if (n == 0) // base case
n
return 0; // from before
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!
} }
if (________ == nullptr)
return ________;
if (________ == nullptr)
return true;
A list of just one
item is, by
definition, in order. // we'll see this later
return s;
}
Answer: p->val, p->next
59
return inorder'(________);
}
Answer: p->val, p->next->val, p->next
62
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
1300
int biggest(Node *cur)
{
if (cur->next == nullptr)
return(cur->val); 1400
1400
int rest = biggest( cur->next );
return max( rest, cur->val );
}
if (n == 0) // smallest array
return _______;
Answer: -1
68
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
if (n == 1) // smallest array
return _______;
Answer: 0
71
int p = posmax(arr+1,
posmax'(arr+1,n-1);
n-1);
if (arr[0] > arr[p+1])
return 0;
return p+1;
}
73
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
5 19 8 15
x
75
Helper Wrapper
function function
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!
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.
GameBoard b;
while (!gameIsOver())
{
move = getBestMoveForX(); // Get X move from AI
applyMove(‘X’, move);
GameBoard b;
while (!gameIsOver())
{
move = getBestMoveForX(); // Get X move from AI
applyMove(‘X’, move);
Win for O
Win for O
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
Object-Oriented Design
So, how does a computer scientist
go about designing 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:
An Example
Often, we start with a textual
specification of the problem.
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
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
This will help you figure out what private data each class
needs, and will also help determine inheritance.
113
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:
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
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:
Do this instead:
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:
Do this instead:
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.
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.
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();
}
};