CSE143X Notes for Friday, 11/2/12

Allison Obourn covered the lecture for me. I am including notes from lectures I've given with an example relevant to the evil hangman program and a discussion of the Angle class and the Comparable interfade.

I mentioned that our next programming assignment will involve String manipulation which is really a CSE142 topic, but I didn't intend the String processing to be the hard part of the assignment. So I said that I wanted to solve a short problem to remind people how basic String manipulation works.

I said that I wanted to write a method called dashes that would take a String as input and that would return a new String with dashes inserted between letters. So given the input "hello", the method would return "h-e-l-l-o".

Someone said that we could build up a temporary String that the method could return. So the basic structure will look like this:

        public static String dashes(String s) {
            String result = ??
            // fill up string
            return result;
        }
Someone said that we could loop over the characters of the string. Remember that the string has a method called length that tells you the number of characters in the string and a method called charAt that gets you the individual characters of the string:

        public static String dashes(String s) {
            String result = ??
            for (int i = 0; i < s.length(); i++) {
                // do something with s.charAt(i)
            }
            return result;
        }
Someone suggested that each time through the loop we want to add to the current result a dash and the next character, so I modifed the loop to be:

        for (int i = 0; i < s.length(); i++) {
            result = result + "-" + s.charAt(i);
        }
The problem with this is that it would add a leading dash that we don't want. And putting the dash after the call on charAt would put an extra dash afterwards. This is a classic fencepost problem that we can solve by processing the first character before the loop.

So we decided to initialize the string to the first character of the word:

        String result = s.charAt(0);
Unfortunately, this isn't going to work. You can't assign a string the value of a character. Someone suggested casting, but that doesn't work either. The usual way to do this in Java is to append the character to an empty string, because you can always concatenate a value to a string:

        String result = "" + s.charAt(0);
But because we processed the first character before the loop, we had to change the loop bounds to start at 1 instead of 0. So our final version became:

        public static String dashes(String s) {
            String result = "" + s.charAt(0);
            for (int i = 1; i < s.length(); i++) {
                result = result + "-" + s.charAt(i);
            }
            return result;
        }
I mentioned one last detail about this method. It fails in one case. Someone said that it doesn't properly handle an empty string, which is correct. We have assumed that there is a character at position 0. That won't work for an empty string. We could add a special case for that, but I said that in this case I would probably add a precondition that makes this clear:

        // pre: s is not an empty string
It's important to consider these cases and document any preconditions, but sometimes assumptions like this are reasonable. For example, in the hangman programming assignment, you will never be asked to deal with an empty string because the program itself guarantees that all words are at least of length 1.

I explored several issues having to do with classes by developing a class called Angle that could be used to keep track of the angles that are used to specify latitudes and longitudes. For example, Seatac Airport is at a latitude of 47 degrees 39 minutes North and a longitude of 122 degrees 30 minutes West. This will be a simple class for demonstration purposes only.

We are only going to keep track of degrees and minutes, so we began with a class that has fields for each and a constructor:

        public class Angle {
            private int degrees;
            private int minutes;
        
            public Angle(int degrees, int minutes) {
                this.degrees = degrees;
                this.minutes = minutes;
            }
        }
We started with some client code that constructed an array of Angle objects and that printed it out:

        import java.util.*;

        public class Fri {
            public static void main(String[] args) {
                Angle a1 = new Angle(23, 26);
                Angle a2 = new Angle(15, 48);
                List<Angle> list = new ArrayList<Angle>();
                list.add(a1);
                list.add(a2);
                System.out.println(list);
            }
        }
When we ran this program, it produced output like the following:

        [Angle@42719c, Angle@30c221]
The text being generated in each case includes the name of the class (Angle) followed by an at sign followed by a hexadecimal (base 16) address. This is what the built-in toString method produces. To get better output, we decided to add a toString method to the class.

Ideally we would show something like 23 degrees and 26 minutes using the standard symbols used for degrees and minutes:

23° 26′
But those characters aren't easy to work with in Java, so instead we'll settle for "d" for degrees and "m" for minutes:

23d 26m
We wrote the following toString method to produce that output:

        public String toString() {
            return degrees + "d " + minutes + "m";
        }
When we ran the client code again, it produced the following output:

        [23d 26m, 15d 48m]
Then I discussed how to include a method that would add two of these angles together. In a sense, what I'd like to be able to say in the client code is something like this:

        Angle a1 = new Angle(23, 26);
        Angle a2 = new Angle(15, 48);
        Angle a3 = a1 + a2;
We can't write it that way because the "+" operator is used only for numbers and String concatenation. Some programming languages like C++ allow you to do something called "operator overloading," but Java doesn't allow that. So we have to settle for having a method called "add" that we can use to ask one of these angles to add another one to it:

        Angle a1 = new Angle(23, 26);
        Angle a2 = new Angle(15, 48);
        Angle a3 = a1.add(a2);
I pointed out that this is a common convention in the Java class libraries. For example, there is a class called BigInteger for storing very large integers that has a method called add that is similar.

