Lab 9: Arrays


 

Introduction

In past exercises, we have dealt with sequences of values by processing the values one at a time. Today, we want examine a new kind of object called an array that can store not just one value, but an entire sequence of values. Like a String, an array is a subscripted object, meaning the values within it can be accessed via a subscript or index. It is important to remember that Java uses zero-base indexing. I.E., if we have an array named theArr holding N objects, the first object has index 0.

By storing a sequence of values in an array, we can design operations for the sequence, and then implement such operations in a method that receives the entire sequence through an array parameter.

To introduce the use of the array, today's exercise is to write a program that processes the names and scores of students in a course, and assigns letter grades (A, B, C, D or F) based on those scores, using the "curve" method of grading. The format of the input is a series of lines, each having the form:

   name score

As usual, we will use object-centered design to design our solution.

 

Analysis

With a little thought, it should be evident that this problem requires that we process the sequence of scores more than once. More precisely, to assign letter grades using the "curve" method, we must first compute the average and standard deviation of the scores. Once we have these values, we can determine the letter grade corresponding to each score. We must thus process the sequence of scores at least twice.

One way to solve this problem would be to ask the user to retype the values each time we need to process them. Clearly, this is an unacceptable solution. We must have some place where the data values can be held between uses. By using an array we can process a sequence of values multiple times in an efficient manner.

 

Behavior

Our program should display a greeting. It should then read a sequence of names and a sequence of scores from the keyboard. It should then compute the average and standard deviation of the sequence of scores, and display these values. Using the average and standard deviation, it should compute the sequence of letter grades that correspond to the sequence of scores. It should then display each student's name, score and letter grade.

 

Objects

If we examine our behavioral description for objects, we find these:

Description

Type

Kind

Name

a greeting

String

constant

none

a sequence of names

String[]

varying

nameArr

a sequence of scores

double[]

varying

scoreArr

the average score

double

varying

average

the standard deviation of the scores

double

varying

stdDeviation

a sequence of letter grades

char[]

varying

gradeArr

We can thus specify the behavior of our program this way:

   Input(keyboard): a sequence of names and scores.
   Output(screen): the average and standard deviation of the scores,
                    plus the sequences of names and scores,
                    and the sequence of corresponding letter grades.

 

Operations

From our behavioral description, we have these operations:

Description

Defined?

Name

Package/Module?

1

Display a string

yes

println()

ann.easyio

2

Read a string

yes

readWord()

ann.easyio

3

Read a sequence of names and scores

no

--

--

4

Compute the average of a sequence scores

no

--

--

5

Compute the standard deviation of a sequence scores

no

--

--

6

Compute the sequence of letter grades corresponding to a sequence scores

no

--

--

7

Display sequences of names, scores and letter grades

no

--

--

As you can see, we have a few methods to write.

 

Algorithm

We can organize the preceding objects and operations into the following algorithm:

   0. Display a greeting.
   1. Fill nameArr and scoreArr with values from the keyboard.
   2. Output the mean and standard deviation of the values in scoreArr.
   3. Compute gradeArr, an array of the letter grades corresponding
       to the scores in scoreArr.
   4. Display nameArr, scoreArr and gradeArr, value-by-value.

We will thus use three different array objects, nameArr storing String values, scoreArr storing double values, and gradeArr storing char values.

 

Getting Started

A skeleton program is provided in Grades.java. Since some of the operations we will be creating are reusable, we store them in a class module, consisting of the implementation file DoubleArrayOps.java, and the documentation file DoubleArrayOps.doc.

Make a new project for this exercise (e.g., Grades), and then save copies of these files into the project folder. Also copy the packages ann and hoj and add them to your project. Then open the file Grades.java, and take a few moments to look it over.

As indicated by its documentation, Grades.java is a skeleton program that (partially) encodes the algorithm given above. Today's exercise will consist of completing this program.

 

Defining array Objects

