CPSC 120 Lecture Notes, Wednesday, Feb 11, 1998

ASSIGNMENTS/ANNOUNCEMENTS

A GENERAL PRIORITY QUEUE

The priority queues that we have been studying so far have consisted of collections if integers (or strings). But obviously you might like to have a priority queue whose elements are floating point numbers, or characters, or perhaps some user-defined type.

The basic ideas of the priority queue specification, and also its implementation, really do not depend very much on the type of the elements. All that is required is that there is a way to compare two elements.

We would really like to avoid rewriting the priority queue class for every different kind of element.

We can use Java's interface facility to avoid such wasteful re-writing.

REVIEW OF INTERFACES

The concept of an interface takes the notion of an abstract class to its logical extreme. An interface is like an abstract class where NOTHING has an implementation.

Syntax:

interface < interface name > {
    < constant declarations >	// all are public and final
    < abstract method declarations >  // all are public and abstract
}
Then you can have a class that implements an interface:
class < class name > implements < interface name > {
    // rest of class definition
}
In fact, a class can implement several interfaces. The syntax is
class < class name > implements < interface1 >, < interface2 >, ... {
It can only extend (inherit from) one. It can extend one and implement others. For instance,
class < class name > extends < parent class > implements < interface name > {

Constants declared in an interface can be used as if they were declared locally in a class that implements the interface. An interface is a useful way to group together a collection of constants to be shared by several class hierarchies.

We can use the name of the interface as a type. Thus a method can have as a formal parameter an object whose type is a particular interface. The corresponding actual parameter can be any object that implements the interface.

INTERFACE FOR PRIORITY QUEUE ELEMENTS

For priority queues, all we need to know is that there is a way to compare two elements:
interface ComparisonKey {

// k1.compareTo(k2) returns 0 if k1 "equals" k2
//                          1 if k1 "is greater than" k2
//                         -1 if k1 "is less than" k2

    int compareTo(ComparisonKey k);

// convert the object to a printable string:

    String toString();
}
As is always the case with an interface, compareTo and toString are abstract methods: they have no body. Any class that implements this interface must provide actual code for these methods.

What's the usefulness of this interface? It will allow us to have a priority queue class that will work with any kinds of items, as long as they implement this interface.

USING THE COMPARISON KEY INTERFACE

The plan is:
  1. Change the specification of the PriorityQueue class to consist of a collection of ComparisonKey's, with the methods any class that implements the interface ComparisonKey can be used in place of ComparisonKey.
  2. Define a class called PQItem that implements ComparisonKey
  3. sortPQ, the sorting algorithm that uses a priority queue, can also be generalized to work on an array of ComparisonKey's.

Now suppose we use the array representation of a priority queue. We just have to change int to ComparisonKey in the right places:

class PriorityQueue {

    private ComparisonKey[] A = new ComparisonKey[100];	// int -> ComparisonKey
    private int next;

    PriorityQueue() {
	next = 0;
    }

    public void insert(ComparisonKey x) {		// int -> ComparisonKey
	A[next] = x;
	next++;
    }

    public ComparisonKey remove() {			// int -> ComparisonKey
	int max = A[0];	
	int maxLoc = 0;
	for (int cur = 1; cur < next; cur++) {
	    if (max.compareTo(A[cur]) == -1) {	     // use compareTo method!!!
		max = A[cur];
		maxLoc = cur;
	    }
	}
	A[maxLoc] = A[next-1];
	next--;
	return max;
    }
}

Now, here is a possible PQItem class. This is for integers. Note that there is overhead in doing this for integers, but it gives us generality.

class PQItem implements ComparisonKey {

    private int key;

    PQItem(int value) {	    // constructor
	key = value;
    }

    public int compareTo(ComparisonKey k) {
	int otherKey = ( (PQItem) k).key; // convert argument to a PQItem
					  // and extract its key
	if (key < otherKey) return -1;
	if (key > otherKey) return 1;
	return 0;	// keys are equal
    }
(If you don't like having to remember that -1 means "less than" etc., you can put some constants in the ComparisonKey interface, such as LT equals -1, EQ equals 0, GT equals 1, say, and then return LT, EQ or GT.)

If we want a PQItem class for strings, we would change the implementation of PQItem as follows:

What is particularly powerful, though, is that we can define the priority any way we want to for our own user-defined class. Suppose you want the items to be student records. You might want to prioritize the student records according to GPA, breaking ties according to whatever weird rule you like. Or perhaps you want to prioritize them by student name, breaking ties according to year of birth. All those decisions will be encapsulated inside the compareTo method. (Of course, you also will need to change the constructor.)

Finally, here is the sorting algorithm:

void sortPQ (ComparisonKey[] A) {	// type of A is only difference
    int n = A.length;
    PriorityQueue pq = new PriorityQueue();	
    for (int i = 0; i < n; i++) pq.insert(A[i]);  
    for (int i = n-1; i >= 0; i++) A[i] = pq.remove();
}
IMPORTANT TO NOTICE: If you do change the PQItem class, you will need to recompile the new PQItem.java file, to get a new PQItem.class file, before running a program that uses it.

IMPORTANCE OF MODULARITY AND INFORMATION HIDING

Why is it valuable to be able to do these kinds of things?

The public/private visibility modifiers of Java, and the discipline of not making the internal details be available outside are forms of information hiding.

Information hiding promotes modular programming -- you can switch implementations of one class without affecting (correctness of) other classes.

The key to abstraction is separating WHAT (the specification) from HOW (the implementation.