Pages

Showing posts with label 2007. Show all posts
Showing posts with label 2007. Show all posts

Sunday, 26 June 2011

Mehran Sahami Handout #39 CS 106A November 14, 2007

Mehran Sahami Handout #39
CS 106A November 14, 2007
Assignment #6—NameSurfer
Due: 3:15pm on Wednesday, November 28th
The NameSurfer assignment was created by Nick Parlante and further revised by Patrick Young and Eric Roberts
This assignment has two primary goals. The first is to give you an opportunity to use Java interactors to create an application that looks more like a modern interactive program complete with buttons, text fields, and a resizable graphical display. The second goal is to pull together the various facilities you have learned about so far to create an interesting application that—unlike Breakout, Hangman, and Yahtzee—is not a game but rather a useful program that presents data on some fascinating sociological questions.
Overview of the NameSurfer project
Against all bureaucratic stereotypes, the Social Security Administration, provides a neat web site showing the distribution of names chosen for children over the last 100 years in the United States (http://www.ssa.gov/OACT/babynames/). The Social Security Administration provides data that shows the 1000 most popular boy and girl names for children at 10 year intervals. The data can be boiled down to a single text file that looks something like this:
NamesData.txt
. . .
Sam 58 69 99 131 168 236 278 380 467 408 466
Samantha 0 0 0 0 0 0 272 107 26 5 7
Samara 0 0 0 0 0 0 0 0 0 0 886
Samir 0 0 0 0 0 0 0 0 920 0 798
Sammie 537 545 351 325 333 396 565 772 930 0 0
Sammy 0 887 544 299 202 262 321 395 575 639 755
Samson 0 0 0 0 0 0 0 0 0 0 915
Samuel 31 41 46 60 61 71 83 61 52 35 28
Sandi 0 0 0 0 704 864 621 695 0 0 0
Sandra 0 942 606 50 6 12 11 39 94 168 257
. . .
Each line of the file begins with the name, followed by the rank of that name in each of the 11 decades since 1900, counting the current one: 1900, 1910, 1920, and so on up to 2000. A rank of 1 indicates the most popular name that year, while a rank of 997 indicates a name that is not very popular. A 0 entry means the name did not appear in the top 1000 names for that year and therefore indicates a name that is even less popular. The elements on each line are separated from each other by a single space. The lines happen to be in alphabetical order, but nothing in the assignment depends on that fact.
As you can see from the small excerpt from the file, the name Sam was #58 in the first decade of the 1900s and is slowly moving down. Samantha popped on the scene in the 1960s (possibly because the show Bewitched, which had a main character names Samantha ran on television during those years) and is moving up strong to #7. Samir barely appears in the 1980s (at rank #920), but by the current decade is up to #798. The database counts children born in the United States, so trends in particular names tend to reflect the evolution of ethnic communities over the years.
– 2 –
Figure 1. Sample run of the NameSurfer program (with names "Sam" and "Samantha")
The goal of this assignment is to create a program that graphs these names over time, as shown in the sample run in Figure 1. In this diagram, the user has just typed Samantha into the box marked ―Name‖ and then clicked on the ―Graph‖ button, having earlier done exactly the same thing for the name Sam. Whenever the user enters a name, the NameSurfer program creates a new plot line showing how that name has fared over the decades. Clicking on the ―Clear‖ button removes all the plot lines from the graph so that the user can enter more names without all the old names cluttering up the display.
To give you more experience working with classes that interact with one another, the NameSurfer application as a whole is broken down into several separate class files, as follows:
• NameSurfer—This is the main program class that ties together the application. It has the responsibility for creating the other objects and for responding to the buttons at the bottom of the window, but only to the point of redirecting those events to the objects represented by the other classes.
• NameSurferConstants—This interface is provided for you and defines a set of constants that you can use in the rest of the program simply by having your classes implement the NameSurferConstants interface, as they do in the starter files. The NameSurferConstants interface therefore has the same role that YahtzeeConstants did in Assignment #5.
– 3 –
• NameSurferEntry—This class ties together all the information for a particular name. Given a NameSurferEntry object, you can find out what name it corresponds to and what its popularity rank was in each decade.
• NameSurferDataBase—This class keeps track of all the information stored in the data files, but is completely separate from the user interface. It is responsible for reading in the data and for locating the data associated with a particular name.
• NameSurferGraph—This class is a subclass of GCanvas that displays the graph of the various names by arranging the appropriate GLine and GLabel objects on the screen, just as with the various graphical programs you’ve written this quarter.
Even though the class structure sounds complicated, the NameSurfer application code is about the same size as Yahtzee. Even if the scale of the project is comparable to the last assignment, the wise course is to start on the assignment soon and keep up with the milestones described in this handout.
Milestone 1: Assemble the GUI interactors
If you look at the bottom of Figure 1, you will see that the region along the SOUTH edge of the window contains several interactors: a JLabel, a JTextField, and three JButtons. Since putting up interactors is something you haven't done in previous assginments, you probably want to work on this step before it becomes complicated with all the other parts of the assignment. Thus, your first milestone is simply to add the interactors to the window and create an implementation for the actionPerformed method that allows you to check whether you can detect button clicks and read what’s in the text field.
The simplest strategy to check whether your program is working is to change the definition of the NameSurfer class so that it extends ConsoleProgram instead of Program, at least for the moment. You can always change it back later. Once you have made that change, you can then use the console to record what’s happening in terms of the interactors to make sure that you’ve got them right. For example, Figure 2 shows a possible transcript of the commands used to generate the output from Figure 1, in which the user has just completed the following actions:
1. Entered the name Sam in the text field and clicked the Graph button.
2. Entered the name Samantha in the text field and then typed the ENTER key.
3. Clicked the Clear button.
The hard part about reaching this milestone is understanding how interactors work. Once you do, writing the code is quite straightforward – it's only 10 to 15 lines of code.
Figure 2. Illustration of Milestone 1
– 4 –
Milestone 2: Implement the NameSurferEntry class
The starter file for the NameSurferEntry class appears in full as Figure 3 on the following page. As with the other files supplied with this assignment, the starter file includes definitions for all of the public methods we expect you to define. The method definitions in the starter files, however, do nothing useful, although they occasionally include a return statement that gives back a default value of the required type. In Figure 3, for example, the getRank method always returns 0 to satisfy the requirement that the method returns an int as defined in its header line.
Methods that will eventually become part of the program structure but that are temporarily unimplemented are called stubs. Stubs play a very important role in program development because they allow you to set out the structure of a program even before you write most of the code. As you implement the program, you can go through the code and replace stubs with real code as you need it.
The NameSurferEntry class encapsulates the information pertaining to one name in the database. That information consists of two parts:
1. The name itself, such as "Sam" or "Samantha"
2. A list of 11 values indicating the rank of that name in each of the decades from 1900 to 2000, inclusive
The class definition begins with a constructor that creates an entry from the line of data that appears in the NamesData.txt file. For example, the entry for Sam looks like this:
Sam 58 69 99 131 168 236 278 380 467 408 466
The idea behind the design of this constructor is that it should be possible to read a line of data from the file and then create a new entry for it using code that looks like this:
String line = rd.readLine();
NameSurferEntry entry = new NameSurferEntry(line);
The implementation of the constructor has to divide up the line at the spaces, convert the digit strings to integers (using Integer.parseInt), and then store all of this information as the private state of the object in such a way that it is easy for the getName and getRank methods to return the appropriate values.
The last method in the starter implementation of NameSurferEntry is a toString method whose role is to return a human-readable representation of the data stored in the entry. For example, if the variable entry contains the NameSurferEntry data for Sam, you might want entry.toString() to return a string like this:
"Sam [58 69 99 131 168 236 278 380 467 408 466]"
Defining toString for a class has the wonderful advantage that it makes it possible to print out objects of that class using println, just as you do for primitive values. Whenever Java needs to convert an object to a string, it always calls its toString method to do the job. The default definition of toString in the Object class doesn’t supply much useful information, and you will find that your debugging sessions get much easier if you can look easily at the values of your objects.
– 5 –
To show that you’ve got NameSurferEntry implemented correctly, you might want to write a very simple test program that creates an entry from a specific string and then verifies that the other methods work as they are supposed to.
Figure 3. Starter file for the NameSurferEntry class
/*
* File: NameSurferEntry.java
* --------------------------
* This class represents a single entry in the database. Each
* NameSurferEntry contains a name and a list giving the popularity
* of that name for each decade stretching back to 1900.
*/
import acm.util.*;
import java.util.*;
public class NameSurferEntry implements NameSurferConstants {
/**
* Creates a new NameSurferEntry from a data line as it appears
* in the data file. Each line begins with the name, which is
* followed by integers giving the rank of that name for each
* decade.
*/
public NameSurferEntry(String line) {
// You fill this in //
}
/**
* Returns the name associated with this entry.
*/
public String getName() {
// You need to turn this stub into a real implementation //
return null;
}
/**
* Returns the rank associated with an entry for a particular
* decade. The decade value is an integer indicating how many
* decades have passed since the first year in the database,
* which is given by the constant START_DECADE. If a name does
* not appear in a decade, the rank value is 0.
*/
public int getRank(int decade) {
// You need to turn this stub into a real implementation //
return 0;
}
/**
* Returns a string that makes it easy to see the value of a
* NameSurferEntry.
*/
public String toString() {
// You need to turn this stub into a real implementation //
return "";
}
}
– 6 –
Milestone 3: Implement the NameSurferDataBase class
The next step in the process is to implement the NameSurferDataBase class, which contains two public entries:
• A constructor that takes the name of a data file and uses that to read in the entire set of data from the file into internal data structures that allow the class to keep track of all the records as a database.
• A findEntry method that takes a name, looks it up in the stored database, and returns the NameSurferEntry for that name, or null if that name does not appear.
The code for this part of the assignment is not particularly difficult. The challenging part lies in figuring out how you want to represent the data so that you can implement the findEntry method as simply and as efficiently as possible.
To test this part of the program, you can add a line of code or two to the NameSurfer program so that it creates the NameSurferDataBase and then change the code for the button handlers so that clicking the ―Graph‖ button looks up the current name in the data base and then displays the corresponding entry (using its toString method), as shown in Figure 4 below.
Figure 4. Illustration of Milestone 3
Milestone 4: Create the background grid for the NameSurferGraph class
The next step in the process is to begin the implementation of the NameSurferGraph class, which is responsible for displaying the graph in the window by building the underlying model. The starter code for the NameSurferGraph class appears in Figure 5 on the next page.
– 7 –
Figure 5. Starter file for the NameSurferGraph class
/*
* File: NameSurferGraph.java
* --------------------------
* This class is responsible for updating the graph whenever the
* list of entries changes or the window is resized.
*/
import acm.graphics.*;
import java.awt.event.*;
public class NameSurferGraph extends GCanvas
implements NameSurferConstants, ComponentListener {
/**
* Creates a new NameSurferGraph object that displays the data.
*/
public NameSurferGraph() {
addComponentListener(this);
// You fill in the rest //
}
/**
* Clears the list of name surfer entries stored inside this class.
*/
public void clear() {
// You fill this in //
}
/**
* Adds a new NameSurferEntry to the list of entries on the display.
* Note that this method does not actually draw the graph, but
* simply stores the entry; the graph is drawn by calling update.
*/
public void addEntry(NameSurferEntry entry) {
// You fill this in //
}
/**
* Updates the display image by deleting all the graphical objects
* from the canvas and then reassembling the display according to
* the list of entries. Your application must call update after
* calling either clear or addEntry; update is also called whenever
* the size of the canvas changes.
*/
public void update() {
// You fill this in //
}
/* Implementation of the ComponentListener interface */
public void componentHidden(ComponentEvent e) { }
public void componentMoved(ComponentEvent e) { }
public void componentResized(ComponentEvent e) { update(); }
public void componentShown(ComponentEvent e) { }
}
– 8 –
There are a couple of important items in the NameSurferGraph starter file that are worth noting:
1. This class extends GCanvas, which means that every NameSurferGraph object is not only a GCanvas but also an instance of all the superclasses of the GCanvas class. GCanvas is a subclass of Component in the standard java.awt package and therefore is part of the hierarchy of classes that can be added to the display area of a program. Moreover, it means we can call any of the GCanvas methods, such as adding or removing GObjects, from within NameSurferGraph.
2. The starter file includes a tiny bit of code that monitors the size of the window and calls update whenever the size changes. This code requires only a couple of lines to implement, but would be hard to explain well enough for you to implement on your own. Writing a page of description so that you could add a couple of lines seemed like overkill, particularly given that the strategy is easiest to learn by example.
To start the process of adding the graphing code, go back to the NameSurfer class and change its definition so that it extends Program rather than the temporary expedient of extending ConsoleProgram (if you were using that for debugging). At the same time, you should remove the various println calls that allowed you to trace the operation of the interactors in the earlier milestones.
Now, you'll need to declare a NameSurferGraph private instance variable in your main NameSurfer class:
private NameSurferGraph graph;
You should then change the constructor of the NameSurfer class so that it creates a new NameSurferGraph object and adds that object to the display, as follows:
graph = new NameSurferGraph();
add(graph);
If you run the program with only these changes, it won’t actually display anything on the screen. To create the graph, you need to implement the update method, which will almost certainly involve defining private helper methods as well. As a first step, write the code to create the background grid for the graph, which consists of the vertical line separating each decade, the horizontal lines that provide space for the top and bottom borders (which are there to ensure that the text labels stay within the window bounds), and the labels for the decades. As with all the graphical applications you’ve written, the lines and labels are represented using GLine and GLabel objects, which you add to the graph in the appropriate positions.
– 9 –
Milestone 5: Complete the implementation of NameSurferGraph class
In addition to creating the background grid, the update method in NameSurferGraph also has to plot the actual data values. As you can see from Figure 5, the NameSurferGraph class includes two methods for specifying what entries are displayed on the screen. The addEntry method adds a new NameSurferEntry to a list of values that are currently on the display. The clear method deletes all of the entries in that list so as to clear the graph.
It is important to note that neither addEntry or clear actually changes the display. To make changes in the display, you need to call update, which deletes any existing GObjects from the canvas and then assembles everything back up again. At first glance, this strategy might seem unnecessary. It would, of course, be possible to have addEntry just add all of the GLines and GLabels necessary to draw the graph.
The problem with that approach is that it would no longer be possible to reconstruct the entire graph. In this example, you need to do just that to create a new graph whenever you change the size of the display. By storing all of the entries in an internal list, the NameSurferGraph class can redraw everything when update is invoked from the componentResized method.
There are a couple of points that you should keep in mind while implementing this part of the program:
• To make the data easier to read, the lines on the graph are shown in different colors. In the sample applet on the web site, the first data entry is plotted in black, the second in red, the third in blue, and the fourth in magenta. After that, the colors cycle around again through the same sequence.
• The fact that rank 1 is at the top of the window and rank 1000 is at the bottom means that it can sometimes seem confusing that rank 0—which means that the name does not appear in the top 1000 values for that year—appears at the bottom. To reduce the apparent discontinuity between rank 1 and rank 0, the entries for names that are absent from the data for a decade are listed with an asterisk instead of a numeric rank. You can see several examples of this convention in the data for Samantha in Figure 1.
Possible extensions
There are a lot of things that you could do to make this program more interesting. Here are just a few possibilities:
• Add features to the display. The current display contains only lines and labels, but could easily be extended to make it more readable. You could, for example, put a dot at each of the data points on the graph. Even better, however, would be to choose different symbols for each line so that the data would be easily distinguishable even in a black-and-white copy. For example, you could use little circles for the first entry, squares for the second, triangles for the third, diamonds for the fourth, and so on. You might also figure out what the top rank for a name is over the years and set the label for that data point in boldface.
– 10 –
• Allow deletion as well as addition. Because the screen quickly becomes cluttered as you graph lots of names, it would be convenient if there were some way to delete entries individually, as opposed to clearing the entire display and then adding back the ones you wanted. The obvious strategy would be to add a ―Delete‖ button that eliminated the entry corresponding to the value in the ―Name‖ box. That approach, however, has a minor drawback given the design so far. If you added a bunch of entries to the graph and then deleted the early ones, the colors of the later entries would shift, which might prove disconcerting. Can you redesign the color-selection strategy so that displayed entries retain their color even if other entries are removed?
• Try to minimize the overprinting problem. If the popularity of a name is improving slowly, the graph for that name will cross the label for that point making it harder to read. You could reduce this problem by positioning the label more intelligently. If a name were increasing in popularity, you could display the label below the point; conversely, for names that are falling in popularity, you could place the label above the point. An even more challenging task is to try to reduce the problem of having labels for different names collide, as they do for Sam and Samantha in Figure 1.
• Adjust the font size as the application size changes. One of the wonderful features of this program is that it redraws itself to fill the available space if you change the size of the window. If you make it too small, however, the labels run together and become unreadable. You could eliminate this problem by choosing a font size that allows each label to fit in the space available.
• Rewrite the application to use the model/view/controller pattern. The NameSurfer prorgam turns out to be an ideal application for the model/view/controller pattern described in Chapter 14. If you finish the standard assignment, you might try to reimplement it so that it maintains a separate model that can support multiple viewers. If you made this change, you could, for example, add a ―Table‖ button to the application that would add a second viewer to the application, which would display the results in tabular rather than graphical form.

Mehran Sahami Handout #38A CS 106A November 14, 2007

Mehran Sahami Handout #38A
CS 106A November 14, 2007
Solution to Section #7
Based on a handout by Eric Roberts
// File: BoxDiagram.java
// This program allows the user to create a set of boxes with labels
// and then drag them around in the window.
import acm.graphics.*;
import acm.program.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
public class BoxDiagram extends GraphicsProgram {
/* Initializes the program */
public void init() {
contents = new HashMap<String,GObject>();
createController();
addActionListeners();
addMouseListeners();
}
/* Creates the control strip at the bottom of the window */
private void createController() {
nameField = new JTextField(MAX_NAME);
nameField.addActionListener(this);
addButton = new JButton("Add");
removeButton = new JButton("Remove");
clearButton = new JButton("Clear");
add(new JLabel("Name"), SOUTH);
add(nameField, SOUTH);
add(addButton, SOUTH);
add(removeButton, SOUTH);
add(clearButton, SOUTH);
}
/* Adds a box with the given name at the center of the window */
private void addBox(String name) {
GCompound box = new GCompound();
GRect outline = new GRect(BOX_WIDTH, BOX_HEIGHT);
GLabel label = new GLabel(name);
box.add(outline, -BOX_WIDTH / 2, -BOX_HEIGHT / 2);
box.add(label, -label.getWidth() / 2, label.getAscent() / 2);
add(box, getWidth() / 2, getHeight() / 2);
contents.put(name, box);
}
/* Removes the box with the given name */
private void removeBox(String name) {
GObject obj = contents.get(name);
if (obj != null) {
remove(obj);
}
}
– 2 –
/* Removes all boxes in the contents table */
private void removeContents() {
Iterator<String> iterator = contents.keySet().iterator();
while (iterator.hasNext()) {
removeBox(iterator.next());
}
contents.clear(); // Clear all entries in the hashmap
}
/* Called in response to button actions */
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == addButton || source == nameField) {
addBox(nameField.getText());
} else if (source == removeButton) {
removeBox(nameField.getText());
} else if (source == clearButton) {
removeContents();
}
}
/* Called on mouse press to record the coordinates of the click */
public void mousePressed(MouseEvent e) {
last = new GPoint(e.getPoint());
currentObject = getElementAt(last);
}
/* Called on mouse drag to reposition the object */
public void mouseDragged(MouseEvent e) {
if (currentObject != null) {
currentObject.move(e.getX() - last.getX(),
e.getY() - last.getY());
last = new GPoint(e.getPoint());
}
}
/* Called on mouse click to move this object to the front */
public void mouseClicked(MouseEvent e) {
if (currentObject != null) currentObject.sendToFront();
}
/* Private constants */
private static final int MAX_NAME = 25;
private static final double BOX_WIDTH = 120;
private static final double BOX_HEIGHT = 50;
/* Private instance variables */
private HashMap<String,GObject> contents;
private JTextField nameField;
private JButton addButton;
private JButton removeButton;
private JButton clearButton;
private GObject currentObject;
private GPoint last;
}

