Q1: 10 points Q2: 15 points Q3: 15 points Q4: 15 points Q5: 15 points Q6: 10 points Q7: 20 points Total: 100 points
public class Dog {
private String name;
private int age;
// public get and set methods, two-parameter constructor
public class SledDog extends Dog {
private String breed = "Husky";
// public get and set methods, three-parameter constructor
public class LinearNode {
private T element;
private LinearNode next;
// public get and set methods, zero-parameter and one-parameter constructors
}
public class DogTeam {
private LinearNode leadNode;
private int size;
// public get and set methods, zero-parameter constructor
public void addToLead (SledDog newLead) {...}
public SledDog removeLead ( ) {...}
public void switchLastTwo ( ) {...}
public SledDog removeYoungest ( ) {...}
public int countHuskies ( ) {...}
}
// block of code to be run before other code fragments:
Dog ace = new Dog("Ace", 6);
Dog biscuit = new Dog("Biscuit", 1);
Dog cardie = new Dog("Cardie", 3);
Dog duncan = new Dog("Duncan", 1);
SledDog balto = new SledDog("Balto", 92, "Husky");
SledDog king = new SledDog("King", 73, "Husky");
SledDog buck = new SledDog("Buck", 108, "Mixed");
// back to class definitions
public class Cell {
private int x;
private int y;
private boolean isOpen;
// public get and set methods, two- and three-parameter constructors
}
public class SCell extends Cell {
private boolean seen;
// public get and set methods, constructors
public class QCell extends SCell {
private int distance;
private QCell parent;
// public get and set methods, constructors
Remember that the Maze class is a two-dimensional array of SCell
objects in Project #2 and of QCell
objects in Project
#4. It has public methods isPath
and path
. For
Question #7 here, we are givingit an additional public method
getCell (int x, int y)
that returns the cell in position (x, y).
Here is a picture of a 3 by 3 hexagonal grid, ready for a game of Hex as defined in Question #7:
| RED
| _ _
_/ \_ _/ \_ _/ \_ _/
/ \ / \ / \ /
| 0, 0 | 1, 0 | 2, 0 |
| | | |
_/ \_ _/ \_ _/ \_ _/
/ \ / \ / \ / BLUE
| 0, 1 | 1, 1 | 2, 1 |
| | | |
BLUE _/ \_ _/ \_ _/ \_ _/
/ \ / \ / \ /
| 0, 2 | 1, 2 | 2, 2 |
| | | |
_/ \_ _/ \_ _/ \_ _/ \_
/ \ / \ / \ / \
RED
Stacks and queues are both linear data structures giving only limited access to their elements. Stacks support a push operation, adding an element, and a pop operation that removes and returns the last element added. Queues support an enqueue operation, adding an element, and a dequeue operation that removes and returns the element that has been in the queue the longest.
enqueue
(in QueueADT
, in L&C) and
add
(in Queue
, in java.util
)
These operations both add a new element to the queue. The
add
operation returns a boolean telling whether the
add succeeded -- the enqueue
operation is void. I
was strict about taking off a point if you did not say this about
the return point.
LinearNode
and linear list
A linear node is an object that holds a content element and a pointer to another linear node. A linear list is a linear data structure containing elements -- it can be built out of linear nodes but could also be implemented with an array.
LinearNode<T>
and
LinearNode<SledDog>
LinearNode<T>
is the first line of a declaration of
the generic class LinearNode
-- here T
is a type variable that may be used in the code of the
declaration. When the generic class is actually used, the T must
be replaced by an actual type, such as SledDog
in the
second example. When this happens, a new class is defined, for
example
LinearNode<SledDog>
, by replacing every T in
the declaration with the supplied type, in this case SledDog
.
Both are general search techiques, which we employed in Projects #2 and #4 respectively to search a maze. In DFS the elements in the process of being investigated are placed on a stack, so that when an element is finished it is popped and we continue processing its predecessor on the stack. In BFS these elements are placed on a queue, so that the distance-1 neighbors of the source are checked, then the distance-2 neighbors, and so on. BFS this first discovers a path with the minumum number of edges in it.
DropoutStack
class so that there is
a new method called dropped( )
, which returns an array full of
all the elements that have been dropped by the calling stack since it was
created.
We add a new instance field to the class, a data structure that will
contain all the dropped elements. This could be a stack, queue,
ArrayList
, or other list, or an array that we resize
ourselves as needed, but it must be able to contain all the
dropped elements. We change the push method so that when we drop
an element, we add it to this data structure. When the method
dropped
is called, we create an array out of the data
structure and return it.
int
value k and get back an array
containing all the cells, if any, at distance exactly k from z.
As in part (a), we need an expandable data structure to hold all the
elements at distance k, which we can then convert into an array
and return. The most efficient way to find all the distance-k
elements is to run a variant of the path method of Project #4,
without a destination node, and stop when the first cell at
distance k comes off the queue. At that time the queue will
contain exactly the elements at distance k, and we know how many
there are.
A less efficient but acceptable way to assemble the list is to
run the original path method for the given source and every
possible (open) cell as destination, placing the cell into the
holding structure if the distance from the source is exactly k.
char
values) and be able to encrypt or decrypt arbitrary messages.
We first adapt L&C's encrypt and decrypt methods so that the key and message are each parameters to the method instead of instance fields of the calling object. We then give the user a way to specify the key and message, by a method that gets the input directly or through a constructor. I took off two points from anyone who didn't note that the message, as well as the key, need to be specifiable by the user.
DropoutStack<Dog> d = new DropoutStack<Dog>( ) // recall default capacity is 3
d.push (ace);
Dog dog1 = d.peek( );
d.push (duncan);
Dog dog2 = d.pop( );
d.push (cardie);
d.push (ace);
if (dog1 = d.peek( )) d.pop( );
d.resize (5);
System.out.println (d.size( ));
Before the if statement there are three dogs on the stack with Ace on
the top -- the dropout stack never drops out in this example.
Since dog1
and the top element of the stack are
both the object ace
created in the code base, the
d.pop( )
operation is run and we are left with
Cardie
and Ace on the stack. The resize
operation does not
change the number of elements on the stack, so size
is still equal to 2, and the output is "2".
// new method in DogTeam class
public void reorder ( ) {
DogTeam temp = new DogTeam( );
while (!isEmpty( ))
temp.addToLead (remove( ));
leadNode = temp.leadNode;}
// then run this code
DogTeam team = new DogTeam( );
team.addToLead (king);
for (int i = 0; i < 4; i++) {
team.addToLead (balto);
team.reorder ( );
team.addToLead (buck);}
for (int j = 0; j < 6; j++)
team.removeLead( );
System.out.println (team.countHuskies( ));
The We begin with a team containing only King. Then four times we
successively add Balto, reverse the team, and add Buck. The
first time this gives us (Buck, King, Balto), the second gives
us (Buck, Balto, King, Buck, Balto), the third gives us (Buck,
Balto, Buck, King, Balto, Buck, Balto), and the fourth gives
us (Buck, Balto, Buck, Balto, King, Buck, Balto, Buck, Balto)
for a total of nine dogs. The next for loop removes the first
six of these dogs, leaving us with (Balto, Buck, Balto).
Since Balto is a husky and Buck is not (all SledDogs are
originally set to be huskies by default but this can be
overridden in the constructor or later), the output is "2".
reorder
method in fact reverses the order of the dogs
in the team without adding or subtracting any dogs. (Note
that assigning the lead node of one DogTeam as the lead node
of another changes the second DogTeam to equal the first, even
though no other pointers are changed.)
String [ ] init = {"1111", "1001", "1001", "1111"};
Maze m = new Maze (4, 4, init); // this is a project #4 Maze
QCell [ ] path1 = m.path (0, 0, 3, 3);
System.out.println (path1.length);
There are two shortest paths from (0, 0) to (3, 3), and the BFS in the
path method will return one of them. These paths have "length
6" and the distance from source to destination is 6, but the
path array path1
has seven nodes in it because it
has both the first and last node. So the output is "7".
DogTeam
class:)
public boolean containsHusky( ) {
boolean done = false;
LinearNode<SledDog> current = leadNode;
while (!done) {
if (current.getElement( ).getBreed( ).equals ("Husky"))
done = true;
current = current.getNext( );}
return done;}
If this method is called from a DogTeam that contains no huskies
(including the case of an empty team), the while loop will
continue to the end of the team and throw a
NullPointerException
when current
becomes null and we try to apply its getNext
method. This is not an "infinite loop", as some of you claimed.
public class DogTeamDriver {
public static void main (String [ ] args) {
DogTeam team = new DogTeam( );
for (int i = 0; i < 6; i++)
team.addToLead (balto);
LinearNode<SledDog> temp = team.leadNode;
for (int j = 0; j < 3; j++) {
team.getElement.setBreed ("Cairn Terrier");
temp = temp.getNext( );}
System.out.println (team.countHuskies( ));}}
There are actually three errors in this class, only one of
which I deliberately put there. (Of course I gave full credit
for any of the three.) My intended error was the missing
parentheses on the method call getElement
, which
would make the class fail to compile. Many of you noted that
the instance field leadNode
of DogTeam
is private, and thus cannot be referenced in the
DogTeamDriver
class -- this would also make this
class fail to compile. (It would be ok to use the field if it
were public, however.) Finally, the class references the
SledDog
variable balto
, which is
defined in the code base. But since this declaration does
not occur within the code for DogTeamDriver
,
the reference to balto
will also cause the
class to not compile.
StackADT<Dog> s = new ArrayStack<Dog>( );
DogTeam team = new DogTeam( );
for (int i = 0; i < 4; i++)
s.push (new SledDog("Dog" + i, 3, "Dachshund"));
while (!s.isEmpty( ))
team.addtoLead (s.pop( ));
System.out.println (team.countHuskies( ));
This is a subtle error -- many of you were suspicious of the use of
both Dog and SledDog variables in the same code fragments, but
only some of those people pointed out the actual error. Since
the class SledDog
extends Dog
, a
SledDog
is-a Dog
, and there is
no reason that a SledDog
object cannot be pushed
onto a stack of Dogs. The problem comes when we pop one of
these dogs off of the stack, and try to add it to the DogTeam.
Although this dog would always be a SledDog if we could run this
code, the compiler does not know this
because it came out of a stack of Dogs. So the code fragment
will not compile.
LinkedQueue
, like all of
L&C's
collection interfaces, has a toString
method that
returns a description of every element in the collection.)
B:)
LinkedQueue<Dog> q = new LinkedQueue<Dog>( );
for (int i = 0; i < n; i++)
q.enqueue (cardie);
while (!q.isEmpty( )) {
System.out.println (q);
q.dequeue( );}
Despite the note given during the exam, most of you did not notice
that the next-to-last line takes O(n) time to execute, since
printing Since this line occurs within a while loop that executes n
times, the total time is O(n2) -- the length of the
string is actually O(k) where k is the number of elements
then in the queue, but we know that 1 + 2 + 3 + ... + n
is O(n2). The other parts of the code take only
O(n) time, so the O(n2) dominates.
q
really means printing the string
q.toString( )
. This string is of length O(n)
because it contains the toString output for each element of
the queue.
LinkedQueue
class that takes
parameter
k
and returns (but does not remove) the
k
'th
element in the calling queue, if there is one. Analyze the
worst-case running time of your method in terms of n, the
number of elements in the queue.
This is clearly O(n) in the worst case as it does O(k)
iterations of a loop that does O(1) work, and k might be as
large as n, the size of the queue. I did not deduct points
for algorithms that threw exceptions when k was too big.
public T getElementK (int k) {
if (k >= size( )) return null;
LinearNode<T> current = front;
for (int i = 0; i < k; i++)
current = current.getNext( );
return current.getElement( );}
LinkedStack
.
Analyze its worst-case running time in terms of n, the number
of
elements in the stack.
public void reverse( ) {
if (isEmpty( )) return;
LinkedStack<T> temp = new LinkedStack<T>( );
while (size( ) > 1) {
LinearNode<T> curr = top.getNext( );
LinearNode<T> prev = top;
while (curr.getNext( ) != null) {
curr = curr.getNext( );
prev = prev.getNext( );}
prev.setNext(null);
curr.setNext(temp.top);
temp.top = curr;
temp.size++;
size--;}
top.setNext(temp.top);
size += temp.size;}
Though of course you shouldn't assume so from the name, this method
actually does reverse the order of elements of the stack. It
repeatedly finds the last element of the stack, removes it,
and pushes it onto the temporary stack. Once it has done this
for all but one element of the original stack (the element
that was originally last), it appends the temporary stack to
the original one.
The outer while loop executes n - 1 times, and the inner one runs
down the entire current stack and does O(1) work for each
element it passes. Again we have a sum 1 + 2 + ... + (n - 1),
which is a total of O(n2).
public void feedTeam(
)
to be added to the DogTeam
class. Assume
that the SledDog
class has a method public
boolean feed( )
which has some side effect on the
calling SledDog
and returns true
if
the calling dog is "full" and false
if it is not.
(We do not ask the dogs directly whether they want more food.)
Your method should put the dogs of the colling team in a
queue and call the feed
method for each in turn,
adding each dog to the back of the queue if it is not yet
full. Your method should not terminate until all dogs are full
-- do not worry about the case where some dog remains hungry
indefinitely.
public void feedTeam ( ) {
QueueADT<SledDog> q = new ArrayDeque<SledDog>( );
LinearNode<SledDog> current = leadNode;
while (current != null) {
q.enqueue (current.getElement( ));
current = current.getNext( );}
while (!q.isempty( )) {
if (q.first( ).feed( ))
q.dequeue( );
else q.enqueue (q,dequeue( ));}}
The game works like this. Initially all cells are unowned. Red and Blue alternate moves, with Red moving first. A move consists of making a previously unowned cell belong to the player moving. The winner is the first player to make a path, of cells that they own, between the two sides of the grid that they own -- north and south for Red, east and west for Blue.
Your task is the implement this game using the Maze class
from either Project #2 or Project #4 (the differences are
unimportant.) You may asssume that the Maze class has been
altered to give a hexagonal grid, but you may not change the
Maze class otherwise. (But the Maze class has a method
public QCell getCell (int x, int y)
which returns
the cell in position (x, y).)
Remember that the two-parameter constructor for Maze makes every
cell open. You may find it useful to run this, then use for
loops and getCell(i, j).setIsOpen (false)
to make
every cell closed.
You also get a Position
class, where a
Position
object has int
fields
x
and
y
, methods getX
, setX
,
getY
, setY
, and a two-parameter
constructor.
You will get input from the players by two methods
getRedMove
and getBlueMove
, each of
which return a Position
object and have no
parameters. (I did not say this clearly enough on the test
paper, but my intention was that you should not write
these methods, but assume that they are given to you and treat
them with black boxes.) Don't worry about excpetions that might
be thrown by these two methods -- it is ok if they crash your
program.
You should write a class HexGame
so that a
HexGame object allows the two players to play one game of Hex.
Use one or more Maze objects to store the state of the game.
When it is Red's move, you should call getRedMove
as many times as needed to get a valid Position
(in
the grid and unowned) and then update your Maze objects to
reflect the Red move. It then becomes Blue's turn, and you get
and process a valid Blue move the same way. The game should
continue until one player has won -- it turns out the draws in
this game are impossible. Announce invalid moves and the end of
the game on via System.out.println
.
Here is a good way to get started. Before you have
necessarily decided how to store the state of the game, assume
that you have methods isValidRedMove
,
isValidBlueMove
, processRedMove
,
processBlueMove
, hasRedWon
, and
hasBlueWon
already written. You will get
substantial partial credit for writing a main method of
HexGame
that calls on these methods. Then you can
decide on the representation and write these six methods.
Finally, the easiest way to code hasRedWon
and
hasBlueWon
is inefficient, in that these methods
make O(n2) calls to the isPath
method of
Maze
, when it is possible to make only O(1) calls.
You can get up to 16 of the 20 points for this problem for this
inefficient implementation. You can get full credit for coding
the inefficient implementation, then describing clearly in
English how to fiex it to get only O(1) calls. (Of course you
can also get full credit for coding the efficient version
immediately.)
This version uses O(n2) calls to
public class HexGameDriver {
public static void main (String [ ] args) {
// takes width and height from command line
HexGame h = new HexGame (args[0], args[1]);
h.play( );}}
public class HexGame {
private int w, h;
private Maze redMaze, blueMaze;
public HexGame (int width, int height) {
w = width; h = height;
redMaze = new Maze (w, h);
blueMaze = new Maze (w, h);
for (int i = 0; i < w; i++)
for (int j = 0; j < h; j++) {
redMaze.getCell(i, j).setIsOpen (false);
blueMaze.getCell(i, j).setIsOpen (false);}}
public void play ( ) {
while (!hasRedWon( ) && !hasBlueWon( )) {
Position move = getRedMove( );
while (!isValidRedMove (move)) {
System.out.println ("Invalid move for Red, try again:");
move = getRedMove( );}
processRedMove (move);
if (hasRedWon( )) break;
move = getBlueMove( );
while (!isValidBlueMove (move)) {
System.out.println ("Invalid move for Blue, try again:");
move = getBlueMove( );}
processBlueMove (move);}
if (hasRedWon( ))
System.out.println ("Red has won!");
else System.out.println ("Blue has won!");}
public boolean isValidRedMove (Position move) {
int x = move.getX( );
int y = move.getY( );
if ((x < 0) || (x >= w) || (y < 0) || (y >= h))
return false;
if (redMaze.getCell(x, y).getIsOpen( )) return false;
if (blueMaze.getCell(x, y).getIsOpen( )) return false;
return true;
public boolean isValidBlueMove (Position move) {
return isValidRedMove (move);}
public void processRedMove (Position move) {
redMaze.getCell(x, y).setIsOpen (true);}
public void processBlueMove (Position move) {
blueMaze.getCell(x, y).setIsOpen (true);}
public boolean hasRedWon ( ) {
for (int i = 0; i < w; i++)
for (int j = 0; j < w; j++)
if (redMaze.isPath (i, 0, j, h - 1)) return true;
return false;}
public boolean hasBlueWon ( ) {
for (int i = 0; i < h; i++)
for (int j = 0; j < h; j++)
if (redMaze.isPath (0, i, w - 1, j)) return true;
return false;}}
isPath
to
test for a winner on an n by n grid. A trick to reduce this to O(1)
calls is as follows. Make redMaze
a Maze of height h + 2
instead of h, and make the top and bottom rows open. Similarly, make
blueMaze
a Maze of width w + 2 instead of w, and make the
left and right rows all open. Then we can ask just whether there is a
path from any particular cell on one end to any particular cell on the
other, and this will be true if and only if there is any path across
in the original HexGame.
Last modified 22 October 2011