Second Summer 2002


Lecture Notes Twenty-Three: Vectors, Hashtables, Leftovers.
First here's a Selection Sort that we have developed in class.

class One {
    static void sort(int[] a) {
	for (int start = 0; start < a.length - 1; start++) {
	    for (int j = start; j < a.length; j++) {
		if (a[start] > a[j]) { // sorting in ascending order
		    int temp = a[start]; 
		    a[start] = a[j]; 
		    a[j] = temp; 
		}
	    } 
	} 
    } 
    public static void main(String[] args) {
	int[] numbers = new int[args.length]; 
	for (int i = 0; i < numbers.length; i++) {
	    numbers[i] = Integer.parseInt(args[i]); 
	} 
	System.out.println("Here's the initial array: "); 
	One.show(numbers); 
	System.out.println("Let me sort it in ascending order..."); 
	One.sort(numbers);
	System.out.println("... Done\nHere it is sorted: "); 
	One.show(numbers); 

    } 
    static void show(int[] a) {
	for (int i = 0; i < a.length; i++) {
	    System.out.print(a[i] + " "); 
	} 
	System.out.println(); 
    } 
} 
Here's how it runs:
frilled.cs.indiana.edu%javac One.java
frilled.cs.indiana.edu%java One 3 4 1 2 6 7
Here's the initial array: 
3 4 1 2 6 7 
Let me sort it in ascending order...
... Done
Here it is sorted: 
1 2 3 4 6 7 
frilled.cs.indiana.edu%
To clarify the idea of scope of a variable let's look at this example:
class Two {
    public static void main(String[] args) {
	int a = 1; 

	{ 
	    int b = 2;

	    System.out.println("a: " + a);
	    a = 4; // change will be seen by the other block
	    System.out.println("b: " + b);

	} 

	// System.out.println("b: " + b); 

	{ 
	    int b = 3;

	    System.out.println("a: " + a);
	    System.out.println("b: " + b);

	} 

    } 
}
I'll let you experiment with it.

Here's a second method of sorting: Bubble Sort.

class Three {
    static void sort(int[] a) {
	boolean done; 
	do { 
            done = true;
	    for (int i = 0; i < a.length - 1; i++) {
		if (a[i] > a[i + 1]) { // sort in ascending order 

		    int temp = a[i];
		    a[i] = a[i + 1];
		    a[i + 1] = temp;
		    done = false; 

		}
	    }
	} while (! done); 
    } 
    static void main(String[] args) {
	int[] numbers = new int[args.length]; 
	for (int i = 0; i < numbers.length; i++) {
	    numbers[i] = Integer.parseInt(args[i]); 
	} 
	System.out.println("Here's the initial array: "); 
	Three.show(numbers); 
	System.out.println("Let me sort it in ascending order..."); 
	Three.sort(numbers);
	System.out.println("... Done\nHere it is sorted: "); 
	Three.show(numbers); 

    } 
    static void show(int[] a) {
	for (int i = 0; i < a.length; i++) {
	    System.out.print(a[i] + " "); 
	} 
	System.out.println(); 
    } 
} 
It runs just like the other one, but the mechanism is different.

The outer do-while loop is normally added at the end.

For this reason I find the following method much more intuitive.

class Four { 

    static void sort(int[] a) {
	boolean done = true; 
	for (int i = 0; i < a.length - 1; i++) {
	    if (a[i] > a[i + 1]) { // sort in ascending order 
		int temp = a[i];
		a[i] = a[i + 1];
		a[i + 1] = temp;
		done = false;
	    }
	}
	if (! done) 
	    sort(a);
    } 