Mehran Sahami Handout #36 CS 106A November 9, 2007

Mehran Sahami Handout #36
CS 106A November 9, 2007
CS 106A Graphics Contest
Submission deadline: 3:15pm on Friday, November 30th
Based on a handout by Eric Roberts
The graphics programs you write using the acm.graphics package tend to be more exciting than the console-based programs you wrote in Assignment 2. The Breakout and Hangman games—exciting as they are—only scratch the surface of what you can do with the graphics package. To give you a chance to explore the capabilities of the package in greater depth, we are pleased to announce the CS 106A Graphics Contest. Note that this is a purely optional contest, so you should not feel obligated to enter if you don't have the time. It's really a chance to let you show some more creativity in working with features of the graphics package and showcase some of your programming skills if you so choose.
Each of you is eligible to submit one entry for the contest, where an entry consists of an original program that uses the acm.graphics package. You are free to use any of the capabilities in those packages (including animations, mouse and keyboard listeners, interactors, and even capabilities described in the course textbook and javadoc even if they have not been covered in class). You may not, however, display anything on the window that does not come from one of the classes in acm.graphics, as discussed below in the official rules.
Selection criteria
The entries will be judged by the CS 106A staff (see official rules below), and a prize will be awarded in each of two categories:
• Aesthetic merit. This prize is awarded based on the aesthetic value of the graphical image produced.
• Algorithmic sophistication. This prize is based on the difficulty of the underlying programming task and the sophistication of the displayed images.
In both categories, programming style will be part of the evaluation. Please note that you don’t have to specify a category; all entries will be eligible for either prize.
Prizes
The grand prize in each of the categories will be that we will replace whatever individual score most negatively affects your grade—which may be an assignment, the midterm, or the final exam—with a 100% in the computation of the final grade. (Note: that means that as a contest winner that you could choose not take the final exam and you would get a 100% on it, since by not taking the exam, it would be the score that would most negatively effect your grade). Those of you who submit ―serious‖ entries (as deemed by the course staff) get a chance in a random drawing at the end of the quarter for one additional grand prize.
Official contest rules on back of page
– 2 –
Official rules
1. Only students registered in CS 106A are eligible to submit entries in the contest.
2. Only one entry per person will be accepted.
3. All entries must be submitted electronically using Eclipse’s submit facility by 3:15P.M. on Friday, November 30th. Late entries will not be accepted, and you cannot use late days for the contest.
4. You are required to do all of your graphical operations using the classes from acm.graphics. Thus, you may not dig into the official Java documentation and come up with some other way of displaying graphics or user-interface tools such as buttons and scrollbars. For example, it is also illegal to override the paint method in GObject and to implement your own style of graphics that way.
5. Contest entries should be sensitive to Stanford’s individual and cultural diversity. Programs or narratives that have the effect of perpetuating negative stereotypes will not be considered for prizes.
6. Contest entries will be evaluated initially by Mehran Sahami and Ben Newman. The best entries will then be judged by the section leaders, who will choose the winners in each category. Winners will be annouced during the last week of classes.

Mehran Sahami Handout #34 CS 106A November 5, 2007

Mehran Sahami Handout #34
CS 106A November 5, 2007
Section Handout #6: More Arrays and HashMaps
Portions of this handout by Eric Roberts
1. Image processing (Chapter 11, exercise 12, page 458)
Write a method flipHorizontal that works similarly to the flipVertical method presented in the chapter except that it reverses the picture in the horizontal dimension. Thus, if you had a GImage containing the image on the left (of Jan Vermeer’s The Milkmaid, c. 1659), calling flipHorizontal on that image would return a new GImage as shown on the right:

2. Name Counts
Write a program that asks the user for a list of names (one per line) until the user enters a blank line (i.e., just hits return when asked for a name). At that point the program should print out how many times each name in the list was entered. You may find that using a HashMap to keep track of the information entered by user may greatly simplify this problem. A sample run of this program is shown below.

Mehran Sahami Handout #34A CS 106A November 7, 2007

