Books / Arrays and Array Algorithms in Java / Chapter 6
Two-Dimensional Arrays
A two-dimensional array, an array whose components are themselves arrays, is necessary or useful for certain kinds of problems. For example, you would use this type of array if you are doing a scientific study in which you have to track the amount of precipitation for every day of the year.
One way to organize these data would be to create a one-dimensional array, consisting of 365 elements:
double rainfall[] = new double[365];
However, with this representation, it would make it very difficult to calculate the average rainfall within a given month, which might be an important part of your study.
A better representation for this problem would be to use a two-dimensional array, one dimension for the months and one for the days. The following statement declares the array variable rainfall and creates a 12 by 31 array object as its reference:
double rainfall[][] = new double[12][31];
Thus, rainfall
is an array of arrays. You can think of the first array as the 12 months required for the problem. And you can think of each month as an array of 31 days. The months will be indexed from 0 to 11, and the days will be indexed from 0 to 30.
The problem with this representation is that when we want to refer to the rainfall for January 5, we would have to use rainfall[0][4]
. This is awkward and misleading. The problem is that dates—1/5/1999—are unit indexed, while arrays are zero indexed. Because it will be difficult to remember this fact, our representation of the rainfall data may cause us to make errors when we start writing our algorithms.
We can easily remedy this problem by just defining our array to have an extra month and an extra day each month:
double rainfall[][] = new double[13][32];
This representation creates an array with 13 months, indexed from 0 to 12, with 32 days per month, indexed from 0 to 31. However, we can simply ignore the 0 month and 0 day by using unit indexing in all of the algorithms that process the array. In other words, if we view this array as a two-dimensional table, consisting of 13 rows and 32 columns, we can leave row 0 and column 0 unused.
As Figure [fig-rainfall] shows, the very first element of this 416-element array has subscripts (0,0) while the last location has subscripts (12,31). The main advantages of this representation is that the program as a whole will be much easier to read and understand and much less prone to error.
In order to refer to an element in a two-dimensional array, you need to use two subscripts. For the rainfall array, the first subscript will specify the month and the second will specify the day within the month. Thus, the following statements assign 1.15 to the rainfall element representing January 5, and then print its value:
As the figure above shows, the very first element of this 416-element array has subscripts (0,0) while the last location has subscripts (12,31). The main advantages of this representation is that the program as a whole will be much easier to read and understand and much less prone to error.
In order to refer to an element in a two-dimensional array, you need to use two subscripts (or indexes). For the rainfall
array, the first subscript will specify the month and the second will specify the day within the month. Thus, the following statements assign 1.15 to the rainfall element representing January 5, and then print its value:
rainfall[1][5] = 1.15; // Rainfall for January 5
System.out.println( rainfall[1][5] );
Just as in the case of one-dimensional arrays, it is an error to attempt to reference an element that is not in the array. Each of the following examples would cause Java to raise an IndexOutOfBoundsException
:
rainfall[13][32] = 0.15 ; // No such element
rainfall[11][33] = 1.3; // No such column
rainfall[14][30] = 0.74; // No such row
If the initial values of an array’s elements are supposed to be zero, there is no need to initialize the elements. Java will do it automatically when you create the array with new. However, for many array problems it is necessary to initialize the array elements to some other value. For a two-dimensional array, this would require a nested loop. To illustrate this algorithm, let’s use a nested for loop to initialize each element of the rainfall array to 0:
// Note that both loops are unit indexed.
for (int month = 1; month < rainfall.length; month++)
for (int day = 1; day < rainfall[month].length; day++)
rainfall[month][day] = 0.0;
Note that both for loops use unit indexing. This is in keeping with our decision to leave month 0 and day 0 unused.
Remember that when you have a nested for
loop, the inner loop iterates faster than the outer loop. Thus, for each month, the inner loop will iterate over 31 days. This is equivalent to processing the array as if you were going across each row and then down to the next row in the representation shown in the figure above.
Note that for a two-dimensional array, both dimensions have an associated length variable, which is used in this example to specify the upper bound of each for loop. For the rainfall array, the first dimension (months) has a length of 13 and the second dimension (days) has a length of 32.
Another way to view the rainfall array is to remember that it is an array of arrays. The length of the first array, which corresponds to the number (13) of months, is given by rainfall.length. The length of each month’s array, which corresponds to the number of days (32) in a month, is given by rainfall[month].length
.
The outer loop of the nested for loop iterates through months 1 through 12, and the inner for loop iterates through days 1 through 31. In this way, 372 = 12 x 31
elements of the array are set to 0.0. In table below, the boldface numbers along the top represent the day subscripts, while the boldface numbers along the left represent the month subscripts.
Two-Dimensional Array Methods
Now that we have figured out how to represent the data for our scientific experiment, let’s develop methods to calculate some results. First, we want a method to initialize the array. This method will simply incorporate the nested loop algorithm we developed previously:
public void initRain(double rain[][]) {
for (int month = 1; month < rain.length; month++)
for (int day = 1; day < rain[month].length; day++)
rain[month][day] = 0.0;
} // initRain()
Note how we declare the parameter for a multidimensional array. In addition to the element type (double
), and the name of the parameter (rain
), we must also include a set of brackets [] for each dimension of the array.
Note also that we use the parameter name within the method to refer to the array. As with one-dimensional arrays, the parameter is a reference to the array, which means that any changes made to the array within the method will persist when the method is exited.
The avgDailyRain()
Method
One result that we need from our experiment is the average daily rainfall. To calculate this result, we would add up all of the rainfalls stored in the 12x31 array and divide by 365. Of course, the array itself contains more than 365 elements. It contains 416 elements, but we’re not using the first month of the array, and within some months—those with fewer than 31 days—we’re not using some of the day elements. For example, there’s no such day as rainfall[2][30]
, which would represent February 30. However, because we initialized all of the array’s elements to 0, the rainfall recorded for the non-days will be 0, which won’t affect our overall average.
The method for calculating average daily rainfall should take our two-dimensional array of as a parameter, and it should return a . Its algorithm will use a nested for loop to iterate through the elements of the array, adding each element to a running total. When the loops exits, the total will be divided by 365 and returned:
public double avgDailyRain(double rain[][]) {
double total = 0;
for (int month = 1; month < rain.length; month++)
for (int day = 1; day < rain[month].length; day++)
total += rain[month][day];
return total/365;
}
The avgRainForMonth()
Method
One reason we used a two-dimensional array for this problem is so we could calculate the average daily rainfall for a given month. Let’s write a method to solve this problem. The algorithm for this method will not require a nested for loop. We will just iterate through the 31 elements of a given month, so the month subscript will not vary. For example, suppose we are calculating the average for January, which is represented in our array as month 1:
double total = 0;
for (int day = 1; day < rainfall[1].length; day++)
total = total + rainfall[1][day];
Thus, the month subscript is held constant (at 1) while the day subscript iterates from 1 to 31. Of course, in our method we would use a parameter to represent the month, thereby allowing us to calculate the average daily rainfall for any given month.
Another problem that our method has to deal with is that months don’t all have 31 days, so we can’t always divide by 31 to compute the monthly average. There are various ways to solve this problem, but perhaps the easiest is to let the number of days for that month be specified as a third parameter. That way, the month itself and the number of days for the month are supplied by the user of the method:
public double avgRainForMonth(double rain[][],
int month, int nDays) {
double total = 0;
for (int day = 1; day < rain[month].length; day++)
total = total + rain[month][day];
return total/nDays;
} // avgRainForMonth()
Given this definition, we can call this method to calculate and print the average daily rainfall for March as in the following statement:
System.out.println("March: " + avgRainForMonth(rainfall,3,31));
Note that when passing the entire two-dimensional array to the method, we just use the name of the array. We do not have to follow the name with subscripts.
Passing Part of an Array to a Method
Instead of passing the entire rainfall array to the avgRainForMonth() method, we could redesign this method so that it is only passed the particular month that’s being averaged. Remember that a two-dimensional array is an array of arrays, so if we pass the month of January, we are passing an array of 32 days. If we use this approach, we need only two parameters: the month, which is array of days, and the number of days in that month:
public double avgRainForMonth(double monthRain[], int nDays) {
double total = 0;
for (int day = 1; day < monthRain.length; day++)
total = total + monthRain[day];
return total/nDays;
} // avgRainForMonth()
Given this definition, we can call it to calculate and print the average daily rainfall for March as in the following statement:
System.out.println("March: " + avgRainForMonth(rainfall[3],31));
In this case, we’re passing an array of double to the method, but in order to reference it, we have to pull it out of the two-dimensional array by giving its row subscript as well. Thus, rainfall[3]
refers to one month of data in the two-dimensional array, the month of March. But rainfall[3]
is itself a one-dimensional array. Figure below helps to clarify this point.
It’s important to note that deciding whether to use brackets when passing data to a method is not just a matter of whether you are passing an array. It is a matter of what type of data the method parameter specifies. So, whenever you call a method that involves a parameter, you have to look at the method definition to see what kind of data that parameter specifies. Then you must supply an argument that refers to that type of data.
For our two-dimensional rainfall array, we can refer to the entire array as rainfall. We can refer to one of its months as rainfall[j]
, where j
is any integer between 1 and 12. And we can refer to any of its elements as rainfall[j][k]
, where j
is any integer between 1 and 12, and k
is any integer between 1 and 31.
The class below creates the rainfall array in the main()
method. It then initializes the array and prints out average daily rainfall and average daily rainfall for the month of March. However, note that we have made a slight modification to the initRain()
method. Instead of just assigning 0 to each element, we assign a random value between 0 and 2.0:
rain[month][day] = Math.random() * 2.0;
Using the Math.random()
method in this way enables us to generate some realistic test data. In this case, we have scaled the data so that the daily rainfall is between 0 and 2 inches. (Rainfall like this would probably be appropriate for an Amazonian rain forest!) Testing our algorithms with these data provides some indication that our methods are in fact working properly.
public class Rainfall {
/**
* Initializes the rainfall array
* @param rain is a 2D-array of rainfalls
* Pre: rain is non null
* Post: rain[x][y] == 0 for all x,y in the array
* Note that the loops use unit indexing.
*/
public void initRain(double rain[][]) {
for (int month = 1; month < rain.length; month++)
for (int day = 1; day < rain[month].length; day++)
rain[month][day] = Math.random() * 2.0; // Random rainfall
} // initRain()
/**
* Computes average daily rainfall for a year of rainfall data
* @param rain is a 2D-array of rainfalls
* @return The sum of rain[x][y] / 356
* Pre: rain is non null
* Post: The sum of rain / 365 is calculated
* Note that the loops are unit indexed
*/
public double avgDailyRain(double rain[][]) {
double total = 0;
for (int month = 1; month < rain.length; month++)
for (int day = 1; day < rain[month].length; day++)
total += rain[month][day];
return total/365;
} // avgDailyRain()
/**
* Computes average daily rainfall for a given month containing nDays
* @param monthRain is a 1D-array of rainfalls
* @param nDays is the number of days in monthRain
* @return The sum of monthRain / nDays
* Pre: 1 <= nDays <= 31
* Post: The sum of monthRain / nDays is calculated
*/
public double avgRainForMonth(double monthRain[], int nDays) {
double total = 0;
for (int day = 1; day < monthRain.length; day++)
total = total + monthRain[day];
return total/nDays;
} // avgRainForMonth()
public static void main(String args[]) {
double rainfall[][] = new double[13][32];
Rainfall data = new Rainfall();
data.initRain(rainfall);
System.out.println("The average daily rainfall = "
+ data.avgDailyRain(rainfall));
System.out.println("The average daily rainfall for March = "
+ data.avgRainForMonth(rainfall[3],31));
} // main()
}//Rainfall