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.
PROFESSOR: So we're going to
pick up where we left off last
Friday on recursion.
Well first of all, can anyone
tell me what recursion is or
what a recursive function is?
No one knows.
OK.
AUDIENCE: To divide
and conquer.
PROFESSOR: OK.
It's a divide-and-conquer
technique.
How does it do it?
It's a recursive function.
AUDIENCE: You have
a base case.
And usually a return function,
you return
the value of something.
But because you keep returning
the function back.
PROFESSOR: Right.
So it's a function that
calls itself.
And it works by one, identifying
a base case, which
is the smallest sub-problem
possible.
And then in the other case, the
recursive case, it tries
to chunk the problem down into
a smaller sub-problem that it
then solves by calling itself.
So we went over a
few examples.
And one of the things that we
wanted to talk about is that
for many problems, a recursive
function can be written
iteratively, or actually
for most problems.
So there usually is some sort of
a subjective choice in how
to write a function.
And it really comes down to
ease of understanding.
So what we've done is we've
taken a couple of the
algorithms that we
showed last week.
And we've written them
iteratively.
We also have the recursive
version.
We'll compare and contrast and
see where we would want to use
a recursive function.
So on the screen on the
left here, we have the
multiplication version, the
recursive multiplication, that
we showed you last week.
It's a little different because
it turns out there's a
couple of simplifications
you can make.
Can someone walk me through
how this works?
So what's my base case first?
AUDIENCE: 0
PROFESSOR: 0, right?
And so obviously when n is 0, if
we multiply by 0, then our
result is 0.
Now there are two
recursive cases.
And I'm not really sure how to
explain this intuitively.
But let's say that
my n is positive.
So I'm multiplying
by a positive n.
Well, then all I'm going to do
is take m and just add it to
the recursive version of
itself n minus 1 times.
That's how to read that.
And then analogously, if n is
less than or equal to 1, then
I'm going to take negative m and
add the recursive result
of n plus 1 times m.
It is not too intuitive right?
So if we implement it
iteratively though, I think
it's a little easier
to understand.
Now this is also a subjective
judgment,
so you might disagree.
You're free to.
Here's our base case again. n is
equal to 0 or m is equal to
0, return 0.
In this case though, if we don't
have the base case, then
we're going to initialize
the result variable.
And then for n is greater than
or equal to 1, we're just
going to enter a while
loop and keep adding
m to result n times.
It's a little bit easier to
understand I think than the
recursive version.
And then same thing for--
Oh I have a bug here.
Same thing for n is less than
equal to negative 1.
So I'm going to run the two
versions of this function.
So here's the recursive
version and here's the
iterative function.
They both return the
same exact thing.
They both work in generally
the same manner.
It's just that in one case we're
using recursion to solve
it, which I don't find
too intuitive.
And the other case, we're
using WHILE loops.
All right.
So in this case in my opinion,
writing this iteratively, it
makes a little bit more
intuitive sense.
But in other cases, let's say
good old Fibonacci, we can
write the recursive
version and then
the iterative version.
So here's recursive Fibonacci.
We have our base
case or cases.
And then we have our
recursive case.
You can almost rewrite
the mathematical
formula from this directly.
All right now.
Now what was it?
So here's the iterative
version of Fibonacci.
We still have our base case.
But when we get to what was the
recursive case, we have to
do a lot of bookkeeping.
We have to save off the previous
Fibonacci what we're
currently computing.
And then we have to iterate,
get the next Fibonacci and
then save off the prior
versions of it.
This is all stuff that in the
recursive version gets done
for us by virtue of just calling
another function.
So this is an example of a
case where your recursive
version is actually a little
bit easier to understand.
Doesn't mean that it's
more efficient.
And later on in the class we'll
actually use this to
talk about complexity.
But the left version I think is
easier to understand than
the right version.
Are there any disagreements?
If you disagree, I'm not
going to bite you.
So anyway, we can run this.
And we can see that the
output's identical.
AUDIENCE: What's x-range?
PROFESSOR: x-range?
Probably something I shouldn't
have put in there.
x-range is like range, except
that it returns what's known
as a generator object that
you can iterate over.
So that I don't have to explain
that right now--
Well actually, we'll probably
talk about it
later in the semester.
The difference is efficiency.
Range will return an
entire list to you.
Whereas x-range is a little bit
more conservative in how
it manages it's memory
for these purposes.
But changing it won't make a
difference in the program.
And for a program as simple as
this, range is perfectly fine.
I just used x-range
out of habit.
So we'll do one last example.
And then we'll move on
to a different topic.
If I didn't mention it before,
in problem set 4, recursion is
highly recommended for the
final portion of it.
So it's kind of important you
understand what's going on.
Anyway, so remember we
looked at bisection
early on in the semester.
And we showed you an iterative
version of bisection.
This shouldn't really
be unfamiliar to
anyone at this point.
So all this is doing is finding
the square root of a
number using bisection search.
And we set our low and our
high, get our midpoint.
And we just keep looping until
we get a value that when we
square it is close
enough to x.
And on each iteration we set
our lows and our highs,
depending on how good
our guess was.
Now the recursive version
looks like this.
It has a few more
lines of code.
And before I launch into it,
did we explain default
parameters to you,
for functions?
So Python has this feature where
if you have a function
such as rec bisection search,
you can specify that certain
parameters are optional
or you can give
default values to them.
So let's just show a
simple example so I
can get past this.
So if I define this function,
this one's really easy.
All it's going to do
is print out x.
I can call it like this, in
which case it's going to pass
150 in and x will be 150 when
the function's executing.
See I'm not lying.
Or I can call it like this.
And it'll be 100.
So that's, in a nutshell, what
default parameters do for you.
They're useful in some
instances, as in this example.
So in this recursive version of
bisection square root, we
have a low and a high parameter
that we specify.
It's exactly equivalent to the
low and the high parameter in
this iterative version.
This is a common idiom for
recursive functions in Python.
If we're calling it for the
first time, we're not going to
specify in a low and a high.
So low and high will be none
coming into this function.
And then we just set
them as we did in
this iterative version.
And then we set the midpoint.
And then we have slightly
different structure here.
If the midpoint that we guess is
close enough to the square
root of x, then we just
return the midpoint.
On the other hand, if it's too
low of a guess, then we're
going to recursively call
ourselves with the same x,
same epsilon, but we're
going to use
midpoint for the low parameter.
So midpoint, in this
case, is here and
the same high parameter.
And then if we've guessed
too high, then our low
parameter is low.
And then our high parameter's
the midpoint.
So it's doing the exact
same thing as
the iterative version.
We have recursive, iterative,
recursive, iterative.
Same thing, just different
forms.
All right.
Before I leave recursion, does
anyone have any questions, or
want to ask anything,
or complain?
No?
All right.
AUDIENCE: Do you use a lot of
recursion in your work?
Do you normally use iterative
or recursion?
Or is it just case by case?
PROFESSOR: It's case by case.
It depends on the problem.
And what we are trying to show
here is that there are some
problems that are better
expressed recursively and
others that are better expressed
iteratively.
And by better, it's a very
subjective term.
In my mind, it means more
intuitive, easier to
understand.
It allows you to focus on
solving the problem rather
than fiddling with code.
On the other hand, sometimes
efficiency comes into play.
And we're going to be talking
about that pretty shortly.
And in that case, you might want
to do a recursive version
because it's easier
to understand.
But it takes too long
to run, so you write
an iterative version.
Computer programming, in a lot
of cases, actually in all
cases, is a bunch
of trade offs.
Often times you'll trade off
speed for memory, elegance for
efficiency, that
sort of thing.
And part of the skill of
becoming good computer
programmers is figuring
out where those
balance points are.
And it's something that I think
comes only comes with
experience.
All right, so we've talked about
floating point to death.
But we just want to really
emphasize it.
Because it's something that
even for experienced
programmers, still
trips us up.
So the thing that we want you to
understand is that floating
point is inexact.
So you shouldn't compare
for exact equality.
So looking at the code here,
I have to find a variable
10/100, which is just 10 over
100, and 1/100, which is just
1 over 100, and then 9/100,
which is 9 over 100.
And so in real math, this
condition would be true.
I add 1/100 and 9/100.
I should get 10/100.
So if we were not dealing
in computer land,
this would print out.
But because we are dealing in
computer-land, we get that.
And the reason is because of
Python's representation.
Now when you write print x, if
x is a float variable, Python
does a little bit of nice
formatting for you.
It kind of saves you from its
internal representation.
So here is 10/100, as you
just printed out.
It's what you would
expect it to be.
But this is what Python sees
when it does its math.
And it's not just Python.
This applies for anything
on a binary computer.
It's an inherent limitation.
And you know we can
get arbitrarily
close, but never exact.
And then again, we have 1/100
and 9/100, Python
will show us 0.1.
So when you print these out,
they'll look fine.
If you were writing debugging
code and you were wondering
why if you compared x to y, it
wasn't exactly equal, you
would naturally print out
x and then print out y.
But it would look
equal to you.
But the code wouldn't
be working properly.
Well the reason is, is that the
internal application that
Python is using to compare
them is that.
So what's the solution?
It's a natural question.
I don't know the answer.
AUDIENCE: If they're close
enough, then it would be
inside variance--
PROFESSOR: Right.
We're going to say
good enough.
And the traditional way of
representing that is epsilon.
Epsilon, you've seen in
your problem sets.
And you've seen it
in code before.
And if you've come to office
hours, someone's probably
explained it to you.
Epsilon is the amount of error
we're willing to tolerate in
our calculations.
So in Python-land, you can
have arbitrary precision.
Don't quote me on that though.
But for purposes of this class,
if you're using an
epsilon it's like 0.0001,
we're not
going to get too upset.
All this function does, and this
is a handy function to
keep around, is it just tests to
see if the distance between
x and y are less than epsilon.
If it is, then we say they're
close enough to each other to
be considered equal.
So I don't like the function
named compare.
I don't think it's intuitive.
Close enough is probably
better.
But it's also going
to break my code.
Uh oh.
This is an actual bug.
Line 203.
What did I do to myself?
I commented out my definition
is what I did.
All right.
So if we compare the two values,
10/100 and 1/100 plus
9/100 and we use our close
enough, our compare function,
then yeah it's within epsilon.
Again, notice here that we're
using a default parameter.
So if we don't pass in
something explicitly.
So I can say something
like this.
Let's make epsilon
really tiny.
So if I make epsilon really,
really tiny, then it's
going to say no.
So how you determine epsilon
really depends on your
specific application.
If you're doing high precision
mathematics, you're modeling
faults on a bridge or something,
probably I want to
be pretty precise.
Because if you have the wrong
epsilon, then you might have
cars falling of the bridge
or the bridge collapsing.
And it would just
be a bad day.
So are there any questions so
far about floating point?
No.
AUDIENCE: You normally
go as close as the
areas will let you.
PROFESSOR: What's that?
AUDIENCE: You can't get
as close anymore.
How can you make the
error that small.
Because it's not going
to get that close.
PROFESSOR: Well yeah,
that's what I mean.
So there is a limit to how
close you can get.
And it depends on the
language and it also
depends on the hardware.
There are, and this group is
getting a little bit more
technical than I want, but you
can define pretty precisely
the smallest value that
epsilon can be.
In a language like C, its
defined as the minimum
difference between two floating
point variables
that's representable on the
host machine's hardware.
So yeah, there is a limit.
There are some math packages
though, and we'll be using
something called NumPy later
on the semester, that allow
you to do pretty high precision
mathematics.
Keep that in the back
of your mind.
But yeah you're right.
You do eventually hit a limit.
OK so the last thing that I
want to cover on floating
point is that even though it's
inexact, it's consistent.
So let's say I define a variable
9/100 plus 1/100.
And it's exactly what it
says, 9/100 plus 1/100.
Now we know that this is not
going to equal 10/100, right.
We just demonstrated
that ad nauseum.
And also, yeah, still defined.
The question though is, if I
subtract 1/100 from this
variable that I've defined, this
9/100 plus 1/100, will
9/100 now be equal to
9/100 plus 1/100?
So in other words, will
this be true?
And the answer is yes.
And the reason is that if I'm
adding or subtracting, even
though 1/100 we know is an
inexact representation, it's
still the same.
And so when we do the
subtraction, we're subtracting
the same inexact value.
So this appeared as a quiz
question at one point.
It probably won't
this semester.
But it's something
to keep in mind.
So any questions on
floating point?
If you're a mathy type and want
to, look up IEEE 754.
And this will give you all
the gory details about
representation and mathematical
operations on
floating point.
And if you don't, then
don't worry about it.
It's not required
for the class.
OK.
So the next topic we want
to cover is pseudocode.
So can someone take a stab at
defining pseudocode for me?
AUDIENCE: From what I gathered,
it's basically
you're writing out what you're
planning on doing in just
normal English.
PROFESSOR: I wouldn't just
say normal English.
But it's an English of sorts.
And a lot of the difficulty
that programmers have with
writing programs or new programs
is that we don't
naturally think in computer
languages.
You think in English.
Or well, you think in
a human language.
So what pseudocode allows us to
do is to kind of be in the
intermediate.
We still want to develop a
step by step process for
solving a problem, but we want
to be able to describe it in
words and not variables
and syntax.
Sometimes what'll happen is
programmers will get so
wrapped around kind of getting
the syntax right that they'll
forget the problem that they're
actually trying to solve.
So let's walk through
an example.
Let's talk about pseudocode
for Hangman.
And because you've all done
this on the problem set, I
don't have to explain
the rules right.
So what would be a good
kind of English
first step for Hangman?
AUDIENCE: You have
to choose a word.
PROFESSOR: Right.
So let's not be too specific.
We'll just say, select
random word.
OK.
Now what would be another
good step, next step.
AUDIENCE: Display the amount
of spaces maybe?
PROFESSOR: So display a masked
version of the word.
AUDIENCE: Exactly.
Hide the word but display it.
PROFESSOR: Hide the word
but display it.
AUDIENCE: Well, display
the amount of spaces.
You probably want to state how
many letters are in the word
at some point.
PROFESSOR: Ah, that's
a good point.
Where should that go?
AUDIENCE: That should probably
be before the display.
PROFESSOR: OK.
So tell how many letters.
All right.
Now what would come
after this?
AUDIENCE: After display?
PROFESSOR: Yeah.
AUDIENCE: See how many letters
you have to choose from.
PROFESSOR: OK.
AUDIENCE: First time
you don't.
PROFESSOR: At first
time you don't.
But the nice thing about
pseudocode is that we can barf
things onto paper and then
rearrange them as we--
It's sort of like brainstorming
in a sense.
You're trying to derive
the structure.
And it's easier to do like
this than to try
and do it in code.
But yeah, you're right.
You don't have to.
AUDIENCE: You would ask the
person to put a letter.
PROFESSOR: OK.
For a letter.
And then what would
come after that?
AUDIENCE: So then you want
to check if it's the --
PROFESSOR: Check if
it's in the word.
All right.
And?
AUDIENCE: If it is--
PROFESSOR: If it is?
AUDIENCE: Add it to the word.
PROFESSOR: Add, let's say,
to correct letters guess.
AUDIENCE: Yeah.
PROFESSOR: OK.
And if it isn't?
AUDIENCE: If it isn't,
reject it.
PROFESSOR: Let's say--
AUDIENCE: You want to remove
it from the options.
PROFESSOR: So if it's not, then
we're going to remove
from options.
So letters remaining.
Probably want to tell the
user they're wrong too.
AUDIENCE: And use up a turn.
PROFESSOR: What's that?
AUDIENCE: You want
to use up a turn.
PROFESSOR: I'm sorry.
AUDIENCE: Then you
use up a turn.
If you had a set amount
of turns.
PROFESSOR: OK, so we're
actually going get
to that in a second.
AUDIENCE: I sent last week.
[INAUDIBLE] game.
PROFESSOR: I actually played
all of your Hangman games.
It was quite fun.
So again, yeah you're right.
So we have a number of guesses
that are remaining.
And the thing is that we know
that the user has a certain
number of terms.
So we're probably going to
repeat a lot of this.
So at some point, we probably
want to have a WHILE.
It'll just say, WHILE we have
guesses left remaining.
By the way, the reason why I
program computers is because
my handwriting is horrible.
So WHILE we have guesses
remaining, we're going to keep
doing all this.
All right?
And then we're going
to remove--
But is this the only
stopping criteria?
What if they win?
So WHILE they have guesses
remaining and
the word is not guessed.
So this is in essence your
Hangman program.
It's English language.
It's not easy to read because
that's my handwriting.
But it's kind of easy
to understand it at
an intuitive level.
And the reason we're talking
about this is because we're
going to get to some more
complicated programs as we
move through the semester.
And a good starting off point
for a lot of you, when you're
trying to do your problem sets,
is instead of trying to
jump right into the coding
portion of it, to sit down
with a piece of paper, index
cards, or a whiteboard and
kind of sketch out a high level
view of the algorithm.
So that we can see this
in code form.
So let's say that I want to
write a function that tests a
number to see if it's prime.
First question is, what
is a prime number?
AUDIENCE: One where the only
factors are 1 and itself.
PROFESSOR: Right.
So a number that is only
divisible by itself and 1.
Are even numbers prime?
Can they ever be prime?
Really?
What about 2?
Right.
So 2 is one of our
special cases.
All right.
So what would be maybe a good
starting off point for
pseudocode to test primality,
knowing those facts.
AUDIENCE: [INAUDIBLE]
PROFESSOR: All right.
Can I erase this or should
I leave it up?
Because I can go over there.
It's not like I'm erasing
any deep dark secrets.
There's no magic here.
All right so test number, if,
what, equal to, say what?
2?
Yeah.
Why not?
And maybe 3.
Now what do I do if it is?
Are they prime?
So I'm done right.
So I'm going to return true.
Now what do I do if the number
given is not 2 or 3?
AUDIENCE: [INAUDIBLE]
PROFESSOR: You're talking about
the module operator.
Right.
So we will use that.
That will tell us whether or not
an integer divides evenly
into another integer or the
remainder after an integer is
divided into another integer.
Let me ask you another
question.
What is the maximum value of
an integer divisor for a
non-prime number?
So for a composite number.
AUDIENCE: So itself.
PROFESSOR: What's that?
AUDIENCE: Number itself.
PROFESSOR: OK, excluding
the number itself.
Well let's say that I have n
as the number I'm testing,
square root of n, because I'm
not going to have a factor
that's larger than that.
And I ask that because there's
a loop involved.
So how would I go about
this systematically?
I'd probably start
at, let's say 5.
OK.
And then test if n modula, let's
say 5, is equal to 0.
Now if n is evenly divisible by
5, then that must mean that
n is composite, because
5 is a factor.
So if it is then return false.
Now what if it isn't?
So that means that n is not
evenly divisible by 5.
Does that mean that the number's
automatically prime?
So after 5, what would
be a good number to
test, to move to?
All right.
6?
No.
That wouldn't be it.
Because if 6 is a factor,
then obviously it's not.
Whatever.
So we're going to
move on to 7.
So basically we're going to
test all the odd numbers.
And this is going to be
the same as that.
So this repetition indicates
here that I
probably need a loop.
So instead of doing this, I want
to say x is equal to 5
while x is less than--
We're going to test if
x evenly divides n.
And if it does, return false.
And if it doesn't, then we just
increment x and repeat.
And what happens when x
becomes greater than
square root of n?
Well the WHILE loop's
going to stop.
And that also means that if I've
made it to that point,
then I've not found any numbers
between 5 and square
root of n that will
evenly divide n.
So that means that n is prime.
So if I translate this into
code, it would look
something like this.
Now I see.
So first we're going to
check if n is less
than or equal to 3.
If it's 2 or 3, then
we'll return true.
If it's not 2 or 3, then
that means it's 1 or 0.
So return false.
So we've got those cases.
And then we're going
to iterate--
or if n is greater than 3,
we're going to iterate--
now why would you go from 2--
we're going to integrate through
all the possible
divisors and check
for divisibility.
And if we evenly divide
it, return false.
And if we make it through the
loop, we'd return true.
AUDIENCE: Does that RETURN
stop the loop?
PROFESSOR: Yes.
Well think about what
RETURN is doing.
You're in this function, test
primality, And as soon as
Python sees return, that's
telling Python to kick out of
the function and return
whatever is
after the return statement.
So this false here, it says
return false, that means that
it doesn't matter where you are,
it's just going to kick
out of the innermost function or
the function that encloses
that return and return
that value.
Any questions?
All right.
So testing primality, 1 is
false, 2 is true, 3 is true, 4
is false, and 5 is true.
So it looks like the
program works.
And if no one has any questions,
I'm going to move
on to the last major topic.
Everyone's good on pseudocode?
All right.
AUDIENCE: What would the main
purpose of pseudocode is for
yourself when you're writing a
program or when you want to
explain it to other people?
PROFESSOR: Both.
So the question was, is writing
pseudocode useful for
just understanding a program
yourself or for explaining it
to other people?
The answer is both.
I don't know.
It's the difference between
showing someone the derivative
of the function and then
explaining that what you're
doing is finding a function that
gives you the slope of a
function at that point.
So it's one is more
intuitive for some
people than the other.
A mathematician would
understand the
former pretty quickly.
An English major would
understand the latter maybe.
So when I explain my research
to people, I don't tell them
that I mess around with Gaussian
Mixture models and
Hidden Markov models.
I tell them that I'm trying
to figure out how people
mispronounce words when they
speak foreign languages.
A lot easier for people
to digest.
With debugging, what are bugs?
AUDIENCE: Mistakes.
PROFESSOR: Mistakes.
And if you see one bug, there
are probably many more.
So when you're debugging, your
goal is not to move quickly.
This is an instance where the
maxim fast is slow and slow is
fast comes into play.
You want to be very deliberate
and systematic when you're
trying to debug code.
You want to ask the question
why your code is
doing what it does.
And remember, the first
recitation I said, that your
computer's not going to do
anything that you do
not tell it to do.
It's not something that
people do naturally.
If you watch some of the TAs and
sometimes a student will
say, how do you find
the bug so quickly?
Well it's because I've been
programming for 18 years.
Professor Guttag's
been programming
for longer than that.
So a lot of it is experience.
And it's just when we've debug
our own programs and when we
were learning to program, it
was as painful for us as it
was for you.
So that said, you want to start
with asking, how could
your code have produced the
output that it did?
Then you want to figure out
some experiments that are
repeatable and that you have
an idea of what the
output should be.
So after you do that, then you
want to test your code one by
one on these different test
cases and see what it does.
And in order to see
what it does, you
can use a print statement.
So when you think you found a
bug and you think you have a
solution to your code, you want
to make as few changes as
possible at a time, it's because
as you're making
corrections, you can still
introduce bugs.
Let's see.
So a useful way to do this
is to use a test harness.
So when we actually grade your
problem sets, a lot of the
time the TAs will put together
a set of test
cases for your code.
So one of the things is a lot of
the times when you get one
of the problems or when you look
at the problems, it'll
have some example input
and output.
But that doesn't necessarily
mean that we
only test on that.
There's additional test
cases that we use.
And it's not to trip you up.
It's because there's a lot
of different variations.
And it's also, if you read the
specification, you follow the
specification, then
you'll be fine.
Moving on.
So let's look at an example.
I have a function here,
is palindrome.
You've seen this
before, right?
Yes?
Yeah.
OK.
So it's supposed to return
true if string s is a
palindrome.
And so I've written
this function.
And I've also written
a test harness.
Now there's a lot more code in
the test harness, but it's
pretty simple code.
When you're writing functions,
you want to think of the type
of input you could receive.
And you want to think
of, what are the
kind of boundary cases?
So the extremes of input
that you can get.
We call these boundary
cases, edge cases.
For the is_palindrome function,
it would be like the
empty string would be one.
Or just a single character.
These are the kind of minimum
we can have or
we could think of.
On the opposite end of the
spectrum, theoretically we
could have an infinitely
long string.
So we're not going to
actually test for an
infinitely long string.
Anyway, all we're going to do is
in our test harness, we're
just going to run the function
on these inputs.
And we know that an empty
string should be true.
We know that a single character
string should be true.
We know that if I have a string
that's two characters
long and they're the
same character,
that should be true.
If they're two characters,
then it should be false.
And what I'm going to do now
is I'm looking at kind of
expecting what we call
expected input.
So after I've hit my edge cases,
I'm going to look at
all the strings of an even
length and make sure that the
function works properly.
And then I'm going to look at
strings with an odd length.
And then once I get to this
point where I've tested a
number of different lengths, and
in this case, it's just 2
through 5 or 0 through
5, if you want to
include the edge cases.
Then I'm going to say, well it
looks like all tests are pass.
And I think that this function
works pretty good for anything
we can expect it to encounter
reasonably.
So the way that you use test
harnesses, is every time you
make a change to your program,
you want to run the test
harness, because that'll catch
any bugs you may have
introduced.
And so I'm going to finish up
with this really quickly
because I know my time's up.
So I got a bug.
It's telling me that one of
my test cases failed.
So line 299, which is
this test case.
So what we can do is now that we
know that it fails, we can
say, maybe printout our input
and see what we have.
And instead of just running, I'm
just going to run that one
test case that failed.
So obviously this
should be true.
And what we're seeing is that
on the first call to
is_palindrome, s is abba.
And then on the recursive call
to it, we only get bba.
That means that we've
only chopped
of the front character.
So you see the bug?
Well here.
So there we go.