Mehran Sahami Handout #34A
CS 106A November 7, 2007
Solutions to Section #6
1. Image processing
private GImage flipHorizontal(GImage image) {
int[][] array = image.getPixelArray();
int width = array[0].length;
int height = array.length;
for (int row = 0; row < height; row++) {
for (int p1 = 0; p1 < width / 2; p1++) {
int p2 = width - p1 - 1;
int temp = array[row][p1];
array[row][p1] = array[row][p2];
array[row][p2] = temp;
}
}
return new GImage(array);
}
Solution for Problem #2 Name Counts on back of page.
– 2 –
2. Name Counts
/*
* File: CountNames.java
* ---------------------
* This program shows an example of using a HashMap. It reads a
* list of names from the user and list out how many times each name
* appeared in the list.
*/
import acm.program.*;
import java.util.*;
public class CountNames extends ConsoleProgram {
public void run() {
HashMap<String,Integer> nameMap = new HashMap<String,Integer>();
readNames(nameMap);
printMap(nameMap);
}
/*
* Reads a list of names from the user, storing names and how many
* times each appeared in the map that is passed in as a parameter.
*/
private void readNames(Map<String,Integer> map) {
while (true) {
String name = readLine("Enter name: ");
if (name.equals("")) break;
// See if that name previously appeared in the map. Update
// count if it did, or create a new count if it didn't.
Integer count = map.get(name);
if (count == null) {
count = new Integer(1);
} else {
count = new Integer(count + 1);
}
map.put(name, count);
}
}
/*
* Prints out list of entries (and associated counts) from the map
* that is passed in as a parameter.
*/
private void printMap(Map<String,Integer> map) {
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
int count = map.get(key);
println("Entry [" + key + "] has count " + count);
}
}
}

Mehran Sahami Handout #31 CS 106A October 31, 2007

Mehran Sahami Handout #31 CS 106A October 31, 2007 ArrayLists Reference for Hangman
Based on a handout by Patrick Young This handout gives you a quick reference for some of the concepts related to ArrayLists that may be useful to you for implementing Part III of the Hangman assignment. ArrayLists You can think of an ArrayList as being a special object which is used to store a list of other objects. In your case, you’ll be using an ArrayList to store the list of Strings which can be chosen as words for Hangman. Using ArrayLists ArrayLists are defined in the java.util package. To use ArrayLists, you’ll need to import the package: import java.util.*; Declaring an ArrayList variable ArrayLists can store a variety of different types of information—for example, you can create an ArrayList of GOvals, an ArrayList of GRects, or in the case of Hangman an ArrayList of Strings. When declaring a variable of type ArrayList, you’ll need to specify what type of data is stored in the ArrayList using a special angular bracket notation. Your declaration will look something like this: private ArrayList<String> wordList; The information in the angular brackets tells Java that this is an ArrayList of Strings. ArrayLists themselves are objects and are created using constructors, just as GOvals and GRects are created using constructors. Here is how we would create a new ArrayList and assign it to our variable wordList: wordList = new ArrayList<String>(); Adding elements to your ArrayList Call the add method to add elements to your ArrayList. Because your ArrayList has been declared to contain Strings, you can call the add method with a String as an argument: String word = readLine("?"); // reading a String from the user wordList.add(word);
Accessing elements of your ArrayList To access an element of your ArrayList, you can call the get method, passing in the index of the element you want to access. Remember, ArrayLists (as with regular arrays in Java) are zero-indexed—in other words, the first element in the list is actually at index 0, the second element is at index 1, and so forth. Because your list is declared to contain Strings, Java assumes that the element returned by get is a String: int index = 0; // index of first element in ArrayList String word = wordList.get(index); Determine the size of your ArrayList Your ArrayList will keep track of the number of element which you have added. To determine the number of elements in the ArrayList, call the size method: int totalCount = wordList.size(); Chapter 11.8 is your friend for more information about ArrayLists There’s a lot more to learn about ArrayLists. This handout is just providing a quick references for the Hangman assignment. We will cover ArrayLists more extensively in class and you can read more about ArrayLists in Chapter 11.8 of the class textbook.

Friday, 24 June 2011

Mehran Sahami Handout #30 CS 106A October 29, 2007


Mehran Sahami Handout #30
CS 106A October 29, 2007
Section Handout #5: Files, Arrays, and ArrayLists
Portions of this handout by Eric Roberts
1. Word count
Write a program WordCount that reads a file and reports how many lines, words, and
characters appear in it. Suppose, for example, that the file lear.txt contains the
following passage from Shakespeare’s King Lear:
Poor naked wretches, wheresoe'er you are,
That bide the pelting of this pitiless storm,
How shall your houseless heads and unfed sides,
Your loop'd and window'd raggedness, defend you
From seasons such as these? O, I have ta'en
Too little care of this!
Given this file, your program should be able to generate the following sample run:
For the purposes of this program, a word consists of a consecutive sequence of letters
and/or digits, which you can test using the static method Character.isLetterOrDigit.
Also, you should not count the characters that mark the end of a line, which will have
different values depending on the type of computer.
2. Histograms
Write a program that reads a list of exam scores from the file
MidtermScores.txt (which contains one score per line) and
then displays a histogram of those numbers, divided into the
ranges 0–9, 10–19, 20–29, and so forth, up to the range
containing only the value 100. If, for example,
MidtermScores.txt contains the data shown in the right
margin, your program should then be able to generate a
histogram that looks as much as possible like the following
sample run:
MidtermScores.txt
73
58
73
93
82
62
80
53
93
52
92
75
65
95
23
100
75
38
80
77
92
60
98
95
62
87
97
73
78
72
55
58
42
31
78
70
78
74
70
60
72
75
84
87
62
17
92
78
74
65
90
3. Unique Names
Write a program that asks the user for a list of names (one per line) until the user enters a
blank line (i.e., just hits return when asked for a name). At that point the program should
print out the list of names entered, where each name is listed only once (i.e., uniquely) no
matter how many times the user entered the name in the program. You may find that
using an ArrayList to keep track of the names entered by user may greatly simplify this
problem.
A sample run of this program is shown below.

Mehran Sahami Handout #30A CS 106A November 1, 2007


Mehran Sahami Handout #30A
CS 106A November 1, 2007
Solution to Section #5
Portions of this handout by Eric Roberts
1. Word count
/*
* File: WordCount.java
* --------------------
* Counts the characters, words, and lines in a file.
*/
import acm.program.*;
import java.io.*;
public class WordCount extends ConsoleProgram {
public void run() {
int lines = 0;
int words = 0;
int chars = 0;
BufferedReader rd = openFileReader("File: ");
try {
while (true) {
String line = rd.readLine();
if (line == null) break;
lines++;
words += countWords(line);
chars += line.length();
}
rd.close();
} catch (IOException ex) {
println("An I/O exception has occurred");
}
println("Lines = " + lines);
println("Words = " + words);
println("Chars = " + chars);
}
/**
* Asks the user for the name of an input file and returns a
* BufferedReader attached to its contents. If the file does
* not exist, the user is given another chance to try.
*/
private BufferedReader openFileReader(String prompt) {
BufferedReader rd = null;
while (rd == null) {
String name = readLine(prompt);
try {
rd = new BufferedReader(new FileReader(name));
} catch (IOException ex) {
println("Can't open that file.");
}
}
return rd;
}
– 2 –
/**
* Counts the words (consecutive strings of letters and/or digits)
* in the input line.
*/
private int countWords(String line) {
boolean inWord = false;
int words = 0;
for (int i = 0; i < line.length(); i++) {
char ch = line.charAt(i);
if (Character.isLetterOrDigit(ch)) {
inWord = true;
} else {
if (inWord) words++;
inWord = false;
}
}
if (inWord) words++;
return words;
}
}
2. Histogram
/*
* File: Histogram.java
* --------------------
* This program reads a list of exam scores, with one score per line.
* It then displays a histogram of those scores, divided into the
* ranges 0–9, 10–19, 20–29, and so forth, up to the range containing
* only the value 100.
*/
import acm.program.*;
import acm.util.*;
import java.io.*;
public class Histogram extends ConsoleProgram {
public void run() {
initHistogram();
readScoresIntoHistogram();
printHistogram();
}
/* Initializes the histogram array */
private void initHistogram() {
histogramArray = new int[11];
for (int i = 0; i <= 10; i++) {
histogramArray[i] = 0;
}
}
– 3 –
/* Reads the exam scores, updating the histogram */
private void readScoresIntoHistogram() {
try {
BufferedReader rd
= new BufferedReader(new FileReader(DATA_FILE));
while (true) {
String line = rd.readLine();
if (line == null) break;
int score = Integer.parseInt(line);
if (score < 0 || score > 100) {
throw new ErrorException("That score is out of range");
} else {
int range = score / 10;
histogramArray[range]++;
}
}
} catch (IOException ex) {
throw new ErrorException(ex);
}
}
/* Displays the histogram */
private void printHistogram() {
for (int range = 0; range <= 10; range++) {
String label;
switch (range) {
case 0: label = "00-09"; break;
case 10: label = " 100"; break;
default:
label = (10 * range) + "-" + (10 * range + 9);
break;
}
String stars = createStars(histogramArray[range]);
println(label + ": " + stars);
}
}
/* Creates a string consisting of n stars */
private String createStars(int n) {
String stars = "";
for (int i = 0; i < n; i++) {
stars += "*";
}
return stars;
}
/* Private instance variables */
private int[] histogramArray;
/* Name of the data file */
private static final String DATA_FILE = "MidtermScores.txt";
}
– 4 –
3. Unique Names
/*
* File: UniqueNames.java
* ----------------------
* This program asks the user for a list of names (one per line)
* until the user enters a blank line. Then the program prints
* out the list of names entered, where each name is listed only
* once (i.e., uniquely) no matter how many times the user entered
* the name in the program.
*/
import acm.program.*;
import java.util.*;
public class UniqueNames extends ConsoleProgram {
public void run() {
ArrayList<String> list = new ArrayList<String>();
while (true) {
String name = readLine("Enter name: ");
if (name.equals("")) break;
if (!list.contains(name)) {
list.add(name);
}
}
println("Unique name list contains:");
printList(list);
}
/* Prints out contents of ArrayList, one element per line */
private void printList(ArrayList list) {
for(int i = 0; i < list.size(); i++) {
println(list.get(i));
}
}
}

Thursday, 23 June 2011

Mehran Sahami Handout #26 CS 106A October 22, 2007


