The following content is provided under a Creative
Commons license.
Your support will help MIT OpenCourseWare continue to
offer high-quality educational resources for free.
To make a donation or view additional materials from
hundreds of MIT courses, visit MIT OpenCourseWare at
ocw.mit.edu.
ERIC GRIMSON: Good morning.
Professor Guttag has to be out of town today.
My name's Professor Grimson.
I'm going to be subbing for him.
And just to show you how much we value education here at
MIT, and especially in EECS, you get the incoming
chancellor as a substitute teacher.
So how cool is that?
All right, maybe not very cool.
But I'm going to talk to you anyway
for the next 50 minutes.
At the end of last lecture, I think Professor Guttag
introduced dictionaries to you, a really powerful type.
It's got a great capability, which is it's a tool, a data
type that lets you association almost any kind of structure
with a key.
So it could be that you're associating keys with numbers,
you're associating keys with strings.
But you could be associating keys with other things,
including dictionaries.
And I don't know if he showed this to you or not, but those
keys themselves can also be really powerful.
It could be just a string.
But the key could also be a tuple, or a list, an x- and
y-coordinate, or a set of names, or other things.
So it's a really great data structure to use.
Now, one of the things you could ask, though, is, gee,
what if Python or whatever language you're using didn't
come with dictionaries?
Could we still get the same power?
And the answer is sure.
So I want to show you a little example.
So on your handout, if we look at this little procedure up
here at the top, key search, I could build it out of lists.
I could just use a list as my way of storing it.
So let's look at what this procedure.
Says it says, OK, if I've got a list, and I've got a key, K,
I could write a little loop that just walks down the list,
saying, is the first part of each element equal to the key
I'm looking for?
And if it is, I'll return the second part of that element.
And if I get to the end of the list, I haven't found it, I
return none.
So notice I'm making a choice here.
I'm assuming that I'm storing in the dictionary things that
are lists too long, key and the associated value, another
key and the associated value, another key and
the associated value.
But this would work fine.
So if I didn't have dictionaries,
I could build it.
If I wanted to make things that had a more complicated
lookup, I'd have to make sure that that
equality test did it.
But you could see how I might do it.
So the question then can be, so why do we bother with
dictionaries, if we could just use it with lists?
Here's my question to you.
So how long in this implementation is it going to
take me to figure out if something's in my dictionary?
Oh my god, he's asking questions at 10
o'clock in the morning.
This is really terrifying.
Somebody help me out.
How long?
Yeah?
AUDIENCE: Probably on average on the order
the size of the list.
ERIC GRIMSON: Yeah.
On average, it's going to take me half the size of the list.
But that's the same thing as being the size of the list.
So if that list is really long, I'm toast.
If it's not in the dictionary, in fact, I'm going to have to
go all the way through the list before I get to the end
to decide it's not there.
So this is not as efficient an implementation as we'd like.
Now, the flip that is you say, OK, how long does it take for
the dictionaries, the built-in associated retrieval that you
have inside of Python?
And the interesting answer there is that that retrieval
is constant.
It takes the same amount of time independent of the size
of the dictionary.
And that's wonderful.
We're not going to talk about how today.
You're going to see that later on in the term.
But it is something that drives home, if you like, the
point that different data structures
have different costs.
And while some are easier to implement in, they may not be
as efficient, some may be a little more difficult to
implement in, but they are much more efficient.
And that's one of the great things about dictionaries.
The second thing I think you saw at the end of last
lecture, and I want to just highlight in a slightly
different way, is I believe Professor Guttag showed you a
little example of a very simple translation function.
We had a little dictionary.
I'm going to give you a version right here, and
un-comment it.
In fact, there's a little dictionary that simply
associates English words with French words.
And yes, it's pretty simple, but it's a little way of
pairing those things up.
And the idea would be if I have a sentence just a string
that consists of a bunch of words, I'd like to translate
that English sentence, if I can, into French.
And let's look at the code that's going to do it.
I'm going to, again, un-comment it here.
It's on your handout.
I want to walk you through it.
So first thing I'm going to do is actually write a little
procedure the says, given a word in English, I want to
find the corresponding word in French.
And that's pretty easy.
It says, if you give me a, word, and you give me a
dictionary, I'm just going to look it up in the dictionary.
That's just that associative retrieval.
It does all the work to go into the dictionary, say, if
that's a key for something in the dictionary, it's going to
give it back to me.
And in particular, I can get it back by simply retrieving
it out of the dictionary.
And notice I'm doing this a little more carefuly.
I'm not just directly going, and saying, give me the thing
corresponding to the word in the dictionary, because it
might not be there.
So that test basically says, if the word is in the
dictionary, it'll return true.
And in that case, I get out the corresponding element.
If it's not in the dictionary, I'm just going to be stuck
with the English words, so we'll just return the word.
Now, let's look at the more fun part of it.
I now want to translate a sentence.
And the sentence is a string of words.
I need to find where the words are.
I need to look them up.
I need to gather together a translation.
So notice what the heart of this thing does, right down
here in this part of the loop that I'm going to highlight
right there.
Oops, I mis-did that.
Let me try it again, right there.
What's that doing?
It's walking down the sentence.
A sentence is some big, long string.
And it says for each character C in the sentence, I need to
find where the word is.
So I'm going to keep walking along until I find a place
where there's a space.
So that first test there is saying, if C is not a space,
just add it onto the end of the word.
Oh, yeah, I need to have inititalized word, which I did
right up here.
I said, let's set word to just be an empty string.
So I'm just walking along the sentence until I find a space.
Until I do that, I'm just gathering together all of
those characters.
So I'm building up the word.
When I get to the place where in fact C is a space, then
here's what I'm going to do.
I'm going to translate the word using that procedure I
just wrote.
It looks it up in the dictionary.
It either gives me back the French word, if it's there,
and if not, it just gives me back the English word.
And, oh yeah, I'm doing a whole long sentence.
So I need to add it together.
So I'm simply going to add it to the end of what I've
translated so far.
And I'm doing a slightly funky thing in there.
I'm inserting a space in between, just to keep the
words separate.
And of course, I need to have initialized translation up
here, which I did, to be an empty string.
So what's this do?
Walks along character by character.
When it gets to a space, it says, I've got a word.
Find the translation, add it to the end of this other
internal variable, and keep going.
And I'll do that until I get to the end of that sentence.
And then I'll just return the translation.
Nice little iterative loop.
And it's simply using two pieces to
make all of this happen.
I lied too you-- sorry, I didn't lie.
I misspoke to you.
Which is, what's that funky thing at the end?
What's it returning?
It's returning this strange thing this says,
"Translation," starting with the first element, a copy of
all of that, plus a translation of the word.
So what assumption am I making about the inputs that causes
me to do this strange thing at the end?
I'm actually making two assumptions about my input.
But I'm making a particular assumption.
Why do I not just return translation when I'm done?
Why am I doing this last piece?
Anybody help me out?
Boy, I notice everybody sits way back at the back, where I
can't make any eye contact.
How do I characterize words in my string?
They have to end with a space.
Ooh, is there a space at the end of my example?
So I guess I haven't shown you an example.
But I'm assuming if I give you a sentence, you don't usually
have spaces at the end of sentences.
So I'm making an assumption, is that the words, except for
the last one, are characterized by spaces.
So this last little piece here is to get the very last word,
and translate it.
Because it won't be a space.
Let's try a couple of examples.
Let's just un-comment these, and see if this does it.
Yes, commenting them again is not a good idea.
We'll go the other direction.
Looks like it did right.
John-- we should have translated John into Jean.
I don't know what the hell Eric is in French, but John
mange du pan, Eric boit du vin.
John has that right.
He's eats bread.
I like to drink wine.
And, of course, tout le monde aime 6.00.
Or in this case, everyone likes 6.00.
You just got HASS credit, by the way, for listening to me
do French really badly.
OK, as I said, I made an assumption, which that the
words end in spaces.
What's the other assumption I made here?
I know you're just looking at the code.
But I made another assumption inside of my code.
And it's always good to figure out what
those assumptions are.
Yeah?
AUDIENCE: That the first word is a noun, so we don't have to
translate it?
ERIC GRIMSON: I didn't assume that.
I could if I was doing a more clever translation.
But I'm actually making no assumptions
linguistically here.
I'll give you a hint.
How many spaces do I have between words?
Just one.
I'm sort of building that into this assumption.
It's a reasonable assumption to make.
But it points out, if I wanted to improve the code, I should
think about these other cases.
And of course, to build a real translation system, I'd need
something much more sophisticated.
So this shows you little example of using the
dictionaries.
But I want to use this to lead into the main part of today's
lecture, which is, why did I write this separate little
procedure up here called translate word?
I could have buried that inside the code.
And the answer is twofold.
First one is it saves a small amount of code.
If I'm going to use it in multiple places, I don't want
to rewrite the code.
And that actually is valuable, not just to save you typing.
It means I only have to debug it once.
It's one piece of code.
But the real reason I wanted to introduce it is it gives me
what you might think of as modular abstraction.
Those are fancy words that basically say I am isolating
that function in one place.
If I decide to change how I'm going to translate words, I
don't have to search through all of my code to find all the
places where I was using that.
I just need to change the definition of that procedure.
And I agree, in a simple example like this, it looks
pretty simple.
But if you've got a million lines of code, you're using it
10,000 different places, you don't want to have to find all
the places to change.
So this is an example of modular abstraction, isolating
where that thing is.
And that is an example, in fact, of a general
problem-solving principle we're going to use, called
divide and conquer.
Divide and conquer is basically the idea of taking a
hard problem, and breaking it up into some simpler pieces,
or into smaller problems, where those smaller problems
have two properties.
Small problems are easier to solve than the original one.
More importantly, the solutions to the small
problems can easily be combined--
I want to stress easily--
to solve the big problem.
And we're going to look at a bunch of examples
today to show that.
This is a really old idea.
Julius Caesar used it.
He did it in Latin.
My Latin is terrible, but it's something like divide et
impera, which literally means divide and rule.
That's how he created the Roman Empire.
The British knew this really well.
That's how they control the Indian subcontinent
brilliantly for several decades.
Ben Franklin actually knew it.
In particular, he knew how good the British were at this.
So there's a famous quote.
You may well remember-- when he signed the Declaration of
Independence, he said, quote, "We must all hang together, or
assuredly we will hang separately," meaning divide
and conquer is going to be a real problem.
So second HASS credit for you.
We just did some history.
Why are we going to use it today?
Boy, you're a tough audience, I noticed, by the way.
That's all right.
We're going to use it today, because we're going to use it
as a tool for problem-solving.
And in particular, we're going to use it with
one particular example.
You're going to see divide and conquer later on in the term,
when Professor Guttag talks about algorithm design.
Today, I'm going to show you one great technique for doing
divide and conquer kind of algorithms.
And that is the technique of recursion.
How many people here have heard that term used before by
computer scientists?
OK, not to worry.
If you've heard it, you probably think that it's a
really subtle programming technique.
I'll let you in on a secret.
That's a PR job.
It's what computer scientists tell you to make you think
that they're much smarter than they really are.
It's not subtle, and it's much more than
a programming technique.
And we're going to talk about it.
Now, it gets used not just as a programming technique.
It gets used two other ways in computer science.
And I'm going to talk about both.
It's both a way of describing or defining problems, so it's
a way of characterizing a problem independent of how we
might implement it.
And it is a way of designing solutions.
So an example of divide and conquer.
Let me give you an example just to show you of why it's a
way of defining a problem.
Consider the part of the legal code that defines the notion
of a natural-born US citizen, and remind you, to be eligible
to run for president, which I hope you all do, you have to
be a natural-born US citizen.
Definition has two parts.
Part number one, anyone born within the United States is a
natural-born citizen.
Part number two, anyone born outside the United States,
both of whose parents are citizens of the United States,
is a natural-born citizen, as long as one parent has
lived in the US.
A little more complicated.
What does that actually say?
Well, it's got two parts.
And that's what a recursive definition has.
The first part is simple.
You're born here, natural-born US citizen.
Notice the second part.
It says, you weren't born here, you might still be a
natural-born citizen.
But what you have to do?
You have to decide if your parents are US citizens--
which they could be by having been born here, but there are
other ways to be naturalized--
and that may require you determining if your
grandparents are US citizens, which may require you to-- ah.
So you may have to chain down several sets of problems.
Those two parts are what exactly we're going to use
when we talk about recursive definitions.
There's what we call a base part, which typically
describes the simplest version of the problem.
And then there is an inductive part that tends to describe
how you reduce the problem to simpler
versions of the same problem.
So in fact, let me write those both down.
We have--
it's called a base case.
This typically gives us a direct answer.
It just tells us very simply, using very simple methods,
whether this is something that satisfies that recursive
definition.
And there is the recursive or inductive case.
Here, you reduce to a simpler version of the same problem,
plus some other simple operations.
OK, again, a bunch of words.
Let me show you some examples.
I'm going to start with three or four different examples,
just to show you how quickly we do this.
What I want you to see in all of these examples is that when
I describe the problem, I'm describing it in terms of
what's the simplest case, and then how do I build solutions
to bigger problems from solutions to smaller versions
of the same problems?
OK, here's the first case I'm going to do.
Suppose I tell you I want to build a little procedure to do
exponentiation, integer exponents.
I want to compute b to the n.
But I tell you I've got a really cheap machine, and all
I can do is multiplication.
So I can't use exp.
Mathematician--
a course 18 person would say, what's b to the n?
That's b times b times b, n times.
How could I solve this?
Well, recursively, I'd like to say, suppose I could solve
smaller versions of the same problem.
And if somebody gave me a solution to that smaller
version, how would I build a solution
to the bigger problem?
Oh, that's easy.
That's the same as b times b to the n minus 1.
All right, I can see you're all going, well, duh.
I guess that's what chancellors know.
They're not very bright.
Of course, you know that.
But notice what I did.
I've now reduced it to a simpler version of the same
problem, recursively.
This basically says I wanted to solve b to the n.
It's b times b to the n minus 1.
Oh, yeah, but I've got to figure out
when to unwrap this.
So there's an if there.
And that's true as long as n is greater than one--
actually, as long as n is greater than 0.
We'll do it that way.
And if n is equal to 0, I know the answer is just 1.
I know you don't believe me, but that's really cool.
Why is it really cool?
What do I have?
I have a base case.
And if it's equal to 0, I know the answer right away.
If n is bigger than 0, oh, let's assume that I've got
somebody who will give me the solution to
that smaller problem.
When I get it, I can just use it, do the
multiplication, and I'm done.
And so, in fact, here is a simple piece of implementation
of that, which I know is not in your handout, but I'm just
going to show to you, because I simply want you to see the
form of it.
It's an exact Python translation
of what I just said.
It says, if I want to take an exponent of b to the n, if n
is equal to 0, just return 1.
If not, solve a smaller version of the same problem
right there.
Call to the same procedure, same function,
but different argument.
And when I get that answer back, just multiply it by b,
and return that.
This may look a little funky.
This is the kind of thing that your high school geometry
teacher would rap your knuckles with, although
they're not allowed to do that anymore.
You don't define things in terms of themselves.
This is not geometry.
This is programming.
And this is a perfectly legal recursive
definition of a problem.
It will stop, because it will keep unwinding those recursive
calls until it gets down to the base case.
And I'm going to show you an example of that in a second.
I want to show you a much nicer example of recursion.
And again, part of my message here is when you get a
problem, don't instantly start writing code.
Think about, how do I break this down recursively?
So I have brought some very high-tech tools with me today.
This is my version of the tower of Hanoi problem.
How many people know the problem of the tower of Hanoi?
Only a few.
OK, so here's the story.
There's a temple in Hanoi staffed by a bunch of monks.
In that temple, there are three tall,
jewel-encrusted spikes.
Mine aren't nearly as fancy.
And there are 64 golden disks, all of a different size.
Stack starts out on one of those spikes, and the monks
move one disk at a time.
Their goal is to move the entire stack of 64 from one
spike to another.
And the rules are they can it of one disk at a time, but
they can never cover up a smaller disk
with a larger disk.
I have to tell you, I don't know what happens when they
move the entire stack.
I mean, the universe ends, or you all get A's in 600, or
something equally as cool.
Question is, could we write a piece of code to help the
monks, to tell them how to move them?
All right, so let's figure this out.
And I'm going to show you some examples.
So I'm going to move a stack of size one.
Well, that's not very hard.
Watch carefully, because you're going to
write code to do this.
I want to move a stack of size two, so I've got to just make
sure that I move the bottom one off.
That's not so hard.
Now, I want to move a stack of size three.
I've got to be a little more careful, because I can't cover
up the smaller one with a larger one.
But that doesn't look very hard.
Got the solution, right?
Now, we go for stack of size four.
This one definitely takes a little bit more care, because
you really can't cover it up.
But as long as you do it right, you can actually move
the-- oops, and I didn't do it right.
You've got to move the pieces in the right way.
I do this for taking money off of Harvard students in Harvard
Square, by the way.
Got it?
Real easy to see the solution, right?
You could write code for that right now.
I'm not going to do five, but it's really
easy to see the solution.
Yeah.
I blew it, too.
I did one move I had to backtrack on.
Let's think about this recursively.
What's the recursive solution?
Break it down into a simpler problem, or a problem of a
smaller size.
Ah, here's the solution.
To solve this, I've got a stack, I've got a stack I'm
going to, I've got a spare stack.
What's the solution?
You take a stack of size n minus 1, move it onto the
spare stack.
Now I've got a simple problem.
I can always move a stack of size one.
And then I move a stack of size n minus 1 to the target.
And, of course, how I move a stack of size n minus 1?
Well, I just unwrap it one more.
That's a really easy explanation, right?
And it's really easy to write code to do exactly that.
So let me show it to you.
Again, I know this isn't in your handout, but I just
wanted to see it.
And you could write this yourself.
I'm going to write a little procedure
right here called Hanoi.
What are my arguments?
Going to tell how big a stack there is.
That's m.
And I'm just going to give it labels, the from stack, the to
stack, and the spare stack.
Look how simple the code is.
Says, if it's a stack of size 1, just move it.
I'll just print out the instruction.
Move it from the from stack to the target
stack, or the to stack.
If it's bigger than 1, what do I do?
I move a stack of size n minus 1 onto the spare stack.
I move a stack of size 1, which is what's left, onto the
target stack.
And then, I move that stack over here that's on the spare
stack over to the target stack.
It's what I just showed you right there.
OK?
So let's try it.
Yes, I know I still like French.
We're going to do Hanoi.
Move a stack of size 1.
And we'll just give this some labels, just
from, to, and spare.
Well, duh.
You just move it there.
All right.
Let's try a stack of size 2.
It's just what I did.
I'm sure you remember that.
Let's be a little more daring here.
There's the solution to move a stack of size 5.
I'll let you check it separately,
make sure it's right.
One of the things you can also see here--
I'm not going to talk about it.
You might think about it, ask your TA in recitation is, how
long does it take to solve this problem?
How long is it going to take those monks to actually move a
stack of size 64?
I'll give you a hint.
The answer is measured in billions of years.
This is an exponential problem.
And you can see that growth right away.
That's a separate topic.
But notice coming up with that solution on your own, maybe
not so easy.
Thinking about it recursively, very easy to think about.
And that's the way we want to look at it.
OK, let me give you another example of breaking a problem
down recursively, and then writing the code to do.
I want to decide if a sentence is a palindrome.
Remember what a palindrome is?
It is not an ex-governor from Alaska.
It is a string of characters--
I guess an airport in Alaska-- it's a string of characters
that have the property that they read the
same front to back.
The most famous one in English-- which is, of course,
amusing because it's attributed to a Frenchman-- is
Napoleon supposedly saying, "Able was I ere I saw Elba."
Same thing back to front.
How would I write a piece of code to decide if something is
a palindrome?
I'm going to do it in a second.
You've got it on the handout.
But let's think about it for a second.
What would the base cases be--
or base case?
Somebody help me out.
What's a good base case here?
What's the shortest possible sentence I could have?
I?
A?
Don't worry about whether it's a legal sense of not.
It might need a verb in there.
Base case is presumably, if I've got a string one
character long, it's a palindrome.
If I've got a string zero characters long, it's probably
a palindrome as well.
How would I take a longer string and break it down into
a simpler version of the same problem to decide if something
is a palindrome?
Anybody want to help me out?
Is that a hand up there, or are you just
scratching your head?
Well-- yeah?
AUDIENCE: Maybe take the first and the last element, see if
they're equal.
If they are, then cut them off, and--
ERIC GRIMSON: Yeah.
What's a palindrome?
The easy way to start it is take the things at the end,
first and last character.
If they're not the same, it doesn't matter what's
happening in the middle.
This thing can't be a palindrome.
So let's check those.
And oh, yeah, if those are the same character, and I pull
them off, what do I have?
I have a smaller version of the same problem.
I have a new sentence that's now two characters less.
Do the same thing.
Say, is that a palindrome?
So if these characters match, and that's a
palindrome, I'm done.
How do I tell if that's a palindrome?
Check if their two end characters match, and the
things in the middle.
So let's look a little piece of code to make this happen.
I'm going to do it in a couple of pieces.
Here's the first piece I'm going to write.
I'm going to walk you through it.
First thing I'm going to do is I'm going to do this outside
of the recursive call is I need to convert a string
that's put in to make sure that, in fact,
it's in a form I want.
So I don't care about the spaces.
Able was I ere I saw Elba.
The spaces aren't in the same place.
That's OK.
It's really the characters.
And I don't really care about capitalization.
So this little procedure basically says, given a
string, convert it all into lowercase-- and if you haven't
seen that, I'm just importing from a module called string
some built-in procedures--
and this one simply takes the string, and
makes it all lowercase.
And then what do I do?
Well, just like we did before, I'm just going to walk down
that string, gathering together all of the
characters.
So this little loop just says, let's initialize ans to be an
empty string.
And then for each character in s, if it is a lowercase
character-- and that little test simply does that.
It says, if it's in the set of lowercase characters I'm going
to add it to the end.
And when I'm done, I'm just going to return ans.
Going to return the answer.
A little procedure.
And, by the way, this is a nice piece of programming
style, as well.
I want to separate out things that I want to do once from
the things I'm going to call multiple times.
So I don't need to re-check every time that my string is
all lowercase, I'm just going to convert it out.
Now, let's look at how do we test this.
Well, it's literally just a translation of what we said.
But let's look at the pieces.
It says, if I give you just a string of characters--
I've gotten rid of the spaces, I've made it all lowercase--
what does it say to do?
I've got to check for the base cases.
And here, I'm actually going to be careful.
We could have discovered this if we programmed.
There actually are two base cases here, which is, is the
string of length one, or is of length zero?
Why would I end up with two base cases?
Why don't I just check for a string of length one?
Yeah?
AUDIENCE: [INAUDIBLE]
ERIC GRIMSON: Exactly.
I can have an odd or an even number of characters.
So as I'm clipping them off the ends, I might end up with
nothing in the middle, or I might have one
in the middle exactly.
And we might have discovered it if we tried programming it.
But right, exactly right.
So I can capture that by just saying, if the length is less
than or equal to 1, which gets both of those.
In that case, we know the palindrome will return true.
Otherwise, what do we do?
Well, we do what the gentleman over here suggested.
We take the first and the last character of the string--
again, remind you s with an index of minus 1 goes
backwards 1, if you like, and gives me the last character.
If those two characters are the same, I've now reduced it
to a simpler version of the same problem.
So I simply say, if that's true, and everything else is a
palindrome, return true.
Now, if you've not seen this particular little funky form
right here, that is taking string s, and saying, give me
a copy of everything starting with the first--
which means not the 0th element--
and ending with everything up to, but not including, the
last element.
We'll use this to make copies of other kinds of lists.
But that's all that's doing, is saying, give me what's in
that string, throwing away the first and the last element.
And that gives me exactly the recursive call I want.
I'm now saying what?
If the first and last character are the same, and if
what's left over is itself a palindrome, I'm golden.
Now, let me just wrap all of that up in a
little piece here.
I'll un-comment this.
Which is simply going to say, I'm going to print out, or put
a comment in it.
And I'm simply going to put the two pieces together.
Given a string, I'm going to convert it into all lowercase
characters.
And then I'm going to check, is this thing a palindrome?
So again, let's try some examples.
So we'll pick on Professor Guttag.
His name is almost a palindrome.
Not quite.
So we're going to give him a name change.
It helps if I can type.
Oh, good.
Guttag is not, but Guttug, whatever that means in German,
is, because the Gs, the Us, and the Ts all match up.
Oh, and let's see.
Let's try a couple of other ones here.
And I actually typed these in.
We'll check to see if Napoleon really was right when he used
his palindrome.
If you can't read this last one, it says, "Are we not
drawn onward, we few, drawn onward to--" and I can't read
the tail end of that--
"to new era." And if we try both of those, they are.
What's my point, you're wondering.
I solved this problem by simply breaking it down into
simpler versions of the same problem.
That's the tool that you want.
If you were just trying to think about, how do I keep
track of my indices as I'm walking along?
I'm going to come in from both ends, so I've
got to add and subtract.
And I got to make sure I'm checking things.
You could write a nice iterative loop
that would do it.
Actually, I'll take back the word nice.
You could write an iterative loop that would do it.
But it is not crisp, it's not clean, and it's really
easy to screw up.
Or you could say, let's take advantage of recursion.
Let's just break it down into a simpler
version of the same problem.
You'd get a very nice, simple piece of code.
Now, this may still feel a little mysterious.
I wouldn't blame you if it did.
So let's do the following.
I'm going to comment these out so that we're not constantly
looking at them.
And I'm going to show you what happens if we actually look
inside of this thing to see what's going on.
So just give me a second here.
We're going to comment all of those out.
And let's build a version of this that just prints out as
we go along.
So I'm going to show you right here.
It's the same basic pieces.
And actually, I realized I need to leave
is characters around.
Let me go find my is characters part--
two characters part.
Sorry, give me a second here.
Going to need that.
So we'll un-comment that region.
That's just doing the conversion for us.
I think this is going to work.
Let's look at what we're doing here.
This is exactly the same thing.
But I'm just going to put some print statements in it, which
as I'm sure you've already heard from Professor Guttag,
is a good thing to do as well.
I want you to see what happens.
So the only changes I'm making here are when I come into the
thing that's doing the checking, I'm going to print
out a little thing that says, what are you calling me with,
so you can see how it does the work.
And then, if it's a base case, I'm going to print out a
statement that says I'm in the base case.
And otherwise, I'm just going to print out something that
says, here's what I'm about to return for the piece of the
string I'm looking at.
So it's just instrumenting, if you like, what I'm going to do
inside of here.
And the other piece I'm going to do is I'm going to have a
little space called indent, so that every time I call a
smaller version of the problem, I'm just going to
indent over a little bit, so you can see how it unwraps it.
And if we do this, just run that through.
Let's try it.
So if I call is palindrome print with Guttag--
it would really help if I could type, wouldn't it?
Yes, it would really help if I could type.
You're all being really polite, going, ah, he missed a
character there.
But we're not going to tell him, because he's going to
have to figure it out for himself.
Administrators cannot possibly do this.
Ah, notice what it did.
This is how you can see the recursive unwinding.
It said--
I'm going to check this thing out.
It said way up there-- actually,
I'll just do it here.
It said, I'm going to call it initially with Guttag.
Oh, the two Gs worked so that recursively--
and notice the push-in here-- said, I'm calling it again
with U-T-T-A. Oh, that one didn't work.
So I didn't have to check any further, and I pushed it out.
On the other hand, if I do this, ha, you can see all of
the stages.
You can see it unwinding it.
It says, to decide if Guttug is a palindrome,
well, I check the ends.
And I've got to check U-T-T-U. I don't know what that is.
Something interesting.
Which says, I got to check that.
Which means I've got a check, oh, there's that base case of
the empty one.
I can now return true-- and I'm going to open this up so
you can see it--
from the base case, true for that, true for
that, true for that.
Let's do one last one, assuming I can type.
I can't.
Well, you get the idea, right?
It's a little messy.
But we go up a little bit, you can start seeing--
I'm obviously just going to run out of those pieces
there-- but notice what it's doing.
It's stripping off each of those characters in turn.
You can see how deep this goes in, which is why you've got
all those weird spaces there as I keep going in.
But it starts off by saying, look at
that, oh, look at that.
I've got to look at--
[GIBBERISH]
Which says, I've got to look at [GIBBERISH]
You get the idea.
But notice the key thing.
I'm simply reducing it to a simpler
version of the same problem.
You can also see that to get the answer out, I've got to
stack up a whole bunch of things.
All those indents are basically held operations,
which is one of the properties of a recursive procedure.
You have to hold onto intermediate things.
And that can cause, in some languages,
some efficiency issues.
But in terms of solving the problem, this is really easy.
I've just broken it down into those simple pieces.
And that's really nice.
So let me finish up with one last example
of a recursive procedure.
Again, my goal here is to let you see, what am I doing?
I keep repeating this, but it's important.
To solve a problem, figure out how to break it down into a
simpler version of the same problem.
One of the classic examples of recursion is Fibonacci.
How many people here know the Fibonacci numbers?
A few more, good.
For those of you who don't, here's the story.
Probably heard the phrase, "They breed like rabbits."
It's been used to describe a population that the speaker
thinks is growing too quickly.
Works with rabbits.
It works if you put two pennies in a drawer.
There's a whole bunch of ways in which this happens.
The history of this is actually very old.
It goes back to 1202, when an Italian mathematician named
Leonardo of Pisa, also known as Fibonacci, developed a
formula that he thought would help him quantify the notion
of how rapidly do rabbits breed.
And here was his model.
His model is not great, and we'll see that in a second.
But here's his model.
You start with a newborn pair of rabbits, one
male and one female.
You put them in a pen.
You assume that rabbits are able to mate at
the age of one month.
And you further assume that they have a one-month
gestation period.
So they can produce offspring at the end of a month.
Finally, let's suppose that these mythical rabbits never
die and that the female always produces one new pair--
that is, a male and a female every month from its
second month on.
The question is, how many female rabbits will there be
at the end of a year?
Hm.
OK, let's see if we can figure this out.
OK, at month 0, when it starts off, there's 1 female rabbit
and 1 male rabbit.
At the end of the first month, there's still one female
rabbit, but she's now pregnant.
So there's still 1.
At the end of the second month, what do we have?
Well, that initial female is still there.
And she has produced one pair.
So there is a second female.
At the end of the third month, that initial female has again
been pregnant and has produced.
And these two are still there.
So there are 3.
At the end of the fourth month, both of these females
have now produced offspring.
And those 3 females are now pregnant.
There are 5.
At the end of the fifth month, those 3 females
have produced offspring.
Those 5 are now pregnant.
There are 8.
And at the end of the sixth month, there are 13.
What did I just describe?
I just described a nice recursive relationship.
That says, gee, there ought to be a base case.
And there ought to be a recursive case.
And, in fact, in this case, we can describe this very nicely.
The number of females at month end is the number of females
there were two months earlier--
because they've all given birth, so that's
that many new females--
plus the number of females there were at the previous
month, who are now all in whelp.
Ah, that's a recursive relationship.
If I wanted to figure out how many females there are at
month end, what do I have to do?
Well, I just have to say, what's the base case?
Because there's the recursive case.
And the base case is--
let me do it this way.
If n is equal to 0 or 1, it's 1.
And as a consequence, I should be able to figure out how
quickly do rabbits breed, at least according to 12th
century Italian mathematicians.
A couple of things to notice here, by the way.
Sort of similar to what I saw in the towers of Hanoi
problem, I'm going to have multiple recursive calls.
To solve this problem of size n over here, I've got to solve
two smaller problems, and then do the simple operation of
adding them together.
That's OK.
It's going to change the complexity of my algorithm,
but it's perfectly fine to have multiple recursive calls.
And as I saw in my case of the palindrome, I may have more
than one base case.
That's also OK, as long as I can ground this out.
All right, so let's just look at it.
How would we write this?
Let me get rid of that, comment that out.
We can write a little
procedure to compute Fibonacci.
And there's the procedure.
It's in your handout.
I've got some things up at the top here that are just making
sure that I got the right kinds of arguments to pass in,
which is a cleaner, crisp way of doing it.
But what do I say?
I say, if either x is 0 or x is 1, two base cases, just
return the answer, which is 1.
Otherwise, solve two--
right there--
smaller sub-problems.
And what's the simple operation?
Just add the two things together, and return it out.
And if I do this, we can check to see if we actually compute
Fibonacci properly.
All right, let's do a test fib of 0.
It says, ah, fib of 0 is 1.
To do a test fib of 1-- there's my other base case.
Aha, it says, there they go.
And now, let's do something a little larger.
There it is.
As it gets each computation it's doing, it says, to get
fib of 3, I've got to solve fib of 0, fib of
1, and fib of 2.
And there's the answer.
And just to make sure we're really doing this well, just
reproduce my table over there.
Do I expect you to get all of that code right away?
No, although it's pretty easy to understand.
What I do expect you to see, though, is notice what I did.
I took a recursive problem, and broke it down
into simpler pieces.
And then I used the solutions of those pieces to give me the
solution to the larger problem.
By the way, Leonardo Pisa had it wrong.
In 1859, as I'm sure many of you know, a wonderful
Australian farmer named Thomas Austin imported 24 rabbits
from England to use as targets for hunting.
10 years later there were 2 million rabbits being shot and
trapped in Australia.
And fib, while it grows fast, doesn't go quite that fast.
So Leonardo of Pisa had it wrong.
Of course, as you probably also know, Australia, and I
think New Zealand, have the wonderful property of having
more sheep than people.
I don't know what that says, other than it's a great
recursive problem.
Fibonacci, by the way, actually
shows up in other places.
Again, it may not be a perfect model of rabbit populations,
but it has some other really interesting properties.
One of them is, if you let n get close to infinity, and you
look at the ratio of fib of n over fib of n minus 1--
it sounds like a strange thing to look at.
It basically tells you how quickly does fib grow.
Does anybody know the answer to this?
The golden ratio, 1 plus root 5 over 2, which Leonardo--
the other Leonardo, the really good Leonardo-- by the way,
used to design buildings with.
So there's a very strange connection.
The other one thing I like about this is that Fibonacci
shows up, actually, a lot in nature.
So, for example, did you know that the number of petals on
most flowers is a Fibonacci number?
For example, black-eyed Susans, 13 petals.
Field daisies, 34 petals.
You might ask why.
Oh, sorry.
I'm out of time.
I won't answer it today.
But you can ask Professor Guttag next time around why it
is that petals come in Fibonacci numbers.
What's the message of this lecture?
Recursion, divide and conquer.
Break a problem down into simpler
versions of the same problem.
Life is really easy.
And you'll see Professor Guttag next time.