Computer Science 15-100 (Sections T & U), Spring 2008
Homework 10a
Due: Fri 11-Apr-2008 at
10am (online submission) and at recitation (physical copy)
(no late submissions accepted <--
really!)
Note that both parts of hw10 are due on Friday. Do not leave this whole assignment until Thursday night!!! We still recommend that you complete Part A by Tuesday, and we will still provide Monday night office hours. You are strongly advised to take advantage of them.
Exercise 1: Card Sense
Your task is to write a Java program that solves this "Card Sense" problem taken from page 17 of the June 2008 edition of "Dell Easy Fast'n'Fun Variety Puzzles". The problem:
Five playing cards -- the queen of diamonds, the ace of spades, the seven of spades, the nine of hearts, and the jack of clubs -- were shuffled and put in a pile, one on top of another. Using the information in the clues below, can you identify each card's position in the pile?
- The seven is directly above the heart.
- The spades are adjacent.
- The ace is somewhere above the diamond.
- The red cards are not adjacent.
To solve this, we must first choose an encoding for cards, so we can represent a deck of 52 cards using the integers 0,...,51. To do this, let's assign the first 13 as clubs (so 0 is the ace of clubs, 1 is the 2 of clubs, up to 12 being the king of clubs), the next 13 as diamonds (so 13 is the ace of diamonds, 14 is the 2 of diamonds, and so on), the next 13 as hearts (so 26 is the ace of hearts, 27 is the 2 of hearts, etc), and the last 13 as spades (so 39 is the ace of spades, 40 is the 2 of spades, and 51 is the king of spades).
Write a static method, solveCardSense, that takes no parameters and returns an array of 5 ints (using this encoding) holding the answer to this problem. The first element in the array is the top card, the last element the bottom card.
Do not hardcode your answer (that is, don't solve this by hand and then just return the correct array)! Instead, you must solve this by starting with the hand in the order given, and trying every permutation of those five cards, checking each permutation against the 4 rules given above. When one matches all 4 rules (and indeed one and only one permutation will do so), return it. If none matches (which will not happen), return null.
Exercise 2: Baseball Statistics
In this exercise, we will make use of Sean Lahman's most-excellent Baseball Archive. In particular, we will use a collection of comma-separated data files that Sean has graciously made available for research purposes (such as this) on the web at http://baseball1.com/statistics/lahman55_csv.zip. In this part of this assignment, we will just load in one of the files (Master.csv -- the one holding the players' names and basic biographical info, but no batting, fielding, or other statistics). In the next part of the assignment (hw10b), we will read in more files and do some more interesting analyses of this data.
Here (for Hw10a), your task is to finish the following partially-completed code so that the method testPlayersClass passes all tests. You should read the comments very carefully -- they are very helpful and should be closely adhered to.
Feel free to manually inspect the data -- just open Master.csv in your favorite text editor (like Notepad or even JCreator) or you can get a column view by opening it in a spreadsheet like Excel. This surely would be helpful when debugging. You might also want to save your own tiny version of this file, including just a few lines from the original, again for testing purposes. Don't forget to include the header line in such a file (and don't forget to skip that header line when reading in the file in the Players constructor!).
Note that we will test your code using a different set of data (a different Master.csv!), so be sure not to hardcode any answers!!!
// Hw10.java: Baseball statistics
// Based on Sean Lahman's most-excellent baseball archive:
// http://baseball1.com/statistics/lahman55_csv.zip
// Be sure to use version 5.5 so our answers match!!!!
import java.util.*;
import java.io.File;
public class Hw10 {
public static void main(String[] args) throws Exception {
testPlayersClass();
}
private static void testPlayersClass() throws Exception {
System.out.println("Testing Players class");
// Create an instance of the Players class. Give it the name of the file
// as a parameter. The constructor will open this file and read every line
// from it, creating a new Player object for each line (except the first
// line, which contains the column labels, and so is skipped). Actually, a
// small number of lines have null or empty strings for playerID's, and
// these are skipped, too. All other 16,852 lines are converted into
// instances of the Player class (see below), and all these are stored in a
// private ArrayList called "playerList" inside the Players instance.
// Hint: first download and unzip the necessary files from:
// http://baseball1.com/statistics/lahman55_csv.zip
// Be sure to use version 5.5 so our answers match!!!!
// This contains files with comma-separated values, where each line
// represents an entry in an Excel-like table, and the first row
// contains all the labels for that table.
// Here are the 33 field headers for the "Master.csv" file, along with
// their corresponding int offsets:
// 0 1 2 3 4 5
// "lahmanID","playerID","managerID","hofID","birthYear","birthMonth",
// 6 7 8 9
// "birthDay","birthCountry","birthState","birthCity",
// 10 11 12 13 14
// "deathYear","deathMonth","deathDay","deathCountry","deathState",
// 15 16 17 18 19 20
// "deathCity","nameFirst","nameLast","nameNote","nameGiven","nameNick",
// 21 22 23 24 25 26
// "weight","height","bats","throws","debut","finalGame",
// 27 28 29 30 31 32
// "college","lahman40ID","lahman45ID","retroID","holtzID","bbrefID"
// Note that we will not make use of some of the 33 fields in our Player
// instances.
// The biggest "trick": many values have double-quotes around them, which
// you should delete. HOWEVER: the nicknames field (which we don't even
// use) has commas in it, which is awful, because this is comma-delimited
// data! Yuck! So before you use the line, you should do two things at
// once: delete the double-quotes AND replace all the commas that are
// INSIDE double-quotes to semicolons. Remember to do this using a
// StringBuffer and not by adding a bunch of Strings!!! Anyhow, once
// you've done this replacement, then we have clean comma-delimited data,
// so you can use the "split" method from String, splitting on the commas.
// More notes: some fields will be blank, others may say NULL. When
// converting these into int's, convert them into the value -1. Also,
// some values (like heights) are entered as doubles in the data. Convert
// these into ints by truncation. You may find the following method
// useful: Double.parseDouble(String s)
// Finally: we are not going to deal with Exceptions here, so you should
// just declare that each of your methods "throws Exception". This is not
// great programming style, but it's good enough for this stage of 15-100.
Players players = new Players("Master.csv");
// Here we will access that ArrayList just to confirm it was created
// with the right number of elements and the right first and last player.
ArrayList playerList = players.getPlayerList();
verify(playerList.size() == 16852);
Player player = (Player)playerList.get(0);
verify(player.getName().equals("Hank Aaron"));
player = (Player)playerList.get(16851);
verify(player.getName().equals("Drew Macias"));
// The constructor for the Players instance also creates a HashMap that
// maps the player id's to the instances of the players. For example,
// idToPlayerMap.get("aaronha01") returns the Player instance for Hank
// Aaron (though we still need to cast this to a Player, as it is returned
// as an Object).
HashMap idToPlayerMap = players.getIdToPlayerMap();
verify(idToPlayerMap.size() == 16852);
player = (Player)idToPlayerMap.get("aaronha01");
verify(player.getName().equals("Hank Aaron"));
player = (Player)idToPlayerMap.get("maciadr01");
verify(player.getName().equals("Drew Macias"));
// Now, we exposed the idToPlayerMap and the playerList for testing
// purposes. In reality, these variables should probably not be exposed
// via public methods. Instead, the Players class should provide accessors
// that use these Java collections (the ArrayList and HashMap) to provide
// read-only access to the underlying player data. Like this:
// [ Note: your "idToPlayer" method MUST use the "idToPlayerMap" HashMap,
// and MUST NOT iterate over all the players to find a match! ]
Player p1 = players.idToPlayer("aaronha01");
verify(p1.getName().equals("Hank Aaron"));
verify(p1.toString().equals("Player<Hank Aaron, playerID=aaronha01>"));
// Now, converting id's to players was straightforward, as id's are unique.
// Player names are not unique, so we cannot just return a single
// player. Instead, we will return an ArrayList containing all the
// players with the given name.
// [ In this case, you may construct the ArrayList on each call
// to nameToPlayers, rather than storing them in another map. ]
ArrayList playersNamedHankAaron = players.nameToPlayers("Hank Aaron");
verify(playersNamedHankAaron.size() == 1); // there is only one
Player p2 = (Player)playersNamedHankAaron.get(0);
verify(p2.getName().equals("Hank Aaron"));
verify(p2 == p1); // returns the same reference and not a copy!
// Another test of the nameToPlayers method:
ArrayList playersNamedJohnSullivan = players.nameToPlayers("John Sullivan");
verify(playersNamedJohnSullivan.size() == 5); // There are 5 John Sullivans
for (int i=0; i<5; i++) {
Player johnSullivan = (Player)playersNamedJohnSullivan.get(i);
verify(johnSullivan.getName().equals("John Sullivan"));
// The next line does a simple check to verify these are
// not duplicates (it doesn't do this perfectly, however -- why not?)
if (i > 1) verify(johnSullivan != playersNamedJohnSullivan.get(i-1));
}
// The 'mostCommonNames' method will return an ArrayList containing
// the names of players that occur most frequently. Since there may
// be a tie, and we want ALL the most-frequent names, we return an
// ArrayList rather than a single name.
// To write the "mostCommonNames' method, you must: in the call to the
// method, create a new local variable that is a HashMap, where the key
// is the player name and the value is the count for that name. Then,
// iterate over every Player in the ArrayList of players. For each one,
// get its count from the HashMap. You'll have to do this as an Integer
// and not an "int", because the result can be null (if we did not store
// a count yet -- it won't be zero, it will be null!). If it's null,
// put a 1 in its place. Otherwise, put a value 1 larger back in place
// of the returned value. Of course, as you do this, keep track of what
// the max is (both the max value and the name associated with this value),
// and when you are done, return an ArrayList containing the names with
// the max count. Hint: each time you find a name with a new max, you
// do not neet to create a new ArrayList to return; you can just use the
// "clear" method to remove all the entries from the current ArrayList.
// Also note that the HashMap you created as a local variable will become
// garbage when your method returns.
ArrayList mostCommonNames = players.mostCommonNames();
verify(mostCommonNames.size() == 1); // only 1 name has the max occurrences
verify(mostCommonNames.get(0).equals("John Sullivan"));
System.out.println("Passed all tests!");
}
private static void verify(boolean b) { if (!b) throw new RuntimeException("ack!"); }
}
@SuppressWarnings("unchecked")
class Players {
// YOU WRITE THIS!!!
}
class Player {
private String playerID;
private boolean inHallOfFame;
private int birthYear, birthMonth, birthDay;
private String birthCity, birthState, birthCountry;
private String nameFirst, nameLast, name;
private int weight, height;
private String batsSide, throwsSide;
public Player(String playerID, boolean inHallOfFame,
int birthYear, int birthMonth, int birthDay,
String birthCity, String birthState, String birthCountry,
String nameFirst, String nameLast,
int weight, int height,
String batsSide, String throwsSide) {
this.playerID = playerID;
this.inHallOfFame = inHallOfFame;
this.birthYear = birthYear;
this.birthMonth = birthMonth;
this.birthDay = birthDay;
this.birthCity = birthCity;
this.birthState = birthState;
this.birthCountry = birthCountry;
this.nameFirst = nameFirst;
this.nameLast = nameLast;
this.name = nameFirst + " " + nameLast;
this.weight = weight;
this.height = height;
this.batsSide = batsSide;
this.throwsSide = throwsSide;
}
public String toString() {
// YOU WRITE THIS, TOO!
}
public String getPlayerID() { return this.playerID; }
public boolean getInHallOfFame() { return this.inHallOfFame; }
public int getBirthYear() { return this.birthYear; }
public int getBirthMonth() { return this.birthMonth; }
public int getBirthDay() { return this.birthDay; }
public String getBirthCity() { return this.birthCity; }
public String getBirthState() { return this.birthState; }
public String getBirthCountry() { return this.birthCountry; }
public String getNameFirst() { return this.nameFirst; }
public String getNameLast() { return this.nameLast; }
public String getName() { return this.name; }
public int getWeight() { return this.weight; }
public int getHeight() { return this.height; }
public String getBatsSide() { return this.batsSide; }
public String getThrowsSide() { return this.throwsSide; }
}
Carpe diem!