Mehran Sahami Handout #26
CS 106A October 22, 2007
Strings and Ciphers
Based on a handout by Eric Roberts.
Cryptography, derived from the Greek word meaning hidden, is the science of
creating and decoding secret messages whose meaning cannot be understood by others
who might intercept the message. In the language of cryptography, the message you are
trying to send is called the plaintext; the message that you actually send is called the
ciphertext. Unless your adversaries know the secret of the encoding system, which is
usually embodied in some privileged piece of information called a key, intercepting the
ciphertext should not enable them to discover the original plaintext version of the
message. On the other hand, the intended recipient, who is in possession of the key, can
easily translate the ciphertext back into its plaintext counterpart.
Caesar ciphers
One of the earliest documented uses of ciphers is by Julius Caesar. In his De Vita
Caesarum, the Roman historian Suetonius describes Caesar’s encryption system like this:
If he had anything confidential to say, he wrote it in cipher, that is, by so changing
the order of the letters of the alphabet, that not a word could be made out. If
anyone wishes to decipher these, and get at their meaning, he must substitute the
fourth letter of the alphabet, namely D, for A, and so with the others.
Even today, the technique of encoding a message by shifting letters a certain distance in
the alphabet is called a Caesar cipher. According to the passage from Suetonius, each
letter is shifted three letters ahead in the alphabet. For example, if Caesar had had time to
translate the final words Shakespeare gives him, ET TU BRUTE would have come out as
HW WX EUXWH, because E gets moved three letters ahead to H, T gets moved three to W,
and so on. Letters that get advanced past the end of the alphabet wrap around back to the
beginning, so that X would become A, Y would become B, and Z would become C.
Caesar ciphers have been used in modern times as well. The “secret decoder rings” that
used to be given away as premiums in cereal boxes were typically based on the Caesar
cipher principle. In early electronic bulletin board systems, users often disguised the
content of postings by employing a mode called ROT13, in which all letters were cycled
forward 13 positions in the alphabet. And the fact that the name of the HAL computer in
Arthur C. Clarke’s 2001 is a one-step Caesar cipher of IBM has caused a certain amount of
speculation over the years.
Let's consider writing a simple program that encodes or decodes a message using a Caesar
cipher. The program needs to read a numeric key and a plaintext message from the user
and then display the ciphertext message that results when each of the original letters is
shifted the number of letter positions given by the key. A sample run of the program
might look like the example on the following page.
– 2 –
For the Caesar cipher, decryption does not require a separate program as long as the
implementation is able to accept a negative key, as follows:
Letter-substitution ciphers
Although they are certainly simple, Caesar ciphers are also extremely easy to break.
There are, after all, only 25 nontrivial Caesar ciphers for English text. If you want to
break a Caesar cipher, all you have to do is try each of the 25 possibilities and see which
one translates the ciphertext message into something readable. A somewhat better
scheme is to allow each letter in the plaintext message to be represented by an arbitrary
letter instead of one a fixed distance from the original. In this case, the key for the
encoding operation is a translation table that shows what each of the 26 plaintext letters
becomes in the ciphertext. Such a coding scheme is called a letter-substitution cipher.
The key in such a cipher can be represented as a 26-character string, which shows the
mapping for each character, as shown in the following example:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Q W E R T Y U I O P A S D F G H J K L Z X C V B N M
Letter-substitution ciphers have been used for many, many years. In the 15th century, an
Arabic encyclopedia included a section on cryptography describing various methods for
creating ciphers as well as techniques for breaking them. More importantly, this same
manuscript includes the first instance of a cipher in which several different coded
symbols can stand for the same plaintext character. Codes in which each plaintext letter
– 3 –
maps into a single ciphertext equivalent are called monoalphabetic ciphers. More
complex codes in which the representation for a character changes over the course of the
encryption process are called polyalphabetic ciphers.
The second problem to consider is to write a program that implements this more general
letter-substitution cipher. The program should ask the user to enter a 26-letter key and
the plaintext message. It should then display the ciphertext and, to ensure that the
encryption is working, what you get if you decrypt the ciphertext using the same key:
– 4 –
Caesar ciphers solution
/*
* File: CaesarCipher.java
* -----------------------
* This program translates a line of text into its Caesar cipher
* form.
*/
import acm.program.*;
public class CaesarCipher extends ConsoleProgram {
public void run() {
println("This program uses a Caesar cipher for encryption.");
int key = readInt("Enter encryption key: ");
String plaintext = readLine("Plaintext: ");
String ciphertext = encryptCaesar(plaintext, key);
println("Ciphertext: " + ciphertext);
}
/*
* Encrypts a string by adding the value of key to each character.
* The first line makes sure that key is always positive by
* converting negative keys to the equivalent positive shift.
*/
private String encryptCaesar(String str, int key) {
if (key < 0) {
key = 26 - (-key % 26);
}
String result = "";
for (int i = 0; i < str.length(); i++) {
char ch = encryptCharacter(str.charAt(i), key);
result += ch;
}
return result;
}
/*
* Encrypts a single character using the key given. This
* method assumes the key is non-negative. Non-letter
* characters are returned unchanged.
*/
private char encryptCharacter(char ch, int key) {
if (Character.isLetter(ch)) {
ch = (char) ('A'
+ (Character.toUpperCase(ch) - 'A' + key) % 26);
}
return ch;
}
}
– 5 –
Letter-substitution ciphers solution
/*
* File: LetterSubstitutionCipher.java
* -----------------------------------
* This program translates a line of text using a letter-substitution
* cipher.
*/
import acm.program.*;
public class LetterSubstitutionCipher extends ConsoleProgram {
public void run() {
println("This program implements a letter-substitution cipher.");
String key = readKey();
String plaintext = readLine("Plaintext: ");
String ciphertext = encrypt(plaintext, key);
String decryption = decrypt(ciphertext, key);
println("Ciphertext: " + ciphertext);
println("Decryption: " + decryption);
}
/**
* Reads an encryption key from the user and checks it for
* legality (see isLegalKey below). If the user enters an
* illegal key, the user is asked to reenter a valid one.
*/
private String readKey() {
String key = readLine("Enter 26-letter key: ");
while (!isLegalKey(key)) {
println("That key is not legal.");
key = readLine("Reenter key: ");
}
return key;
}
/**
* Checks to see whether a key is legal, which means it meets
* the following conditions:
* (1) It is 26 characters long,
* (2) It contains only uppercase letters, and
* (3) It has no duplicated letters.
*/
private boolean isLegalKey(String key) {
if (key.length() != 26) return false;
for (int i = 0; i < 26; i++) {
char ch = key.charAt(i);
if (!Character.isUpperCase(ch)) return false;
for (int j = i + 1; j < 26; j++) {
if (ch == key.charAt(j)) return false;
}
}
return true;
}
– 6 –
/**
* Encrypts a string according to the key.
*/
private String encrypt(String str, String key) {
String result = "";
for (int i = 0; i < str.length(); i++) {
char ch = encryptCharacter(str.charAt(i), key);
result += ch;
}
return result;
}
/**
* Encrypts a single character according to the key.
*/
private char encryptCharacter(char ch, String key) {
if (Character.isLetter(ch)) {
ch = key.charAt(Character.toUpperCase(ch) - 'A');
}
return ch;
}
/**
* Decrypts a string according to the key.
*/
private String decrypt(String str, String key) {
String result = "";
for (int i = 0; i < str.length(); i++) {
char ch = decryptCharacter(str.charAt(i), key);
result += ch;
}
return result;
}
/**
* Decrypts a single character according to the key.
*/
private char decryptCharacter(char ch, String key) {
int index = key.indexOf(ch);
if (index != -1) {
ch = (char) ('A' + index);
}
return ch;
}
}

Mehran Sahami Handout #25 CS 106A October 22, 2007


Mehran Sahami Handout #25 CS 106A October 22, 2007 Strings Examples Based on examples by Eric Roberts and Patrick Young. Checking for palindromes public boolean isPalindrome(String str) { for(int i = 0; i < str.length() / 2; i++) { if (str.charAt(i) != str.charAt(str.length() - (i + 1))) { return false; } } return true; } Reversing strings and a simpler version of checking for palindromes
public String reverseString(String str) { String result = ""; for(int i = 0 ; i < str.length(); i++) { result = str.charAt(i) + result; } return result; } public boolean simpleIsPalindrome(String str) {
return (str.equals(reverseString(str))); } Counting uppercase characters import acm.program.*; public class CountUppercase extends ConsoleProgram { private int countUppercase(String str) { int upperCount = 0; for(int i=0; i<str.length(); i++) { char ch = str.charAt(i); if (Character.isUpperCase(ch)) { upperCount++; } } return upperCount; } public void run() { String str = readLine("Enter String: "); println(countUppercase(str) + " upper case letters"); } }
– 2 –
Replace first occurrence public String replaceFirstOccurrence(String str, String orig, String repl) { int index = str.indexOf(orig); if (index != -1) { str = str.substring(0, index) + repl + str.substring(index + orig.length()); } return str; }

Saturday, 11 June 2011

Mehran Sahami Handout #45 CS 106A December 3, 2007


Mehran Sahami Handout #45
CS 106A December 3, 2007
Section Handout #9: Objects and Data structures
Parts of this handout by Eric Roberts and Patrick Young
1. Primitive vs. Objects
Let's say a student writes the following line of code in a predicate method (i.e., a method that returns a boolean):
public boolean IsNameQ() {
String name = readLine("Enter name: ");
return (name == "Q");
}
The author of this code thinks that the program will return true if the player’s name is "Q". What’s the problem here?
Now consider if the code were written as:
public boolean IsNameQ() {
String name = readLine("Enter name: ");
char ch = name.charAt(0);
return ((ch == 'Q') && (name.length() == 1));
}
How is the code above different with respect to checking for equality with the value "Q"?
Continued on next page
– 2 –
2. Data structure design
So far in CS106A, we've worked a good deal with arrays and ArrayLists. While arrays have fixed sized, ArrayLists grow as more elements are added (usually, to the end). We could think of potentially an even more powerful idea: an expandable array. The idea here is that we could think of an array that dynamically expanded to accomodate whatever index we try to access. For example, if we started with an empty array, and tried to add an element at index 14, the array would automatically grow large enough, so that elements 0 through 14 all existed (and we could store the given value at index 14). All the elements of the array that had not previously been given a value would have the value null. Then if we tried store a value at, say, index 21, the array would again grow automatically to have space for elements up to an including index 21. Note that the value at index 14 would still appear to be at index 14 in the expanded array.
Being able to expand an array dynamically is useful enough that it might be worth creating an abstraction to implement it. Such an abstraction is shown in Figure 1 (on the next page), which shows the structure of an ExpandableArray class that allows the client to call set on an index even if it doesn’t currently exist in the array. When such a situation occurs, the implementation of the ExpandableArray class has to allocate a new internal array that is large enough to hold the desired element and then copy all of the existing elements into the new array. The point of this problem is simply to make that operation general by creating a new class that provides the functionality, and we make the class sufficiently general by having it be able to store any type of object (hence the use of the type Object for the array elements). To create an expandable array, and set some of it's values (whih in this case are Strings), you might have code such as:
ExpandableArray myList = new ExpandableArray();
myList.set(14, "Bob");
myList.set(21, "Sally");
When you wanted to retrieve the value at a particular element of the expandable array, you could do something like the following:
String value = (String) myList.get(14); // Note the cast
if (value != null) {
println("Got value: " + value);
}
In writing your solution, you should keep the following points in mind:
• The underlying structure in your implementation must be an array of objects. Although using a HashMap might well be easier, it will be less efficient than the array in terms of the time necessary to look up a specific index.
• Notice that the definition of the abstraction explicitly states that the get method should return null if the specified index value does not exist. That means that you will need to check whether the index is out of bounds before trying to select that element.
• You may assume that all of the index values supplied by the client are nonnegative and do not need to check for such values in the implementations of get and set.
– 3 –
Figure 1. Proposed structure of the ExpandableArray class
/**
* This class provides methods for working with an array that expands
* to include any positive index value supplied by the caller.
*/
public class ExpandableArray {
/**
* Creates a new expandable array with no elements.
*/
public ExpandableArray() {
. . . You fill in the implementation . . .
}
/**
* Sets the element at the given index position to the specified.
* value. If the internal array is not large enough to contain that
* element, the implementation expands the array to make room.
*/
public void set(int index, Object value) {
. . . You fill in the implementation . . .
}
/**
* Returns the element at the specified index position, or null if
* no such element exists. Note that this method never throws an
* out-of-bounds exception; if the index is outside the bounds of
* the array, the return value is simply null.
*/
public Object get(int index) {
. . . You fill in the implementation . . .
}
}

Mehran Sahami Handout #45A CS 106A December 5, 2007


Mehran Sahami Handout #45A
CS 106A December 5, 2007
Solution to Section #9
Parts of this handout by Eric Roberts and Patrick Young
1. Primitive vs. Objects
In the first example, the student is thinking a little too literally about the expressions they’ve written them, seeing them as what they want them to mean as opposed to what they in fact do mean. The problem lies in the comparison:
(name == "Q")
The correct English translation of this statement is: compare the address of the object name to the address of the constant string "Q". In other words, name is a reference to a String object. Since name was read in from the user, this comparison will always return false, as it cannot be the same underlying object as the constant string "Q". If we actually want to compare the values held in those String objects, we should write:
name.equals("Q")
For comparing values, the == operator should only be used with primitive types, such as int, double, boolean, and char. Variables that represent objects (like String) are always references (addresses to some location in memory).
In the second example the code actually works as intended. In the expression:
(ch == 'Q')
we are using the == operator to compare the primitive type char. Se we are comparing primitives (and not object references), the == operator is comparing actual char values rather than memory addresses. This works just as we would want it to.
Continued on next page
– 2 –
2. Data structure design
/*
* File: ExpandableArray.java
* --------------------------
* This class provides methods for working with an array that expands
* to include any positive index value supplied by the caller.
*/
public class ExpandableArray {
/**
* Creates a new expandable array with no elements.
*/
public ExpandableArray() {
array = new Object[0]; // Allows us to check length of array
// even when no elements exist
}
/**
* Sets the element at the given index position to the specified.
* value. If the internal array is not large enough to contain that
* element, the implementation expands the array to make room.
*/
public void set(int index, Object value) {
if (index >= array.length) {
// Create a new array that is large enough
Object[] newArray = new Object[index + 1];
// Copy all the existing elements into new array
for (int i = 0; i < array.length; i++) {
newArray[i] = array[i];
}
// Keep track of the new array in place of the old array
array = newArray;
}
array[index] = value;
}
/**
* Returns the element at the specified index position, or null if
* no such element exists. Note that this method never throws an
* out-of-bounds exception; if the index is outside the bounds of
* the array, the return value is simply null.
*/
public Object get(int index) {
if (index >= array.length) return null;
return array[index];
}
/* Private instance variables */
private Object[] array;
}

Mehran Sahami Handout #44 CS 106A November 30, 2007


