Q1: 20 points Q2: 15 points Q3: 15 points Q4: 15 points Q5: 15 points Q6: 20 points Q7: 20 points Total: 120 points
Question text is in black, solutions in blue.
Here is a code base from previous assignments, that is assumed to be available throughout the exam:
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<T> {
private T element;
private LinearNode<T> next;
// public get and set methods, zero-parameter and one-parameter constructors
}
public class DogTeam {
private LinearNode<SledDog> 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 ( ) {…}
}
Code 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 = newSledDog ("Buck", 108, "Mixed");
Some useful API information for L&C's code base:
public interface StackADT<T> {
public void push (T element);
public T pop ( );
public t peek ( );
public boolean isEmpty( );
public int size ( );
public String toString( );
public interface QueueADT<T> {
public void enqueue (T element);
public T dequeue ( );
public T first ( );
public boolean isEmpty( );
public int size ( );
public String toString ( );
public interface ListADT<T> extends Iterable<T> {
public T removeFirst( );
public T removeLast( );
public T remove (T element);
public T first( );
public T last( );
public boolean contains (T target);
public boolean isEmpty( );
public int size( );
public Iterator<T> iterator( );
public String toString( );}
public interface OrderedListADT<T> extends ListADT<T> {
public void add (T element);}
public interface UnorderedListADT<T> {
public void addToFront (T element);
public void addToRear (T element);
public void addAfter (T element, T target);}
public class SortingandSearching <T> {
public static <T extends Comparable<? super T >> boolean
linearSearch (T [ ] data, int min, int max, T target) {...}
public static <T extends Comparable<? super T >> boolean
binarySearch (T [ ] data, int min, int max, T target) {...}
public static <T extends Comparable<? super T >> void
selectionSort (T [ ] data) {...}
public static <T extends Comparable<? super T >> void
quickSort (T [ ] data, int min, int max) {...}
public static <T extends Comparable<? super T >> void
mergeSort (T [ ] data, int min, int max) {...}}
public interface BinaryTreeADT<T> {
public T getRoot ( );
public boolean isEmpty( );
public int size( );
public boolean contains (T target);
public T find (T target);
public String toString( );
public Iterator<T> iteratorPreOrder( );
public Iterator<T> iteratorPostOrder( );
public Iterator<T> iteratorInOrder( );
public Iterator<T> iteratorLevelOrder( );}
public interface SetADT<T> extends Iterable<T> {
public void add (T element);
public T removeRandom ( );
public T remove (T element);
public SetADT<T> union (SetADT<T> set);
public boolean contains (T target);
public boolean equals (SetADT<T> set);
public boolean isEmpty ( );
public int size ( );
public Iterator<T> iterator( );
public String toString ( );}
The pop operation removes and returns the most recently added element in the stack. The peek operation returns that element without removing it.
A postfix expression has its operators after their arguments, e.g. "22+". A prefix expression has the operator first, e.g., "+22".
A circular array stores its data in an interval of entries of an ordinary array, with variables to denote the beginning and end of the array. The interval may wrap around from the last element to the first, and both the beginning and end of the interval may move as elements are added and deleted from the structure being implemented (such as a queue or a deque).
A queue is a linear data structure where the only element that may be removed is the one that has been there longest -- thus we add to one end of the queue and remove from the other. A deque is a linear data structure where we may add or remove elements from either end.
Linear search of a data structure checks each element in turn to see whether it is the target element -- in the worst case it takes O(n) time. Binary search requires an ordered data structure -- it checks the middle element, compares it to the target, and recursively searches the first or second half of the remaining interval depending on the result. It takes O(log n) rounds and thus O(log n) time if each round can be done in O(1) time.
hasNext( )
in an iterator and next(
)
in an iterator
The hasNext
method
returns a boolean telling whether there is any element of the structure
yet to be returned. The next
method returns the next element of the structure.
Direct recursion is when a method calls itself. Indirect recursion is when a method calls another method which then calls the first method, possibly through other calls as well.
HashSet
and HashMap
Both are hash table implementations of (real) Java interfaces. A HashSet
supports
adding, removing, and testing membership of keys in a set. A HashMap
stores pairs,
each consisting of a key and a value. It can add and remove pairs, test membership for keys,
and alos operate on the collection of values.
Both are methods of dealing with collisions. In chaining, multiple keys with the that map to the same entry in the table are placed in a linked list, either an ordinary one with pointers or an implicit one using an overflow area of the array. In open addressing, a second or subsequent key mapped to a given entry in the table is placed in a different entry computed in some way that can be reproduced when searching for the key.
A child of a node is one of the nodes directly pointed to, below it. A descendant of a node is another node that can be reached by a path of zero or more "child" operations. (Technically, both the node itself and its children are counted among the descendants -- I did not take off points for missing this.)
list
method consisted of numbers (to be replaced
by letters according to the phone code) and stars (to be replaced by
any letter at all). Suppose we allow letters to appear in the input
string as well, to be replaced only by themselves. (So the call
list ("J*zz*")
would return only "jazzy"
.)
What modifications to your Project #7 code would you need?
At some point during the list
method, we try all the letters
that match a particular digit or star in the input string -- three or four
letters for a digit and 26 letters for a star. When we get a letter in the
input string, we must try just that letter in our search.
SetADT
object was useful for
simulating a bag to draw the numbers. A Bingo card
is
a five by five array of numbers, where the first column's numbers
are in the range 1-15, the second in 16-30, the third in 31-454, the
fourth in 46-60, and the last in 61-75. No number can appear twice
in a column, and the third square of the third column has no
number. Explain (in English) how you would use one or more Set
objects to create a random Bingo card.
We create five Set<Integer>
objects, one for each column.
We fill each with the 15 numbers in the appropriate range, then use the
removeRandom
five times for each column (four for the middle
column) to get the numbers to put onto the card.
DogTeam
objects. Indicate (in English) how
you would write a method that would take such an input and output an
array with the same DogTeam
objects, but sorted in
increasing order of average age. (For example, a team whose
dogs averaged an age of 2.4 would come before one whose dogs
averaged an age of 3.) (You may assume that each team in the array
has at least one dog.)
We need to be able to compute the average age for each team, by adding the
ages of the dogs in the team and then dividing by the number of dogs to get
a double
result. We could either add a double averageAge
field to each DogTeam
object, to be updated whenever a dog is added
or removed, or just write a compareTo
method for DogTeam
that compares average ages. Once we can compare DogTeam
objects by
average age, we can use any comparison-based sorting method to sort the teams and
place them in an array -- for example, we could put all the teams in a priority
queue and then fill the array in order by removing the teams from the queue one by
one. Note that we are now sorting teams, where Project #6 sorted dogs.
// This is an L&C priority queue, where "next" is the element
// in the queue whose priority is the smallest integer. It breaks
// ties by earlier creation, i.e., it is FIFO on elements with the
// same priority. Assume that the dogs from above have been created.
PriorityQueue<Dog> pq = new PriorityQueue<Dog> ( );
pq.addElement (cardie, cardie.getAge( ));
pq.addelement (king, king.getAge( ));
Dog z = pq.removeNext( );
pq.addElement (duncan, duncan.setAge( ));
z.setAge(1);
pq.addElement (z, z.getAge( ));
z = pq.removeNext( );
pq.addElement (biscuit, biscuit.getAge( ));
pq.addElement (buck, buck.getAge( ));
pq.removeNext( );
System.out.println (pq.removeNext( ).getName( ));
We add Cardie and King, remove Cardie and set z
to Cardie, add Duncan,
change the age of z
to 1, and put the new (Cardie, 1) object back in the
priority queue. We now have three dogs in the queue. We next remove Duncan (who was
added to the queue before Cardie), and then add Biscuit and Buck. We remove Cardie
(who was added before Biscuit), and then remove Biscuit, so that the final string printed
is "Biscuit".
// Again, assume that the dogs from above have been created.
// This code is not in the DogTeam class.
DogTeam t = new DogTeam( );
t.addToLead (king);
t.addToLead (balto);
t.addtoLead (king);
t.switchLastTwo( );
SledDog s = t.removeLead( );
s.setName ("Yukon King");
SledDog z = t.removeLead( );
System.out.println (z.getName( ));
We add King, Balto, and King to the team, and then switch the last two so that the first two dogs in the team are each copies of the same dog King. When we remove the first dog and change its name, the name of the second dog changes as well because it is the same dog (not just a different dog with the same attributes). So the string printed is "Yukon King".
Integer
objects, we successively add 3, 1, 4, 15, 9, 2,
6, and 5. Then we remove 4, 9, and 3 in that order. Draw the
resulting tree.
We form the tree which has 3 at the root, left child 1 with right child 2, and right child
4 with a chain of 15 (right child), 9 (left child), 6 (left child), and 5 below it. When
we remove the unary nodes 4 and 9, we replace them by their only children. This gives us root
3, left child 1 with right child, and right child 15 with left child 6 which has left child 5.
The method given by L&C to remove a node with two children replaces the node by its in-order
successor, the smallest node in its right subtree. In this case we would replace the 3 with the
5, getting a five-node tree where the root 5 has left child 1 with right child 2 and right child
15 with left child 6. I gave full credit if you replaced the 3 with the 2, which is its in-order
predecessor, but not for other stranger removal procedures.
public int summation (int n) {
int count = 0, sum = 0;
while (count < n) {
sum += count;
if (count < n) sum += (count + 1);
if (count < n - 1) count += 2;}
return sum;}
This code will compile and run, but never terminate if the input parameter n is odd.
Once count
becomes n - 1, the while
loop will continue to execute
over and over without changing the value of count
. Note that it is not at all
clear what "summation" the method is attempting to compute, so it is incorrect to say that it
produces unintended output.
// method to be added to DogTeam class
public SledDog[ ] toArray ( ) {
SledDog [ ] result = new SledDog[ ];
LinearNode<SledDog> cur = leadNode;
int index = 0;
while (cur != null) {
result [index] = cur.getElement( );
cur = cur.getNext( ); index++;}
return result;}
This method will not compile because the array result
is created
without giving a size.
// method to be added to DogTeam class
public void reverse ( ) {
SledDog lead = this.removeLead( );
DogTeam temp = new DogTeam( );
while (this.getSize( ) > 0)
temp.addToLead (this.removeLead( ));
temp.reverse( );
this.addToLead (lead);
while (temp.getSize( ) > 0)
this.addToLead (temp.removeLead( ));}
The problem with this recursive method is that it has no base case -- it
moves all but one element of the calling team to a temporary team and then
recurses on that, so that the recursion will continue until the method is
called from an empty team.
What happens then depends on the definition of Despite what many of you thought, this method would correctly reverse the team
if it had a correct base case such as removeLead
.
As defined in Discussion #4, removeLead
runs and returns null
when called from an empty team, so the call to reverse
on an empty
team will lead to another call to reverse
on an empty team, and thus
the recursion will continue until the method stack overflows. But if you instead
said that removeLead
from an empty team would throw an exception, I
gave full credit (since I gave you neither the code nor the spec for removeLead
on the handout).
if (size <= 1) return
.
public static String binary (int n) {
if (n == 0) return "0";
if (n == 1) return "1";
if (n % 2 == 0) return binary (n/2) + "0";
return binary (n/2) + "1";}
Each recursive call is with a parameter half the size of the calling method's parameter, and takes O(1) time excluding the call. There are thus log n calls before one of the O(1)-time base cases is reached, for a total time of O(log n).
// n is size of input stack s
public static void reverse (StackADT<Dog> s) {
if (size <= 1) return;
StackADT<Dog> temp = new ArrayStack<Dog> ( );
while (s.size( ) > 1)
temp.push (s.pop( ));
reverse (temp);
Dog last = s.pop( );
while (!temp.isEmpty( ))
s.push (temp.pop( ));
s.push (last);}
Each call is to a stack that is one element smaller than the calling stack, so the depth of the recursion is O(n) before the O(1)-time base case is reached. Each recursive call takes O(n) time to move the elements to and from the temporary stack, or rather O(k) where k is the size of the particular calling stack. The total time is thus proportional to n + (n-1) + (n-2) + ... + 2 + 1, which is O(n2).
// uses L&C code base; n is size of input queue
public static void reorder (QueueADT<Dog> q) {
int n = q.size( );
if (n >= 11) return;
Dog d = q.first( );
QueueADT<Dog> temp = new ArrayQueue<Dog> ( );
for (int i = 0; i < 5; i++)
if (!q.isEmpty( )) temp.enqueue (q.dequeue( ));
while (!temp.isEmpty( ))
q.enqueue (temp.dequeue( ));
while (d != q.first( ))
q.enqueue (q.dequeue( ));}
The running time is O(1) because if n is greater than 10, the method returns after doing
only O(1) work. The maximum time it ever takes, then, is the maximum time it takes for
the sizes up to 10, which is a constant, independent of n.
The first two loops take O(1) time as they move up to five elements each. The last loop
moves n - 5 elements if n is large enough, so the time would be O(n) if not for the "cutout"
statement for n greater than 10.
a) Recall the following code from Project #7 ("Some Games With Words"):
public class PTNode {
private String elem;
private PTNode [ ] child = new PTNode [26];
public PTNode (String w) {elem = w;}
public String getElem ( ) {return elem;}
public void setElem (String w) {elem = w;}
public PTNode getChild (char ch) {return child[ch - 'a'];}
public void setChild (char ch, String w)
{child[ch - 'a'] = new PTNode (w);}
// next method added for this problem
public void setChildNull (char ch) {child[ch - 'a'] = null;}
public class PrefixTree {
private PTNode root;
private int size; // should be the number of leaves
// getters and setters, other methods
Write a method public boolean removeString (String
target)
to be added to the PrefixTree
class.
If target
is the element of a leaf of the prefix tree,
your method should remove its node, remove any other leaves that
become
leaves as a result of its removal, and return true
. (But
don't ever remove the root node ""
.) If
target
is not a leaf or is not in the tree at all, the
method should return false
and do nothing else. You may
assume that if the tree has more than one node, then all the leaves
are at depth 5 as in the tree built in Project #7. Also, you may call
the
contains
method for PrefixTree
, which was
provided to you for Project #7.
public boolean removeString (String target) {
if (!contains (target) return false;
if (target.length( ) != 5) return false;
PTNode cur = root;
boolean done = false;
int depth = 0;
while (!done) {
int childCount = 0;
for (char ch = 'a'; ch <= 'z'; ch++)
if (cur.getChild (ch) != null) childCount++;
if (childCount == 1) {
cur.setChildNull (target.charAt (depth));
done = true;}
else {cur = cur.getChild (target.charAt (depth));
depth++;}}
return true;}
b) Implement a stripped-down version of the generic class
HashSet<T>
as follows. Assume that the class
T
has a satisfactory hashcode( )
method. A
HashSet<T>
object should have an instance field
int capacity
and an array of
ArrayList<T>
objects (as in L&C) of size
capacity
. Write a constructor public HashSet (int
c)
that will create an empty HashSet
object with
capacity c
. Write methods public boolean add
(Object o)
and public boolean contains (Object o)
to add an element and test membership for an element, respectively.
The method add
should return false
if the
object o
is already there, but not throw an exception.
(The parameter types of these two methods are Object
to
match java.util.HashSet
, but your methods may throw
exceptions (which you need not catch) if their inputs are not
T
objects.)
public class HashSet<T> {
int capacity;
ArrayList<T> [ ] table;
public HashSet (int c) {
capacity = c;
table = (ArrayList<T> [ ]) new Object [capacity];
for (int i = 0; i < capacity; i++)
table[i] = new ArrayList<T> ( );}
public boolean add (Object o) {
if (contains(o)) return false;
T t = (T) o; // cast will fail if parameter is not a T
int h = t.hashcode( ) % capacity;
table[h].add (t);
return true;}
public boolean contains (Object o) {
T t = (T) o; // case will fail if parameter is not a T
int h = t.hashcode( ) % capacity;
if (table[h].isEmpty( )) return false;
return table[h].contains(t);}}
Your task is to write an entire class Sudoku
, where a
Sudoku
object will allow a user to play the game. You
should store the board as a two-dimensional array of int
values, using 0 to represent a cell that has no number yet. Your
constructor should create a board with all 0 values. You should write
three methods:
public boolean move (int i, int j, int k)
, which
sets the value of cell (i, j) to be k,
public boolean check( )
, which returns whether the
board is filled successfully, and
public void undo( )
, which reverses the effect of
the last move that has not already been reversed by an
undo
command. If there are no moves that have not
already been reversed, the undo
command should do
nothing and not throw an exception.
For regular credit, write the class so that a move (i, j,
k)
command is only value if the cell (i, j)
is not yet filled,
and k
is in the correct range. The move
method
returns true
if the move is valid.
For up to five points extra credit, allow moves to replace an existing number rather than just fill in a blank cell. This makes the undo method more complicated.
You may use any of L&C's given data structures such as
ArrayStack
, ArrayQueue
,
ArrayList
, ArrayBinaryTree
, or
ArraySet
, assuming that they correctly implement the
corresponding ADT given on the handout.
There were a number of variations on this, many of them correct. You could check the
correctness of a row, column, or box by keeping a Set and rejecting if you ever found a number
that was already in the set. Some people had the
public class Move {
public int i, j, k, prev;
public Move (int newI, int newJ, int newK, int newPrev) {
i = newI; j = newJ; k = newK; prev = newPrev;}}
public class Sudoku {
int [ ] [ ] board = new int [9] [9];
Stack <Move> s = new Stack<Move>;
public Sudoku( ) {
for (int i = 0; i < 9; i++)
for (int j = 0; i < 9; j++)
board [i] [j] = 0;} // actually default makes this unneeded
public boolean move (int i, int j, int k) {
if (i < 0 || i > 8 || j < 0 || j > 8 || k < 1 || k > 9)
return false;
// use next line only if replacement not allowed
// if (board [i] [j] != 0) return false;
s.push (new Move (i, j, k, board[i][j]));
board [i][j] = k;
return true;}
public void undo ( ) {
if (s.isEmpty( )) return;
Move m = s.pop( );
// for when replacement is forbidden
// board [m.i] [m.j] = 0;
board [m.i] [m.j] = m.prev;}
public boolean check ( ) {
Set<Integer> s = new ArraySet<Integer>( );
Set<Integer> t = new ArraySet<Integer>( );
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
s.add (new Integer (board[i][j]));
t.add (new Integer (board[j][i]));}
if (s.size( ) != 9) return false;
if (t.size( ) != 9) return false;
s = new ArraySet<Integer>( );
t = new ArraySet<Integer>( );}
for (int k = 0; k < 9; k += 3)
for (int l = 0; l < 9; l += 3) {
for (int kk = 0; kk < 3; kk++)
for (int ll = 0; ll < 3; ll++)
s.add (new Integer (board [k+kk] [l+ll]));
if (s.size( ) != 9) return false;
s = new ArraySet<Integer>( );}
return true;}}
move
method check whether the
move created a conflict in a row, column, or box -- in that case the check
method
only had to ask whether the board was completely filled with nonzero numbers. Many people had
undo methods that would only undo one move, where you were supposed to be able to use repeated
undos to go back in time as far as you like.
Last modified 27 December 2011