    static void main(String[] args) {
	int[] numbers = new int[args.length]; 
	for (int i = 0; i < numbers.length; i++) {
	    numbers[i] = Integer.parseInt(args[i]); 
	} 
	System.out.println("Here's the initial array: "); 
	One.show(numbers); 
	System.out.println("Let me sort it in ascending order..."); 
	One.sort(numbers);
	System.out.println("... Done\nHere it is sorted: "); 
	One.show(numbers); 

    } 
    static void show(int[] a) {
	for (int i = 0; i < a.length; i++) {
	    System.out.print(a[i] + " "); 
	} 
	System.out.println(); 
    } 
}  
Yes, recursion, in some cases, amounts to just a loop.

One could improve on the Bubble Sort technique above.

Here's an applet that illustrates the differences between three sorting methods:

The applet, I believe, is very exciting.

It shows how the methods work, and illustrates topics from chapter 15.

Let's move on.

Last time we talked about

These are more or less the same topic.

We can illustrate them both by this example:

class Horse {
   int numberOfLegs(); 
    void fun() {
	System.out.println("I am a Horse."); 
    } 
} 

class Unicorn extends Horse {
    void fun() {
	System.out.println("I am a Unicorn. Like a horse, but with a horn."); 
    }
}

class Experiment {
    public static void main(String[] args) {
	Horse      a = new Horse();
        System.out.println("A horse has " + a.numberOfLegs + " legs."); 
	Horse      b = new Unicorn(); 
	Unicorn    c = new Unicorn(); 
        System.out.println("A unicorn has " + c.numberOfLegs + " legs."); 
	// Unicorn d = new Horse(); // Not allowed. 
	System.out.print("First test:  "); 
	a.fun(); // a horse, of course 
	System.out.print("Second test: "); 
	c.fun(); // a unicorn, of course
	System.out.print("Third test:  "); 
	b.fun(); // what will this be? 

    } 
} 
That's what you need to know to understand applets.

It's also important to understand how Vectors work.

We will talk about Vectors in a little while.

Here's the example promised on developing things in stages:

frilled.cs.indiana.edu%cat Stages.java
class One {
    int add(int n, int m) {
        if (m == 0) return n;
        else return add(n+1, m-1);
    }
}

class Two extends One {
    int mul(int n, int m) {
        if (m == 1) return n; 
        else return add(n, mul(n, m-1)); 
    }
}

class Three extends Two {
    int pow(int n, int m) {
        if (m == 0) return 1; 
        else return mul(n, pow(n, m-1)); 
    }
}

class Calculator {
    public static void main(String[] args) {
        Three calc = new Three(); 
        int n = 3, m = 5; 
        System.out.println(n + " + " + m + " = " + calc.add(n, m)); 
        System.out.println(n + " * " + m + " = " + calc.mul(n, m)); 
        System.out.println(n + " ^ " + m + " = " + calc.pow(n, m)); 
    }
}

frilled.cs.indiana.edu%javac Stages.java
frilled.cs.indiana.edu%java Calculator
3 + 5 = 8
3 * 5 = 15
3 ^ 5 = 243
frilled.cs.indiana.edu%
Here's a different, somewhat similar, example on interfaces.

frilled.cs.indiana.edu%cat Example.java
interface Multiplier {
    int mul(int n, int m);
}

class Alpha implements Multiplier {
    public int mul(int n, int m) {
        return n * m; 
    }
}

class Beta implements Multiplier {
    public int mul(int n, int m) {
        int result = 0; 
        for (int i = 0; i < m; i++) 
            result += n; 
        return result; 
    }
}

class Gamma implements Multiplier {
    public int mul(int n, int m) {
        if (m == 1) return n; 
        else return n + mul(n, m-1); 
    }
}

class Example {
    public static void main(String[] args) {
        Alpha a = new Alpha(); 
        Beta b = new Beta(); 
        Gamma g = new Gamma(); 
        int n = 5, m = 3; 
        System.out.println(n + " * " + m + " = " + a.mul(n,m) + " (by Alpha)");
        System.out.println(n + " * " + m + " = " + b.mul(n,m) + " (by Beta )");
        System.out.println(n + " * " + m + " = " + g.mul(n,m) + " (by Gamma)");
    }
}