Mehran Sahami Handout #44
CS 106A November 30, 2007
Packaging Your Program into a Distributable JAR File
Based on a handout by Eric Roberts and Brandon Burr
Now that you’ve written all these wonderful programs, wouldn’t it be great if you could package them up and send them to your mom, dad, friends, and pets so that she could see what you’ve done? Because we here in CS106A feel that no parent should be spared the joy of sitting through a simplified version of a game he or she has undoubtedly played a million times before, here’s a short guide to making an executable JAR file in Eclipse!
If your program uses no external JAR files
Eclipse makes it easy to package your code up into a JAR file that you can double-click to run, especially for those programs that aren’t using other JAR files. In other words, if your program is using the ACM libraries (or some other non-standard Java libraries), you’ll have to do the slightly more complicated method that uses a manifest file. Both examples are described below.
Using the ACM libraries, Step 1
Our programs that have used the ACM libraries have started running via the public void run() method. We changed Eclipse to allow this, to make things easier for you. But in reality, a Java program needs to start at a particular method in a class, the public static void main(String[] args) method. If your program uses the ACM libraries, you’ll need to edit your code to have a main(). You can do this easily by simply adding the following code to the class that has the public void run(), substituting the name of that class for „Yahtzee‟ below.
public static void main(String[] args) {
new Yahtzee().start(args);
}
If you remember at the beginning of the quarter, we said that you needed the special Stanford version of Eclipse to run your programs, this is because of the edit (mentioned above) that we made to Eclipse. But if you add this main() method your program should run fine in any Java compiler.
Using the ACM libraries (or using any external JAR files), Step 2
Now that we have a normal running Java program, let’s package it up into a JAR file. A JAR file is simple a Java ARchive – a file that contains a set of Java class files as well as potentially other files that will be used by the program. One important issue to point out here is if we need to specify other libraries that our program will need to reference. The easiest way do this in Eclipse is by using a manifest file when creating your JAR file. The manifest file allows you to specify things like which is the main class to run when the JAR file is double-clicked, or what the external libraries are, or even security information about the JAR file. If you aren’t using other JAR files, you don’t need to use the manifest file. Eclipse provides a straightforward way of exporting your program. In either case, you can create a JAR file as follows.
– 2 –
Go through the process of Exporting your project to a JAR file as shown below. In Eclipse, highlight (click on the name of) the project you wish to create a JAR file from:
Go to the File menu and select the Export... command. This should give you the following window:
Click on Java to expand the list of options, and then click on the option JAR file. Hit the „Next >‟ button in the window.
– 3 –
You will see the JAR Export window:
Click on the name of your project (Assignment5, in this case) to make sure the (default package) is selected, and select the destination of the JAR file using the „Browse...” button. Then hit „Next‟.
– 4 –
You will then see the following window:
This screen isn’t too important for our purposes. Just hit the „Next >‟ button again.
– 5 –
You will then see the following window:
Okay, now here’s where the important stuff happens. First, near the bottom of the window, select the Main class using the „Browse...‟ button. The main class (Yahtzee, in this case) should show up in the list if you correctly added the main() method described above.
If your program doesn’t reference other JAR files (i.e., it does not use the ACM libraries or any other libraries), that’s it. You’re done! You don’t need to worry about the manifest file stuff. Just hit the „Finish‟ button, and go double-click the JAR file you just created. Make sure you see the last section of this handout on data files and distribution, though.
– 6 –
If you do need to reference other JAR files (i.e., like the ACM libraries), then you need to create a manifest file. To do this, we will go through this exporting process twice. The first time is to generate a manifest file, and the second time is to use that file when exporting our program. So, from here, make sure „Generate the manifest file‟ radio button near the top of the window is selected, and that check box for „Save the manifest in the workspace' is checked. Use the „Browse…‟ button associated with the „Manifest file‟ text box to select the destination of the manifest file. Click the Assignment5 folder (or whatever your project is named), and then in the box type in the name “manifest”. This screen should look something like the image above when you’re done (i.e., the Manifest file will likely be '/Assignment5/manifest'). Now hit the 'Finish' button.
You should see the manifest file show up in the package explorer:
If you double click on the manifest file, you should see its contents. You need to edit the manifest file to add the line "Class-Path: " followed by the names of all the JAR files that this program uses, separated by spaces. In this case, that would include acm.jar and yahtzeelib.jar. When you’re done the manifest file will look something like this:
– 7 –
Make sure to save the updated manifest file. Now that we have this manifest file, repeat the entire above process of exporting a JAR file (i.e., click on your project name, pick Export... from the file menu, select the JAR file option for exporting, etc.). However, this time you will do something different when you get to the last window (shown below):
When you get here, make sure to click the radio button for “Use existing manifest from workspace”.
– 8 –
You should then have a screen that looks like this:
Now, hit “Finish” button. Eclipse will use the manifest file we just created previously to make our yahtzee.jar. If it asks you to overwrite the old yahtzee.jar, just say “Yes”.
We’re almost there!
– 9 –
Distributing your program
Now you have your yahtzee.jar file, containing your Yahtzee code, but you can’t simply send the yahtzee.jar to your friends. This jar doesn’t contain the code in the other two JAR files (acm.jar and yahtzeelib.jar), nor does it contain any data files your program might use (text files with data, or even sounds or images). What you’ll want to do is create a new folder, place your yahtzee.jar file in it, along with any other JAR files your program uses (like acm.jar and any other the ones you added to the manifest file) and data files you use. Once you have all of these files in a single folder, you should be able to just double-click your yahtzee.jar file, and have it run your application. You will need to distribute this entire folder to anyone that you want to share your program with. Usually the easiest way is to just zip the folder up into a single file, and then email that – just make sure that your friends know how to unzip the file!

Mehran Sahami Handout #43 CS 106A November 28, 2007


