RU beehive logo ITEC dept promo banner
ITEC 120
2012fall
dbraffitt
ibarland

homeinfolectslabsexamshws
tutor/PIsbreeze (snow day)
Object120 + its docsjava.lang docsjava.util docs

lab09b
looping over strings

Today's lab will (again) be done individually; we well check off countOccurrences below.

  1. Review: We have seen a loop that adds up all the numbers in [0,n) — that is, 0, 1, ..., n-1:

      /** Return the sum of all the natural numbers numbers up to (but not including) n.
       * @param n The upper limit to sum to.
       * @return the sum of all the natural numbers numbers up to (but not including) n.
       */
      static int sumUpTo( int n ) {
        int sumSoFar = 0.0;
        int i = 0;
        while (i < n) {
          // Each time through the loop, keep track of (in your head):
          //    At this moment, i is         , and sumSoFar is          
          sumSoFar = sumSoFar + i;
          i = i + 1;
        }
        return sumSoFar;
      }
    
    So i counts 0, 1, 2, ..., incrementing each pass through the loop-body. We update our sumSoFar each time through as well, incrementing it by i. So when the loop finishes (the moment i becomes ≥ n), sumSoFar contains the sum of each value i had been: 0, 1, 2, ..., n-1.

    We also saw that if we instead want to add up (say) √0 + √1 + √2 + √3 + … + √(n-1), we can have i count 0, 1, 2, …, n-1 as before, but when we update sumSoFar, we increment it by only √i, instead of i:

      /** Return the sum of *the square root of* all the natural numbers numbers up to (but not including) n.
       * @param n The upper limit to sum to.
       * @return the sum of  the square root of  all the natural numbers numbers up to (but not including) n.
       */
      static double sumSqrtUpTo( int n ) {
        double sumSoFar = 0.0;
        int i = 0;
        while (i < n) {
          // Each time through the loop, keep track of (in your head):
          //    At this moment, i is         , and sumSoFar is          
          sumSoFar = sumSoFar + Math.sqrt(i);
          i = i + 1;
        }
        return sumSoFar;
      }
    

  2. Looping over strings: We can also use this same technique to loop over each character in a string.
    First, let's imagine what we'd try if we didn't know about loops:

      static void printEachChar( String wrd ) {
        System.out.println( "The char at index 0 is: '" + (wrd.charAt(0)) + "'" );
        System.out.println( "The char at index 1 is: '" + (wrd.charAt(1)) + "'" );
        System.out.println( "The char at index 2 is: '" + (wrd.charAt(2)) + "'" );
        System.out.println( "The char at index 3 is: '" + (wrd.charAt(3)) + "'" );
        System.out.println( "The char at index 4 is: '" + (wrd.charAt(4)) + "'" );
        System.out.println( "The char at index 5 is: '" + (wrd.charAt(5)) + "'" );
        // ⋮ 
      }
    
    Okay, but we'd have to copy/paste this forever, to handle arbitrarily big strings. But then, eventually our index will be too big, and we'll get an error. Let's guard against that error:
      static void printEachChar( String wrd ) {
        if (0 < wrd.length()) {
          System.out.println( "The char at index 0 is: '" + (wrd.charAt(0)) + "'" );
        }
        if (1 < wrd.length()) {
          System.out.println( "The char at index 1 is: '" + (wrd.charAt(1)) + "'" );
        }
        if (2 < wrd.length()) {
          System.out.println( "The char at index 2 is: '" + (wrd.charAt(2)) + "'" );
        }
        if (3 < wrd.length()) {
          System.out.println( "The char at index 3 is: '" + (wrd.charAt(3)) + "'" );
        }
        // ⋮
      }
    
    Well, at least this is "correct", if you allow for the fact that I have to repeat this forever.
      static void printEachChar( String wrd ) {
        int i = 0;
        if (i < wrd.length()) {
          System.out.println( "The char at index " + (i) + " is: '" + (wrd.charAt(i)) + "'" );
          i = i + 1;
        }
        if (i < wrd.length()) {
          System.out.println( "The char at index " + (i) + " is: '" + (wrd.charAt(i)) + "'" );
          i = i + 1;
        }
        if (i < wrd.length()) {
          System.out.println( "The char at index " + (i) + " is: '" + (wrd.charAt(i)) + "'" );
          i = i + 1;
        }
        if (i < wrd.length()) {
          System.out.println( "The char at index " + (i) + " is: '" + (wrd.charAt(i)) + "'" );
          i = i + 1;
        }
        // ⋮ 
      }
    
    We still have to repeat this forever, but at least it's an immediate copy/paste — don't have to change a single character. But hey, if it doesn't even change, can't we have the computer do this forever?
      static void printEachChar( String wrd ) {
        int i = 0;  // Or, "nextIndexToLookAt"
        while ( i < wrd.length() ) {
          System.out.println( "The char at index " + (i) + " is: '" + (wrd.charAt(i)) + "'" );
          i = i + 1;
        }
      }
    
    Aha! Perfect!

    Now we can take this last version, and actually do something useful with each character — that is, with wrd.charAt(i) as i counts 0 up to the length:

      /** Return the 'fingerprint' of a string --
       * a number which is a summary of it's info (which is different from the fingerprint
       * of most other Strings, but not necessarily every other String.
       * This is also called a "hash value".
       * @param wrd The String to fingerprint.
       * @return `wrd`s fingerprint.
       */
      static int fingerPrint( String wrd ) {
        int i = 0;  // Or, "nextIndexToLookAt"
        int fingerPrintSoFar = 0;
        while ( i < wrd.length() ) {
          fingerPrintSoFar = fingerPrintSoFar + (int)(wrd.charAt(i));
          i = i + 1;
        }
        return fingerPrintSoFar;
      }
    

  3. Complete the test cases, a stub function, and then the body for countOccurrences.
        /** Test countOccurrences */
        static void testCountOccurrences() {
          assertEqualInts( countOccurrences("abracadabra", 'a' ),      );
          assertEqualInts( countOccurrences("abracadabra", 'b' ),      );
          assertEqualInts( countOccurrences("abracadabra", 'd' ),      );
          assertEqualInts( countOccurrences("abracadabra", 'z' ),      );
          assertEqualInts( countOccurrences("AAA", 'A' ),      );
          assertEqualInts( countOccurrences("A", 'A' ),      );
          assertEqualInts( countOccurrences("A", 'b' ),      );
          assertEqualInts( countOccurrences("", 'b' ),      );
        }
        
    We've used/written assertEqualInts before; you can find its code below.
  4. Now, complete the signature and stub function for countOccurrences.
        /** Return how many times given character occurs in a String.
         * @param s the string to search through
         * @param target the char to count.
         * @return how many occurrences of `target` occur in `s`.
         */
    
        //Your stub here
        
  5. Finally, we'll write the actual body of countOccurrences.

    You can refer back to yesterdays' lecture for the code which goes through and processes every character of a String.

  6. Raise your hand, and we'll look at your test cases and check you off!

