Computer Science 15-112, Spring 2012
Class Notes: Writing Functions: Annotated Examples (Self-Study)
- onesDigit
- tensDigit
Writing Functions: Annotated Examples
- onesDigit
- Problem Statement
For
this problem, we wish to write a function that takes an integer and
returns its ones digit. Before we write the function, though, we
consider various test cases, to be sure we understand precisely how the
function should work.
- Test Cases
What
should onesDigit(123) return? Well, hopefully it's clear that it
should return the number 3 (right?). So far, so good. What
about onesDigit(7890)? In this case, the ones digit is 0, so it
should return 0. Ok. What about a single-digit number, say
onesDigit(6)? Easy enough -- just return that digit, so it should
return 6. Are there any other cases? Yes. What about
a negative number? What should onesDigit(-54) return? The
answer is that the sign should not matter, and it should return positive 4.
- Automated Test Function
Ok, so we've thought through the test cases. Next, we place these into an automated test function, like so:
def testOnesDigit():
print "Testing onesDigit... ",
assert(onesDigit(123) == 3)
assert(onesDigit(7890) == 0)
assert(onesDigit(6) == 6)
assert(onesDigit(-54) == 4)
print "Passed all tests!"
See
how we simply converted the test cases from the previous step into
Python code that can automatically test whether or not our onesDigit
function works properly?
- Stub Solution
This
step is not required but is quite useful. We'll just write a tiny
version (or "stub") of our solution which does not really try to solve
the problem, instead returning some bogus answer. Why? This
lets us run our test code, at least, and watch it successfully
determine that our "stub" does not actually work properly. Here
is what we mean:
def testOnesDigit():
print "Testing onesDigit... ",
assert(onesDigit(123) == 3)
assert(onesDigit(7890) == 0)
assert(onesDigit(6) == 6)
assert(onesDigit(-54) == 4)
print "Passed all tests!"
def onesDigit(x):
return 3 # stub, for testing
testOnesDigit() # actually run the test!
When we run this, we get the following error:
Traceback (most recent call last):
File "C:/Temp/foo", line 12, in <module>
testOnesDigit() # actually run the test!
File "C:/Temp/foo", line 4, in testOnesDigit
assert(onesDigit(7890) == 0)
AssertionError
But
this is just what we expected! Well, mostly. Curiously,
upon inspection we see that we passed the first test. By sheer
luck, when we returned 3, we passed the test assert(onesDigit(123) ==
3). But we failed on the next test, since onesDigit(7890) should
equal 0, not 3.
- Solve, Test, Repeat
So
now we understand the problem statement and we have a nifty way to
automatically test our solution. Great! All that's left is to
actually solve this thing. And how do we do that? In this
case, it's actually quite simple: the x % y is the remainder when
x is divided by y, and the 1's digit is nothing more than the remainder
when we divide a number by 10 (right?). And so we get this:
def testOnesDigit():
print "Testing onesDigit... ",
assert(onesDigit(123) == 3)
assert(onesDigit(7890) == 0)
assert(onesDigit(6) == 6)
assert(onesDigit(-54) == 4)
print "Passed all tests!"
def onesDigit(x):
return x % 10 # first attempt!
testOnesDigit() # actually run the test!
We run this and we did not pass our tests. We failed on this assertion:
assert(onesDigit(-54) == 4)
Darn.
But at least we passed the first three tests. So it's a
start. So it looks like we work for positives and not for
negatives. Why not? Think about how the remainder function
works differently for positives and for negatives and you should
(hopefully) realize our mistake.
Regardless of whether you see
why, it should be easy to see what the fix is (well, at least one fix,
among several options): just take the absolute value so we're
only dealing with positives! So we get:
def testOnesDigit():
print "Testing onesDigit... ",
assert(onesDigit(123) == 3)
assert(onesDigit(7890) == 0)
assert(onesDigit(6) == 6)
assert(onesDigit(-54) == 4)
print "Passed all tests!"
def onesDigit(x):
return abs(x) % 10 # second attempt!
testOnesDigit() # actually run the test!
We run this and we get:
Testing onesDigit... Passed all tests!
Had
we failed again, we would edit our solution and repeat this step until
we've passed all our tests. But we did not fail, and so we are
done. We have solved the onesDigit problem! Wahoo!!!
- Ten's Digit
- Problem Statement
For
this problem, we wish to write a function that takes an integer and
returns its tens digit.
- Test Cases
tensDigit(1)
should return 0. tensDigit(23) should return 2.
tensDigit(456) should return 5. And tensDigit(-7890) should
return 9. Is that enough test cases? What do you think?
- Automated Test Function
def testTensDigit():
print "Testing tensDigit... ",
assert(tensDigit(1) == 0)
assert(tensDigit(23) == 2)
assert(tensDigit(456) == 5)
assert(tensDigit(-7890) == 9)
print "Passed all tests!"
- Stub Solution
def testTensDigit():
print "Testing tensDigit... ",
assert(tensDigit(1) == 0)
assert(tensDigit(23) == 2)
assert(tensDigit(456) == 5)
assert(tensDigit(-7890) == 9)
print "Passed all tests!"
def tensDigit(x):
return 3 # stub, for testing
testTensDigit() # actually run the test!
When we run this, we get the following error:
Traceback (most recent call last):
File "C:/Temp/foo", line 12, in <module>
testTensDigit() # actually run the test!
File "C:/Temp/foo", line 3, in testTensDigit
assert(tensDigit(1) == 0)
AssertionError
Again, this is just what we expected!
.
- Solve, Test, Repeat
Say that after some thought we think (x / 10) might be a solution here. So, let's try it:
def testTensDigit():
print "Testing tensDigit... ",
assert(tensDigit(1) == 0)
assert(tensDigit(23) == 2)
assert(tensDigit(456) == 5)
assert(tensDigit(-7890) == 9)
print "Passed all tests!"
def tensDigit(x):
return x / 10 # our first attempt
testTensDigit() # actually run the test!
We run this and we did not pass our tests. We failed on this assertion:
assert(tensDigit(456) == 5)
Darn.
But at least we passed the first two tests. So it's a
start. At this point, we know that we failed when x = 456, in
that we did not return 5, but we don't know what we did return.
That would be handy information. So let's get it!
We'll simply print this out in the interactive shell. Type this in:
tensDigit(456)
And Python prints out:
45
Ahhh!
Now we see what went wrong. We meant to only get that 5,
but we got more than we bargained for! We got all the digits
except the one's digit. So by dividing x by 10, what we really
did was shift x one digit to the right. Re-read that last
sentence and think about it.
Now, after shifting to the right,
the old ten's digit has become the new one's digit. Right?
Yes!!!! So now we have a plan to fix our code:
def testTensDigit():
print "Testing tensDigit... ",
assert(tensDigit(1) == 0)
assert(tensDigit(23) == 2)
assert(tensDigit(456) == 5)
assert(tensDigit(-7890) == 9)
print "Passed all tests!"
def onesDigit(x):
return abs(x) % 10
def tensDigit(x):
return onesDigit(x / 10) # our second attempt
testTensDigit() # actually run the test!
We run this and we get:
Testing tensDigit... Passed all tests!
And so we've now successfully solved another problem!!!! Hurray!!!
Not so fast!!! What happens if we call tensDigit(-11)?
It returns 2. Oh no!!! What happened? For one thing,
we realize our test cases were incomplete (whoops), since they did not
expose this bug. Figure it out, and fix both our code and our test
cases!
carpe diem -
carpe diem - carpe diem - carpe diem
- carpe diem - carpe diem -
carpe diem - carpe diem - carpe
diem