| CSCI A201/A597 Lecture Notes Twenty-Six
Second Summer 2000
|
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 these 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 the 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 are managing them now.
|
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.
|
|
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.
|
|
How do you feel about your program?
|
I feel proud of being able to manipulating three arrays at the same time.
|
|
Tired, perhaps. 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.
|
|
The programmer must ensure that the arrays always have the
same length and that each slide is filled with values that
actually belong together.
|
Most importantly, any method that operates on a slice must
get all arrays as parameters, which is tedious to program.
|
|
The remedy is simple.
|
Yes: look at the slice, and find the concept that
it represents.
|
|
Then make the concept into a class.
|
class Product {
String name;
double score;
double price;
}
|
|
Then use an array of objects.
|
Product[] data = new Product[DATA_LENGTH]
|
|
The program has now one array (of objects).
|
Vectors are arrays of objects, which can grown 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:
|
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.
|
This shows that the process of eliminating arrays was successful.
|
|
The set of parallel arrays is replaced by a single array.
|
Each element in the resulting array corresponds to a slice in the set of parallel arrays.
|
|
Once you have this single concept available, it suddenly becomes much easier to give the program a better structure.
|
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 didn't have a product object?
|
You would have to return three values: the name, price, and score of the next product.
|
Georg Theiner was the first in this semester's A201 to point it out, earlier
when he was working on his Fractions program.
|
Well done, Georg!
|
|
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.
|
|
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.
|
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?
|
|
It would have to be something like this:
|
Good name, too.
|
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;
}
void drawOnto(Graphics2D g2) {
for (int i = 0; i < cornersSize; i++) {
Point2D.Double from = corners[i];
Point2D.Double to = corners[(i + 1) % cornersSize];
Line2D.Double side = new Line2D.Double(from, to);
g2.draw(side);
}
}
}
|
Now, can you show this to me in a running program?
|
How about an applet?
|
import java.applet.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
public class Three extends Applet {
Polygon p;
public void start() {
final int CORNERS = 6;
p = new Polygon(CORNERS);
Random gen = new Random();
for (int i = 0; i < CORNERS; i++) {
int x = gen.nextInt(this.getWidth());
int y = gen.nextInt(this.getHeight());
Point2D.Double p = new Point2D.Double(x, y);
this.p.add(p); // with your permission...
}
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setColor(Color.red);
this.p.drawOnto(g2);
}
}
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;
}
void drawOnto(Graphics2D g2) {
for (int i = 0; i < cornersSize; i++) {
Point2D.Double from = corners[i];
Point2D.Double to = corners[(i + 1) % cornersSize];
Line2D.Double side = new Line2D.Double(from, to);
g2.draw(side);
}
}
}
Looks like you're generating a new hexagon everytime you
restart the applet, and paint paints it.
|
Yes, but the hexagon takes care of its own painting.
|
The paint method simply gives it the keys,
|
... and the polygon draws itself on the context indicated by paint.
|
|
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
|
... to remember how many elements are actually in the array.
|
|
You can also dynamically grow an array
|
... by allocating a larger array,
|
|
... shoveling the contents from the smaller in the larger array,
|
... and then 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 (zero).
|
|
As with arrays, vector positions start at 0.
|
The number of elements currently stored in a vector is obtained by the size method.
|
Reading an element from a Vector is considerably more complicated.
|
Yes, a Vector can hold objects of any type at all.
|
A Vector collects values of type Object, and all Java classes are
subclasses of the generic class Object.
|
When you insert an element into a vector with the add or set method,
the object reference is automatically converted to a plain Object reference.
|
That means, though, that you get only Object references when you
retrieve objects from a vector, no matter what you put in.
|
To recover the original type that you have put in the vector you need to cast back to it,
|
... in the exact same way that
we recover the Graphics2D that the paint methods in an
applet actually receives from the system.
|
A good knowledge of inheritance would come in handy now.
|
|
I know...
|
So, let's look at an example instead!
|
import java.util.*;
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);
Vector data = new Vector();
int dataSize = 0;
while (true) {
Product p = Two.readProduct(console, dataSize);
if (p == null) break;
data.add(p);
dataSize += 1;
}
System.out.println("\n** Data has been entered now.");
System.out.println("** We need to process it.");
Product p = (Product)data.get(0);
double highest = p.score / p.price;
for (int i = 0; i < dataSize; i++) {
p = (Product)data.get(i);
if (highest < p.score / p.price)
highest = p.score / p.price;
}
System.out.println("** We then need to print the results.");
for (int i = 0; i < dataSize; i++) {
Two.printProduct((Product)data.get(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();
}
}
|
I see the vector related statements are marked.
|
Yes, and notice that the helper methods are unchanged.
|
dataSize is used just as an ornament.
|
Yes, and tomorrow we will implement our own Vector class!
|
|
I can hardly wait.
|
I can see that.
|
Last updated: August 1, 2000 by Adrian German for A201