frilled.cs.indiana.edu%javac Example.java
frilled.cs.indiana.edu%java Example
5 * 3 = 15 (by Alpha)
5 * 3 = 15 (by Beta )
5 * 3 = 15 (by Gamma)
frilled.cs.indiana.edu%

An interface is an element of pure design. Now let's talk about Vectors.

What is a Vector? Here's a hint.
http://java.sun.com/products/jdk/1.2/docs/api/java/util/Vector.html

Can I make a summary? Go for it.

A Vector object responds to the following messages:
void addElement (Object obj)
Adds the object to the end of the vector, increasing its size by one.

boolean contains (Object obj)
Returns true if the object is among the values stored in the vector, or false otherwise.

Object elementAt (int index)
Returns the object at the specified index position.

int indexOf (Object obj)
Returns the index position of the first instance of the object in the vector (if the object is in the vector) or -1 otherwise.

int indexOf (Object obj, int index)
Same as above, but starts the search from the specified index.

void insertElementAt(Object obj, int index)
Inserts the object at the specified index position, after shifting down objects at and below the insertion point.

boolean isEmpty ()
Returns true if the vector contains no objects, or false otherwise.

void removeElement (Object obj)
Removes the first occurrence of the specified object, and then shifts up objects at and below the deletion point.

void removeElementAt(int index)
Deletes the object at the specified index position, and then shifts up objects at and below the deletion point.

void setElementAt (Object obj, int index)
Replaces the existing object at the specified index position with the specified object.

int size ()
Returns the number of objects currently stored in the vector.

That's only part of what a Vector does best!

To create a new Vector ... one should invoke a no-arg constructor.

Something like this:
Vector a = new Vector();
This creates a new (and empty) Vector object.

Can you define a class Vector? Here's a reasonable start:
class Vector {

  Object[] localStorage = new Object[0];

  int size() {
    return localStorage.length; 
  }

  void addElement(Object obj) {

    int currentLength = this.size(); 

    Object[] aux = new Object[currentLength + 1]; 
    aux[currentLength] = obj;

    for (int i = 0; i < currentLength; i++) { 
      aux[i] = localStorage[i]; // transfer elements 
    } 

    localStorage = aux; 
  } 

  public String toString() {

    String returnString = "Vector of size " + size() + ": ("; 

    for (int i = 0; i < localStorage.length; i++) 
      returnString += " " + localStorage[i];  // delay printing... 

    return returnString + ")";  
  } 

  public static void main(String[] args) {

    Vector v = new Vector(); 
    System.out.println(v); // convenient printing 

    v.addElement(new Integer(2)); 
    System.out.println(v); 

    v.addElement(new Integer(4)); 
    System.out.println(v);

    v.addElement(new Integer(6)); 
    System.out.println(v); 
  } 
}

What would this be? An operational model for java.util.Vector

"What I cannot create, I cannot understand." So we put one together, just for the fun of it.

Very good. Can I see this running? No.

What do you mean? Of course you can!
tucotuco.cs.indiana.edu% javac Vector.java
tucotuco.cs.indiana.edu% java Vector
Vector of size 0: ()
Vector of size 1: ( 2)
Vector of size 2: ( 2 4)
Vector of size 3: ( 2 4 6)
tucotuco.cs.indiana.edu%

Now let's look at the code line by line. It will be my pleasure.

