First Summer 2004


Lecture Notes Twenty: Parallel arrays. Arrays of object data. Vectors.
Let's now imagine a different situation than the one with which we introduced Java arrays. Different but similar.

Assume the user has three pieces of information to enter each time:
  • name of product,
  • price in dollars, and a
  • performance score.

The best product is the one that has the highest ratio between performance and price. Yes, simply picking the lowest price may not be the best bargain.

So now we want to analyze our data set from a different, more enhanced perspective. Looks like we are going to build three arrays.

Indeed, that will be our first approach. Easy to write after all those little programs.

We will then propose a better alternative. Let's write the first one first.
import java.util.StringTokenizer; 

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

	System.out.println("Hello, and welcome to the evaluator. "); 

	ConsoleReader console = new ConsoleReader(System.in); 

	int dataSize = 0; 

	while (true) {
	    System.out.print("[" + dataSize + "]% "); 

	    String line = console.readLine(); 

	    if (line == null) break; 

            StringTokenizer tokenizer = new StringTokenizer(line); 

	    String product = tokenizer.nextToken(); 
	    double score = Double.parseDouble(tokenizer.nextToken()); 
	    double price = Double.parseDouble(tokenizer.nextToken()); 

	    dataSize += 1; 

	} 

        System.out.println("\n** Data has been entered now."); 

	System.out.println("** We need to process it."); 

	System.out.println("** We then need to print the results."); 

	System.out.println("\nThank you for using our program!"); 
    } 
} 

Well, where are the arrays? This is just the framework, the protocol.

Indeed, the program doesn't store anything. Well, then let me
  • declare,
  • initialize, and
  • build
these three arrays:
import java.util.StringTokenizer; 

class One {
    public static void main(String[] args) {
        System.out.println("Hello, and welcome to the evaluator. "); 
        ConsoleReader console = new ConsoleReader(System.in); 
        int dataSize = 0; 
	final int DATA_LENGTH = 1000; 
	String[] names = new String[DATA_LENGTH];
	double[] scores = new double[DATA_LENGTH];
	double[] prices = new double[DATA_LENGTH]; 
        while (true) {
            System.out.print("[" + dataSize + "]% "); 
            String line = console.readLine(); 
            if (line == null) break; 
            StringTokenizer tokenizer = new StringTokenizer(line); 
            String product = tokenizer.nextToken(); 
            double score = Double.parseDouble(tokenizer.nextToken()); 
            double price = Double.parseDouble(tokenizer.nextToken()); 
	    names[dataSize] = product;
	    scores[dataSize] = score;
	    prices[dataSize] = price; 
            dataSize += 1; 
        } 
        System.out.println("\n** Data has been entered now."); 

        System.out.println("** We need to process it."); 

        System.out.println("** We then need to print the results."); 

	for (int i = 0; i < dataSize; i++) 
	    System.out.println(names[i] + " " + 
			       scores[i] + " " + prices[i]);

        System.out.println("\nThank you for using our program!"); 
    } 
} 

I see you have defined three arrays, and that you're are managing them now in your code. Yes, I collect the data and print it at the end.

What kind of processing are you going to do? Find the best bargain, the product with the highest ratio.

Isn't it a pain to have to maintain three arrays at the same time? I'm not sure yet. Let me finish the program first.

Sure, this will be good practice. I'll also add a "quit" keyword to what the program understands,

... which would make the conversation a bit cleaner, indeed. Here's the updated version:
import java.util.StringTokenizer; 

class One {
    public static void main(String[] args) {
        System.out.println(
          "Hello, and welcome to the evaluator. Type quit to quit."); 
        ConsoleReader console = new ConsoleReader(System.in); 
        int dataSize = 0; 
	final int DATA_LENGTH = 1000; 
	String[] names = new String[DATA_LENGTH];
	double[] scores = new double[DATA_LENGTH];
	double[] prices = new double[DATA_LENGTH]; 
        while (true) {
            System.out.print("[" + dataSize + "]% "); 
            String line = console.readLine(); 
            if (line == null || line.equals("quit")) break; 
            StringTokenizer tokenizer = new StringTokenizer(line); 
            String product = tokenizer.nextToken(); 
            double score = Double.parseDouble(tokenizer.nextToken()); 
            double price = Double.parseDouble(tokenizer.nextToken()); 
	    names[dataSize] = product;
	    scores[dataSize] = score;
	    prices[dataSize] = price; 
            dataSize += 1; 
        } 
        System.out.println("\n** Data has been entered now."); 
        System.out.println("** We need to process it."); 
	double highest = scores[0] / prices[0]; 
	for (int i = 0; i < dataSize; i++) {
	    if (highest < scores[i] / prices[i]) 
                highest = scores[i] / prices[i]; 
	} 
        System.out.println("** We then need to print the results."); 
	for (int i = 0; i < dataSize; i++) {
	    System.out.print  (names[i] + " " +  // print not println
			       scores[i] + " " + prices[i]);
	    if (highest == scores[i] / prices[i]) 
		System.out.println(" ** "); 
	    else System.out.println(); 
	}
        System.out.println("\nThank you for using our program!"); 
    } 
} 

