Lecture 08: Quiz Review and Supporting Lists

Announcements

A student wrote and asked about tests timing out. The timeout is there to catch infinite loops; if you have a test time out, that’s the reason, not that your computer is slow.

A student wrote and asked about letter grades. I wrote:

This is an experimental course. I will not assign letter grades to numerical grades until after all numerical grades are finalized. But given the large fraction of the course grade that is relatively straightforward to get full credit on (in-class exercises, labs), it would be unwise to expect a large curve.

To expand slightly: actually computing the curve is a lot of work; I’m not going to do it yet. But you know as well as I that the in-class exercises and labs are basically free points. You should be thinking about how you’re doing on the HWs, the programming assignments, and the quizzes, particularly if you plan to take 187 in the future. They’re the best indicator you (and I) have your abilities.

Quiz review

So the quiz was on 121 material, which is appropriate, since we’d been reviewing up until the week before.

Most people did OK on the quiz: mean 15.3, median 17. If you got below about a 15, you might want to think about some things. Be glad you’re not in 187, I guess. Did you just have a bad day? Or was it something more fundamental? Come to office hours to talk with Garrett or me about additional review you might be able to do on your own.

By far the place where most people seemed to have the most trouble on the programming portion of the quiz (the third question). So we’re going to spend part of today’s lecture going over the four programming problems. I’ll point out some of the odd or incorrect things people did along the way and try to give you some advice about how to avoid making these mistakes in the future.

A. A sequence is said to be a trincremental if each value is exactly three greater than the previous value. Write a method isTrincrementalSequence that takes a single array of ints as a parameter and returns true if and only if the array represents such a sequence. Assume the array contains at least two values.

So let’s run through some options.

Good:

boolean isTrincremental(int[] a) {
    for (int i = 1; i < a.length; i++) {
        if (a[i] != a[i-1] + 3) {
            return false;
        }
    }
    return true;
}

Bad:

(Sample error: return true too early.)

(Sample error: set boolean each time through the loop.)

Ugly:

(Sample w/ counter.)

(Sample w/ boolean.)

(Sample w/ bounds error.)

B. A dupledrome is a word that contains only doubled letters, like “llaammaa” or “ssaabb”. Write a method isDupledrome that takes a single String parameter representing a word, and returns true if and only if the word is a dupledrome. The charAt method of String will be helpful. Assume the value of the parameter is a single, lowercase word.

boolean isDupledrome(String s) {
    if (s.length() % 2 != 0) {
        return false;
    }

    for (int i = 0; i < s.length(); i +=2) {
        if (s.charAt(i) != s.charAt(i+1)) {
            return false;
        }
    }
    return true;
}

C. A sequence is said to be a doubling sequence if each value is exactly twice the previous value. Write a method isDoublingSequence that takes a single array of doubles as a parameter and returns true if and only if the array represents such a sequence. Assume the array contains at least two values.

boolean isDoubling(double[] a) {
    for (int i = 1; i < a.length; i++) {
        if (a[i] != a[i-1] * 2) {
            return false;
        }
    }
    return true;
}

D. A palindrome is a string that reads the same backward and forward, for example, “abba” or “amanaplanacanalpanama”. Write a method that takes only a String as a parameter that returns true if and only if the value of the parameter is a palindrome. The charAt} method of String will be helpful. Assume the value of the parameter is a lowercase string containing only alphabetic characters.

boolean isPalindrome(String s) {
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) != s.charAt(s.length() - i - 1)) {
            return false;
        }
    }
    return true;
}

Putting it together

So we’ve been talking about Lists and generics and reviewing 121 stuff like boolean conditions and flow control. I thought it would be interesting to put it all together into working example.

We’re going to design and write a class to represent simplified postal addresses (street numbers and names). Then we’re going to dump it several instances into a list. Finally, we’re going to define a custom Comparator on a postal address that will let us sort the list in a special way.

Let’s get going.

Given the problem statement, we know we’re going to want to define a class that defines objects containing a street number and name:

public class PostalAddress {
    public final int number;
    public final String streetName;
}

What is up with Marc and his use of public final? Here’s what’s up: when you know a data type is going to remain fixed, and that any particular object isn’t going to have its data change, there’s no reason to deal with the pain of writing private instance variables and then turning around to write public accessors (get methods). “But Marc, what if you change how the value is stored, or how it is determined?” Then you’ll be giving a different name (or writing an accessor, etc.) and your IDE will flag all occurrences for you to fix. Or better yet, will allow you to refactor them all yourself.

(See “Effective Java, 2nd edition” which though eight years old is one of the best bits of reading you can do once you’re an intermediate Java programmer.)

Anyway, now let’s add a few constructors. First the obvious one:

public PostalAddress(int number, String streetName) {
    this.number = number;
    this.streetName = streetName;
}

Now one that does some simple parsing:

public PostalAddress(String textAddress) {
    String[] matches = textAddress.split("\\s+", 1);
    this.number = Integer.parseInt(matches[0]);
    this.streetName = matches[1];
}

OK! Now we’ve got a simple class. Let’s try making a few of them and adding them to a list:

List<PostalAddress> addresses = new ArrayList<PostalAddress>();

for (int i = 1; i <= 10; i++) {
    addresses.add(new PostalAddress(i, "Maple St"));
}

and maybe printing them out:

System.out.println(addresses);