We wrote this as the first version of our add method:

        public Angle add(Angle other) {
            int d = degrees + other.degrees;
            int m = minutes + other.minutes;
            return new Angle(d, m);
        }

I pointed out that we're referring to other.degrees and other.minutes, which are private fields. This works because the understanding of the word private is that it is "private to the class." This is not at all the way that we as humans understand the meaning of private (if something is private to me, then it shouldn't be available to other humans). But in Java, one Angle object can access private elements of another Angle object because they are both of the same class.

I modified the client code to add this angle to the list as well:

        Angle a1 = new Angle(23, 26);
        Angle a2 = new Angle(15, 48);
        Angle a3 = a1.add(a2);
        List<Angle> list = new ArrayList<Angle>();
        list.add(a1);
        list.add(a2);
        list.add(a3);
        System.out.println(list);
When we ran it, we got the following output:

        [23d 26m, 15d 48m, 38d 74m]
Clearly the program has added the two angles to get a third, but the third angle is listed as having 74 minutes, which isn't right. The first thing we did was to discuss the constructor. We added the following precondition:

        // pre: minutes <= 59 and minutes >= 0 and degrees >= 0
        public Angle(int degrees, int minutes) {
            this.degrees = degrees;
            this.minutes = minutes;
        }
Then we added code to throw an exception when the precondition was not satisfied:

    // pre: minutes <= 59 and minutes >= 0 and degrees >= 0
    //      (throws IllegalArgumentException if not true)
    public Angle(int degrees, int minutes) {
        if (minutes < 0 || minutes > 59 || degrees < 0)
            throw new IllegalArgumentException();
        this.degrees = degrees;
        this.minutes = minutes;
    }
But we still needed to handle the case where the minutes become larger than 59. We were able to fix this with a simple if statement:

        public Angle add(Angle other) {
            int d = degrees + other.degrees;
            int m = minutes + other.minutes;
            if (m >= 60) {
                m -= 60;
                d++;
            }
            return new Angle(d, m);
        }
It also would have been possible to use integer division and the mod operator to figure this out.

When we ran the code again, we got this output, indicating that it had correctly added the two angles together:

        [23d 26m, 15d 48m, 39d 14m]
Then I said that I wanted to explore how to modify the class so that we can put a collection of Angle objects into sorted order. For example, I added the following client code to add some randomly generated Angle objects to our list and to put them into sorted order:

        Random r = new Random();
        for (int i = 0; i < 5; i++)
            list.add(new Angle(r.nextInt(90), r.nextInt(60)));
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);
Unfortunately, this code did not compile. That's because we haven't told Java how to compare Angle objects to put them into order. For example, we know that an angle of 45 degrees and 15 minutes is more than an angle of 30 degrees and 55 minutes, but how is Java supposed to figure that out?

If you want to use utilities like Arrays.sort and Collections.sort, you have to indicate to Java how to compare values to figure out their ordering. There is an interface in Java known as the Comparable interface that captures this concept.

Not every class implements Comparable because not all data can be put into sorted order in an intuitive way. For example, the Point class does not implement the Comparable interface because it's not clear what would make one two-dimensional point "less than" another two-dimensional point. But many of the standard classes we have seen like String and Integer implement the Comparable interface. Classes that implement the Comparable interface can be used for many built-in operations like sorting and searching. Some people pronounce it as "come-pair-a-bull" and some people pronounce it as "comp-ra-bull". Either pronunciation is okay. The interface contains a single method called compareTo.

        public interface Comparable<T> {
            public int compareTo(T other);
        }
So what does compareTo return? The rule in Java is that:

We're going to discuss interfaces in more detail later in the quarter, but for now, the important thing to know is that we have to include an appropriate compareTo method and we have to modify the class header to look like this:

        public class Angle implements Comparable<Angle> {
            ...
        }
We completed the compareTo method fairly quickly. I pointed out that in general the degrees field tells you which Angle is larger, so we can almost get by with writing the method this way:

        public int compareTo(Angle other) {
            return degrees - other.degrees;
        }
Consider the case where an Angle object has a value for degrees that is larger than the other object's value of degrees. Then the expression above returns a positive integer, which indicates that the object is greater. If an Angle object has a lower value for degrees than the other one, then this expression returns a negative value, which indicates that it is less than the other Angle object.

The only case where this fails is when the degree values are equal. In that case, we should use the minutes fields in a similar manner to break the tie. So the right way to code this is as follows:

        public int compareTo(Angle other) {
            if (degrees == other.degrees)
                return minutes - other.minutes;
            else
                return degrees - other.degrees;
        }
When we ran the client code with this new version of the code, we got output like the following:

        [23d 26m, 15d 48m, 39d 14m]
        [23d 26m, 15d 48m, 39d 14m, 13d 57m, 57d 59m, 3d 21m, 30d 42m, 10d 19m]
        [3d 21m, 10d 19m, 13d 57m, 15d 48m, 23d 26m, 30d 42m, 39d 14m, 57d 59m]
We went through this somewhat quickly, but I said that we'd have several more examples in section.


Stuart Reges
Last modified: Sun Nov 4 06:21:44 PST 2012