Extra: check-sums and ISBNs

The following problems aren't required for today, but may be in the future -- or they might be for extra credit.

Variant:: If you want to implement 13-digit ISBN checksums instead of 10-digit, you are encouraged to. However, don't use the code from the wikipedia page; instead of having a two loops (one for odd indices, one for even) write your code using just one loop.
  1. Ten-digit book numbers — ISBN-10s — have the following “checksum” property:
    the digits x1x2x3…x9x10 have the property that
    (1·x1 + 2·x2 + 3·x3 + … + 9·x9 + 10·x10) % 11 = 0.
    This scheme means1that if somebody mis-types a digit, or transposes two digits, a computer can recognize that an error has been made.

    For example, for the ten digits "0306406152",

    ( 1·0 + 2·3 + 3·0 + 4·6 + 5·4 + 6·0 + 7·6 + 8·1 + 9·5 + 10·2 ) % 11
    = ( 0 + 6 + 0 + 24 + 20 + 0 + 42 + 8 + 45 + 20 ) % 11
    = 165 % 11
    = 0 (since 11 goes into 165 fifteen times with remainder 0)
    Thus "0306406152" is a valid ISBN-10.

    Test case for you to try by hand, to understand what our code will have to do:
    Which of these is a valid ISBN-10?

    By the way, mathemeticians have a concise way of writing this sum:

    10
    (Σ i·xi ) mod 11 = 0
    i=1
    You'll see this summation-notation if you look on (say) Wikipedia. Don't be intimidated; whenever you see a high-falutin’ “Σ”, just think about writing it out as a summation in your head. (In fact, you can think of math as being a programming-language that has a convenient syntax for loops that add.)


  2. Write the following method:
    class ISBNChecker {
    
      boolean isValidISBN10( String isbn ) {
        return false;  // stub.
      }
    
    }
    
  3. Here are some tests for isValidISBN10.
    class ISBNCheckerTester {
    
      public static void main( String[] args ) {
        ISBNChecker looper = new ISBNChecker();
        System.out.println( "\nTest some contrived ISBNs: " );
        assertEqualBools( looper.isValidISBN10("0000000000"), true  );
        assertEqualBools( looper.isValidISBN10("1000000001"), true  );
        assertEqualBools( looper.isValidISBN10("1000000000"), false  );
        assertEqualBools( looper.isValidISBN10("0100000002"), true  );
        assertEqualBools( looper.isValidISBN10("0010000003"), true  );
        assertEqualBools( looper.isValidISBN10("0001000004"), true  );
        assertEqualBools( looper.isValidISBN10("0000000019"), true  );
        assertEqualBools( looper.isValidISBN10("0000000027"), true  );
        assertEqualBools( looper.isValidISBN10("2000000010"), true  );
        assertEqualBools( looper.isValidISBN10("4000000020"), true  );
        assertEqualBools( looper.isValidISBN10("5000000080"), true  );
        System.out.println( "\nTest some real and incorrect ISBNs: " );
        assertEqualBools( looper.isValidISBN10("0306406152"), true  );
        assertEqualBools( looper.isValidISBN10("0306406162"), false );
        assertEqualBools( looper.isValidISBN10("0306406154"), false );
        assertEqualBools( looper.isValidISBN10("0553565699"), true  );
        assertEqualBools( looper.isValidISBN10("1553565699"), false );
      }
    
    }
    
  4. Extra challenge: Actually, sometimes the check-digit works out to be 10. Since that won't fit into a single digit, in that case the letter 'X' is used (reminiscent of the roman numeral).

    Fix your code above so that ISBN-10s ending in the letter 'X' are also recognized. (Hint: put your change inside charToInt.)

        System.out.println( "\nTest some X-digit ISBNs: " );
        assertEqualBools( looper.isValidISBN10("000000000X"), false );
        assertEqualBools( looper.isValidISBN10("050000000X"), true  );
        assertEqualBools( looper.isValidISBN10("000020000X"), true  );
        assertEqualBools( looper.isValidISBN10("300000002X"), true  );
    

  5. Extra extra challenge: ISBN-10s are created by using 9 digits of “real” data, and then constructing the 10th digit so that the above property holds. The formula to compute the tenth digit2 is

    That is, compute

    Your task: Write char createCheckDigit( String isbnPart ).
  6. Extra extra extra challenge: Can you modify isValidISBN10 so that it is much much shorter, by having it call createCheckDigit as a helper?
  7. Extra extra extra extra challenge: Read about ISBN-13, and implement a verifier isValidISBN13 for that as well.
    Then, write isValidISBN which can take a 10-digit or a 13-digit input, and return isValidISBN10 or isValidISBN13 as appropriate.