Can you show me the program live? I sure can:
frilled.cs.indiana.edu%java One
Hello, and welcome to the evaluator. Type quit to quit.
[0]% one 1 2
[1]% two 4 9
[2]% six 2 1
[3]% ten 3 3
[4]% quit

** Data has been entered now.
** We need to process it.
** We then need to print the results.
one 1.0 2.0
two 4.0 9.0
six 2.0 1.0 ** 
ten 3.0 3.0

Thank you for using our program!
frilled.cs.indiana.edu%

Looks like a fractions program to me... Ratios, they're ratios.

Can you also print these ratios? Yes, but I will let you do that, it's quite easy.

Well, how do you feel about your program? Actually I feel quite proud of being able to manipulate three arrays at the same time.

Tired, perhaps, you should feel. Proud. I am a real programmer, am I not?

If you say so. How do you call your arrays? They are called parallel arrays.

The ith slice
  • names[i]
  • prices[i]
  • scores[i]
... contains data that needs to be processed together.

Parallel arrays become a headache in larger programs, with thicker slices. Are you sure?

The programmer must ensure that the arrays always have the same length and that each slice is filled with values that actually belong together. But it can get worse that that. Now that I think of it, any method that operates on a slice must get all arrays as parameters, which is tedious even by my standards.

The remedy is simple. Yes:
  1. Look at the slice, and
  2. Find the concept that it represents.

3. Then make the concept into a class: That exactly is it!
class Product {
  String name; 
  double score; 
  double price; 
}

Then use an array of objects. Let me do that:
Product[] data = new Product[DATA_LENGTH]

The program now has one array (of objects). I heard vectors are arrays of objects, which can grow and shrink as necessary.

We'll get to that in a second. Can you now eliminate the parallel arrays in our application? I sure can, and here it is:
import java.util.StringTokenizer; 

class Product {
    String name;
    double score; 
    double price;
    Product(String n, double s, double p) {
	name = n; score = s; price = p; 
    } 
} 

class Two {
    public static void main(String[] args) {
        System.out.println(
          "Hello, and welcome to the evaluator. Type quit to quit."); 
        ConsoleReader console = new ConsoleReader(System.in); 
        int dataSize = 0; 
	final int DATA_LENGTH = 1000; 
	Product[] data = new Product[1000]; 
        while (true) {
	    Product p = Two.readProduct(console, dataSize); 
            if (p == null) break; 
            data[dataSize] = p; 
            dataSize += 1; 
        } 
        System.out.println("\n** Data has been entered now."); 
        System.out.println("** We need to process it."); 
	double highest = data[0].score / data[0].price; 
	for (int i = 0; i < dataSize; i++) {
	    if (highest < data[i].score / data[i].price) 
                highest = data[i].score / data[i].price; 
	} 
        System.out.println("** We then need to print the results."); 
	for (int i = 0; i < dataSize; i++) {
	    Two.printProduct(data[i], highest); 
	}
        System.out.println("\nThank you for using our program!"); 
    } 

    public static Product readProduct(ConsoleReader console, int rank) {
	System.out.print("[" + rank + "]% ");
	String line = console.readLine(); 
        if (line == null || line.equals("quit")) return null; 
        StringTokenizer tokenizer = new StringTokenizer(line); 
	String name = tokenizer.nextToken();
	double score = Double.parseDouble(tokenizer.nextToken());
	double price = Double.parseDouble(tokenizer.nextToken());
	return new Product(name, score, price); 
    } 

    public static void printProduct(Product p, double highest) {
	System.out.print(p.name + " " + p.score + " " + p.price);
	if (highest == p.score / p.price)
	    System.out.println(" ** ");
	else System.out.println();
    } 
} 



The program now has a single array of Product objects, and we use static methods to split the program in several modules. This shows that the process of eliminating arrays was successful.

1. The set of parallel arrays is replaced by a single array. 2. Each element in the resulting array corresponds to a slice in the set of parallel arrays.

3. Once you have this single concept available, it suddenly becomes much easier to give the program a better structure. 4. The program easily factors out methods for reading and printing objects.

Indeed. To really see the advantage of using objects instead of parallel arrays, consider the readProduct method in the program above.

How would you implement that method if you couldn't rely on using a Product object? You would have to return three values: the name, price, and score of the next product.

And how can you return three values at the same time? I don't know, can you?

Nope. In Java you can't return more than one value in a method. But you can put three values in an object and return the object.

Objects are containers too. Of course, and that's what they do best!

Now that we've seen arrays of objects, let's look into ... arrays as object data.

Here's a simpler example first: Looks simple to me.

