Programming Exercises
For Chapter 12
THIS PAGE CONTAINS programming exercises based on
material from Chapter 12 of this on-line
Java textbook. Each exercise has a link to a discussion of one possible solution of that
exercise.
Exercise 12.1:
In Section 12.2, I mentioned that a
LinkedList can be used as a queue by using the
addLast() and removeFirst() methods to enqueue
and dequeue items. But, if we are going to work with queues,
it's better to have a Queue class. The data for the
queue could still be represented as a LinkedList,
but the LinkedList object would be hidden as a
private instance variable in the Queue object.
Use this idea to write a generic Queue class for
representing queues of Objects. Also write a generic
Stack class that uses either a LinkedList
or an ArrayList to store its data. (Stacks and queues
were introduced in Section 11.3.)
See the solution!
Exercise 12.2:
In mathematics, several operations are defined on sets.
The union of two sets A and B is a set
that contains all the elements that are in A together with all the
elements that are in B. The intersection
of A and B is the set that contains elements that are in both
A and B. The difference of A and B
is the set that contains all the elements of A except
for those elements that are also in B.
Suppose that A and B are variables of type set in
Java. The mathematical operations on A and B can be computed
using methods from the Set interface. In particular:
The set A.addAll(B) is the union of A and B;
A.retainAll(B) is the intersection of A and B;
and A.removeAll(B) is the difference of A and B.
(These operations change the contents of the set A, while the mathematical
operations create a new set without changing A, but that difference
is not relevant to this exercise.)
For this exercise, you should write a program that can be used as
a "set calculator" for simple operations on sets of non-negative integers.
(Negative integers are not allowed.) A set of such
integers will be represented as a list of integers, separated by commas and, optionally, spaces
and enclosed in square brackets. For example: [1,2,3]
or [17, 42, 9, 53,108]. The characters
+, *, and - will be used for the union,
intersection, and difference operations. The user of the program will
type in lines of input containing two sets, separated by an operator.
The program should perform the operation and print the resulting set.
Here are some examples:
Input Output
------------------------- -------------------
[1, 2, 3] + [3, 5, 7] [1, 2, 3, 5, 7]
[10,9,8,7] * [2,4,6,8] [8]
[ 5, 10, 15, 20 ] - [ 0, 10, 20 ] [5, 15]
To represent sets of non-negative integers, use TreeSets containing
objects belonging to the wrapper class Integer. Read the user's
input, create two TreeSets, and use the appropriate TreeSet
method to perform the requested operation on the two sets.
Your program should be able to read and process any number of lines of
input. If a line contains a syntax error, your program should
not crash. It should report the error and move on to the next
line of input. (Note: To print out a Set, A,
of Integers, you can just say System.out.println(A).
I've chosen the syntax for sets to be the same as that used by the
system for outputting a set.)
See the solution!
Exercise 12.3:
The fact that Java has a HashMap class means that no Java
programmer has to write an implementation of hash tables from
scratch -- unless, of course, you are a computer science student.
Write an implementation of hash tables from scratch. Define the
following methods: get(key), put(key,value),
remove(key), containsKey(key), and size(). Do not
use any of Java's generic data structures. Assume that both
keys and values are of type Object, just as for HashMaps.
Every Object has a hash code, so at least you don't have to
define your own hash functions. Also, you do not have to
worry about increasing the size of the table when it becomes too full.
You should also write a short program to test your solution.
See the solution!
Exercise 12.4:
A predicate is a boolean-valued function
with one parameter. Some languages use predicates in
generic programming. Java doesn't, but this exercise looks
at how predicates might work in Java.
In Java, we could use "predicate objects" by defining an
interface:
public interface Predicate {
public boolean test(Object obj);
}
The idea is that an object that implements this interface knows
how to "test" objects in some way. Define a class
Predicates that contains the following generic methods
for working with predicate objects:
public static void remove(Collection coll, Predicate pred)
// Remove every object, obj, from coll for which
// pred.test(obj) is true.
public static void retain(Collection coll, Predicate pred)
// Remove every object, obj, from coll for which
// pred.test(obj) is false. (That is, retain the
// objects for which the predicate is true.)
public static List collect(Collection coll, Predicate pred)
// Return a List that contains all the objects, obj,
// from the collection, coll, such that pred.test(obj)
// is true.
public static int find(ArrayList list, Predicate pred)
// Return the index of the first item in list
// for which the predicate is true, if any.
// If there is no such item, return -1.
(In C++, methods similar to these are included as a standard
part of the generic programming framework.)
See the solution!
Exercise 12.5:
One of the examples in Section 12.4 concerns the
problem of making an index for a book. A related problem is making
a concordance for a document. A
concordance lists every word that occurs in the document, and for each
word it gives the line number of every line in the document where
the word occurs. All the subroutines for creating an index
that were presented in Section 12.4 can also be used to create
a concordance. The only real difference is that the integers
in a concordance are line numbers rather than page numbers.
Write a program that can create a concordance. The document should
be read from an input file, and the concordance data should be written
to an output file. The names of the input file and output file should
be specified as command line arguments when the program is run.
You can use the indexing subroutines from Section 12.4, modified to
write the data to a file instead of to System.out. You
will also need to make the subroutines static. If you need
some help with using files and command-line arguments, you can find an
example in the
sample program WordCount.java,
which was also discussed in Section 12.4.
As you read the file, you want to take each word that you encounter
and add it to the concordance along with the current line number.
Your program will need to keep track of the line number. The end of each line
in the file is marked by the newline character, '\n'. Every time you encounter
this character, add one to the line number. One warning: The method
in.eof(), which is defined in the TextReader, skips over
end-of-lines. Since you don't want to skip end-of-line characters, you
should not use in.eof() -- at least, you should not use it
in the same way that it is used in the program WordCount.java.
The function in.peek() from the TextReader class returns
the nul character '\0' at the end of the file. Use this function
instead of in.eof() to test for end-of-file.
Because it is so common, don't include the word "the" in your
concordance. Also, do not include words that have length less than 3.
See the solution!
Exercise 12.6:
The sample program SimpleParser5.java
from Section 12.4 can handle expressions that contain
variables, numbers, operators, and parentheses. Extend this program so that
it can also handle the standard mathematical functions sin, cos,
tan, abs, sqrt, and log. For example, the program should
be able to evaluate an expression such as sin(3*x-7)+log(sqrt(y)),
assuming that the variables x and y have been given values.
In the original program, a symbol table holds a value for each variable that
has been defined. In your program, you should add another type of symbol to
the table to represent standard functions. You can use objects belonging
to the following class:
class StandardFunction {
// An object of this class represents
// one of the standard functions.
static final int SIN = 0, COS = 1, // Code numbers for each
TAN = 2, ABS = 3, // of the functions.
SQRT = 4, LOG = 5;
int functionCode; // Tells which function this is.
// The value is one of the above codes.
StandardFunction(int code) {
// Constructor creates the standard function specified
// by the given code, which should be one of the
// above code numbers.
functionCode = code;
}
double evaluate(double x) {
// Finds the value of this function for the
// specified parameter value, x.
switch(functionCode) {
case SIN:
return Math.sin(x);
case COS:
return Math.cos(x);
case TAN:
return Math.tan(x);
case ABS:
return Math.abs(x);
case SQRT:
return Math.sqrt(x);
default:
return Math.log(x);
}
}
} // end class StandardFunction
Add a symbol to the symbol table to represent each function.
The key is the name of the function and the value is an object
of type StandardFunction that represents the function.
For example:
symbolTable.put("sin", new StandardFunction(StandardFunction.SIN));
In your parser, when you encounter a word, you have to be able to
tell whether it's a variable or a standard function. Look up the
word in the symbol table. If the associated value is non-null
and is of type Double, then the word is a variable.
If it is of type StandardFunction, then
the word is a function. Remember that you can test the type
of an object using the instanceof operator.
For example: if (obj instanceof Double)
See the solution!