Ugh, what’s this PostalAddress@677327b6 nonsense? Well, it turns out when you print an object, Java tries to coerce it to a String using its toString method. We haven’t written one, so we get the Object default, which is what you see. It is derived from the class name and the hashCode() method. Since we haven’t defined the latter, we get its default, which is usually but not always the object’s memory address. Yuck. Let’s make it better:

public String toString() {
    return streetName + ", " + number;
}

Now it’s a little better. Hey, let’s see if 6 Maple Street is in our list:

System.out.println(addresses.contains(new PostalAddress("6 Maple St")));

false? What? Maybe our constructor is broken, let’s try the other one:

System.out.println(addresses.contains(new PostalAddress(6, "Maple St")));

Nope, still false. Why? Let’s look at the List.contains method javadoc. Effing trinary. What does (o==null ? e==null : o.equals(e)) mean? This is a very terse way to express an if/else statement that returns a value.

Break it on the ? and the :. If the thing before the ? evaluates to true, return the thing between the ? and :; else return the thing after the :. Here, it means return true if both things are null; if not, only if equals returns true. Ahh, but we haven’t written an equals, which means we’re using the default one from Object. …which checks hashCode, which will be different for different instances of the object. (Look it up in the java doc) Let’s fix this problem?

public boolean equals(PostalAddress o) {
    return number == o.number && streetName.equals(o.streetName);
}

But it still doesn’t work? Oh man, you’re gonna love this. Note that contains (and equals) work on Objects, not PostalAddresses. So we need to change the signature. And then check the type:

public boolean equals(Object o) {
    if (!(o instanceof PostalAddress)) return false;
    PostalAddress p = (PostalAddress)o;
    return number == p.number && streetName.equals(p.streetName);
}

OK, better. But I will note we are omitting some important-in-practice details, as we’re ignoring null and we’re violating the contract for hashCode:

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

We’ll come back to hashCode in more detail when we get to Sets later, but for now we’ll ignore this problem.

OK, our last task. Let’s say we want to be able to sort our address list. We use the sort function on the List, which if passed null uses the natural order, or if passed a Comparator uses it to sort the list. But what’s the natural order of a PostalAddress? It’s only defined if the PostalAddress implements Comparable. So looks like we’re doing one or the other.

Generally, you want to define Comparable if you expect values of the data type to be compared and you want there to be a canonical way to compare them. You usually define custom Comparators for things like the custom sort. Let’s do both. First, let’s impose a natural order on PostalAddress so that they sort first by street name, then by number. Add implements Comparable<PostalAddress> to the class signature, and Eclipse can helpfully add the missing method:

@Override
public int compareTo(PostalAddress o) {
    // TODO Auto-generated method stub
    return 0;
}

Well that won’t do.

Class exercises

Implement the method. (JavaDoc for compareTo on board.) See: http://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html

@Override
public int compareTo(PostalAddress o) {
    if (streetName.compareTo(o.streetName) < 0) return -1;
    if (streetName.compareTo(o.streetName) > 0) return 1;
    if (number < o.number) return -1;
    if (number > o.number) return 1;
    return 0;       
}

or slightly more concisely:

@Override
public int compareTo(PostalAddress o) {
    if (streetName.compareTo(o.streetName) < 0) return -1;
    if (streetName.compareTo(o.streetName) > 0) return 1;
    return Integer.compare(number, o.number);
}

Let’s create the list out of order, print it, sort it, then print it:

for (int i = 10; i >=1; i -= 2) {
    addresses.add(new PostalAddress(i, "Maple St"));
}       
for (int i = 1; i < 10; i += 2) {
    addresses.add(new PostalAddress(i, "Birch St"));
}
System.out.println(addresses);
addresses.sort(null);
System.out.println(addresses);      

Hey, it works!

Now let’s define a custom comparator for use in doing a “postal sort”. That is, we still want to sort such that street names are alphabetical, but we want the numbers sorted as all odd first (in ascending order), then all even (in descending order). This is how the truck might go up and down the street (on board).

What does that look like? Let’s declare a new Comparator:

public class PostalOrderComparator implements Comparator<PostalAddress> { ... }

Again, Eclipse helpfully fills it out with the method we need to implement, so let’s do it.

It will be similar to but more complicated than the compareTo method we just wrote. A tip: x % 2 == 0 if and only if x is even. x % 2 == 1 iff it’s false.

public int compare(PostalAddress o1, PostalAddress o2) {
    if (o1.streetName.compareTo(o2.streetName) < 0) return -1;
    if (o1.streetName.compareTo(o2.streetName) > 0) return 1;
    if (o1.number % 2 == 1 && o2.number % 2 == 0) return -1;
    if (o1.number % 2 == 0 && o2.number % 2 == 1) return 1;
    if (o1.number % 2 == 1) return Integer.compare(o1.number, o2.number);
    if (o1.number % 2 == 0) return Integer.compare(o2.number, o1.number);
    return 0;
}

And let’s check it out:

for (int i = 6; i >=1; i -= 2) {
    addresses.add(new PostalAddress(i, "Maple St"));
}       
for (int i = 1; i < 6; i += 2) {
    addresses.add(new PostalAddress(i, "Birch St"));
}
for (int i = 6; i >=1; i -= 2) {
    addresses.add(new PostalAddress(i, "Birch St"));
}       
for (int i = 1; i < 6; i += 2) {
    addresses.add(new PostalAddress(i, "Maple St"));
}
System.out.println(addresses);
addresses.sort(null);
System.out.println(addresses);      
addresses.sort(new PostalOrderComparator());
System.out.println(addresses);      

Things we might do to improve this? Add an isOdd and/or isEven method for readability, perhaps.