Chapter 19. Arrays

Often programs must manipulate many similar objects. For example, if we were writing a program for searching the library's catalog, we might imagine that the program would have a different object for each book. We could hope to have a separate variable referencing each object, but this isn't practical if our program deals with thousands of objects (as with our library example). Luckily, Java provides a construct called an array for this situation.

19.1. Basics

An array is essentially a line of variables Each single variable in the array is called an array element.

To create a variable for referencing an array, use a variable declaration of the following form.

<typeOfElement>[] <arrayVariableName>;

For example, the following creates a variable score that can reference an array of numbers.

double[] score;

This creates a variable that can reference an array. Like object variables, it does not actually reference an array yet. To create an array and assign the array variable to reference it, we will use the new keyword.

score = new double[6];

This is the syntax for creating an array: the new keyword, followed by the type name of each array element, followed by the length of the array enclosed in brackets. The integer in brackets can be any expression (2 * numStudents instead of 3, for example).

Creating such an array sets up the score variable to look as follows in the computer's memory. (The numbers in the array, however, all default to 0.)

To work with an array, we reference individual elements according to their array index. The first array element has an index of 0, the second has an index of 1, and so on.

A detail to remember

Yes, that's one less than the array length. So if you declare an array score of length 3, the three array indices are 0, 1, and 2, since Java always begins numbering from 0. (Starting from 0 turns out to be more convenient than the intuitive choice of 1.) So even though the array has a length of 3, there is no array element with an index of 3

If you try to access an undefined array index, the program will crash with an ArrayIndexOutOfBoundsException. So be careful with array indices.

To refer to an array element in an expression, type the array variable name, followed by the element's array index enclosed in brackets. You can also do this on the left-hand side of an assignment statement to alter an array element's contents.

double[] score = new double[3];
score[0] = 97.0;
score[1] = 83.0;
score[2] = 66.0;
println("Average = " + ((score[0] + score[1] + score[2]) / 3.0));

In these statements we create an array of three numbers, called score. We assigned its three boxes to refer to three test scores, 97, 83, and 66. And finally we printed the average of these. The computer will display 82.0.

The significance of arrays comes when you use an expression to access a particular element of the array. Figure 19.1 contains a short program that reads a sequence of numbers into an array and then prints the numbers in reverse order. For example, the user might experience the following in running the program. (What the user types is in boldface.)

How many scores? 3
Type the scores now.
2
3
5
Here they are in reverse order.
  5.0
  3.0
  2.0

There's no way we could write a program to accomplish this using what we had seen in previous chapters. Using arrays, however, allows us to store an arbitrarily large sequence of data with no troubles.

Figure 19.1: The PrintReverse program.

  1  import acm.program.*;
  2  
  3  public class PrintReverse extends Program {
  4      public void run() {
  5          // create the array
  6          int num_scores = readInt("How many scores? ");
  7          double[] score;
  8          score = new double[num_scores];
  9  
 10          // fill the array
 11          for(int i = 0; i < num_scoresi++) {
 12              score[i] = readDouble("Score " + i + ": ");
 13          }
 14  
 15          // print it in reverse
 16          println("Here they are in reverse order.");
 17          for(int i = num_scores - 1; i >= 0; i--) {
 18              println("  " + score[i]);
 19          }
 20      }
 21  }

Java includes a special technique for accessing the length of an array, using the word length. In any expression, you can write the array name, followed by a period and the word length, and the value will be the number of items that the array was created to hold. As an example, we could rewrite line 11 of Figure 19.1 as follows.

