Question text is in black, solutions in blue.
Q1: 24 points Q2: 20 points Q3: 20 points Q4: 16 points Q5: 20 points Total: 100 points
The structure could be a structure of Objects, which could then
hold objects of any class.
Alternatively, the classes of the different objects could all
inherit from a common superclass, and the generic structure could
have its type variable set to that superclass.
Three lines of output: the first
public class Sport {
public void type( ) {
System.out.println("Indoor or Outdoor");}
}
public class Kickball extends Sport {
public void type( ) {
System.out.println("Outdoor"):}
public static void main (String [] args) {
Sport s = new Sport( );
Kickball k = new Kickball( );
s.type( );
k.type( );
s = k;
s.type( );}
}
"Indoor or Outdoor"
, the
second and third both "Outdoor"
. The code that runs
is determined by the object's class when it is created, not
by the type of variable it happens to be stored in.
Integer
objects and then
instantiates them.
We could also use autoboxing and just say "foo[i] = i".
Integer [ ] foo = new Integer[10];
for (int i = 0; i < 10; i++)
foo[i] = new Integer(i);
int
and an Integer
in Java?
An int
is a primitive value holding an integer in the
range from about -2 billion to about 2 billion.
Integer
is the wrapper class for int
s,
so that an Integer
is an object that has one field of
type int
. For example, Integer
values can
be put into generic collections while int
values, as seen
in Question 2d, cannot.
A checked exception is one that may not be thrown from a called method
to the calling method unless there is a matching throws
clause in the header of the called method. The compiler will insist
that this clause be there if there is any chance of a checked
exception being thrown and not caught inside the called method. An
unchecked exception may propagate all the way to the main method
without there being any throws clauses. The checked exceptions we
have encountered are those involving file I/O.
Group<T>
. What instance methods can be run on
an object of type T
in this code?
Only methods that are defined for the Object
class and
thus are available to any of its subclasses. The only ones of
these we have referred to much are equals
and
toString
, but there are others that you may look up
in the API.
There was a constant NUMBER_OF_STATES
which we could
change from 10 to 8, which would cause all the changes we need in
the actual operation of the game. The code that draws the Hangman
figure was given to you, and it would have to be changed to
accommodate the new diagram.
LinkedStack<T>
generic class that
implements an interface StackInterface<T>
.
Suppose that after completing the project, we want to replace the
linked stack with a homebuilt ArrayStack<T>
generic class that implements the same interface. What changes
should be made in the postfix evaluator?
Only one -- when we declare the If the StackInterface
object
that we use as a stack, we declare a new ArrayStack
instead of a new LinkedStack
. Every use of the stack was
via a method of StackInterface
, which should have exactly
the same behavior in the two implementations.
ArrayStack
were bounded in such a way as to
affect
the operation of the evaluator, we could add additional code to deal
with a full stack, so it's not wrong to say we would do this.
public interface InterfaceA {
public void methodA( );}
public interface InterfaceB extends InterfaceA {
public void methodB( );}
public class ClassA implements InterfaceA {
public void methodA( ) { }
public void methodB( ) { }
}
public class ClassB extends ClassA implements InterfaceB {
public ClassB( ) { }
... // other methods not shown
}
InterfaceB obj = new ClassA( );
The last assignment is illegal and produces a compiler error
because the class Some people thought that ClassA
does not implement the interface
InterfaceB
. (Assignment of a variable of type X to
a variable of type Y is permissible only if X is equal to Y or
is below it in the inheritance hierarchy.)
ClassA
was not allowed
to declare the method methodB
, because this is a
method of InterfaceB
which it does not implement.
But
implementing an interface only requires you to implement that
interface's methods -- there is no reason you can't implement
other methods that have the same names as those in another interface.
Stack<Object> stack = new Stack<Object>( );
stack.push("hello");
String s = stack.pop( );
As far as the compiler knows, the object popped from the stack is just
an Object
, so there will be a compile error
when this object is assigned to a String
variable.
The push of the string to the object stack is perfectly all
right, however, because the string is-an object.
public class Dog
{
.
.
.
}
public class DogWithLeash extends Dog
{
.
.
.
}
Dog[ ] calls = new Dog[3];
calls[0] = new Dog( );
calls[1] = (DogWithLeash) calls[0];
calls[2] = new DogWithLeash( );
System.out.println(calls[0] + " " + calls[1] + " " + calls[2]);
There will be an exception (a Some people thought that the new Others thought that the print statement was in error or would
lead to unintended output. We don't know what the intended
output is, and we don't know that ClassCastException
)
when the attempt is made at run time to cast the
Dog
object calls[0]
into a
DogWithLeash
. This object was created as a
Dog
and may not be so cast, but the compiler does
not detect this error.
DogWithLeash
object could not be assigned to the
Dog
variable calls[2]
, but it can
because
it is-a Dog
.
Dog
doesn't have a
toString
method that we haven't been shown.
Stack<int> a = new Stack<int>( );
Only object or interface types may replace the type variable in a
generic class. The primitive int
is not an object
or interface type, so this code will not compile.
Write a method that determines whether its parameter string
is a palindrome. Your method should create and use a
single, bounded stack of characters, although there are
certainly ways to solve this problem without a stack. Use the
Java Stack<T>
class which has the following
six methods: push(T e)
, pop( )
,
pop( )
, peek( )
,
empty( )
, size( )
,
and full( )
.
Remember that pop
here both removes and
returns the top element, unlike the pop
methods in DJW.
The size of your stack should be bounded to n/2, where n is the length of the input string.
public boolean isPalindrome (String s) {
The basic idea is simple -- place the first n/2 characters on the
stack, then test them against the last n/2 characters,
rejecting if there is any mismatch. The one real complication
is the if the string is of odd length, we have to ignore the
middle
character.
public boolean isPalindrome (String s) {
int n = s.length( ), i;
Stack<Character> stack = new Stack<Character>(n/2);
for (i=0; i < n/2; i++)
stack.push(s.charAt(i)); // boxed to Character
if (n%2 == i) i++;
while (!stack.empty( )) {
char ch = stack.pop( ); //autounboxing
if (ch != s.charAt(i)) return false;
i++;}
return true;}
true
if two linked lists of Integers contain the
same Integers in the same order (the actual values, not the
objects). Your method may be recursive if you like, though
this is not required. After your method has run, the two
lists should be unchanged. Note that the method you are to
write is not in the LLNode<T>
class.
public class LLNode<T> {
private LLNode<T> next;
private T contents;
public void setContents(T x) {contents = x;}
public T getContents( ) {return contents;}
public void setNext(LLNode<T> t) {next = t;}
public LLNode<T> getNext( ) {return next;}
}
public static boolean lTest
(LLNode<Integer> list1, LLNode<Integer> list2) {
What we need to do it to check the first element of the first list
against the first element of the second, then check the two second
elements, the two third elements, and so forth. If we ever find a
mismatch we reject, if the two lists are different lengths we reject,
and if we reach the end of both lists simultaneously without finding a
mismatch we accept. We need to take care to guard against null
pointer exceptions, especially in the case of lists of unequal
lengths.
There is also a simple recursive solution:
public static boolean lTest
(LLNode<Integer> list1, LLNode<Integer> list2) {
LLNode<Integer> curr1 = list1, curr2 = list2;
while ((curr1 != null) && (curr2 != null))
if (curr1.getContents( ).equals(curr2.getContents( ))) {
curr1 = curr1.getNext( );
curr2 = curr2.getNext( );}
else return false;
return ((curr1 == null) && (curr2 == null));}
public static boolean lTest
(LLNode<Integer> list1, LLNode<Integer> list2) {
if ((list1 == null) && (list2 == null)) return true;
if ((list1 == null) || (list2 == null)) return false;
if (!list1.getContents( ).equals(list2.getContents( )))
return false;
return lTest(list1.getNext( ), list2.getNext( ));}
public static int countit (int n) {
int count = 0;
for (int i = n; i > 0; i /= 2)
for (int j = 0; j < 10; j++)
count += 1;
return count;
}
The inner loop is only executed ten times in each time through the outer loop, and within it are O(1) operations, so it takes only O(1) time on each pass. There are O(log n) passes through the outer loop, because the number of times we halve n before reaching 0 is about log n. So the total time is O(log n).
for (c = 0; c < (n - 1); c++) {
for (d = 0; d < n - c - 1; d++) {
if (array[d] > array[d+1]) {
swap = array[d];
array[d] = array[d+1];
array[d+1] = swap;}}}
We go through the outer loop n-1 times, and this is O(n). We go through the inner loop n-1 times the first time, then n-2, then n-3, and so on until we do it only once the last time. On average we go through the inner loop about n/2 times each time we go through the outer loop. So the total number of times the if statement is executed is O(n2), and the if statement itself takes only O(1) time. The total time is thus O(n2).
public void clear (Stack<Integer> s) {
if (!s.empty( )) {
s.pop( );
clear(s);}}
The method calls itself recursively n times before reaching a base case. Each call to the method uses only O(1) time on its own, so the total time is O(n).
String
objects in alphabetical order, and you want
to insert a new String
into the list so that the
list remains in alphabetical order. Assume that the strings are
each of length O(1). Briefly describe your algorithm to do this,
and give its big-O worst-case time complexity.
The most natural algorithm is to traverse the list until you find a node with a string after the new string in alphabetical order, then insert the new string in front of that string. In the worst case, when there is no such string, it will take O(n) time to find that out and then O(1) time to insert the new string into the list. So the worst-case time is O(n).
public class LLNode<T> {
private LLNode<T> next;
private T contents;
public void setContents (T x) {contents = x;}
public T getContents( ) {return contents;}
public void setNext(LLNode<T> t) {next = t;}
public LLNode<T> getNext( ) {return next;}
public LLNode(T x, LLNode<T> s) {
contents = x; next = t;}
}
public class LList<T> {
private LLNode<T> head;
public LList( ) {head = null;}
.
.
.
}
LList<T>
class
public int count (T x)
that returns the number of times, if any, that the element
occurs in the calling list. By "x occurs in the list", we
mean that the contents of a node are equal to x according
to the .equals
method of the class
T
. Your method should leave the list
unchanged.
This one was pretty straightforward except for people who failed to
guard against null pointer exceptions, or who chose a loop
condition that left the last node of the list unchecked:
public int count(T x) {
int c = 0;
LLNode<T> curr = head;
while (curr != null) {
if (x.equals(curr.getContents( ))) c++;
curr = curr.getNext( );}
return c;}
LList<T>
class
public void reverse( )
that will replace the calling list with a new
LList
that contains the same elements as the
old one, but in reverse order.
There were a number of creative solutions to this one, many of them
correct. A recursive solution was presented in Lecture #11. Here
is one that forms a new list, by taking each element of the original
list and adding it to the tail of the new one, then replaces the
original list with the new one:
public void reverse( ) {
if ((head == null) && (head.getNext( ) == null)) return;
LLNode<T> newhead = head, newtail = head;
head = head.getNext( );
while (head != null) {
newtail.setNext(head);
head = head.getNext( );
newtail = newtail.getNext( );}
head = newhead;}
LList<T>
class
public void deleteLast( )
that will remove the last element in the calling list (the element furthest from the head). The remaining elements, if any, should be left in the same order they were in. If the calling list is empty the method should leave the list empty and not throw an exception. (Hint: There are several ways to do this, and we will accept any method that has the correct behavior. You might find it useful to create one or more additional lists, or call the method from Question 5b, or both.)
Here is a solution that uses the standard method of finding the next
to last node and changing its pointer to delete the last node. Note
that it guards against a null pointer exception, which many of you
did not.
And here is a shorter solution that follows the hint and uses 5b,
again guarding against a null pointer exception. It uses the fact
that removing the first element of a linked list is far simpler
than removing the last:
There was also a recursive solution presented in Lecture #11.
public void deleteLast( ) {
if (head == null) return;
if (head.getNext( ) == null) {
head = null;
return;}
LLNode<T> curr = head;
while (curr.getNext( ).getNext( ) != null)
curr = curr.getNext( );
curr.setNext(null);}
public void deleteLast( ) {
if (head == null) return;
reverse( );
head = head.getNext( );
reverse( );}
Last modified 13 October 2014