Previous: 9.1 Two Dimensional Arrays
Up: 9.1 Two Dimensional Arrays
Next: 9.2 Implementing Multi-Dimensional Arrays
Previous Page: 9.1 Two Dimensional Arrays
Next Page: 9.2 Implementing Multi-Dimensional Arrays

9.1 Two Dimensional Arrays

Our first task is to consider a number of exams for a class of students. The score for each exam is to be weighted differently to compute the final score and grade. For example, the first exam may contribute 30% of the final score, the second may contribute 30%, and the third contribute 40%. We must compute a weighted average of the scores for each student. The sum of the weights for all the exams must add up to 1, i.e. 100%. Here is our task:

WTDAVG: Read the exam scores from a file for several exams for a class of students. Read the percent weight for each of the exams. Compute the weighted average score for each student. Also, compute the averages of the scores for each exam and for the weighted average scores.

We can think of the exam scores and the weighted average score for a single student as a data record and and represent it as a row of information. The data records for a number of students, then, is a table of such rows. Here is our conceptual view of this collection of data:

Let us assume that all scores will be stored as integers; even the weighted averages, which will be computed as float, will be rounded off and stored as integers. To store this information in a data structure, we can store each student's data record, a row containing three exam scores and the weighted average score, in a one dimensional array of integers. The entire table, then, is an array of these one dimensional arrays - i.e. a two dimensional array. With this data structure, we can access a record for an individual student by accessing the corresponding row. We can also access the score for one of the exams or for the weighted average for all students by accessing each column. The only restriction to using this data structure is that all items in an array must be of the same data type. If the student id is an integer, we can even include a column for the id numbers.

Suppose we need to represent id numbers, scores in 3 exams, and weighted average of scores for 10 students; we need an array of ten data records, one for each student. Each data record must be an array of five elements, one for each exam score, one for the weighted average score, and one for the student id number. Then, we need an array, scores[10] that has ten elements; each element of this array is, itself, an array of 5 integer elements. Here is the declaration of an array of integer arrays:

The first range says the array has ten elements: scores[0], scores[1], scores[9]. The second range says that each of these ten arrays is an array of five elements. For example, scores[0] has five elements: scores[0][0], scores[0][1], scores[0][4]. Similarly, any other element may be referenced by specifying two appropriate indices, scores[i][j]. The first array index references the one dimensional array, scores[i]; the second array index references the element in the one dimensional array, scores[i][j].

A two dimensional array lends itself to a visual display in rows and columns. The first index represents a row, and the second index represents a column. A visual display of the array, scores[10][5], is shown in Figure 9.1. There are ten rows, (0-9), and five columns (0-4). An element is accessed by row and column index. For example, scores[2][3] references an integer element at row index 2 and column index 3.

We will see in the next section that, as with one dimensional arrays, elements of a two dimensional array may be accessed indirectly using pointers. There, we will see the connection between two dimensional arrays and pointers. For now, we will use array indexing as described above and remember that arrays are always accessed indirectly. Also, just as with one dimensional arrays, a 2D array name can be used in function calls, and the called function accesses the array indirectly.

We can now easily set down the algorithm for our task:

read the number of exams into no_of_exams
     get weights for each of the exams

read exam scores and id number for each student into a two dimensional array

for each student, compute weighted average of scores in the exams compute average score for each of the exams and for the weighted average print results

We can easily write the top level program driver using functions to do the work of reading scores, getting the weights, computing the weighted averages, printing scores, averaging each set of scores, and printing the averages. The driver is shown in Figure 9.2.

We have declared an array, scores[][], with MAX rows and COLS columns, where these macro values are large enough to accommodate the expected data. We have used several functions, which we will soon write and include in the same program file. Their prototypes as well as those of other functions are declared at the head of the file. In the driver, getwts() reads the weights for the exams into an array, wts[], returning the number of exams. The function, read_scores(), reads the data records into the two dimensional array, scores[][], and returns the number of data records. The function, wtd_avg(), computes the weighted averages of all exam scores, and avg_scores() computes an average of each exam score column as well as that of the weighted average column. Finally, print_scores() and print_avgs() print the results including the input data, the weighted averages, and the averages of the exams.

Let us first write getwts(). It merely reads the weight for each of the exams as shown in Figure 9.3.

The function prompts the user for the number of exam scores, and reads the corresponding number of float values into the wts[] array. Notice that the loop index, i begins with the value 1. This is because the element wts[0], corresponding to the student id column, does not have a weight and should be ignored. After the weights have been read, we flush the keyboard buffer of any remaining white space so that any kind of data (including character data) can be read from the input. The function returns the number of exams, n.

