Lecture 6: Accumulator methods
Methods on trees, and accumulator-style methods on lists
Overview
This lecture practices several patterns of using delegation and helper methods. Each example gets more sophisticated than the previous, and keeping straight which objects are having their methods invoked, versus which objects are being passed as arguments, can be confusing. It helps to step through each method by hand, using the purpose statements to guide your understanding.
Note that we’re using a simplified representation of gender here—
either male or female— and so we are using a boolean to represent it. Given that, we need to choose which boolean value to map to which gender, and our field name reflects whichever convention we pick. A fuller data definition would need a more inclusive datatype here, specifically an enumeration, but we haven’t discussed that form of type definition in Java yet.
interface IAT { } class Unknown implements IAT { Unknown() { } } class Person implements IAT { String name; int yob; boolean isMale; IAT mom; IAT dad; Person(String name, int yob, boolean isMale, IAT mom, IAT dad) { this.name = name; this.yob = yob; this.isMale = isMale; this.mom = mom; this.dad = dad; } }
interface ILoString { } class ConsLoString implements ILoString { String first; ILoString rest; ConsLoString(String first, ILoString rest) { this.first = first; this.rest = rest; } } class MtLoString implements ILoString { MtLoString() { } }
// To compute the number of known ancestors of this ancestor tree // (excluding this ancestor tree itself) int count(); // To compute how many ancestors of this ancestor tree (excluding this // ancestor tree itself) are women older than 40 (in the current year)? int femaleAncOver40(); // To compute whether this ancestor tree is well-formed: are all known // people younger than their parents? boolean wellFormed(); // To compute the names of all the known ancestors in this ancestor tree // (including this ancestor tree itself) ILoString ancNames(); // To compute this ancestor tree's youngest grandparent IAT youngestGrandparent();
class ExamplesIAT { IAT enid = new Person("Enid", 1904, false, new Unknown(), new Unknown()); IAT edward = new Person("Edward", 1902, true, new Unknown(), new Unknown()); IAT emma = new Person("Emma", 1906, false, new Unknown(), new Unknown()); IAT eustace = new Person("Eustace", 1907, true, new Unknown(), new Unknown()); IAT david = new Person("David", 1925, true, new Unknown(), this.edward); IAT daisy = new Person("Daisy", 1927, false, new Unknown(), new Unknown()); IAT dana = new Person("Dana", 1933, false, new Unknown(), new Unknown()); IAT darcy = new Person("Darcy", 1930, false, this.emma, this.eustace); IAT darren = new Person("Darren", 1935, true, this.enid, new Unknown()); IAT dixon = new Person("Dixon", 1936, true, new Unknown(), new Unknown()); IAT clyde = new Person("Clyde", 1955, true, this.daisy, this.david); IAT candace = new Person("Candace", 1960, false, this.dana, this.darren); IAT cameron = new Person("Cameron", 1959, true, new Unknown(), this.dixon); IAT claire = new Person("Claire", 1956, false, this.darcy, new Unknown()); IAT bill = new Person("Bill", 1980, true, this.candace, this.clyde); IAT bree = new Person("Bree", 1981, false, this.claire, this.cameron); IAT andrew = new Person("Andrew", 2001, true, this.bree, this.bill); boolean testCount(Tester t) { return t.checkExpect(this.andrew.count(), 16) && t.checkExpect(this.david.count(), 1) && t.checkExpect(this.enid.count(), 0) && t.checkExpect(new Unknown().count(), 0); } boolean testFemaleAncOver40(Tester t) { return t.checkExpect(this.andrew.femaleAncOver40(), 7) && t.checkExpect(this.bree.femaleAncOver40(), 3) && t.checkExpect(this.darcy.femaleAncOver40(), 1) && t.checkExpect(this.enid.femaleAncOver40(), 0) && t.checkExpect(new Unknown().femaleAncOver40(), 0); } boolean testWellFormed(Tester t) { return t.checkExpect(this.andrew.wellFormed(), true) && t.checkExpect(new Unknown().wellFormed(), true) && t.checkExpect( new Person("Zane", 2000, true, this.andrew, this.bree).wellFormed(), false); } boolean testAncNames(Tester t) { return t.checkExpect(this.david.ancNames(), new ConsLoString("David", new ConsLoString("Edward", new MtLoString()))) && t.checkExpect(this.eustace.ancNames(), new ConsLoString("Eustace", new MtLoString())) && t.checkExpect(new Unknown().ancNames(), new MtLoString()); } boolean testYoungestGrandparent(Tester t) { return t.checkExpect(this.emma.youngestGrandparent(), new Unknown()) && t.checkExpect(this.david.youngestGrandparent(), new Unknown()) && t.checkExpect(this.claire.youngestGrandparent(), this.eustace) && t.checkExpect(this.bree.youngestGrandparent(), this.dixon) && t.checkExpect(this.andrew.youngestGrandparent(), this.candace) && t.checkExpect(new Unknown().youngestGrandparent(), new Unknown()); } }
Do Now!
Draw the ancestry tree for the example data above, and confirm that the test outputs are actually what we expect.
6.1 Counting
// In IAT: // To compute the number of known ancestors of this ancestor tree (excluding this ancestor tree itself) int count();
// In Unknown: // To compute the number of known ancestors of this Unknown (excluding this Unknown itself) public int count() { return 0; }
// In Person: // To compute the number of known ancestors of this Person (excluding this Person itself) public int count() { /* Template: * Fields: * this.name -- String * this.yob -- int * this.isMale -- boolean * this.mom -- IAT * this.dad -- IAT * Methods: * this.count() -- int * Methods of fields: * this.mom.count() -- int * this.dad.count() -- int */ }
Do Now!
What exactly will invoking this.mom.count() produce? Specialize the purpose statement for count to this.mom.
// In Person: public int count() { return this.mom.count() + this.dad.count(); }
Do Now!
Does this pass our tests? Why or why not?
// In Person: public int count() { return 1 + this.mom.count() + this.dad.count(); }
Do Now!
Does this pass our tests? Why or why not?
// In IAT: // To compute the number of known ancestors of this ancestor tree (excluding this ancestor tree itself) int count(); // To compute the number of known ancestors of this ancestor tree (*including* this ancestor tree itself) int countHelp();
// In Unknown: // To compute the number of known ancestors of this Unknown (excluding this Unknown itself) public int count() { return 0; } // To compute the number of known ancestors of this Unknown (*including* this Unknown itself) public int countHelp() { return 0; }
// In Person: // To compute the number of known ancestors of this Person (excluding this Person itself) public int count() { return this.mom.countHelp() + this.dad.countHelp(); } // To compute the number of known ancestors of this Person (*including* this Person itself) public int countHelp() { return 1 + this.mom.countHelp() + this.dad.countHelp(); }
Do Now!
Now does this pass our tests? Why or why not?
6.2 Counting only some items
Do Now!
Adapt the previous solution to count, to design the method femaleAncOver40.
Recall the purpose statement of femaleAncOver40: to compute how many ancestors of this ancestor tree (excluding this ancestor tree itself) are women older than 40 (in the current year). Since this has the same “treat the initial IAT differently from all ancestors” exception, we might well guess that we’ll need a helper method here, too:
// In IAT: // To compute how many ancestors of this ancestor tree (excluding this ancestor tree itself) // are women older than 40 (in the current year). int femaleAncOver40(); // To compute how many ancestors of this ancestor tree (*including* this ancestor tree itself) // are women older than 40 (in the current year). int femaleAncOver40Help();
// In Unknown: // To compute how many ancestors of this Unknown (excluding this Unknown itself) // are women older than 40 (in the current year). public int femaleAncOver40() { return 0; } // To compute how many ancestors of this Unknown (*including* this Unknown itself) // are women older than 40 (in the current year). public int femaleAncOver40Help() { return 0; }
// In Person: // To compute how many ancestors of this Person (excluding this Person itself) // are women older than 40 (in the current year). public int femaleAncOver40() { /* Template: * Fields: * this.name -- String * this.yob -- int * this.isMale -- boolean * this.mom -- IAT * this.dad -- IAT * Methods: * this.count() -- int * this.countHelp() -- int * this.femaleAncOver40() -- int * this.femaleAncOver40Help() -- int * Methods of fields: * this.mom.count() -- int * this.mom.countHelp() -- int * this.mom.femaleAncOver40() -- int * this.mom.femaleAncOver40Help() -- int * this.dad.count() -- int * this.dad.countHelp() -- int * this.dad.femaleAncOver40() -- int * this.dad.femaleAncOver40Help() -- int */ } // To compute how many ancestors of this Person (*including* this Person itself) // are women older than 40 (in the current year). public int femaleAncOver40Help() { /* same template as above */ }
// In Person: // To compute how many ancestors of this Person (excluding this Person itself) // are women older than 40 (in the current year). public int femaleAncOver40() { return this.mom.femaleAncOver40Help() + this.dad.femaleAncOver40Help(); } // To compute how many ancestors of this Person (*including* this Person itself) // are women older than 40 (in the current year). public int femaleAncOver40Help() { if (2015 - this.yob > 40 && !this.isMale) { return 1 + this.mom.femaleAncOver40Help() + this.dad.femaleAncOver40Help(); } else { return this.mom.femaleAncOver40Help() + this.dad.femaleAncOver40Help(); } }
Do Now!
Does this pass our tests? Why or why not?
Exercise
Design the method numTotalGens, which counts how many generations (including this IAT’s generation) are completely known. Design the method numPartialGens, which counts how many generations (including this IAT’s generation) are at least partially known. These methods should match the following behavior:
boolean testNumGens(Tester t) { return t.checkExpect(this.andrew.numTotalGens(), 3) && t.checkExpect(this.andrew.numPartialGens(), 5) && t.checkExpect(this.enid.numTotalGens(), 1) && t.checkExpect(this.enid.numPartialGens(), 1) && t.checkExpect(new Unknown().numTotalGens(), 0) && t.checkExpect(new Unknown().numPartialGens(), 0); }
6.3 Well-formedness
We define an ancestry tree to be well-formed if every Person in it is younger than its parents. We decide that all Unknowns are well-formed (they certainly aren’t older than their parents!). Let’s start our method templates, and see how far we can get:
// In IAT: // To determine if this ancestry tree is well-formed boolean wellFormed();
// In Unknown: // To determine if this Unknown is well-formed public boolean wellFormed() { return true; }
// In Person: // To determine if this Person is well-formed public boolean wellFormed() { /* Template: * Fields: * this.yob -- int * ... others as before * Methods: * ... others as before * this.wellFormed() -- boolean * Methods of fields: * ... others as before * this.mom.wellFormed() -- boolean * this.dad.wellFormed() -- boolean */ }
One solution: age-related helper methods
We could imagine creating a getAge method on IAT, so that we could ask any IAT what its age is...but there is no good answer to return for Unknown, so this approach won’t work.
We could try creating a youngerThan(IAT) method on IAT, so that we could ask any IAT whether it is younger than the given IAT. But this runs into the exact same problem, that we can’t ask the given IAT what its age is.
Asking person A the question “are you younger than person B?” is equivalent to asking person B “are you at least as old as person A?”. So we could imagine creating a atLeastAsOldAs(IAT) method on IAT. This would let us define the method on Unknowns to return true (since Unknown ancestors are at least as old as known ones), but in the method on Person, again we cannot obtain the age of the given IAT.
But! What if, instead of asking the question “are you at least as old as person A?”, we ask the subtly different “are you at least as old as person A’s age?” How does this help? Now, a Person can ask its parents whether they are older than its own age —
after all, a Person knows its own age, and can pass that information along to where it is needed.
// In IAT: // To determine if this ancestry tree is born in or before the given year boolean bornInOrBefore(int yob);
// In Unknown: // To determine if this Unknown is born in or before the given year public boolean bornInOrBefore(int yob) { return true; }
// In Person: // To determine if this Person is born in or before the given year public boolean bornInOrBefore(int yob) { /* Template: * Fields: * this.yob -- int * ... others as before * Methods: * ... others as before * Methods of fields: * ... others as before */ // Hooray -- we have all the information we need! return this.yob <= yob; }
// In Person: // To determine if this Person is well-formed public boolean wellFormed() { /* Template: * Fields: * this.yob -- int * ... others as before * Methods: * ... others as before * this.wellFormed() -- boolean * this.bornInOrBefore(int yob) -- boolean * Methods of fields: * ... others as before * this.mom.wellFormed() -- boolean * this.mom.bornInOrBefore(int yob) -- boolean * this.dad.wellFormed() -- boolean * this.dad.bornInOrBefore(int yob) -- boolean */ return this.mom.bornInOrBefore(this.yob) && this.dad.bornInOrBefore(this.yob) && this.mom.wellFormed() && this.dad.wellFormed(); }
Another solution: accumulating the age information
In this particular case, the helper method bornInOrBefore seems a bit contrived;
it’s not likely to be of much use besides this particular problem. Also notice that
the implementation of wellFormed is going to invoke wellFormed recursively on
this.mom and this.dad —
Do Now!
What does the yob parameter mean? When bornInOrBefore(int yob) was invoked in the code above, whose year of birth are we passing along?
// In IAT: // To determine if this ancestry tree is well-formed boolean wellFormed(); // To determine if this ancestry tree is older than the given year of birth, // and its parents are well-formed boolean wellFormedHelp(int childYob);
// In Unknown: // To determine if this Unknown is well-formed public boolean wellFormed() { return true; } // To determine if this Unknown is older than the given year of birth, // and its parents are well-formed boolean wellFormedHelp(int childYob) { return true; }
// In Person: // To determine if this Person is well-formed public boolean wellFormed() { /* Template: * Fields: * this.yob -- int * ... others as before * Methods: * ... others as before * this.wellFormed() -- boolean * this.wellFormedHelp(int childYob) -- boolean * Methods of fields: * ... others as before * this.mom.wellFormed() -- boolean * this.mom.wellFormedHelp(int childYob) -- boolean * this.dad.wellFormed() -- boolean * this.dad.wellFormedHelp(int childYob) -- boolean */ } // To determine if this Person is older than the given year of birth, // and its parents are well-formed boolean wellFormedHelp(int childYob) { /* same template as above */ }
// In Person: // To determine if this Person is older than the given year of birth, // and its parents are well-formed boolean wellFormedHelp(int childYob) { return this.yob <= childYob && this.mom.wellFormedHelp(this.yob) && this.dad.wellFormedHelp(this.yob); }
// In Person: // To determine if this Person is well-formed: is it younger than its parents, // and are its parents are well-formed boolean wellFormed() { return this.mom.wellFormedHelp(this.yob) && this.dad.wellFormedHelp(this.yob); }
We call these extra parameters, that accumulate information as we progress through recursive calls, accumulators. This is a very powerful mechanism for improving the expressiveness of recursive methods, so Lecture 7 works through another example in this style.