To define an object named arrObj for storing multiple values of type Type, we can write:

   Type arrObj[];

Such a statement declares arrObj as an object capable of storing multiple values of type Type. Since an array can store any type of value, a programmer using an array must specify the type of value they want to store in a given array.

There is an important point that should be noted here. When we declare the variable, we have not set aside any memory to hold the values yet. At this point, arrObj holds a special value which is null. If we were to try and use this array, we would get an error. What we are missing is the creation of the array object. That can be accomplished by:

   arrObj = new Type[IntegerExpression];

The Type must match what we used to declare arrObj and we must have an integer value which tells us the maximum number of values that our array can hold. As usual, we can combine these two into one statement.

One problem with arrays is that once we have created an array, we can not change its size. There are four basic techniques which we can use to deal with this problem, each of which has its own problems.

  1. Use a large constant value when we create the array. Since the value should be large enough for any possible data set, we typically end up with a lot of wasted space.
  2. Ask the user for the number of values and create it with that size. This requires that the user know the number of data values.
  3. If we run out of space, create a larger array and copy the data values. This requires extra code.
  4. Use Java's Vector class which essentially expands and shrinks the storage automatically. This class only holds objects.

In Grade.java, use this information to define nameArr and scoreArr (as array objects capable of storing String and double objects, respectively), in the places indicated by the comments in Grades.java. For the size use a constant value MAX_SCORES which is set to 1000.

 

Once we have read the data into scoreArr, we will use the subArray() method in DoubleArrayOps to replace it with an array that only has valid data values.

Filling an array Object

Next, we want to write fillArrays() which fills our array objects with values from the keyboard

 

Design

We can describe how this method should behave as follows:

Behavior. Our method should receive an empty array of strings and an empty array of doubles from its caller. It should read each name and score from the theKeyboard, appending them to the array of strings and the array of doubles, respectively. When no values remain to be read, our method should pass back the filled array objects. It should return the number of values that were read.

Objects. Using the behavioral description, our method needs the following objects:

Description

Type

Kind

Movement

Name

An empty array of strings

String[]

varying

received,
passed back

nameArr

An empty array of doubles

double[]

varying

received,
passed back

scoreArr

keyboard

Keyboard

varying

static

theKeyboard

a name

String

varying

local

name

a score

double

varying

local

score

number of values read in

int

varying

local,
returned

numberRead

Given these objects, we can specify the behavior of our method as follows:

   Receive: nameArr, a String[]; and
            scoreArr, a double[].
   Passback: nameArr and scoreArr, filled with the values from keyboard.
   Return: numberRead an int.

Since this method seems unlikely to be generally reusable, we will define it within the same file as our main function. Using the above information, place a method stub for fillArrays() after the main function.

Operations. In our behavioral description, we have the following operations:

Description

Defined?

Name

Package/Module?

1

Receive nameArr and scoreArr

yes

method call
mechanism

built-in

2

Read a string from the Keyboard

yes

readWord()

ann.easyio

3

Read a double from from the Keyboard

yes

readDouble()

ann.easyio

4

Append a string to a String[]

no

5

Append a double to a double[]

no

6

Repeat 3-6 for each line of input

no

7

Pass back array objects

yes

parameter mechanism

built-in

8

Return the number read

yes

return

built-in

Algorithm. We can organize these objects and operations into the following algorithm:

   0. Receive nameArr and scoreArr.
   1. Loop
      a. Read name
      b. If name was "done", terminate repetition.
      c. Read score 
      d. Append name to nameArr.
      e. Append score to scoreArr.
      f. Add one to the number read.
      End loop.
   2. Return the number read.

 

Coding

To append values to an array, we need to know the first unused position in the array. But consider the following table:

Values read so far

Locations filled

First free location

0

none

0

1

0

1

2

0, 1

2

3

0, 1, 2

3

The number of values read in is the location that we want to fill.

 

