8.10
Lecture 7: Accumulator methods, continued
Methods on trees, and
accumulator-style methods on lists
Overview
We continue with our ancestry trees example from Lecture 6, working through two subtle examples
with accumulators, and then ending with a brief discussion of how accumulators can behave in
potentially unexpected ways.
7.1 Finding the younger of two IATs
In the section below, we’re going to need to compare two IATs and find the younger of them.
This is similar to the bornInOrBefore method, except we’re going to return an IAT
instead of a boolean. We want a signature
IAT youngerIAT(IAT other); |
Let’s work through the cases:
If this IAT is Unknown, and the given IAT is Unknown, return Unknown.
If this IAT is Unknown, and the given IAT is a Person, return the Person.
If this IAT is a Person, and the given IAT is Unknown, return the Person.
If this IAT is a Person, and the given IAT is Person, return the younger of the two Persons.
The first two cases are nice: if this IAT is Unknown, we just return the given IAT, regardless
of what it is.
public IAT youngerIAT(IAT other) { return other; } |
But the other two Person cases are harder, since the behavior depends on what the given IAT is.
public IAT youngerIAT(IAT other) { |
|
} |
Again, inside this method we can’t determine the age of the given IAT, since it might be Unknown. So
we need a helper method that we invoke on the given IAT (like we invoked on this.mom above) where we
pass along this Person’s age:
IAT youngerIAT(IAT other); |
IAT youngerIATHelp(IAT other, int otherYob); |
public IAT youngerIAT(IAT other) { return other; } |
IAT youngerIATHelp(IAT other, int otherYob) { return other; } |
public IAT youngerIAT(IAT other) { |
|
return other.youngerIATHelp(this, this.yob); |
} |
IAT youngerIATHelp(IAT other, int otherYob) { |
|
if (this.yob > otherYob) { |
return this; |
} |
else { |
return other; |
} |
} |
This implementation of youngerIAT for Person is quite strange at first sight.
Why should we pass both this and this.yob? And why should
invoking a method on other be of any help here?
By invoking a method on other, we allow Java’s dynamic dispatch to determine whether
other is a Unknown or a Person — precisely the remaining question we
had to figure out! We pass this along because it might be the desired result for the method.
We pass this.yob because (like with bornInOrBefore) without it, youngerIATHelp would not
be able to access the year of birth of the given IAT.
(Look at the purpose statement for other.youngerIATHelp very carefully, substituting “this” and “other”
appropriately: If other is an Unknown, then other.youngerIATHelp(this, this.yob) will return this Person.
On the other hand, if other is another Person, then other.youngerIATHelp(this. this.yob) will return
other if other is younger than this Person, or else this Person.)
7.2 Finding the youngest grandparent
To determine the youngest grandparent of a given IAT, let’s try a simpler method first: determine
the youngest parent of a given IAT.
public IAT youngestParent() { return new Unknown(); } |
public IAT youngestParent() { |
|
} |
This is fairly straightforward: we have this.mom and this.dad, and we have youngerIAT to return the younger of them:
public IAT youngestParent() { |
return this.mom.youngerIAT(this.dad); |
} |
Now to implement youngestGrandparent, we can just return the youngerIAT of a Person’s parents’ youngestParents:
IAT youngestGrandparent(); |
public IAT youngestGrandparent() { return new Unknown(); } |
public IAT youngestGrandparent() { |
|
return this.mom.youngestParent().youngerIAT(this.dad.youngestParent()); |
} |
How can we generalize this to greatgrandparents and beyond? Notice that the implementations of youngestParent and youngestGrandparent
are pretty similar: they both invoke youngerIAT on the youngest appropriate relative on this.mom’s side and the youngest appropriate
relative on this.dad’s side.
We can simplify these methods with an accumulator, which keeps track of what number generation we’re examining:
The youngest IAT 1 generation away from this.andrew is this.andrew’s youngest parent.
The youngest IAT 2 generations away from this.andrew is this.andrew’s youngest grandparent.
The youngest IAT 0 generations away from this.andrew is...this.andrew!
Our code can then simplify to the following:
IAT youngestAncInGen(int gen); |
public IAT youngestAncInGen(int gen) { |
if (gen == 0) { |
return this; |
} |
else { |
return new Unknown(); |
} |
} |
public IAT youngestAncInGen(int gen) { |
|
if (gen == 0) { |
return this; |
} |
else { |
return this.mom.youngestAncInGen(gen - 1).youngerIAT(this.dad.youngestAncInGen(gen - 1)); |
} |
} |
Complete the definition of youngestGrandparent, now that youngestAncInGen is defined.
The gen parameter is our accumulator, where this time it is “counting down” to decide at what depth to stop the recursion.
7.3 Potential hazards of accumulator-style methods
Design a method append for lists of Strings twice: first in direct style,
and then again using an accumulator parameter.
Do you notice any differences in the output? (Did you write enough tests?)