Enumerations: the student database examples, part 3

Avoiding overspecialisation

Here is the interface Table we gave previously:
import java.io.*;

interface Table
{
 public boolean delete(String key);
 public void add(KeyObject obj);
 public KeyObject retrieve(String key);
 public void print(PrintStream out);
}

The print method seems a bit of an anomaly, too specific for a general type like Table, but it was introduced for convenience.

However, it is a good example of how a poor design decision may limit us later. If we consider extending our student database program, we might think of various operations which involve going through each of the student records in the database one by one. But in the interface Table In general, we need to avoid as much as possible being too specific in how we define anything we may wish to modify later. It is much easier to modify something that has been defined generally. If we had a general way of going through the items in a table, we could make use of it for printing the items out, but also for other things, such as our case of adding a new mark to all student records. In the implementations of Table we have given, however, there is no way of going through all the items in the table unless we do that inside the class implementing Table since it requires accessing the actual data structure of the table. Doing this, however, would break the careful distinction we have made between the code implementing an abstract data type and the code whia uses it.

The Enumeration interface

What we need is something that will give us each of the items from a database in turn, so that we can then do what we like with them. This is known as an Enumeration. An enumeration is similar to what we have already seen with string tokenizers. An object of type StringTokenizer is created with a string as its argument, and then each time a call of nextToken() is made on it, the next word in the original string is returned (the default is that the space character separates words, but an option for other characters to be considered as separators exists). The call hasMoreTokens can be used to see if the string tokenizer has returned all the words in the string (in which case it returns False in successive calls of nextToken or whether there are more still to return.

An object of type Enumeration has methods nextElement and hasMoreElements that can be called on it. The object is formed with reference to some collection of objects. Each time nextElement is called on it, an item from the collection is returned. No item from the collection is returned more than once, and if nextElement is called enough times, eventually all the items in the collection will get returned. If there are no more items that can be returned from calls of nextElement, a call of hasMoreElements on the enumeration object will return false, otherwise it will return true. Although it is not defined exactly what will happen if nextElement is called when there are no more items to return, that will usually cause an exception to be thrown. The type Enumeration can be defined by the Java interface:

interface Enumeration
{
 boolean hasMoreElements();
 Object nextElement();
}
In fact we do not need to define such an interface, since one is built in already as part of Java in the java.util package.

Using an Enumeration from a Table

We shall define a version of the idea of a table which can return an enumeration. Here is an interface for it:
import java.util.*;

interface ETable
{
 public boolean delete(String key);
 public void add(KeyObject obj);
 public KeyObject retrieve(String key);
 public Enumeration getEnumeration();
}                                                                              
We call this abstract type ETable to distinguish it from Table as we have already used that name. Note the import statement which allows us to use the type Enumeration from java.util. A call to the method getEnumeration returns an Enumeration object which can be used to gain access to each of the itmes stored in the table by repeatedly calling nextElement.

Now we can consider a version of our student database program that makes use of objects of type ETable:

import java.io.*;
import java.util.*;

class StudentDatabase8
{
// Database implemented with unordered partly-filled array
// Uses Enumerations

 public static void main(String[] args)
 throws IOException
 {
  ETable table = new ETable1();
  BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  BufferedReader file;
  Enumeration enum;
  Student student;
  String name;
  int mark;
  char ch;
  do {
      System.out.print(": ");
      StringTokenizer toks = new StringTokenizer(in.readLine());
      ch = toks.nextToken().charAt(0);
      switch(ch)   
         {
          case 'a' :
            table.add(new Student(in));
            break;
          case 'c' :
            enum = table.getEnumeration();
            while(enum.hasMoreElements())
               {
                student = (Student) enum.nextElement();
                System.out.print("Enter mark for "+student.getKey()+": ");
                student.addMark(Integer.parseInt(in.readLine()));
               }
            break;
          case 'd' :
            name = toks.nextToken();
            if(!table.delete(name))
               System.out.println("No student named "+name);
            break;
          case 'f' :
            file = new BufferedReader(new FileReader(toks.nextToken()));
            try {
                 while(true)
                    table.add(new Student(file));
                }
            catch(Exception e) {}
            file.close();
            break;     
          case 'm':
            name = toks.nextToken();
            mark = Integer.parseInt(toks.nextToken());
            student = (Student) table.retrieve(name);
            if(student==null)
               System.out.println("No student named "+name);
            else
               student.addMark(mark);
            break;
          case 'p':
            enum = table.getEnumeration();
            while(enum.hasMoreElements())
               System.out.println(enum.nextElement());
            break;
          case 'q':
            break;
          case 'r':
            name = toks.nextToken();
            student = (Student) table.retrieve(name);
            if(student==null)
               System.out.println("No student named "+name);
            else
               System.out.println(student);
            break;
          case 'w':
            enum = table.getEnumeration();
            name = toks.nextToken();
            PrintStream out = new PrintStream(new FileOutputStream(name));
            while(enum.hasMoreElements())
               out.println(enum.nextElement());
            out.close();
            break;
          default:
            System.out.println(" a - add, c - class mark, d - delete,");
            System.out.println(" f - file read, m - add mark, p - print,"); 
            System.out.println(" q - quit, r - retrieve, w - file write ");
         }
     }
  while(ch!='q');
 }
}
The Enumeration type is used in the code which gives us the p and w commands. As you can see in the code for the p command:
     enum = table.getEnumeration();
     while(enum.hasMoreElements())
        System.out.println(enum.nextElement());
we can create an Enumeration of the table and then loop round repeatedly accessing and printing the items from the table until hasMoreElements returns false. The call of println will automatically call the toString method of the object passed as its argument.

We have added the new command c to the database program to show further use of enumerations. This command goes through all the students in the database prompting the user for a new mark for each. Here is the code which gives it:

     enum = table.getEnumeration();
     while(enum.hasMoreElements())
        {
         student = (Student) enum.nextElement();
         System.out.print("Enter mark for "+student.getKey()+": ");
         student.addMark(Integer.parseInt(in.readLine()));
        }                                                               
As you can see, the same pattern of creating an Enumeration object then using it in a loop is employed. Since the return type of nextElement is Object it is necessary here to use typecasting to view the object returned as of type Student so the method addMark from the class Student can be called on the objects obtained from the enumeration. Note what is returned from calls to nextElement are references to the same objects that are in the database (and they are still references to those same objects when viewed after type casting), so calling addMark on them alters the objects stored in the database.

Constructing an Enumeration from a table implemented using an array

Now let us consider how we can construct an Enumeration object. The simplest implementation of Table was the one which used an array of fixed-size length, with a count stating how much was used to store data, given here. Using the same array-and-count data structure, here is an implementation of ETable:
import java.util.*;

class ETable1 implements ETable
{
// Table class with Enumeration, 
// data stored in unordered partly-filled arrays

 private KeyObject[] array;
 private int count;
 final static int MAX=100;

 public ETable1()
 {
  array = new KeyObject[MAX];
 }

 public boolean delete(String key)
 {
  int i;
  for(i=0; i<count&&!array[i].equalKey(key); i++) {}
  if(i<count)
     {
      array[i]=array[count-1];
      count--;
      return true;
     }
  else
     return false;
 }

 public void add(KeyObject obj)
 {
  int i;
  for(i=0; i<count&&!array[i].equalKey(obj); i++) {}
  if(i==count)
     array[count++] = obj;
 }

 public KeyObject retrieve(String key)
 {
  int i;
  for(i=0; i<count&&!array[i].equalKey(key); i++) {}
  if(i==count)
     return null;
  else
     return array[i];
 }

 public Enumeration getEnumeration()
 {
  return new MyEnumeration();
 }

 private class MyEnumeration implements Enumeration
 {
  int current;

  MyEnumeration()
  {
   current=0;
  }

  public boolean hasMoreElements()
  {
   return current<count;
  }

  public Object nextElement()
  {
   return array[current++];
  }
 }
}
An inner class, MyEnumeration, is used to define enumerations for objects of type ETable1. The inner class is not static like the nested classes we have seen previously, because it needs to make reference to the array and count fields of the ETable1 object it is connected to. An object of the type MyEnumeration given inside ETable1 is linked to an ETable1 object and can refer to its private fields. It also has its own field current. The field current gives the position in the array of the ETable1 object where the object to be returned by the next call of nextElement is found.

A call of getEnumeration on an object of type ETable1 returns an object of type MyEnumeration as defined inside ETable1 (another way of referring to this type is ETable1.MyEnumeration. At first this may seem odd, because how can the world outside the ETable1 class make use of an object defined by the class MyEnumeration which is private to ETable1. It can do so because MyEnumeration implements the public class Enumeration and the outside world sees it as object of type Enumeration.

In this example, if an Enumeration object is returned from an ETable1 object, and then the ETable1 object is altered by calling add or delete on it, the contents of the enumeration are changed. This would not be observed in the above example, because on each time when an enumeration is created, it is always gone right through before any other command on the database is made.

Constructing an Enumeration from a table implemented using an ordered tree

Now let us consider how we could alter the implementation of a table using an ordered binary tree in order to make it provide an enumeration of the table. As above, we will do it by introducing a non-static inner class, with the getEnumeration method simply returning the instance of this class with reference to the object it is called on. So the following method needs to be added:
 public Enumeration getEnumeration()
 {
  return new MyEnumeration();
 }
which is as above, and the following inner class:
 private class MyEnumeration implements Enumeration
 {
  ECell list;

  MyEnumeration()
  {
   list=flatten(tree,null);
  }

  public boolean hasMoreElements()
  {
   return list!=null;
  }

  public Object nextElement()
  {
   Object obj = list.first;
   list = list.next;
   return obj;
  }

  private ECell flatten(Cell t,ECell l)
  {
   if(t==null)
     return l;
   else
      {
       l = flatten(t.right,l);
       l = flatten(t.left,l);
       l = new ECell(t.item,l);
       return l;
      }
  }

  private class ECell
  {
   KeyObject first;
   ECell next;

   ECell(KeyObject f,ECell n)
   {
    first=f;
    next=n;
   }
  }
 }
You can see that this inner class itself has an inner class called ECell. So there is no limit on inner classes themselves containing inner classes, though two levels of inner classes is probably enough before it gets so complicated that a redesign is appropriate. The class ECell is used to make a linked list of KeyObjects, just as we made linked lists before. The only reason it is not declared as static is that you cannot have a static nested class inside a non-static inner class.

So a MyEnumeration object here has inside it a linked list of KeyObjects, and each time nextElement is called on it, it returns the head of the list and sets the list to its tail.

The method flatten which produces the linked list from the tree uses a similar idea to that we used to gain a more efficient quick sort of lists: passing in an accumulator list parameter and then joining the new items to its front. The tree is traversed right-to-left, so the items from the right branch of the tree are joined first to the accumulator before the items from the left branch. Joining the node-value in between makes it right-to-left inorder traversal, although since we made no guarantee that our enumeration would return the items in the table in any particular order, it does not really matter.

In this case, since the linked list which is used in the enumeration is constructed completely at the time the enumeration is constructed, the enumeration would not be affected by any later calls to add or delete items from the table. The calls of nextElement on it will always return the elememnts as they existed in the table at the time the enumeration was created.

An alternative way of dealing with the enumeration would be to use a stack of trees, in the way we showed previously for recursion elimination. Here is the code for doing it this way:

 private class MyEnumeration implements Enumeration
 {
  ECell stack;

  MyEnumeration()
  {
   if(tree==null)
     stack=null;
   else
     stack = new ECell(tree,null);
  }

  public boolean hasMoreElements()
  {
   return stack!=null;
  }

  public Object nextElement()
  {
   Cell t = stack.first;
   stack = stack.next;
   if(t.left!=null)
     stack = new ECell(t.left,stack);
   if(t.right!=null)
     stack = new ECell(t.right,stack);
   return t.item;
  }

  private class ECell
  {
   Cell first;
   ECell next;

   ECell(Cell f,ECell n)
   {
    first=f;
    next=n;
   }
  }
 }
Now ECell is defined so that it gives a linked list of tree objects. Initially the whole tree of the table is pushed onto the stack. Each call of nextElement pops the top element from the stack, returns its node value as the next element, and pushes the left and right branches (unless they are null) onto the stack.

This also has the advantage that the enumeration does not do any unnecessary work. It traverses the tree piecemeal as each call to nextElement is made rather than completely when the enumeration is created. If the enumeration is never used up completely to go through the whole tree, then it has not wasted any effort traversing those parts of the tree which were never asked for with a nextElement call. The idea of evaluating a structure (in this case the list of items from the tree) only as and when needed rather than in advance of its need is developed further and more formally in functional programming under the name lazy evaluation.

Constructing an Enumeration from a hash table

The complete code for an implementation of ETable using a hash table with linear probing is given below:
import java.util.*;

class ETable4 implements ETable
{
// Table class, data stored in hash table
// Handles collisions with linear probing
// Returns an Enumeration

 private KeyObject[] array;
 final static int SIZE=41;

 public ETable4()
 {
  array = new KeyObject[SIZE];
 }

 public boolean delete(String key)
 {
  int i = hash(key);
  while(array[i]!=null&&!array[i].equalKey(key))
     i=(i+1)%SIZE;
  if(array[i]==null)
     return false;
  else
     {
      array[i] = new Deleted();
      return true;
     }
 }

 public void add(KeyObject obj)
 {
  int i = hash(obj.getKey());
  while(array[i]!=null && !(array[i] instanceof Deleted))
     i=(i+1)%SIZE;
  array[i]=obj;
 }

 public KeyObject retrieve(String key)
 {
  int i = hash(key);
  while(array[i]!=null&&!array[i].equalKey(key))
     i=(i+1)%SIZE;
  return array[i];
 }

 public Enumeration getEnumeration()
 {
  return new MyEnumeration();
 }

 private static int hash(String key)
 {
  int val=0;
  key = key.toLowerCase();
  for(int i=0; i<key.length(); i++)
     if(Character.isLetter(key.charAt(i)))
        val=(val*32+key.charAt(i)-'a')%SIZE;
  return val;
 }

 private static class Deleted extends KeyObject
 {
  public String getKey()
  {
   return "";
  }
 }

 private class MyEnumeration implements Enumeration
 {
  int current;

  MyEnumeration()
  {
   current=-1;
   moveCurrent();
  }

  public boolean hasMoreElements()
  {
   return current<SIZE;
  }

  public Object nextElement()
  {
   Object obj = array[current];
   moveCurrent();
   return obj;
  }

  private void moveCurrent()
  {
   current++;
   while(current<SIZE&&
         (array[current]==null||array[current] instanceof Deleted))
      current++;
  }
 }
}
The same idea of a private inner class to create the enumeration is used. It is rather similar to the one used above for a table implemented using just a partially filled array and a count. In both, the MyEnumeration object has a field called current in it which gives the index of the next element in the table to be returned when the next nextElement call is made. However, in this case, the index needs to be increased to move past any unused array cells (including those that have become unused and to indicate that store a reference to an object of type Deleted).

The code for creating an enumeration from a hash table which uses separate chaining is slightly more complicated. Here is its MyEnumeration inner class:

 private class MyEnumeration implements Enumeration
 {
  Cell ptr;
  int current;

  MyEnumeration()
  {
   current=0;
   while(current<SIZE&&array[current]==null)
      current++;
   if(current<SIZE)
      ptr=array[current];
  }

  public boolean hasMoreElements()
  {
   return current<SIZE;
  }

  public Object nextElement()
  {
   Object obj=ptr.first;
   ptr=ptr.next;
   if(ptr==null)
      {
       current++;
       while(current<SIZE&&array[current]==null)
          current++;
       if(current<SIZE)
          ptr=array[current];
      }
   return obj;
  }
 }
It needs to have a field current which gives the location in the array where the next item in the enumeration is to be found, but also a field called ptr which gives the location of that item in the list of items coming from that array cell.
Matthew Huntbach

Last modified: 20 March 2003