class Vector {
  Object[] localStorage = new Object[0];
I see you use an array of Objects inside an instance of my Vector class.

OK, I should have made it private, but now it's too late.


This is the internal (local) storage that is available to every instance of type Vector, ... to every object that is instantiated with the new operator out of class Vector.


I make a distinction between an array of size 0 and no array whatsoever (that is, a null pointer). And so do I.

I start with an array of size 0 because I want my localStorage to always have a useful length to return (a null pointer returns an exception when asked about it's length.) That was exactly my thinking.

So I start with an array of size 0 for uniformity. And implementing size is a breeze.
  int size() {
    return localStorage.length; 
  }

Now let's tell the story of addElement.
OK, here we go:
  void addElement(Object obj) {

This method will reveal our style of implementation. I will not worry about efficiency, I will rely heavily on Java's automatic memory management facilities.

It's good to optimize one's programs. Yes, but as Donald Knuth remarked once, "premature optimization is the root of all evil".

Once my program works fine I can try to optimize it. But first, I need to make it work correctly.

Correctness concerns us most at this stage. Yes. Let's start. We first get the size of the vector (our local storage is always at capacity.)
    int currentLength = this.size(); 

Would've worked just as well if we had talked to localStorage directly, right? Yes, but this way we are expressing ourselves in Vector terms.

Then we get ready to store a new element, ... and allocate space that's bigger with one than our current size.
    Object[] aux = new Object[currentLength + 1]; 

Now place the object that needs to be added in the last position. Indeed, that's what we said we'd be doing.
    aux[currentLength] = obj;

Transfer the objects from the local storage into the new array one by one.
    for (int i = 0; i < currentLength; i++) { 
      aux[i] = localStorage[i]; 
    } 
Then call this new array our localStorage.
    localStorage = aux; 

What will happen to the array that the localStorage variable's previously pointing to? Gone with the wind.

That's quite correct. And since no variable references it any longer, ... it will be collected by Java's garbage collector.

  } 
Curly brace. End of story.

That's how we add a new element to a Vector. Removing an element should be similar.

The rest of my program only serves testing purposes. Yes, but it's still instructive!

If I ever place a Vector in a String context ... it will be this method that will kick in.
  public String toString() {

Let's see how it works. I start by printing the type (vector) and the size.
    String returnString = "Vector of size " + size() + ": (";

You're not really printing, are you? No, just putting together a String representation that will eventually be printed (but not here).

The elements if any will be listed one by one
    for (int i = 0; i < localStorage.length; i++) 
      returnString += " " + localStorage[i]; 
... and the list will appear in between parens.
    return returnString + ")";  
  } 

The main method creates a vector, and adds three elements to it.

We could have used Products, you know?

Yes, but this points out what you need to do if you want to store numbers. You have to use wrapper classes.

Which are number holders. ... and after each addition the Vector is printed.
  public static void main(String[] args) {

    Vector v = new Vector(); 
    System.out.println(v);

    v.addElement(new Integer(2)); 
    System.out.println(v); 

    v.addElement(new Integer(4)); 
    System.out.println(v);

    v.addElement(new Integer(6)); 
    System.out.println(v); 
  } 
}

Well, that was easy! Easy and clear!

What's a Hashtable? Let me look it up!
http://java.sun.com/products/jdk/1.2/docs/api/java/util/Hashtable.html

A Hashtable is a generalized kind of Vector. A Vector is a generalized kind of array.

The Vector is more general than an array because it grows and shrinks dynamically, and the elements don't have to be of the same type. Both arrays and Vectors map indices (numbers) to elements contained.

They're sequences. A Hashtable is more general in that it stores associations between keys and values.

The keys and the values could be any objects. For example Strings.

If you wanted to store associations of this kind:
username --> password
using Vectors or arrays
You'd have to use parallel Vectors or arrays!

Hashtables are built just for that! They're sometimes called associative arrays.

Once the association is built, ... we want to use it.

So, given a username, we need to... ... look up the password that is associated with it.

That's what Hashtables do best! Here's an example that uses a Hashtable object to interactively manage a set of Strings.
import java.util.*;

class Set {
    public static void main(String[] args) {

	Hashtable h = new Hashtable();

	ConsoleReader b = new ConsoleReader(System.in); 
	System.out.println("Ready for input."); 

	while (true) {

	    StringTokenizer s = new StringTokenizer(b.readLine());
	    String command = "", argument = "";

	    if (s.hasMoreTokens()) { command  = s.nextToken(); }

	    if (s.hasMoreTokens()) { argument = s.nextToken(); }

	    if (       command.equals("add")) {
		h.put(argument, argument);
		System.out.println("OK");

	    } else if (command.equals("delete")) {
		h.remove(argument);
		System.out.println("OK");

	    } else if (command.equals("list")) {
		Enumeration e = h.keys();
		while (e.hasMoreElements()) {
		    System.out.println(" :" + e.nextElement());
		}

	    } else if (command.equals("contains")) {
		System.out.println(h.containsKey(argument));

	    } else if (command.equals("quit")) {
		System.exit(0);

	    } else {
		System.out.println("Unknown command: " + 
				   command + " " + argument);
	    }
	} 
    }
}

This is an interactive program. Yes, you need to type commands into it.

Can I see it running? Here's the program (in blue) talking to a user.
frilled.cs.indiana.edu%java Set
Ready for input.
add one
OK
add two
OK
list
 :one
 :two
add three
OK
list
 :one
 :two
 :three
add three
OK
list
 :one
 :two
 :three
delete three
OK
list
 :one
 :two
contains three
false
contains two
true
quit
frilled.cs.indiana.edu%

Arrays and vectors can store linear sequences. Hashtables are functions of just one variable.

It often happens that you want to store collections that have a two-dimensional layout. Such an arrangement, consists of rows and columns of values, and is called (not surprisingly) a two-dimensional array.

... or matrix (sometimes). When constructing a two-dimensional array, you specify how many rows and columns you need.

If you want 10 rows and 8 columns:
int[][] matrix = new int[10][8];
A 10 x 8 matrix of ints.

To access a particular element in the array
matrix[3][4] = 5;
... specify two subscripts, in separate brackets.

In Java, two-dimensional arrays are stored as arrays of arrays. And three dimensional arrays are stored as arrays of two-dimensional arrays.

That means arrays of arrays of arrays. Yes, arrays of (arrays of arrays).

And n-dimensional arrays will be stored as arrays of (n-1)-dimensional arrays. What a recursive definition.

We won't use more than two-dimensions. Let's take a closer look at them though: because a two-dimensional array is really an array of the row arrays,

... the number of rows is: And the number or columns?
int nrows = matrix.length;

Because a two-dimensional array is really an array of the row arrays, ... the number of elements in each row can vary.

But it doesn't have to.
If it doesn't, the number of columns is the same as the length of the first row:
int ncols = matrix[0].length;

Can I see an example of a two-dimensional array that has rows of various lengths? Yes, you can quickly create one with an array initializer:
int[][] b = { {1}, 
              {2, 3}, 
              {4, 5, 6},
              {7, 8, 9, 10} 
            }; 

Can you do the same thing without it? Sure, I thought you'd ask:
int[][] b = new int[4][];
int count = 1; 
for (int i = 0; i < 4; i++) {
  b[i] = new int[i + 1]; 
  for (int j = 0; j <= i; j++) {
    b[i][j] = count; 
    count += 1; 
  } 
} 

I see you have to work harder. Yes, first you allocate space to hold four rows.

You indicate that you will manually set each row by leaving the second array index empty. Then you need to allocate (and fill) each row separately.

You can access each array element as b[i][j], ... but you must be careful that j is less than b[i].length as illustrated below:
class Testing {
    public static void main(String[] args) {

	int[][] b = new int[4][];
	int count = 1;
	for (int i = 0; i < 4; i++) {
	    b[i] = new int[i + 1];
	    for (int j = 0; j <= i; j++) {
		b[i][j] = count;
		count += 1;
	    }
	}

	for (int i = 0; i < b.length; i++) {
	    for (int j = 0; j < b[i].length; j++) {
		System.out.print(b[i][j] + " "); 
	    } 
	    System.out.println(); 
	} 

    } 
} 
Naturally, such "ragged" arrays are not very common. Except when you need them.

Then they become very natural. And they're no longer "ragged".

Exactly, because they fit that problem ... perfectly.

Last updated: Jul 24, 2002 by Adrian German for A201