To put a value into a location of an array, we will use the assignment statement with the indexing operator [ ] as show by the following pattern

   arrayObject[integerExpression] = newValue;

Such a statement computes the integerExpression and uses that for the location that will be changed. The value newValue replaces whatever used to be in that location.

We will continue this loop until the user enters the word "done". We can test for this via the following boolean expression:

   name.equals("done")

Using this information, complete the stub of fillArrays(). Then compile what you have written and make sure that it is free of syntax errors before proceeding.

 

Average

Next, we want to write method average() which given a array of double values, returns the average of those values.

 

Design

We can describe how this method should behave as follows:

Behavior. Our method should receive an array of double values. It should sum the values in the array, and if there is at least one value in the array, our method should return the sum divided by the number of values; otherwise, our method should display an error message and return a default value (e.g., 0.0).

Objects. Using the behavioral description, our method needs the following objects:

Description

Type

Kind

Movement

Name

A array of doubles

double[]

varying

received

numArr

the sum of the values in the array

double

varying

local

sum

an error message

String

constant

local

--

an default return value

double

constant

local

0.0

Given these objects, we can specify the behavior of our method as follows:

   Receive:  numArr, an array of doubles.
   Precondition: numArr has at least one value.
   Return: the average of the values in numArr.

Since this method seems likely to be generally reusable, we will define it within a class named DoubleArrayOps. Using this information, place documentation for average() in DoubleArrayOps.doc, and a method stub in DoubleArrayOps.java.

Operations. In our behavioral description, we have the following operations:

Description

Defined?

Name

Package/Module?

1

Receive numArr

yes

method call
mechanism

built-in

2

Find numValues

yes

.length

built-in

3

Sum the values in a double[]

no

5

Divide two values

yes

/

built-in

5

Return a double value

yes

return

built-in

6

Display an error message

yes

println()

ann.easyio

7

Select 4 and 5 or 6 and 5, but not both

yes

if statement

built-in

Algorithm. We can organize these objects and operations into the following algorithm:

   0. Receive numArr.
   1. Get numValues.
   2. Compute sum, the sum of the values in numArr.
   3. If numValues > 0:
         Return sum / numValues.
      Otherwise
         a. Display an error message.
         b. Return 0.0 as a default value.
      End if.

 

Refinement for Computing the Sum

To sum the values in an array, we need to refine our algorithm. Since there is not a built-in method to accomplish this task we will need to design our own.

Design

We can describe how this part of the method should behave:

Behavior. We should add each of the first numValues data values in the array into the sum. We assume that each of these locations contains a valid value.

Objects. Using the behavioral description, we need the following objects:

Description

Type

Kind

Movement

Name

A array of doubles

double[]

varying

received

numArr

number of values

int

varying

local

numValues

the sum of the values in the array

double

varying

local

sum

location in the array

int

varying

local

i

Operations. In our behavioral description, we have the following operations:

Description

Defined?

Name

Package/Module?

1

initialize sum to 0.0

yes

declaration

built-in

2

get number of values

yes

.length

built-in

3

get value in location i of numArr

yes

[ ]

built-in

4

add value to sum

yes

+=

built-in

5

Repeat (3,4), counting from 1 to numValues-1

yes

for

built=in

Algorithm. We can organize these objects and refine our previous algorithm:

 
   0. Receive numArr.
   1. Get numValues.
   2. Declare and initialize sum.
   3. For each i from 0 to numValues - 1
         a. add numArri to sum
   4. If numValues > 0:
         Return sum / numValues.
      Otherwise
         a. Display an error message.
         b. Return 0.0 as a default value.
      End if.

 

Note that we use the notation numArri to refer to the value in numArr whose index is i.

Coding

Step 1 can be performed by using the expression:

   numArr.length

Step 2 can be performed using a for loop that counts from 0 to the numValues minus one:

   for (int i = 0; i < numValues; i++)
      ...