Mehran Sahami Handout #43 CS 106A November 28, 2007 FlyTunes Program (Data Structures Example) File: Song.java /* * File: Song.java * --------------- * Keeps track of the information for one song * in the music shop, including its name, the band * that it is by, and its price. */ public class Song { /** Constructor * Note that the song name and band name are immutable * once the song is created. */ public Song(String songName, String bandName, double songPrice) { title = songName; band = bandName; price = songPrice; } public String getSongName() { return title; } public String getBandName() { return band; } public void setPrice(double songPrice) { price = songPrice; } public double getPrice() { return price; } /** Returns a string representation of a song, listing * the song name, the band name, and its price. */ public String toString() { return ("\"" + title + "\" by " + band + " costs $" + price); } /* private instance variables */ private String title; private String band; private double price; }
– 2 –
File: Album.java /* * File: Album.java * ---------------- * Keeps track of all the information for one album * in the music shop, including its name, the band that * its by, and the list of songs it contains. */ import java.util.*; public class Album { /** Constructor * Note that the album name and band name are immutable * once the album is created. */ public Album(String albumName, String bandName) { title = albumName; band = bandName; } public String getAlbumName() { return title; } public String getBandName() { return band; } /** Adds a song to this album. There is no duplicate * checking for songs that are added. */ public void addSong(Song song) { songs.add(song); } /** Returns an iterator over all the songs that are * on this album. */ public Iterator<Song> getSongs() { return songs.iterator(); } /** Returns a string representation of an album, listing * the album name and the band name. */ public String toString() { return ("\"" + title + "\" by " + band); } /* private instance variables */ private String title; private String band; private ArrayList<Song> songs = new ArrayList<Song>(); }
– 3 –
File: FlyTunesStore.java /* * File: FlyTunesStore.java
* ------------------------ * This program handles the data management for an on-line music store * where we manage an inventory of albums as well as individual songs. */ import acm.program.*; import java.util.*; public class FlyTunesStore extends ConsoleProgram { public void run() { while (true) { int selection = getSelection(); if (selection == QUIT) break; switch (selection) { case LIST_SONGS: listSongs(); break; case LIST_ALBUMS: listAlbums(); break; case ADD_SONG: addSong(); break; case ADD_ALBUM: addAlbum(); break; case LIST_SONGS_ON_ALBUM: listSongsOnAlbum(); break; case UPDATE_SONG_PRICE: updateSongPrice(); break; default: println("Invalid selection"); break; } } } /** Prompts the user to pick a selection from a menu * of options. Returns the users selection. Note that * there is no bounds checking done on the users selection. */ private int getSelection() { println(); println("Please make a selection (0 to quit):"); println("1. List all songs"); println("2. List all albums"); println("3. Add a song"); println("4. Add an album"); println("5. List songs on an album"); println("6. Update song price"); int choice = readInt("Selection: "); return choice; }
– 4 –
/** Lists all the songs carried by the store */ private void listSongs() { println("All songs carried by the store:"); for(int i = 0; i < songs.size(); i++) { println(songs.get(i).toString()); } } /** Lists all the albums carried by the store */ private void listAlbums() { println("All albums carried by the store:"); Iterator<String> albumIt = albums.keySet().iterator(); while (albumIt.hasNext()) { println(albums.get(albumIt.next()).toString()); } } /** Checks to see if the song (defined by its name and * the band that performs it) is already in the store. It * returns the index of the song in the store's song list * if it already exists and -1 otherwise. */ private int findSong(String name, String band) { int index = -1; for(int i = 0; i < songs.size(); i++) { if (songs.get(i).getSongName().equals(name) && songs.get(i).getBandName().equals(band)) { index = i; break; // don't need to finish the loop } } return index; } /** Adds a new song to the store's inventory and returns that * song to the caller. If the song already exists in the * store, it returns the existing song from the inventory. * Otherwise it returns the new song that was just added to * the inventory. The method may return null if the user * decides not to enter a song (i.e., user just presses * Enter when asked for the song name). */ private Song addSong() { String name = readLine("Song name (Enter to quit): "); if (name.equals("")) return null; String band = readLine("Band name: "); int songIndex = findSong(name, band); if (songIndex != -1) { println("That song is already in the store."); return songs.get(songIndex); } else { double price = readDouble("Price: "); Song song = new Song(name, band, price); songs.add(song); println("New song added to the store."); return song; } }
– 5 –
/** Adds a new album to the store's inventory. If the album * already exists in the store, then the inventory is * unchanged. Otherwise a new album and any new songs it * contains are added to the store's inventory. */ private void addAlbum() { String name = readLine("Album name: "); if (albums.containsKey(name)) { println("That album is already in the store."); } else { String band = readLine("Band name: "); Album album = new Album(name, band); albums.put(name, album); while (true) { Song song = addSong(); if (song == null) break; album.addSong(song); } println("New album added to the store."); } } /** Lists all the songs on a single album in the inventory. */ private void listSongsOnAlbum() { String name = readLine("Album name: "); if (albums.containsKey(name)) { Iterator<Song> it = albums.get(name).getSongs(); println(name + " contains the following songs:"); while (it.hasNext()) { Song song = it.next(); println(song.toString()); } } else { println("No album by that name in the store."); } } /** Updates the price of a song in the store's inventory. * Note that this price update will also affect all albums * that contain this song. */ private void updateSongPrice() { String name = readLine("Song name: "); String band = readLine("Band name: "); int songIndex = findSong(name, band); if (songIndex == -1) { println("That song is not in the store."); } else { double price = readDouble("New price: "); songs.get(songIndex).setPrice(price); println("Price for " + name + " updated."); } }
– 6 –
/* Constants */ private static final int QUIT = 0; private static final int LIST_SONGS = 1; private static final int LIST_ALBUMS = 2; private static final int ADD_SONG = 3; private static final int ADD_ALBUM = 4; private static final int LIST_SONGS_ON_ALBUM = 5; private static final int UPDATE_SONG_PRICE = 6; /* Private instance variables */ // Inventory all the albums carried by the store private HashMap<String,Album> albums = new HashMap<String,Album>(); // Inventory of all the songs carried by the store private ArrayList<Song> songs = new ArrayList<Song>(); }

Mehran Sahami Handout #42 CS 106A November 28, 2007

  Mehran Sahami Handout #42 CS 106A November 28, 2007 Assignment #7—FacePamphlet Due: 3:15pm on Friday, December 7th Note: No late days (free or otherwise) may be used on Assignment #7
For many years, computer have been used as a ubiquitous platform for communication. While email is perhaps still the most common medium for computer-based interaction, social networking applications (such as Facebook, Orkut, and MySpace1) have gained immense popularity in recent years. In this vein, your job for this assignment is to create an application that keeps track of a simple social network. What is a Social Network? For those of you not already familiar with social networks, a social network, in the simplest sense, is a means of keeping track of a set of people (each of whom have a "profile" in the social network) and the relationships (usually involving friendship) between them. For example, let's consider a simple social network that contains four people's profiles: Alice, Bob, and Cathy, and Don. Say now that Alice is friends with both Bob and Don (in which case, we consider Bob and Don to automatically be friends of Alice, reciprocally). And Cathy is also a friend of Don. Graphically, we could draw this "network" as:
Here, each profile in the network is represented by a circle containing the name of the profile (more formally, such circles would be called "nodes") and a friendship relationship between two people (which, for our purposes, is always reciprocal) is shown as a line connecting two profiles of people who are considered friends. The Assignment For this assignment, you will create an application that keeps tracks of the information in such a simple social network. More specifically, your application will allow for user profiles to be added to, deleted from, or looked-up in the social network. Moreover, for each profile, you will keep track of the person's name associated with that profile, an optional image that the person may wish to display with his/her profile, an optional "current status" for the profile (which is basically just a String indicating what activity the owner of that profile is currently engaged in), and a list of friends for each profile.
1 Facebook, Orkut, and MySpace are trademarks of those respective social networking sites. They are referred to here only for eduational expository reasons.
Alice
Bob
Don
Cathy
– 2 –
The Program To see how the program works, we give an example of using the program to create a small social network. Initially, the social network starts out empty (i.e., it contains no profiles). Below we illustrate what the application initially looks like when it is run:
Along the NORTH border of the application, is a text field entitled Name, along with three buttons: Add, Delete, and Lookup. To create a new profile, the user would enter a name in the Name text field and click the Add button. For example, say we entered Mehran Sahami in the text field and clicked Add. Since there is not already a profile with the name "Mehran Sahami" in the network, the resulting screen would look as follows:
– 3 –
In this profile displayed above, we note five display elements of interest:
• Name: The name associated with the profile ("Mehran Sahami") is displayed prominently in the top left corner of the display canvas. The profile name is displayed in the color blue (though that will not be evident in this black and white handout).
• Image: Although there is currently no image associated with this profile, we can see that there is space available to display a picture immediately under the name of the profile.
• Status: Under the area for the image, the current status of the person with this profile is displayed. Since a newly created profile does not have a status yet set, the display simply shows the text "No current status".
• Friends: To the right of the profile's name, there is the header text "Friends:", and space available under this text to list the friends of this profile. Again, since we have just created a new profile, there are no friends yet associated with it, so there are no entries listed under the "Friends:" header.
• Application Message: Centered near the bottom of the display canvas is a message from the application ("New profile created") letting us know that a new profile was just created (which is the profile currently being displayed).
Changing Status
Whenever we have a profile displayed in the canvas display area (we refer to this as the current profile), the interactors along the WEST border of the application can be used to make updates to the current profile. These interactors include text fields and associated buttons to: Change Status, Change Picture, and Add Friend. For example, we can change the status of the current profile above by entering the text coding like a fiend in the text field and clicking Change Status (or we could simply have pressed the Enter key after typing the in the respective text field). The display updates as follows:
– 4 –
In the screen above we see that the status text associated with the current profile has been changed to the text "Mehran Sahami is coding like a fiend". Moreover, the Application Message at the bottom of the display canvas has also been changed to reflect the last action taken, namely "Status updated to coding like a fiend".
Changing Picture
We can now update the image associated with the current profile by entering the name of a valid image file (in this case, MehranS.jpg) in the text field associated with the Change Picture button and pressing the Enter key (or clicking the Change Picture button). The display updates as follows:
The image area in the current profile now displays (a scaled version of) the image from the file MehranS.jpg and the Application Message at the bottom of the display canvas has once again been changed to reflect the last action taken, namely "Picture updated".
Adding Friends
Now, let's add another profile for Julie Zelenski (another intrepid faculty member in the Computer Science department) to the social network so that we can show an example of adding a friend to a profile. In the Name text field at the top of the screen, we enter the text Julie Zelenski and click Add. The display now shows the newly created profile (shown on the next page). Note that although a new profile was created for Julie (which has no image associated with it and no current status), the previous values we entered in the text fields for status (coding like a fiend) and image (MehranS.jpg) are still there simply because the text fields were never cleared. It's important to remember that the values in the text fields do not reflect what is in the current profile we are looking at – rather these fields are simply interactors that allow us to update the values in a profile, and old values entered in these text fields need not be cleared in the program (although this would be a simple extension to add to the program, if you were so inclined).
– 5 –
Since Julie likes to maintain her privacy, she may choose to neither update her image nor her status. But, being the friendly person that she is, she chooses to add Mehran as a friend. This is done by entering the profile name Mehran Sahami in the text field immediately above the Add Friend button and then either clicking the button or pressing the Enter key. After this is done, the display is updated as follows:
In the picture above, we see that Mehran Sahami has been added to the list of friends that Julie has, and the Application Message reads "Mehran Sahami added as a friend."
– 6 –
Looking-up Profiles
Recalling that all friendships are reciprocal (i.e., if Julie has Mehran as a friend, then Mehran must also have Julie as a friend), we go to lookup Mehran's profile. This is accomplished by entering Mehran Sahami in the Name text field in the NORTH region of the application and clicking Lookup. The display then looks as follows:
Here we find that Mehran's profile was updated to have Julie as a friend at the same time that Mehran was added as a friend of Julie in the previous interaction. In this way, the application ensures that all friendships are reciprocal – whenever a friend X is added to a profile Y, then not only is X is added as a friend of Y, but Y should also be added as a friend of X at the same time.
Deleting Profiles
Now let's say that we decide to delete Julie's profile from the social network. We can accomplish this by entering the profile name Julie Zelenski in the text field entitled Name (in the NORTH border region) and clicking the Delete button. After this is done, the display is updated as shown in the next page. We see in the picture below that after we delete a profile, the current profile being displayed is no longer shown (no matter whose profile that was), and the Application Message simply reports that "Profile of Julie Zelenski deleted".
– 7 –
Not only has Julie's profile been removed from the social network, but the profile of all members of the social network that had Julie as a friend must also be updated to remove Julie from their list of friends (since it is not possible to be friends with a non-existent profile). So, if we lookup Mehran's profile again by entering Mehran Sahami in the text field entitled Name (in the NORTH border region) and click the Lookup button, the display will look as follows:
Note that Julie is no longer listed as one of Mehran's friends in the display above. She was removed from Mehran's list of friends when her profile was deleted.
– 8 –
To verify that Julie's profile been removed from the social network, we could try to look it up. To do this, we enter Julie Zelenski in the text field entitled Name and click the Lookup button. The display appears as follows:
Note that when we try to lookup Julie's no-longer existent profile, the current profile that was previously displayed is cleared and we are prompted in the Application Message that "A profile with the name Julie Zelenski does not exist". It's important to note that when there is no current profile being displayed (as is the case above), then the interactors in the WEST border region have no profile to update. Thus, if we were to try to, say, change status by entering the text sleeping in the text field and clicking the Change Status button, the display would update as follows:
– 9 –
As can be seen in the display above, if we try to Change Status when there is no current profile displayed, we are simply prompted with an Application Message saying "Please select a profile to change status". We would receive an analogous prompt (albeit with slightly different wording) if we tried to Change Picture or Add Friend when there was no current profile displayed.
Demo Applet
Although we have described the general functionality of the FacePamphlet program above, there is a web demo applet available on the CS106A web page for Assignments that will allow you to play with the application yourself and get a better sense for how it works. You can always refer to the workings of that demo applet if you have questions about how particular situations should be handled in your FacePamphlet program. Note that in the web demo applet, there are only two image files that are available (named StanfordLogo.jpg and StanfordTree.jpg). As a result, you will not be able to display any other images using the web demo applet, but these two images should be sufficient for you to still see how the application works.
The Details
Similar to the NameSurfer assignment, the FacePamphlet program is broken down into several separate class files, as follows:
• FacePamphlet—This is the main program class that ties together the application. It has the responsibility for creating the other objects and for responding to the interactors in the program.
• FacePamphletConstants—This interface is provided for you and defines a set of constants that you can use in the rest of the program simply by having your classes implement the FacePamphletConstants interface, as they do in the starter files.
• FacePamphletProfile—This class should encapsulate all the information for a single profile in the social network. Given a FacePamphletProfile object, you can find out that profile's name, associated image (or lack thereof), associated status (or lack thereof), and the list of names of friends for that profile.
• FacePamphletDatabase—This class keeps track of all the profiles in the FacePamphlet social network. Note that this class is completely separate from the user interface. It is responsible for managing profiles (adding, deleting, looking-up).
• FacePamphletCanvas—This class is a subclass of GCanvas that displays profiles as well as Application Messages on the display canvas. Note, however, that this class does not implement the ComponentListener interface. As a result, this canvas does not need to worry about updating the display as a result of window resizing. We figured that you got enough practice with that in the NameSurfer assignment, so you don't need to worry about that again here (unless you'd like to add it as a program extension).
To help you with regard to developing your program in stages, we outline some development milestones below, along with more details regarding implementing the functionality provided in the program.
– 10 –
Milestone 1: Assemble the GUI interactors
As seen in the initial start-up screen of the application (shown below), there are a number of interactors (JLabels, JTextFields, and JButtons) in both the NORTH and WEST border regions of the application.
Similar to the NameSurfer assignment, your first milestone is simply to add the interactors to the application window and create an implementation for the actionPerformed method that allows you to check whether you can detect button clicks and read what’s in the text fields. Since you've already had experience doing that in the previous assignment, this milestone hopefully won't present many new challenges.
A few specific issues to note in the implementation of these interactors are the following:
• All text fields are TEXT_FIELD_SIZE characters wide. TEXT_FIELD_SIZE is just a constant set in FacePamphletConstants.
• The Name text field in the NORTH region does not have any actionCommand associated with it. In other words, pressing the Enter key in that text field should have no effect, so you don't need to worry about that case.
• The three text fields in the WEST region do have actionCommands associated with them. The actionCommand associated with each respective text field should be the same as its corresponding button. For example, pressing the Enter key in the text field next to the Change Status button should have the same effect as pressing the Change Status button.
• If a text field is empty when its corresponding button is pressed, then nothing should happen. For example, if the Name text field in the NORTH region has nothing in it when the Add (or Delete, or Lookup) button is clicked (i.e., the text field's value is the empty string ("")), then we should simply not do anything as a result of the button click. This idea applies to all text fields in the application, and helps prevent situations such as trying to add a profile with an empty name, or trying to change the status of a profile to the empty string.
– 11 –
One issue to note is that in laying out the interactors in the WEST border region, you'll notice that there are spaces between the various text field/button pairs (for example, there is space between the Change Status button and the text field associated with Change Picture). These spaces should be produced by adding a JLabel with the label text EMPTY_LABEL_TEXT (this is just a constant defined in FacePamphletConstants) at the appropriate points when adding interactors to the WEST border region. So, your interactor layout code will likely include two lines at various points that look something like this:
add(new JLabel(EMPTY_LABEL_TEXT), WEST);
As you did on the previous assignment, you can take the strategy of changing the definition of the FacePamphlet class so that it extends ConsoleProgram instead of Program, at least for the moment. You can always change it back later. Once you have made that change, you can then use the console to record what’s happening in terms of the interactors to make sure that you’ve got them right. For example, we provide below a transcript of the commands used to generate the output in Figure 1, in which the user has just completed the following actions:
1. Entered the name Mehran in the Name text field and clicked the Add button.
2. Entered the name Julie in the Name text field and clicked the Delete button.
3. Entered the name Eric in the Name text field and clicked the Lookup button.
4. Entered the text sleeping in the Change Status text field and clicked the Change Status button.
5. Entered the text eating in the Change Status text field and pressed the Enter key.
6. Entered the text StanfordLogo.jpg in the Change Picture text field and clicked the Change Picture button.
7. Entered the text MehranS.jpg in the Change Picture text field and pressed the Enter key.
8. Entered the text Julie in the Add Friend text field and clicked the Add Friend button.
9. Entered the text Eric in the Add Friend text field and pressed the Enter key.
Figure 1. Illustration of Milestone 1
– 12 –
Milestone 2: Implement the FacePamphletProfile class
The starter file for the FacePamphletProfile class appears in full as Figure 2 on the following pages. The starter file includes definitions for all of the public methods we expect you to define. The method definitions in the starter files, however, do nothing useful (they are just stubs), although they occasionally include a return statement that gives back a default value of the required type. In Figure 2, for example, the getName method always returns the empty string ("") to satisfy the requirement that the method returns an String as defined in its header line.
The FacePamphletProfile class encapsulates the information pertaining to one profile in the social network. That information consists of four parts:
1. The name of the person with this profile, such as "Mehran Sahami" or "Julie Zelenski"
2. The status associated with this profile. This is just a String indicating what the person associated with the profile is currently doing. Until it is explicitly set, the status should initially be the empty string.
3. The image associated with that profile. This is a GImage. Until it is explicitly set, this field should initially be null since we don't initially have an image associated with a profile.
4. The list of friends of this profile. The list of friends is simply a list of the names (i.e., list of Strings) that are friends with this profile. This list starts empty. The data structure you use to keep track of this list is left up to you.
The last method in the starter implementation of FacePamphletProfile is a toString method whose role is to return a human-readable representation of the data stored in the profile. The general form of the string returned by this method is:
name (status): comma separated list of friend names
For example, if the variable profile contains the FacePamphletProfile data of a profile with name "Alice" whose status is "coding" and who has friends named Don, Chelsea, and Bob, then profile.toString() would return the string:
"Alice (coding): Don, Chelsea, Bob"
The toString method will be useful as you continue to develop your program in stages.
– 13 –
Figure 2. Starter file for the FacePamphletProfile class
/*
* File: FacePamphletProfile.java
* ------------------------------
* This class keeps track of all the information for one profile
* in the FacePamphlet social network. Each profile contains a
* name, an image (which may not always be set), a status (what
* the person is currently doing, which may not always be set),
* and a list of friends.
*/
import acm.graphics.*;
import java.util.*;
public class FacePamphletProfile implements FacePamphletConstants {
/**
* Constructor
* This method takes care of any initialization needed for
* the profile.
*/
public FacePamphletProfile(String name) {
// You fill this in
}
/** This method returns the name associated with the profile. */
public String getName() {
// You fill this in. Currently always returns the empty string.
return "";
}
/** This method returns the image associated with the profile.
* If there is no image associated with the profile, the method
* returns null. */
public GImage getImage() {
// You fill this in. Currently always returns null.
return null;
}
/** This method sets the image associated with the profile. */
public void setImage(GImage image) {
// You fill this in
}
/** This method returns the status associated with the profile.
* If there is no status associated with the profile, the method
* returns the empty string ("").
*/
public String getStatus() {
// You fill this in. Currently always returns the empty string.
return "";
}
/** This method sets the status associated with the profile. */
public void setStatus(String status) {
// You fill this in
}
– 14 –
/** This method adds the named friend to this profile's list of
* friends. It returns true if the friend's name was not already
* in the list of friends for this profile (and the name is added
* to the list). The method returns false if the given friend name
* was already in the list of friends for this profile (in which
* case, the given friend name is not added to the list of friends
* a second time.)
*/
public boolean addFriend(String friend) {
// You fill this in. Currently always returns true.
return true;
}
/** This method removes the named friend from this profile's list
* of friends. It returns true if the friend's name was in the
* list of friends for this profile (and the name was removed from
* the list). The method returns false if the given friend name
* was not in the list of friends for this profile (in which case,
* the given friend name could not be removed.)
*/
public boolean removeFriend(String friend) {
// You fill this in. Currently always returns false.
return false;
}
/** This method returns an iterator over the list of friends
* associated with the profile.
*/
public Iterator<String> getFriends() {
// You fill this in. Currently always returns null.
return null;
}
/** This method returns a string representation of the profile.
* This string is of the form: "name (status): list of friends",
* where name and status are set accordingly and the list of
* friends is a comma separated list of the names of all of the
* friends in this profile.
*
* For example, in a profile with name "Alice" whose status is
* "coding" and who has friends Don, Chelsea, and Bob, this method
* would return the string: "Alice (coding): Don, Chelsea, Bob"
*/
public String toString() {
// You fill this in. Currently always returns the empty string.
return "";
}
}
– 15 –
Milestone 3: Implement the FacePamphletDatabase class
After you have defined the class FacePamphletProfile, you are ready to implement the FacePamphletDatabase class. The starter file for the FacePamphletDatabase class appears in full as Figure 3 on the following pages. As with the other files supplied with this assignment, the starter file includes definitions for all of the public methods we expect you to define.
The FacePamphletDatabase class is used to keep track of all the profiles in the social network. The class which contains five public entries:
• A constructor that has no parameters. You can use this to perform any initialization you may need for the database. Note: depending on how you implement the database, it is entirely possible that your constructor may not need to do anything. It's perfectly fine if that's the case.
• An addProfile method that is passed a FacePamphletProfile, and is responsible for adding that profile to the database. Note that profile names are unique identifiers for profiles in the database. In other words, no two profiles in the database should have the same name and the name associated with a profile will never change. So, when a new profile is being added, if there is already an existing profile with the same name, the existing profile should be replaced by the new profile. Note: depending on what data structure you use to keep track of the database, this behavior may actually be quite easy to implement.
• A getProfile method that takes a name, looks it up in the database of profiles, and returns the FacePamphletProfile with that name, or null if there is no profile with that name.
• A deleteProfile method that takes a profile name, and deletes the profile with that name from the profile database. Note that when we delete a profile from the database, we not only delete the profile itself, but we also update all other profiles in the database so as to remove the deleted profile's name from any friends lists in other profiles. In this way, we ensure that someone cannot be friends with a person who does not have a profile in the database.
• A containsProfile method that takes a profile name, and returns true if there is a profile with that name in the database. Otherwise, it returns false.
The code for this part of the assignment is not particularly difficult. The challenging part lies in figuring out how you want to represent the data so that you can implement the methods above as simply and as efficiently as possible.
– 16 –
Figure 3. Starter file for the FacePamphletDatabase class
/*
* File: FacePamphletDatabase.java
* -------------------------------
* This class keeps track of the profiles of all users in the
* FacePamphlet application. Note that profile names are case
* sensitive, so that "ALICE" and "alice" are NOT the same name.
*/
import java.util.*;
public class FacePamphletDatabase implements FacePamphletConstants {
/** Constructor
* This method takes care of any initialization needed for
* the database. */
public FacePamphletDatabase() {
// You fill this in
}
/** This method adds the given profile to the database. If the
* name associated with the profile is the same as an existing
* name in the database, the existing profile is replaced by
* the new profile passed in. */
public void addProfile(FacePamphletProfile profile) {
// You fill this in
}
/** This method returns the profile associated with the given name
* in the database. If there is no profile in the database with
* the given name, the method returns null. */
public FacePamphletProfile getProfile(String name) {
// You fill this in. Currently always returns null.
return null;
}
/** This method removes the profile associated with the given name
* from the database. It also updates the list of friends of all
* other profiles in the database to make sure that this name is
* removed from the list of friends of any other profile.
*
* If there is no profile in the database with the given name, then
* the database is unchanged after calling this method. */
public void deleteProfile(String name) {
// You fill this in
}
/**
* This method returns true if there is a profile in the database
* that has the given name. It returns false otherwise. */
public boolean containsProfile(String name) {
// You fill this in. Currently always returns false.
return false;
}
}
– 17 –
To test this part of the program, you can add code to the FacePamphlet program so that it creates the FacePamphletDatabase and then change the code for the Add, Delete, and Lookup button handlers as follows:
• Entering a name in the Name text field and clicking the Add button looks up the current name in the database to see if a profile with that name already exists. If the name does not exist, then it adds a new profile to the database and prints out "Add: new profile: " followed by the string version of the profile (using the toString method of the FacePamphletProfile). If the profile name already exists in the database, then it prints out the fact that the profile with that name already exists followed by the string representation of the profile.
• Entering a name in the Name text field and clicking the Delete button looks up the current name in the database to see if it exists. If the name does exist, then it deletes the profile with that name from the database and prints out that the profile was deleted. If the profile name does not exist in the database, then it simply prints out that a profile with the given name does not exist.
• Entering a name in the Name text field and clicking the Lookup button looks up the current name in the database to see if it exists. If the name does exist, then prints out "Lookup: " followed by the string version of the profile. If the name does not exist, then it prints out that a profile with the given name does not exist.
A sample run of this milestone is shown in Figure 4 (on the next page), where the user has just completed the sequence of actions given below. (Note that your text messages need not correspond exactly to those shown in the sample run here, as long as you can still verify that your program is working properly.)
1. Entered the name Mehran in the Name text field and clicked the Add button.
2. Entered the name Julie in the Name text field and clicked the Add button.
3. Again, entered the name Mehran in the Name text field and clicked the Add button.
4. Entered the name Julie in the Name text field and clicked the Delete button.
5. With the name Julie still in the Name text field, clicked the Delete button again.
6. Entered the name Mehran in the Name text field and clicked the Lookup button.
7. Entered the name Julie in the Name text field and clicked the Lookup button.
– 18 –
Figure 4. Illustration of Milestone 3
Milestone 4: Implement functionality for Change Status, Change Picture, and Add Friend buttons
The next step in the process is to complete more of the implementation of the FacePamphlet class, namely the functionality for the Change Status, Change Picture, and Add Friend buttons. The main issue to remember here is that these buttons effect the current profile, if there is one. As a result, one of the first things you should think about in implementing this milestone is how you will keep track of the current profile in the application. To help introduce the notion of the current profile, you might want to update the code for the Add, Delete, and Lookup button handlers so that:
• Whenever a new profile is added, the current profile is set to be the newly added profile. If the user tried to add a profile with the name of an existing profile, then the existing profile with that name is set to be the current profile (this is similar to the case below where the users simply looks up an existing profile).
• Whenever a profile is deleted (whether or not the profile to be deleted exists in the database), there is no longer a current profile (regardless of what the current profile previously was).
• Whenever the user lookups up a profile by name, the current profile is set to be the profile that the user looked up, if it exists in the database. If a profile with that name does not exist in the database, then there is no longer a current profile (regardless of what the current profile previously was).
Once you have a notion of a current profile implemented, then you are ready to actually implement the functionality for the Change Status, Change Picture, and Add Friend buttons.
– 19 –
Implementing Change Status
If the user enters some text in the text field associated with the Change Status button and either presses the Change Status button or hits Enter, the application should update as follows:
• If there is a current profile, then the status for that profile should be updated to the text entered, and you can just print out a message to that effect.
• If there is no current profile, then you should simply prompt the user to select a profile to change the status of (and there should be no changes to any of the profiles in the database).
Implementing Change Picture
If the user enters some text in the text field associated with the Change Picture button and either presses the Change Picture button or hits Enter, the application should update as follows:
• If there is a current profile, then we need to see if the we can create a GImage with the filename of the text entered in the text field. Checking to see if a valid image file exists can be accomplished using the code fragment below (where filename is a String containing the name of the image file we are trying to open):
GImage image = null;
try {
image = new GImage(filename);
} catch (ErrorException ex) {
// Code that is executed if the filename cannot be opened.
}
Note in the code fragment above that the variable image will still have the value null if we were unable to open the image file with the given filename. Otherwise, the value of the variable image will be a valid GImage object (whose value will not be null).
If we obtained a valid GImage, then the image for the current profile should be updated to this image, and you can print out a message to that effect (although you won't be able to display the actual image for now).
• If there is no current profile, then you should simply prompt the user to select a profile to change the image of (and there should be no changes to any of the profiles in the database).
In the starter bundle for this assignment we have provided you with an images folder that contains a number of images (of your beloved course staff as well as a couple Stanford logos) that you can use for this assignment. Of course, you can feel free to use your own image files as well (as long as they are in GIF or JPG format).
– 20 –
Implementing Add Friend
If the user enters some text in the text field associated with the Add Friend button and either presses the Add Friend button or hits Enter, the application should update as follows:
• If there is a current profile, then we need to see if the name entered in the text field is the name of a valid profile in the database. If it is, then we try to add the named friend to the list of friends for the current profile. If the named friend already exists in the list of friends for the current profile, then we simply write out a message that such a friend already exists. If that named friend does not previously exist in the list of friends (i.e., it was successfully added to the list of friends for the current profile), then (recalling that friendships are reciprocal) we also need to update the profile of the named friend to add the name of the current profile to its list of friends. For example, if the current profile was "Mehran" and we tried to add as a friend "Julie" (which, say, is the name of valid profile in the database, which is not already a friend of Mehran), then we should add Julie as a friend of Mehran and also add Mehran as a friend of Julie.
• If the name entered in the Add Friend text field is not a valid profile in the system, we should just print out a message to that effect.
• If there is no current profile, then you should simply prompt the user to select a profile to add a friend to (and there should be no changes to any of the profiles in the database).
To show one possible example of the interactions at this milestone, we show a sample run in Figure 5 on the next page, where the user has just completed the sequence of actions given below. (Note that your text messages don't need to correspond exactly to those shown here, but you should be able to get the idea of what profile, if any, is the current profile at any given time, as well as the updates that are made to it.)
1. Entered the name Mehran in the Name text field and clicked the Add button. Note that at this point the current profile is set to Mehran's profile.
2. Entered the text sleeping in the Change Status text field and clicked the Change Status button.
3. Entered the text MehranS.jpg in the Change Picture text field and clicked the Change Picture button.
4. Entered the name Julie in the Name text field and clicked the Add button. Note that at this point the current profile is set to Julie's profile.
5. Entered the name Mehran in the Add Friend text field and clicked the Add Friend button. Note that Julie's current profile now shows Mehran as a friend.
6. Entered the name Mehran in the Name text field and clicked the Lookup button. Note that at this point the current profile is Mehran's profile and it now shows Julie as friend.
– 21 –
7. With the name Mehran still in the Name text field, we clicked the Delete button. Note that at this point there is no current profile.
8. With the text sleeping still in the Change Status text field, we clicked the Change Status button, and were prompted to select a profile since there is no current profile.
9. Entered the name Julie in the Name text field and clicked the Lookup button. Note that the current profile is now set to Julie's profile, and Mehran is no longer in her friend list since his profile was deleted previously.
Figure 5. Illustration of Milestone 4
– 22 –
Milestone 5: Implement the FacePamphletCanvas class and complete the implementation of the FacePamphlet class
At this point you actually have most of the functionality for keeping track of data in your social network application. All that's left is to create the actual graphical display of profiles, and then tie up a few loose ends to make sure you're displaying appropriate messages to the user.
The starter code for the FacePamphletCanvas class appears in Figure 6 on the next page. The class (which extends GCanvas) contains three public entries:
• A constructor that has no parameters. You can use this to perform any initialization you may need for the canvas. Note: depending on how you implement the canvas, it is entirely possible that your constructor may not need to do anything. It's perfectly fine if that's the case.
• A showMessage method that is passed a String, and is responsible for displaying that string as the Application Message at the bottom of the canvas. The method should display this Application Message text centered horizontally with respect to the width of the canvas, and the vertical baseline for the text should be located BOTTOM_MESSAGE_MARGIN pixels up from the bottom of the canvas. The font for the text should be set to MESSAGE_FONT. Note that BOTTOM_MESSAGE_MARGIN and MESSAGE_FONT are simply constants defined in FacePamphletConstants. Whenever this method is called, any previously displayed message is replaced with the new message text that is passed in.
• A displayProfile method that is passed a FacePamphletProfile, and is responsible for displaying the contents of that profile in the canvas, including the profile's name, image (if any), the status of the profile (if any), and the list of friends (if any). Whenever this method is called, all existing contents of the canvas should be cleared (including any previously displayed profile as well as any displayed Application Messages), and the profile passed in should be displayed. How the various components of the profile should be displayed is discussed in more detail below.
Important Note: The FacePamphletCanvas does not update the display when the window is resized. As a result, this class does not implement the ComponentListener interface, and there is no need to worry about window resizing in implementing this class. We're confident that you had plenty of practice with that on the last assignment.
– 23 –
Figure 6. Starter file for the FacePamphletCanvas class
/*
* File: FacePamphletCanvas.java
* -----------------------------
* This class represents the canvas on which the profiles in the social
* network are displayed. NOTE: This class does NOT need to update the
* display when the window is resized.
*/
import acm.graphics.*;
import java.awt.*;
import java.util.*;
public class FacePamphletCanvas extends GCanvas
implements FacePamphletConstants {
/** Constructor
* This method takes care of any initialization needed for
* the display
*/
public FacePamphletCanvas() {
// You fill this in
}
/** This method displays a message string near the bottom of the
* canvas. Every time this method is called, the previously
* displayed message (if any) is replaced by the new message text
* passed in.
*/
public void showMessage(String msg) {
// You fill this in
}
/** This method displays the given profile on the canvas. The
* canvas is first cleared of all existing items (including
* messages displayed near the bottom of the screen) and then the
* given profile is displayed. The profile display includes the
* name of the user from the profile, the corresponding image
* (or an indication that an image does not exist), the status of
* the user, and a list of the user's friends in the social network.
*/
public void displayProfile(FacePamphletProfile profile) {
// You fill this in
}
}
– 24 –
To start adding the graphical display code for profiles, you should go back to the FacePamphlet class and change its definition so that it extends Program rather than the temporary expedient of extending ConsoleProgram (as you may have been using during the milestones above). At the same time, you should remove the various println calls that allowed you to trace the operation of the interactors in the earlier milestones.
Now, you'll need to declare a FacePamphletCanvas private instance variable in your main FacePamphlet class:
private FacePamphletCanvas canvas;
You should then change the constructor of the FacePamphlet class so that it creates a new FacePamphletCanvas object and adds that object to the display, as follows:
canvas = new FacePamphletCanvas();
add(canvas);
If you run the program with only these changes, it won’t actually display anything on the canvas until you implement the methods of the FacePamphletCanvas class and call them from your FacePamphlet class.
Implementing displayProfile
Much of the layout for graphical display of profiles is dictated by constant values defined in FacePamphletConstants. Here we explain how each component of the profile display should be set up, and refer to the sample screen below as necessary:
– 25 –
• Name: Near the top of the display, the name associated with the profile ("Julie Zelenski" in the example above) should be displayed in the color Blue. Horizontally, the text should located LEFT_MARGIN pixels in from the left-hand side of the canvas. Vertically, the top of the text (not its baseline) should be TOP_MARGIN pixels from the top of the canvas. The font for the text should be set to PROFILE_NAME_FONT.
• Image: Although there is currently no image associated with the profile above, we can see that there is space set aside to display an image immediately under the name of the profile. The space for the image will always be IMAGE_WIDTH by IMAGE_HEIGHT pixels.
If no image is associated with the profile then a rectangle of the dimensions of the image size should be drawn. Horizontally, this rectangle should be located LEFT_MARGIN pixels in from the left-hand side of the canvas. Vertically, the top of the rectangle should be should be IMAGE_MARGIN pixels below the baseline of the profile name text. Centered (both horizontally and vertically) within this rectangle should be the text "No Image" in the font PROFILE_IMAGE_FONT.
If an image is associated with the profile then the image should be displayed (in the same location as the rectangle described above). The image should be scaled so that it displays with IMAGE_WIDTH by IMAGE_HEIGHT pixels. The scale method of GImage should be useful to make image display with the appropriate size.
• Status: Under the area for the image, the current status of the person with this profile should be displayed (Julie's status is "running" in the example above). If the profile currently has no status (i.e., it has an empty status string), the text "No current status" should be displayed. If the profile does have a status, the status text should have the name of the profile followed by the word "is" and then the status text for the profile. In any case, the line describing the profile's status should be located horizontally LEFT_MARGIN pixels in from the left-hand side of the canvas. Vertically, the top of the text (not its baseline) should be located STATUS_MARGIN pixels below the bottom of the image. The font for the text should be set to PROFILE_STATUS_FONT.
• Friends: To the right of the profile's name, there is the header text "Friends:", and the names of the friends of this profile (e.g., Mehran Sahami, Bob Plummer, and Eric Roberts) are listed below. The start of the header text "Friends:" should be horizontally located at the midpoint of width of the canvas. Vertically, the baseline for this text should be the same as the top of the image area. The "Friends:" header text should be displayed in the font PROFILE_FRIEND_LABEL_FONT. Immediately below the header, the friends of this profile should be listed sequentially, one per line, with the same horizontal location as the "Friends:" header text. You can use the getHeight() method of GLabel to determine how to vertically space out the list of friends to get one friend per line. The friend names should be displayed in the font PROFILE_FRIEND_FONT.
– 26 –
Note that you don't need to worry about long friend lists that may overwrite a long status message that the profile may have. This might make for an interesting extension, but is certainly not something you need to worry about for this assignment.
• Application Message: As described previously (but repeated here for completeness) the Application Message text ("Displaying Julie Zelenski" in the example above) should be centered with respect to the width of the canvas, and the baseline for the text should be located BOTTOM_MESSAGE_MARGIN pixels up from the bottom of the canvas. The font for the text should be set to MESSAGE_FONT.
To initally work on implementing displayProfile it might be easiest to simply put a single call to this method (of the canvas) in the code that where you add a new profile to the social network. In this way, when you start your application, you can simply try adding the first profile and see if things display correctly (at least for the initial empty profile). Once you get that working then you can wire up the rest of your program.
Finishing Up
In finishing up the program, you need to make calls at appropriate times to displayProfile and showMessage in your FacePamphlet class. Below we outline the behavior you should produce in your application. If you have any questions, you can always refere to the demo applet on the class web site to see how various situations should be handled.
• Adding a Profile
When a new profile is being added you should see if a profile with that name already exists. If it does, you should display the existing profile and give the user the message "A profile with the name <name> already exists". If the profile does not already exist, you should display the newly created profile and give the user the message "New profile created".
• Deleting a Profile
When a profile is being deleted you should see if a profile with that name exists. If it does, you should delete the profile, clear any existing profile from the display, and give the user the message "Profile of <name> deleted". If the profile does not exist, you should clear any existing profile from the display, and give the user the message "A profile with the name <name> does not exist".
• Looking up a Profile
When a profile is being looked up you should see if a profile with that name exists. If it does, you should display the profile, and give the user the message "Displaying <name>". If the profile does not exist, you should clear any existing profile from the display, and give the user the message "A profile with the name <name> does not exist".
– 27 –
• Looking up a Profile
When a profile is being looked up you should see if a profile with that name exists. If it does, you should display the profile, and give the user the message "Displaying <name>". If the profile does not exist, you should clear any existing profile from the display, and give the user the message "A profile with the name <name> does not exist".
• Changing Status
When the status for a profile is being changed, you should determine if there is a current profile. If no current profile exists, you should just give the user the message "Please select a profile to change status". If there is a current profile, you should update its status, redisplay the profile (to show the changed status), and give the user the message "Status updated to <status>".
• Changing Picture
When the picture for a profile is being changed, you should determine if there is a current profile. If no current profile exists, you should just give the user the message "Please select a profile to change picture". If there is a current profile, you should see if the filename given for the picture contains a valid image, and if it does, you should add the image to the profile, redisplay the current profile (to show the new image), and give the user the message "Picture updated". If the given filename could not be opened, you should just give the user the message "Unable to open image file: <filename>". In that case, the image associated with the profile is unchanged.
• Adding Friend
When a friend is being added to a profile, you should determine if there is a current profile. If no current profile exists, you should just give the user the message "Please select a profile to add friend". If there is a current profile, you should see if the given friend name is the name for a valid profile in the social network. If the name is valid and the current profile does not already have that person as a friend, then you should update the friend list for both the current profile and the named friend, redisplay the current profile (to show the addition of the friend), and give the user the message "<friend name> added as a friend". If the named friend is already a friend of the current profile, you should just display the message "<name of current profile> already has <friend name> as a friend." If the named friend does not have a profile in the social network, then you should simply display the message "<friend name> does not exist."
Congratulations! Once you've gotten this working, you've just finished implementing your very own social network.
– 28 –
Possible extensions
While its pretty exciting to have created a social networking application, there are still a lot of things that you could do to make this program more interesting. Here are just a few possibilities:
• Keep track of additional information for each profile. The current profile only keeps track of a name, image, status and a list of friends. In real social networks, there is much more information about users that is kept track of in profiles (e.g., age, gender, where they may have gone to school, etc.) Use your imagination. The more challenging issue will be how you appropriately display this additional information graphically in the profile display.
• Save and load social networks. While this application let's us keep track of a social network, it is somewhat unsatisfying that we need to construct a whole social network from scratch everytime we run the application. It would be great if there was functionality that allowed us to save the current social network in a file and read it back in later. Implementing such a feature would require you to consider what would be an appropriate file format for keeping track of all the information in the social network which would make it easy for you to write it out and read it back in.
• Finding friends of friends. Another interesting aspect of social networks is not only keeping track of how many people you have as friends, but also how quickly that number grows as you consider all the friends of your friends, and their friends, and so on. Displaying these sorts of properties of the social network are neat features that show just how few degrees of separtion there are between people. Along these same lines, it would be interesting to find and display "friendship chains" that show the shortest sequence of friendship relations that create a chain from one profile to another. For example, if X is a friend of Y, and Y is a friend of Z, then a friendship chain exist that goes: X  Y  Z. Finding even longer chains makes for a fun and challenging problem.
• Adjust the profile display as the application window is resized. You got some practice with this already with the NameSurfer application and it would be an interesting extension to apply some of those same ideas here. The more challenging issue is how you would decide to change font sizes and the size of the image as the display size grew or shrank.
• Go nuts! There's really no shortage of ways that you could extend your FacePamphlet application. In fact, whole companies have been started based on creating a social network application with some cool new features. And if you do end up starting the next multi-billion dollar company based on social networking, just remember where it all started... CS106A!

Search This Blog