We will assume that the data for the student scores is stored in a file in the format of one line per student, with each line containing the student id followed by the exam scores. To read this data into a two dimensional array, we must first open the input file. This is done by the function openfile() shown in Figure 9.4, which prompts for the file name and tries to open the file. If the file opens successfully, the file pointer is returned. Otherwise, the function prints a message and asks the user to retype the file name. The user may quit at any time by typing a newline or end of file. If an end of file is typed or the typed string is empty, the program is terminated. Once the input file is opened, we read data items into the array, filling in the elements one row (student) at a time. We use two index variables, row and col, varying the row to access each row in turn; and, within each row, we vary col to access elements of each column in turn. We will need a doubly nested loop to read the data in this manner. The function is given the number of students, the variable stds, and the number of exams, nexs. We will use column 0 to store the student id numbers and the next nexs columns to store the scores. Thus, in each row, we read nexs+1 data values into the array. This is done by the function, read_scores(), also shown in Figure 9.4.

The input file is first opened using openfile(), and the data records are read into the array called ex[][] within the function. The function returns the number of records read either when EOF is reached or when the array is filled. Each integer data item is read from a file, fp, into a temporary variable, n. This value is then assigned to the appropriate element, ex[row][col]. When all data has been read, the input file is closed and the number of records read is returned.

Notice in main() in Figure 9.2, we pass the 2D array to read_scores() just as we did for one dimensional arrays, passing the array name. As we shall see in the next section, the array name is a pointer that allows indirect access to the array elements. The two dimensional array as as argument must be declared in the function definition as a formal parameter. In Figure 9.4, we have declared it as ex[][COL] with two sets of square brackets to indicate that it points to a two dimensional array. In our declaration, we must include the number of columns in the array because this specifies the size of each row. Recall, the two dimensional array is an array of rows. Once the compiler knows the size of a row in the array, it is able to correctly determine the beginning of each row.

The next function called in main() computes the weighted average for each row. The weighted average for one record is just the sum of each of the exam score times the actual weight of that exam. If the scores are in the array, ex[][], then the following code will compute a weighted average for a single row, row:

wtdavg = 0.0;
     for (col = 1; col <= nexs; col++)
          wtdavg += ex[row][col] * wts[col] / 100.0;
We convert the percent weight to the actual weight multiply by the score, and accumulate it in the sum, wtdavg yielding a float value. The wtdavg will be stored in the integer array, ex[][], after rounding to a nearest integer. If we simply cast wtdavg to an integer, it will be truncated. To round to the nearest integer, we add 0.5 to wtdavg and then cast it to integer:
ex[row][nexs + 1] = (int) (0.5 + wtdavg);
The weighted average is stored into the column of the array after the last exam score. The entire function is shown in Figure 9.5

Computing average of each of the exams and the weighted average is simple. We just sum each of the columns and divide by the number of items in the column, and is also shown in Figure 9.5. For each exam and for the weighted average column, the scores are added and divided by lim, the number of rows in the array, using floating point computation. The result is rounded to the nearest integer and stored in the array, avg[]. Figure 9.6 shows the final two functions for printing the results.

Running the program with data file, wtdin.dat as follows:

3    70   76
52   92   80
53   95   56
54   48   52
55   98   95
57   100  95
61   100  65
62   95   76
63   86   65
70   100  90
71   73   73
75   94   79
produces the following sample session:

In this program, we have assumed that the input file contains only the data to be read, i.e. the student id numbers and exam scores. Our read_scores() function is written with this assumption. However, the input file might also contain some heading information such as the course name and column headings in the first few lines of the file. We can easily modify read_scores() to discard the first few lines of headings.

As a second example of application of two dimensional arrays, consider our previous payroll example. In this case, the data items in a pay data record are not all of the same data type. The id numbers are integers, whereas all the other items are float. Therefore, we must use an array of integers to store the id numbers, and a two dimensional float array to store the rest of the data record. The algorithm is no different from the program we developed in Chapter that computed pay. The difference is that now we use a two dimensional array for all float payroll data instead of several one dimensional arrays. The id numbers are still stored in a separate one dimensional array. Since the data structures are now different, we must recode the functions to perform the tasks of getting data, calculating pay, and printing results, but still using the same algorithms.

The program driver and the header files are shown in Figure 9.7. The program declares an integer array for id numbers and a two dimensional float array for the rest of the data record. The successive columns in the two dimensional array store the hours worked, rate of pay, regular pay, overtime pay, and total pay, respectively. We have defined macros for symbolic names for these index values. As in the previous version, the program gets data, calculates pay, and prints data. The difference is in the data structures used. Functions to perform the actual tasks are shown in Figure 9.8 and 9.9 and included in the same program source file.

Each function uses a two dimensional array, payrec[][]. The row index specifies the data record for a single id, and the column index specifies a data item in the record. The data record also contains the total pay. A sample interaction with the program, pay2rec.c, is shown below.

Sample Session:



Previous: 9.1 Two Dimensional Arrays
Up: 9.1 Two Dimensional Arrays
Next: 9.2 Implementing Multi-Dimensional Arrays
Previous Page: 9.1 Two Dimensional Arrays
Next Page: 9.2 Implementing Multi-Dimensional Arrays

tep@wiliki.eng.hawaii.edu
Wed Aug 17 09:20:12 HST 1994