Homework 5: Building a Voting Machine
Due Tuesday December 8 at 11:59 pm via InstructAssist. Submit to the area that matches which version of the assignment you did (see below).
If you are planning to go on to 3000- or 4000-level CS classes, this would be a good time to switch over to Eclipse if you’ve been using DrJava all term.
1 Problem Description: Building a Flexible Voting System
You are building software to conduct an election. In this election, voters will state their top three choices (rather than a single choice). There are many different ways to determine winners in such preference-based elections. Unfortunately, the people conducting the election haven’t yet decided how they will count the votes. Therefore, they want you to build a voting system that will record votes, but will also let them experiment with different ways to determine the winner.
This starter file gives you a very rudimentary voting program: it presents a login screen that asks a voter to enter the name to vote for, stores that choice, and counts votes for a given candidate. Your job is to
extend the system to store three votes per voter and
provide two different methods for determining who wins the election (described below),
while using exceptions to detect malformed votes and duplicate names on the ballot
and producing clean, encapsulated code.
Your solution to this homework should NOT include the countVotes method from the starter file or the current votes variable of type LinkedList<String>. You will still have a votes variable (or something like it), but with a different type that can handle three ranked choices per voter.
If you want to test your Java skills, spend a little time thinking about how you might do this before reading how we break this assignment into steps below.
1.1 Assignment Options Based on Desired Course Grade
This assignment can be done at two "levels", which differ in the program design criteria that you try to meet (which in turn affects the course grade you can earn).
The full level includes both encapsulation and separating interface from system code (as we will do on Friday for the banking system).
The core level lets you focus on basic Java programming and testing, skipping encapsulation and leaving the interface in with the rest of the code. Doing the core option will cap your overall course grade at B.
The core option is designed for those who are struggling in the course and are more concerned about passing the course than with getting a high grade. If you are in this group, we’d rather see you master the basics than do a poorly-quality job on more material. Focusing in this way may also help you prepare for the programming exam.
You cannot get an A in the course without attempting the full level. This does not mean that you need a particular score on the assignment to get an A, but we need to see you making a good faith attempt at encapsulation and interface organization as part of earning an A.
The subsection headings in the following assignment details clearly mark which details are required for each of full and core.
2 Detailed Problems and Class Names
Warning: Do this assignment in stages, making sure that your program runs properly after each stage. If you try to make all of the changes at once, you’ll spend far longer than needed on this assignment. Build up to your solution step by step. You can get much more credit for fully working early steps than for having most of the steps only partly working.
Update 12/4: Here is a file that you can use to check your work for autograding. It isn’t a Main.java, but rather a file that you can include alongside your other Java files once you have all of the methods and exceptions in place. You will get compilation errors when including this file if you are missing exceptions, methods, throws statements, and other details that autograding will depend on. We posted this instead of a Main.java so as not to interfere with your work on the early steps (when the AutogradeCheck file would not compile).
2.1 Step 1: Record three choices per voter (Core and Full)
Edit the starter code to store the number of first, second, and third choice votes for each candidate on a ballot. Before reading on, think about what data structure(s) you might use for this information. After you’ve come up with your proposal on paper, feel free to look at (and use, or not) our suggested data structure.
Once you have your data structure, add the following two methods:
Add a processVote method that takes three strings (for the first, second, and third choices, respectively) and returns void. This method stores a single voter’s choices in your data structure. (Calling this method corresponds to someone voting in the election.)
Add a addCandidate method that takes one string (for the name of a candidate) and adds the candidate to the ballot, returning void. If the named candidate was already on the ballot, throw a CandidateExistsException whose constructor takes the name as its only argument.
If necessary, you can also do other work in this method to set up your data structures to know about the new candidate (such extra work will not affect autograding as long as the method takes a string, returns void, and adds the candidate to the ballot).
Use Java’s built-in .equals method to check whether names are
the same (our solution will do the same). This will treat names with
different capitalization as different (such as "kathi" and "Kathi"). Our
tests against your code will not try to trick you with this—
Update 12/6: The .contains method uses .equals, so using .contains is fine to check whether a name is in a list.
You do not need any special handling around empty strings passed as names. Our tests will not use empty strings as names and our solution will treat the empty string as a name like any other. Similarly, you may assume that null will not be given as a name.
2.2 Step 2: Add Exceptions to Report Malformed Votes (Core and Full)
A vote is only valid if the voter enters three different names, each of which is a candidate on the ballot. Modify your processVote method to check these criteria, using exceptions (named below) to report problems and require the voter to vote again if their vote is not valid.
Create Exception subclasses named DuplicateVotesException and UnknownCandidateException. The constructor for each method should take a String for the name of the candidate that was voted for multiple times or not on the ballot, respectively. If multiple names were not on the ballot, report the first one (in order of the arguments to processVote).
For purposes of autograding, we need to agree on which of these two exceptions will be thrown if both problems arise on the same call to processVote. Check/throw the UnknownCandidateException before the DuplicateVotesException if both apply; our reference solution and test cases will do/assume the same.
2.3 Step 3: Support Methods to Compute the Winner (Core and Full)
Next, add the following two methods for counting votes. Each returns the name of the winning candidate. In case of a tie, return the name of one candidate with the highest score (any candidate with the highest score would be a valid answer):
In findWinnerMostFirstVotes the winner is the candidate with the most first place votes.
In findWinnerMostPoints the winner is the candidate with the most points under the following formula: three points for each first-place vote they received, two points for each second-place vote they received, and one point for each third-place vote they received.
Update 12/5: You may assume that these methods will only be called after at least one valid vote has been processed. While normally it would be better to just throw an exception if there were no valid votes, we’re using the assumption to reduce the impact of changes to the assignment mid-stream. Our tests of your code will respect this assumption.
You should delete the countVotes method from the starter file once you have these methods in place. We put countVotes in the starter file only to give you an initial understanding of the voting system.
2.4 Step 4: Include Appropriate Access Modifiers (Core and Full)
Put an appropriate access modifier (public, private, or protected) on each of your fields and methods.
2.5 Step 5: Encapsulate Recorded Votes (Full Only)
Now that you have the ability to record votes, encapsulate the data structure that you are using to capture the voting data. You don’t need to be able to customize the specific data structure from outside the ElectionData class (as we did when we passed different data structures into the Banking constructor). However, someone should be able to change the data structure for cast votes simply by changing the name of the class you instantiate within the ElectionData class.
Update 12/4: The phrase "don’t need to customize the data structure from outside" means you don’t have to create an interface when you encapsulate the data. You normally would (and you certainly can if you want), but we won’t be grading for that. It suffices for you to replace the current LinkedList<String> type on votes with a new type. [end update]
In particular, leave the constructor for your ElectionData class as taking no arguments (so it will work with autograding).
Leave the ballots as a LinkedList<String>. You do not need to encapsulate those.
2.6 Step 6: Separate the User Interface from the Voting Data (Full Only)
The starter file put the entire voting system—
In particular, the four key methods for this
assignment—
You do not need to add methods to VotingMachine that report winners (since voters would not be the ones computing the election results); the existing screen method serves as the interface to processVote.
3 Testing Expectations (Core and Full)
Think of your test cases as mock elections: each will set up a ballot, cast some votes, and determine the winner by one of the two voting methods. Here is an example of one way to set up a test case for this assignment: we write a method to populate a ballot and cast votes, then write a checkExpect to test the outcomes.
// method to set up a ballot and cast votes |
ElectionData Setup1 () { |
ElectionData ED = new ElectionData(); |
|
// put candidates on the ballot |
ED.addCandidate("gompei"); |
ED.addCandidate("husky"); |
ED.addCandidate("ziggy"); |
// cast votes |
try { |
ED.processVote("gompei", "husky", "ziggy"); |
ED.processVote("gompei", "ziggy", "husky"); |
ED.processVote("husky", "gompei", "ziggy"); |
} catch (Exception e) {} |
return(ED); |
} |
|
// now run a test on a specific election |
boolean testMostFirstWinner (Tester t) { |
return t.checkExpect(Setup1().findWinnerMostFirstVotes(), |
"gompei"); |
} |
For an election/test that would raise an exception, you might put the processVote calls in the test method directly – that’s also fine. This example just tries to give you an idea of how to get started.
Note that tests, ours and yours, should run directly through the ElectionData methods, not through the screen-based interface.
Your Examples class should contain up to 12 (twelve) tests. You can run multiple tests on the same ballot and/or election, or you can create different elections. Running each test should require calling only the required methods (addCandidate, processVote, findWinnerMostFirstVotes, and findWinnerMostPoints) and operations that are built into Java. Calling any other methods from your ElectionData or VotingMachine classes will break autograding.
3.1 Things To Consider About Testing
Here are some reminders of criteria that have cost students testing points so far this term:
Failing to put either a descriptive name or a descriptive comment on each test case. Doing this does two things: it forces you to articulate the goal of each test, and it helps the graders quickly determine what you were trying to test for.
Only testing situations that "work", rather than both situations that work and ones that don’t. Making sure your code fails when you expect it to is as important as making sure it succeeds when you expect it to.
Not thinking about interesting or unusual situations that could arise in the data, instead testing only routine situations.
In cases when there is more than one correct answer, testing only for the one that your code produces (which might not be the one that our code produces).
We will run your tests/elections against both our correct solution and several broken solutions. Each broken solution will have an error in at least one of the four required methods. We will look to see how thorough your elections were in detecting the broken solutions.
4 Program Design Expectations (Core and Full)
In grading this assignment for program design, we will be looking for various good OO coding practices as we have discussed them this term. This includes:
(Everyone) Creating helper functions where appropriate
(Everyone) Making sure methods are in the appropriate classes
(Everyone) Avoiding unjustified uses of null
(Full only) encapsulation and grouping related data into classes as appropriate
5 What to Turn In
Submit a single zip file that contains all .java files for this assignment. Please check that:
Your Examples class is in its own file named Examples.java. Autograding will not be able to find your tests otherwise.
You’ve included all of the .java files. Students have sometimes omitted files, which breaks autograding.