Tester methods

Here is code we've seen/written previously, for help in testing. All you need for this assignment is assertEqualInts and assertEqualBools, but for completeness we the functions for Strings and doubles too.
(You are welcome to make your own class Helper full of static helper methods, and always include that in all your future projects. As the semester progresses, you can also add other common bits of helper-code to that file. That's about 50% of what class Object120 actually does.)

    /** If two booleans aren't equal, print an error message.
     * Intended for use as a test-helper.
     * @param act The actual boolean returned by a test call.
     * @param exp The expected boolean to be returned by a test call.
     */
    static void assertEqualBooleans(boolean act, boolean exp) {
      if (act==exp) {
        System.out.println("(test passed)");
      }
      else {
        System.out.println( "Actual: " + act + "\nExpect: " + exp );
      }
    }
    
    /** If two ints aren't equal, print an error message.
     * Intended for use as a test-helper.
     * @param act The actual int returned by a test call.
     * @param exp The expected int to be returned by a test call.
     */
    static void assertEqualInts(int act, int exp) {
      if (act==exp) {
        System.out.println("(test passed)");
      }
      else {
        System.out.println( "Actual: " + act + "\nExpect: " + exp );
      }
    }
    

    /** If two Strings aren't equal, print an error message.
     * Intended for use as a test-helper.
     * @param act The actual String returned by a test call.
     * @param exp The expected String to be returned by a test call.
     */
    static void assertEqualStrings(String act, String exp) {
      if (act.equals(exp)) {
        System.out.println("(test passed)");
      }
      else {
        System.out.println( "Actual: " + act + "\nExpect: " + exp );
      }
    }

    /** If two doubles aren't (nearly) equal, print an error message.
     * Intended for use as a test-helper.
     * @param act The actual double returned by a test call.
     * @param exp The expected double to be returned by a test call.
     * @param tolerance The amount of tolerance allowed to still be considered equal
     *    (an absolute amount.)
     *    
     * BUG: the tolerance should probably be a relative to exp, at least when when exp!=0.
     */
    static void assertEqualDoubles(double act, double exp, double tolerance) {
      if (  (act == exp)
         || (Math.abs(act-exp) < tolerance) 
         || (Double.isNaN(act) && Double.isNaN(exp))) {
        System.out.println("(test passed)");
      }
      else {
        System.out.println( "Actual: " + act + "\nExpect: " + exp );
      }
    }

  /** If two doubles aren't (nearly) equal, print an error message.
     * Intended for use as a test-helper.
     * @param act The actual double returned by a test call.
     * @param exp The expected double to be returned by a test call.
     */
    static void assertEqualDoubles(double act, double exp) {
        assertEqualDoubles(act,exp,TOLERANCE);
    }     
    
    /** default The tolerance for assertEqualDoubles */
    private static double TOLERANCE = 0.000001;


1 In ITEC122, you'll learn why this is true.      

2 You might wonder why this formula works; from what we saw above, shouldn't x10 be the negative of that sum, so that ten times it will have the last digit cancel out the first nine? Yes! But in mod 11 (where we can subract a multiple of 11 without changing anything), ten times a number is equivalent to negative-one times that number. More on modular arithmetic in ITEC122.      

homeinfolectslabsexamshws
tutor/PIsbreeze (snow day)
Object120 + its docsjava.lang docsjava.util docs


©2012, Ian Barland, Radford University
Last modified 2012.Oct.25 (Thu)
Please mail any suggestions
(incl. typos, broken links)
to ibarlandradford.edu
Powered by PLT Scheme