To access a value v within the array, the subscript operation can be applied to an array. Its pattern is:

   arrayObject [ index ]

Such an expression returns the value that is stored within arrayObject at index index. Note that we can use an expression like this on either the right or left hand side of the assignment operator (=).

We can use the method System.err.println() to print our error message.

Using this information, complete the stub for average(). Then uncomment the call to average() in the main function, translate and test your program. Verify that your program is correctly computing the average before you proceed.

 

score values

expected average

value computed

none

error message and 0.0

15

15.0

10, 20, 30

20.0

3.7, 2.4, 5.6, 8.8

5.125

 

Standard Deviation

We next need to write method standardDev() which given an array of double values, returns the standard deviation of those values. Unless you have had a course in statistics, you may not know how to compute the standard deviation of a sequence of values, so we will consolidate the normal design steps and provide a specification and algorithm for this task.

 

Design

The specification for this method is as follows:

   Receive:  numArr, a double[].
   Precondition: numArr is not empty.
   Return: the standard deviation of the values in numArr.

Since this method seems likely to be generally reusable, it should also be defined in our DoubleArrayOps class. Using this specification, put documentation for standardDev() in DoubleArrayOps.doc, and a method stub in DoubleArrayOps.java.

An algorithm to compute the standard deviation is as follows:

   0. Receive numArr.
   1. Get numValues.
   2. If numValues > 0:
         a. Define avg, the average of the values in numArr.
         b. Define sumSqrTerms, a double initialized to zero.
         c. Define term, a double.
         d. For each index i of numArr:
               1) Set term to numArri - avg.
               2) Add term^2 to sumSqrTerms.
            End loop.
         e. Return sqrt(sumSqrTerms / numValues).
      Otherwise
         a. Display an error message.
         b. Return 0.0 as a default value.
      End if.

 

Coding

To compute the average of the values in an array (step 2a), we can use the average() method that we just defined.

Step 2d requires a loop similar to the one we used in the previous method.

Using this information, complete the stub for standardDev(). Then uncomment the call to standardDev() in the main function, and translate and test what you have written.

 

score values

expected standard deviation

value computed

none

error message and 0.0

15

0.0

10, 20, 30

8.16496581

3.7, 2.4, 5.6, 8.8

2.40767004

Your standard deviations should be approximately the ones listed in the table. Make sure that your program is correctly computing the standard deviation before you proceed.

 

Define another array Object

This step is easy. In the appropriate place in the main function, define gradeArr as an array of char objects. You don't need to use new since we will create the array in the method we create next.

As usual, recompile to check the syntax of what you have written.

 

Computing Letter Grades

Eventually we will be working with input files stored on disk. Print a hard copy of each of the three scores files (scores1.data, scores2.data and scores3.data). Take a moment and and look over the distribution of scores in each file. If you were assigning grades, what letter grades would you assign for the scores in scores1.data, scores2.data and scores3.data? Take a moment and write beside each person's name (on the hard copies) what letter grade you would give them, based on their score.

Our task is to write a method that computes the appropriate letter grades for the input scores. Since this method needs a sequence of scores to process, and must return a corresponding sequence of letter grades, we can specify what the method must do as follows:

   Receive: scoreArr, an array of double values.
   Return: gradeArr, an array of char values.

Since this method seems pretty tightly tied to this particular grading problem, we will define it locally, within Grades.java. Using this information, define a stub for computeLetterGrades() following the main function.

Students often want grades to be "curved." The "curve" method of grading is based upon the assumption that the scores being graded fall into a normal distribution (i.e., a bell curve). Given a set of scores, the "curve" method determines letter grades as follows:

