#### Lecture 14: Abstractions over more than one argument

Comparison function objects

Let’s continue with our example of Runners from Lecture 13. Today we want to help the race organizers find the final standings, find the winner of the race, and handle check-in registration, which requires knowing the names of all the runners.

Do Now!

What kinds of questions will we need to ask about Runners to solve these problems?

##### 14.1` `Finding the final standings

Do Now!

Reconstruct the insertion-sort algorithm, for a list of Runners as sorted by their times in increasing order.

interface ILoRunner { ILoRunner sortByTime(); ILoRunner insertByTime(Runner r); }

// in MtLoRunner public ILoRunner sortByTime() { return this; } public ILoRunner insertByTime(Runner r) { return new ConsLoRunner(r, this); }

// in ConsLoRunner public ILoRunner sortByTime() { return this.rest.sortByTime().insertByTime(this.first); } public ILoRunner insertByTime(Runner r) { if (this.first.finishesBefore(r)) { return new ConsLoRunner(this.first, this.rest.insertByTime(r)); } else { return new ConsLoRunner(r, this); } }

// in Runner boolean finishesBefore(Runner r) { return this.time < r.time; }

But hard-coding insertByTime and sortByTime and even finishesBefore is inflexible; we can see that this is likely to lead to duplication, just like our find methods in Lecture 13 did. We should abstract away these details, and to do that, we need to be able to compare two Runners. Our predicates from the previous lecture won’t help, as they let us answer a boolean question about one Runner: they have the wrong signature.

Do Now!

What signature should this interface define?

interface ICompareRunners { // Returns true if r1 comes before r2 according to this ordering boolean comesBefore(Runner r1, Runner r2); }

Do Now!

Design this class.

class CompareByTime implements ICompareRunners { public boolean comesBefore(Runner r1, Runner r2) { return r1.time < r2.time; } }

Do Now!

Revise the sorting methods above to use this new abstraction.

interface ILoRunner { ILoRunner sortBy(ICompareRuners comp); ILoRunner insertBy(ICompareRunners comp, Runner r); }

// in MtLoRunner public ILoRunner sortBy(ICompareRunners comp) { return this; } public ILoRunner insertBy(ICompareRunners comp, Runner r) { return new ConsLoRunner(r, this); }

// in ConsLoRunner public ILoRunner sortBy(ICompareRunners) { return this.rest.sortBy(comp).insertBy(comp, this.first); } public ILoRunner insertBy(ICompareRunners comp, Runner r) { if (comp.comesBefore(this.first, r)) { return new ConsLoRunner(this.first, this.rest.insertBy(comp, r)); } else { return new ConsLoRunner(r, this); } }

// in Runner // No more method finishesBefore!

marathon.sortByTime()

marathon.sortBy(new CompareByTime())

##### 14.2` `Three-valued comparisons

The ICompareRunners interface has one method, comesBefore, that returns a boolean value: either the first Runner comes before the second or it doesn’t. But that doesn’t quite give us enough information to distinguish whether two Runners tied, or whether the first Runner comes after the second.

// To compute a three-way comparison between two Runners interface IRunnerComparator { // Returns a negative number if r1 comes before r2 in this order // Returns zero if r1 is tied with r2 in this order // Returns a positive number if r1 comes after r2 in this order int compare(Runner r1, Runner r2); }

// In ConsLoRunner public ILoRunner insertBy(IRunnerComparator comp, Runner r) { // comp.compare will return a negative number if its first argument comes first if (comp.compare(this.first, r) < 0) { return new ConsLoRunner(this.first, this.rest.insertBy(comp, r)); } else { return new ConsLoRunner(r, this); } }

class CompareByTime implements IRunnerComparator { public int compare(Runner r1, Runner r2) { if (r1.time < r2.time) { return -1; } else if (r1.time == r2.time) { return 0; } else { return 1; } } }

Do Now!

Can you simplify the method above, given this flexibility and given that here we’re essentially comparing two numbers?

class CompareByTime implements IRunnerComparator { public int compare(Runner r1, Runner r2) { return r1.time - r2.time; } }

##### 14.3` `Finding the winner of the race, two ways

##### The easy way

Do Now!

How much of our code above can we reuse to solve this new question?

// In ILoRunner // Finds the fastest Runner in this list of Runners Runner findWinner(); // Finds the first Runner in this list of Runners Runner getFirst();

Do Now!

Try implementing these two methods for MtLoRunner and ConsLoRunner. Pay attention to the differences in the purpose statements.

// In MtLoRunner Runner findWinner() { throw new RuntimeException("No winner of an empty list of Runners"); } Runner getFirst() { throw new RuntimeException("No first of an empty list of Runners"); }

// In ConsLoRunner Runner findWinner() { return this.sortBy(new CompareByTime()).getFirst(); } Runner getFirst() { return this.first; }

##### 14.3.1` `The smarter way

In Lecture 27, we’ll learn a bit more precisely what is “extravagant” about this approach.

// In ILoRunner Runner findMin(IRunnerComparator comp);

// In ConsLoRunner public Runner findWinner() { return this.findMin(new CompareByTime()); }

// In MtLoRunner public Runner findMin(IRunnerComparator comp) { throw new RuntimeException("No minimum runner available in this list!"); }

Do Now!

Try to implement this method on the ConsLoRunner class. Where does it get stuck?

// In ConsLoRunner public Runner findMin(IRunnerComparator comp) { /* Fields: * this.first * this.rest * * Methods on fields: * this.rest.findMin(ICompareRunner) * * Methods on parameters: * comp.comesBefore(Runner, Runner) */ ... }

// In ILoRunner // Returns the minimum Runner of the given accumulator and every Runner // in this list, according to the given comparator Runner findMinHelp(IRunnerComparator comp, Runner acc);

Do Now!

Implement findMinHelp for MtLoRunner and ConsLoRunner.

// In MtLoRunner public Runner findMinHelp(IRunnerComparator comp, Runner acc) { return acc; }

// In ConsLoRunner public Runner findMinHelp(IRunnerComparator comp, Runner acc) { if (comp.compare(acc, this.first) < 0) { // The accumulator is still the minimum return this.rest.findMinHelp(comp, acc); } else { // this.first comes before the accumulator return this.rest.findMinHelp(comp, this.first); } } public Runner findMin(IRunnerComparator comp) { return this.rest.findMinHelp(comp, this.first); }

Exercise

Design a comparator class ReverseComparator that takes an IRunnerComparator as a parameter, and whose behavior is the reverse of that parameter. I.e., if the given comparator says one Runner is less than the other, then the ReverseComparator will say that it is greater.

Exercise

Design a findMax method. (Hint: There is a very short solution, using the answer to the previous exercise.)

##### 14.4` `Computing the registration roster: sorting alphabetically

Do Now!

Design a CompareByName comparator that compares two Runners by their names.

class CompareByName implements IRunnerComparator { public int compare(Runner r1, Runner r2) { return r1.name.compareTo(r2.name); } }

And we’re done!