Run this a few times. Do you see how it works?
class Game {
    public static void main(String[] args) {
        System.out.println("Welcome to the Game."); 

        Hand yours     = new Hand(); 
        System.out.print("Here's your hand:      "); 

        yours.show(); 

        Hand computer  = new Hand(); 

        System.out.print("Here's the computer's: "); 

        computer.show(); 

        if (yours.value() > computer.value()) {

            System.out.println("You win this time by " + 
                               (yours.value() - computer.value()) + 
                               " point(s)");

        } else if (yours.value() < computer.value()) {

            System.out.println("Computer wins this time by " + 
                               (computer.value() - yours.value()) + 
                               " point(s).");

        } else {

            System.out.println("This game is tied: " + 
                               yours.value() + " - " + 
                               computer.value()); 

        }

    }
}

class Hand {

    public static final int SIZE = 5; 

    int[] cards; 

    Hand() {
        cards = new int[Hand.SIZE]; 
        for (int i = 0; i < cards.length; i++) {
            cards[i] = (int)(Math.random() * 52) + 1; 
        }
    }

    void show() {
        for (int i = 0; i < cards.length; i++) {
            System.out.print(cards[i] + " "); 
        }
        System.out.println(" = " + this.value()); 
    }

    int value() { 
        int val = 0; 
        for (int i = 0; i < cards.length; i++) {
            val += cards[i]; 
        }
        return val; 
    }

}
A Hand is a sequence of 5 numbers. OK, let's look at something a bit more involved.

What's a polygon? Something with a lot of knees if you know Greek.

A polygon is a closed sequence of lines. To describe a polygon, you need to store the sequence of its corner points.

An array of Points! Yes.

What is a Point? Point2d.Double would work well for us.

What then is a Polygon?
class Polygon {

  Point2D.Double[] corners; 

  int cornersSize; 

  Polygon(int n) {
    corners = new Point2D.Double[n];     
    cornersSize = 0; 
  } 

  void add (Point2D.Double p) {
    corners[cornersSize] = p; 
    cornersSize += 1; 
  } 

} 

We model a polygon as a class containing an array of points (the instance variable corners) The class contains one constructor, which receives the size of the polygon,

... and a method, to add corners (i.e., points). Can you add a draw method?

I couldn't, really, at this point. Fine. Let's then wait until we study applets.

Well, I think arrays are nice. Isn't it a nuisance that their size is fixed, though?

Yes, unfortunately you need to specify the size of an array when an array is allocated, even though the actual size may not be known at that time. If you know that the array can never hold more than a certain number of elements, ...

... you can allocate a large array and partially fill it using a companion variable, ... ... which can be used to remember how many elements are actually in the array.

You can also dynamically grow an array by ... 1. allocating a larger array,

2. shoveling the contents from the smaller array into the larger array, and then... 3. attaching the larger array to the array variable.

This is tedious and repetitive code. The Vector class automates this process.

A vector is an "array" of objects that grows automatically. You add new elements at the end of the vector with the add method.

The Vector class needs to be imported from the same package as the string tokenizer. The constructor of choice is the no-arg constructor, which gives you a vector of size 0.

As with arrays, vector positions start at 0. The number of elements currently stored in a vector is obtained by the size method.

Let's give an example, before . Let's store Strings and report them back.

Sounds good to me.
Here's the program.
import java.util.*; 

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

	Vector words = new Vector(); 

	ConsoleReader console = new ConsoleReader(System.in); 
	System.out.println("Welcome, please enter text."); 

	while (true) {

	    System.out.print("Type> "); 
	    String line = console.readLine(); 
	    if (line.equals("quit") || line.equals("")) break; 
            StringTokenizer nizer = new StringTokenizer(line); 
            while (nizer.hasMoreTokens()) {
		String token = nizer.nextToken(); 
		words.add(token); 
	    }

	}

	System.out.println("Thanks for using this program."); 

	for (int i = 0; i < words.size(); i++) {
	    System.out.println("  " + i + ": " + words.elementAt(i)); 
	}

	System.out.println("Good bye!"); 
    }
}

Great, and here's how it runs. I like Vectors, they seem to be quite handy.
frilled.cs.indiana.edu%javac Words.java
frilled.cs.indiana.edu%java Words
Welcome, please enter text.
Type> I am typing here lines of text.
Type> Once upon a time there lived a man by the name of Stanley. 
Type> quit
Thanks for using this program.
  0: I
  1: am
  2: typing
  3: here
  4: lines
  5: of
  6: text.
  7: Once
  8: upon
  9: a
  10: time
  11: there
  12: lived
  13: a
  14: man
  15: by
  16: the
  17: name
  18: of
  19: Stanley.
Good bye!
frilled.cs.indiana.edu%

Yes, and we'll talk more about Vectors later. Great, I was going to ask you about that.

Last updated: May 16, 2004 by Adrian German for A201