An algorithm for determining the letter grades corresponding to a array of scores is thus as follows:

   0. Receive scoreArr, an array of scores.
   1. Get numValues.
   2. Define gradeArr, an array of characters 
       the same length as scoreArr.
   3. If numValues > 0:
         a. Define avg, the average of the values.
         b. Define standardDev, the standard deviation of the values.
         c. Define F_CUT_OFF as avg - 1.5 * standardDev.
         d. Define D_CUT_OFF as avg - 0.5 * standardDev.
         e. Define C_CUT_OFF as avg + 0.5 * standardDev.
         f. Define B_CUT_OFF as avg + 1.5 * standardDev.
         g. For each index i of scoreArr from 0 to numValues - 1:
               If scoreArri < F_CUT_OFF:
                  Set gradeArri to 'F'.
               Else if scoreArri  < D_CUT_OFF:
                  Set gradeArri to 'D'.
               Else if scoreArri  < C_CUT_OFF:
                  Set gradeArri to 'C'.
               Else if scoreArri  < B_CUT_OFF:
                  Set gradeArri to 'B'.
               Else 
                  Set gradeArri to 'A'.
               End if.
            End loop.
      End if.
   4. Return gradeArr.

Using the information above, complete the stub for computeLetterGrades(). Then compile what you have written to check for syntax errors. (We'll check for logic errors in the next step.)

 

Display the Names, Scores and Grades

The last method is to display the information we have computed. Design and write a method that, given an array of names, an array of scores, and an array of letter grades displays these three array objects in tabular form. For example, with the input

    sam 3.7
    max 2.4
    joe 5.6
    bob 8.8
    done

We would expect the output to appear something like the following:

   Mean score: 5.125
   Std. Dev: 2.40767004

   sam     3.7     D
   max     2.4     D
   joe     5.6     C
   bob     8.8     A

If you find that you have logic errors in what you have written, use the debugger to find them.

 

Reading the Scores from a File

The final task is to convert our program from one that gets its data from the keyboard into one that reads its information from a file. You have been working with files all along as a place where your program code is stored. When you run the Java compiler, it reads the file that you have created and processes it to produce a class file which contains the byte code for your program. The file provides a long term storage facility making it easy for you to modify your code. Similarly, we would like our grades program to have the same capability. That way if we make a mistake entering the grades, we don't have to reenter all the data, but only modify what has changed.

It is important for students to realize that the file and the arrays in the program are different. We will read the data from the file and place it into the array. If we make a change to the the data in the array, the data in the file will be unchanged. Once the program finishes, the array object is gone, but the file remains.

One design choice we could have made earlier was to have the user retype the data values each time they needed to be processed. While that is not a viable option for keyboard input, we could do this with our file. This is not preferred, however, because files are typically stored on hard disks which have large capacity, but slow access time. An array, on the other hand, is typically stored in random access memory (RAM) which allows much faster access. The only reason we might consider reading the data multiple times is if the amount of data in the file is larger than can be fit comfortably into the RAM of our computer. Consider though, that at the time this was written, a typical home computer had at least 64 million bytes of RAM. If our program had access to all of it, we could store somewhere between 100,000 and 1,000,000 scores in RAM.

In the next section, we will go into file access in more depth. For now we will use the class hoj.ReadFile which supports the same methods as the Keyboard class in ann.easyio. This will make it relatively easy to convert our code to work with the file.

We will write a method fillArraysFile() which fills our array objects with values from a file. It will have basically the same design as the method fillArrays() which we wrote earlier.  

Behavior. Our method should receive an empty array of strings and an empty array of doubles from its caller. It should ask the user for the filename. It should open the file. It should read each name and score from the file appending them to the array of strings and the array of doubles, respectively. When no values remain to be read, our method should close the file and pass back the filled array objects. It should return the number of values that were read.

The new behavior is in bold.

Objects. We need some new objects

Description

Type

Kind

Movement

Name

An input file name

String

varying

local

fileName

An input file

ReadFile

varying

local

theFile

 

Operations. We can see how the new operations fit into the previous behavioral description:

Description

Defined?

Name

Package/Module?

1

Receive nameArr and scoreArr

yes

method call
mechanism

built-in

Print a prompt for the file name

yes

print()

ann.easyio

Read a string from the Keyboard

yes

readWord()

ann.easyio

Open the file

yes

ReadFile()

hoj

2

Read a string from the file

yes

readWord()

hoj

3

Read a double from from the file

yes

readDouble()

hoj

4

Append a string to a String[]

no

5

Append a double to a double[]

no

6

Repeat 3-6 for each line of input

no

Close the file

yes

close()

hoj

7

Pass back array objects

yes

parameter mechanism

built-in

8

Return the number read

yes

return

built-in

Again the changes are marked in bold

Algorithm. The algorithm for the new method is:

   0. Receive nameArr and scoreArr.
   1. Prompt for the file name.
   2. Read the file name.
   3. Declare and open theFile.
   4. Loop
      a. Read name from theFile.
      b. If name was empty, terminate repetition.
      c. Read score  from theFile.
      d. Append name to nameArr.
      e. Append score to scoreArr.
      f. Add one to the number read.
      End loop.
   5. Close theFile.
   6. Return the number read.

 

Coding

To start with make a copy of fillArrays() just after itself in Grades.java. Change the name of the copy to fillArraysFile() and change the comments to reflect the new functionality.

Steps 1 and 2 don't involve anything new.

Step 3 is more interesting. Remember that declaring a variable of a certain type and creating an object of that type are different things. In this case, when we create an object of type ReadFile, the constructor requires us to provide the name of the file that we are attempting to open. If the file does not exist, an exception will be thrown. We can either catch the exception or just let the program die. We will let it die for now. If fileName is the name of the String variable holding the file name the user typed in, we can declare and create a new ReadFile object via the code:

   ReadFile theFile = new ReadFile(fileName);

To use the file instead of the keyboard, we need to send the message to theFile instead of theKeyboard. For example if we did

   something = theKeyboard.readInt();

we would change it to be

   something = theFile.readInt();

The same message is being sent to a different object.

The only additional messages that a ReadFile understands as compared to a Keyboard is close(). Step 5 requires us to send the close() message to the object theFile. Once we close the file, we can no longer read values from it unless we open it again by creating a new ReadFile object. It is always good programming practice to close a file immediately after finishing using it.

Using this information, complete the transformation of fillArraysFile().

In your main program, change the call to fillArrays to fillArrayFile.

Then compile what you have written and make sure that it is free of syntax errors before proceeding.

Trying the predefined data files.

Run your program with the input file scores1.data. The output produced should appear something like the following:

   Enter the name of the scores file: scores1.data

   Mean score: 75
   Std. Dev: 11.1803

   joan    55      F
   joe     60      D
   jane    60      D
   jim     65      D
   janet   70      C
   john    70      C
   johanna 75      C
   jack    75      C
   joeline 75      C
   jacques 75      C
   josh    80      C
   janna   80      C
   jason   85      B
   jadzia  90      B
   jon     90      B
   jackie  95      A

If you find that you have logic errors in what you have written, use the debugger to find them.

When scores2.data or scores3.data are processed using the "curve" method, how do the "curved" grades compare with the grades you would have assigned?

What have you learned about grading "on the curve?"

Sample data files are provided in scores1.data, scores2.data, and scores3.data.

Phrases you should now understand:

Array, Zero-based indexing, Index, Subscript, Opening a File, RAM.


 

Submit:

Hard copies of your final versions of Grades.java, DoubleArrayOps.java, DoubleArrayOps.doc, and execution records showing the processing of scores1.data, scores2.data and scores3.data, plus the hard copies of these scores that you printed earlier annotated with the grades you assigned.


Back to This Lab's Table of Contents

Back to the Prelab Questions

Forward to the Homework Projects


Back to the Table of Contents

Back to the Introduction


Copyright 2000 by Prentice Hall. All rights reserved.