for(int i = 0; i < score.lengthi++) { //@ num_scores

It's preferable to use the length keyword when appropriate, in favor of using some other variable that happens to represent the length of the array.

A detail to remember

You'll recall that the String class provides a length method that returns the number of characters in a string. Thus, if we want to know how long the String referenced by a variable name is, we use name.length().

With arrays, though, length is a special word built into Java, not a method. As a result, parentheses are not applied to length when used with an array. To determine how long the array referenced by score, we type score.length, not score.length().

19.2. Computing the mode

We'll now consider a more complex problem. Suppose we have an array of test scores, all integers between 0 and 100, and we want to find the mode — that is, the integer occurring most often. We'll see two techniques for computing this.

In the more obvious technique, found in Figure 19.2, we'll go through each number in the array and count how many times that number appears in the array. As we go along, we track which number has appeared most often. Once we complete counting each number in the array, we'll have found the mode.

Figure 19.2: The computeMode method.

public int computeMode(int[] nums) {
    int maxValue = -1;
    int maxCount = 0;
    for(int i = 0; i < nums.lengthi++) {
        // count number of times nums[i] is in array
        int count = 0;
        for(int j = 0; j < nums.lengthj++) {
            if(nums[j] == nums[i]) {
                count++;
            }
        }

        // remember the highest count we have seen
        if(count > maxCount) {
            maxValue = nums[i];
            maxCount = count;
        }
    }
    return maxValue;
}

An alternative technique is to create a second array, which I'll call tally, which initially has 0 throughout. We then go through the array of test scores, and with each score k, we increment entry k of tally. By the end of this process, tally[i] will hold the number of times i appears among the test scores. Our last step is to see which entry in tally is largest; the index of this entry is the mode.

Figure 19.3: The tallyMode method.

public int tallyMode(int[] nums) {
    // create array of tallies, all initialized to zero
    int[] tally = new int[101];
    for(int i = 0; i < tally.lengthi++) {
        tally[i] = 0;
    }

    // for each array entry, increment corresponding tally box
    for(int i = 0; i < nums.lengthi++) {
        int value = nums[i];
        tally[value]++;
    }

    // now find the index of the largest tally - this is the mode
    int maxIndex = 0;
    for(int i = 1; i < tally.lengthi++) {
        if(tally[i] > tally[maxIndex]) {
            maxIndex = i;
        }
    }
    return maxIndex;
}

This second technique can often be significantly faster: It looks at each test score only once, whereas the first technique will look at each test score as many times as there are test scores. That is, if our parameter array contains n test scores, the first technique will look into the array n² times, whereas the second technique will look into the array only n times.

19.3. Arrays of objects

So far we have been working with arrays of numbers. But we can have arrays of any type of thing we want. For example, suppose we want to simulate a ball bouncing around a window with several circular bumpers inside it.

We might want to store the bumpers as an array of GOvals. Creating such an array is easy enough.

GOval[] bumper = new GOval[10];

However, this doesn't itself create an array of 10 GOval objects: It creates 10 GOval variables, which can potentially reference GOval boxes. But these array entries default to null, and it is our job to populate the array with references to GOval objects.

for(int i = 0; i < bumper.lengthi++) {
    double x = Math.random() * (getWidth() - 30);
    double y = Math.random() * (getHeight() - 30);
    bumper[i] = new GOval(xy, 30, 30);
    bumper[i].setFilled(true);
    bumper[i].setFillColor(new Color(0, 0, 255));
    this.add(bumper[i]);
}

The following diagram illustrates what will be in memory after the array has been populated.

Figure 19.4 contains a complete program implementing a ball bouncing off of circular bumpers. The logic involved uses a tad of trigonometry. This isn't the point of this chapter, so I'll conveniently avoid explaining it here.

Figure 19.4: The Pinball program.

import acm.program.*;
import acm.graphics.*;
import java.awt.*;

public class Pinball extends GraphicsProgram {
    public void run() {
        // Create the ball that will bounce around the screen
        GOval ball = new GOval(25, 25, 30, 30);
        ball.setFilled(true);
        ball.setFillColor(new Color(255, 0, 0));
        this.add(ball);

        // Create the bumpers off of which the ball will bounce
        GOval[] bumper = new GOval[10];
        for(int i = 0; i < bumper.lengthi++) {
            double x = Math.random() * (getWidth() - 30);
            double y = Math.random() * (getHeight() - 30);
            bumper[i] = new GOval(xy, 30, 30);
            bumper[i].setFilled(true);
            bumper[i].setFillColor(new Color(0, 0, 255));
            this.add(bumper[i]);
        }

        double theta = Math.PI / 6; // ball's current direction
        while(true) {
            // wait for one frame, step ball once in current direction
            this.pause(20);
            ball.move(3 * Math.cos(theta), 3 * Math.sin(theta));

            // update direction if bounce off window's edge or bumper
            double x = ball.getX();
            double y = ball.getY();
            if(x < 0 || x + 30 >= this.getWidth()) {
                theta = Math.PI - theta// hits window's left/right edge
            } else if(y < 0 || y + 30 >= this.getHeight()) {
                theta = -theta;          // hits window's top/bottom edge
            } else {
                for(int i = 0; i < bumper.lengthi++) {
                    double dx = bumper[i].getX() - x;
                    double dy = bumper[i].getY() - y;
                    if(dx * dx + dy * dy < 30 * 30) {    // hits bumper
                        double in = Math.atan2(dydx);
                        theta = (in + Math.PI) - (theta - in);
                    }
                }
            }
        }
    }
}