Computer Science 15-112, Spring 2012
Class Notes:  Writing Functions:  Annotated Examples (Self-Study)


  1. onesDigit
  2. tensDigit

Writing Functions:  Annotated Examples

  1. onesDigit
    1. 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.

    2. 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.

    3. 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?

    4. 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.

    5. 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!!!

  2. Ten's Digit
    1. Problem Statement
      For this problem, we wish to write a function that takes an integer and returns its tens digit.

    2. 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?

    3. 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!"


    4. 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!
      .
    5. 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