Copyright (c) 1990, 1994 W. Brainerd, C. Goldberg, and J. Adams.
All rights reserved.
This file may not be copied without permission of the authors.

Programmer's Guide to Fortran 90, 3nd Edition

Chapter 4 Arrays

In ordinary usage, a list is a sequence of values, usually all representing data of the same kind, or otherwise related to one another. A list of students registered for a particular course and a list of all students enrolled at a college are examples.

In Fortran, a collection of values of the same type is called an array. We will also refer to a one-dimensional array as a list.

Frequently, the same operation or sequence of operations is performed on every element in an array. On a computer that performs one statement at a time, it makes sense to write such programs by specifying what happens to a typical element of the array and enclosing these statements in a sufficient number of do constructs (loops) to make them apply to every element. Fortran 90 also has powerful intrinsic operations and functions that operate on whole arrays or sections of an array. Programs written using these array operations are often clearer and are more easily optimized by Fortran compilers. Especially on computers with parallel or array processing capabilities, such programs are more likely to take advantage of the special hardware to increase execution speed.

4.1 Declaring and Using Arrays in Fortran

We introduce the use of arrays with an example involving credit card numbers.

4.1.1 A Credit Card Checking Application

As an example of a problem concerned with a list, suppose that a company maintains a computerized list of credit cards that have been reported lost or stolen or that are greatly in arrears in payments. The company needs a program to determine quickly whether a given credit card, presented by a customer wishing to charge a purchase, is on this list of credit cards that can no longer be honored.

Suppose that a company has a list of 8262 credit cards reported lost or stolen, as illustrated in Table 4-1.

Table 4-1 Lost credit cards.

Account number of 1st lost credit card   2718281
Account number of 2nd lost credit card   7389056
Account number of 3rd lost credit card   1098612
Account number of 4th lost credit card   5459815
Account number of 5th lost credit card   1484131
                   .                        .
                   .                        .
                   .                        .
Account number of 8262nd lost credit card!1383596

Since all of the 8262 numbers in the list must be retained simultaneously in the computer's main memory for efficient searching, and since a simple (scalar) variable can hold only one value at a time, each number must be assigned as the value of a variable with a different name so that the computer can be instructed to compare each account number of a lost or stolen card against the account number of the card offered in payment for goods and services.

4.1.2 Subscripts

It is possible to use variables with the 8262 Fortran names

lost_card_1
lost_card_2
lost_card_3
   .
   .
   .
lost_card_8262

to hold the 8262 values. Unfortunately, the Fortran language does not recognize the intended relationship between these variable names, so the search program cannot be written simply. The Fortran solution is to declare a single object name lost_card that consists of many individual integer values. The entire collection of values may be referenced by its name lost_card and individual card numbers in the list may be referenced by the following names:

lost_card (1)
lost_card (2)
lost_card (3)
   .
   .
   .
lost_card (8262)

This seemingly minor modification of otherwise perfectly acceptable variable names opens up a new dimension of programming capabilities. All the programs in this chapter, and a large number of the programs in succeeding chapters, use this form.

The numbers in parentheses that specify the location of an item within a list are subscripts, a name borrowed from mathematics. Although mathematical subscripts are usually written below the line (hence the name), such a form of typography is impossible on most computer input devices. A substitute notation, enclosing the subscript in parentheses or brackets, is adopted in most computer languages. It is customary to read the expression x(3) as ``x sub 3'', just as if it were written x3.

The advantage of this method of naming the quantities over using the variable names lost_card_1, lost_card_2, ..., lost_ card_8262 springs from the following programming language capability: The subscript of an array variable may itself be a variable, or an even more complicated expression.

The consequences of this simple statement are much more profound than would appear at first sight.

For a start in describing the uses of a subscript that is itself a variable, the two statements

i = 1
print *, lost_card (i)

produce exactly the same output as the single statement

print *, lost_card (1)

namely, 2718281, the account number of the first lost credit card on the list. The entire list of account numbers of lost credit cards can be printed by the subroutine print_lost_cards.

subroutine print_lost_cards (lost_card)

   integer, dimension (1:8262), intent (in) :: lost_card
   integer :: i

   do i = 1, 8262
      print *, lost_card (i)
   end do

end subroutine print_lost_cards

As an example of an array feature in Fortran, the collection of card numbers as a whole can be referenced by the one statement

print *, lost_card

The replacement just made actually creates a different output. The difference is that using the do loop to execute a print statement 8262 times causes each card number to be printed on a separate line. The new version indicates that as many as possible of the card numbers should be printed on one line, which might not produce acceptable output. Adding a simple format for the print statement instead of using the default produces a more desirable result, printing four card numbers per line.

print "(4i8)", lost_card

This is a little better, but another problem is that the number of lost and stolen cards varies daily. The subroutine will not be very useful if it makes the assumption that there are exactly 8262 cards to be printed. This can be fixed easily by changing the declaration to

integer, dimension (:), intent (in) :: lost_card
The colon indicates that the size of the array lost_card is to be assumed from the array that is the actual argument given when the subroutine is called. Also, this passed-on size is used in the print statement to determine how many credit card numbers are to be printed. Thus, we have created a general subroutine for printing a list of integers. However, it is so simple that it can be done with a single statement, so it is not worth showing it.

4.1.3 Array Declarations

The name of an array must obey the same rules as an ordinary variable name. Each array must be declared in the declaration section of the program. A name is declared to be an array by putting the dimension attribute in a type statement followed by a range of subscripts, enclosed in parentheses. For example,

real, dimension (1 : 9) :: x, y
logical, dimension (-99 : 99) :: yes_no

declares that x and y are lists of 9 real values and that yes_no is a list of 199 logical values. These declarations imply that a subscript for x or y must be an integer expression with a value from 1 to 9 and that a subscript for yes_no Must be an integer expression whose value is from -99 to +99.

A list of character strings may be declared in a form like the following:

character (len = 8), dimension (0 : 17) :: char_list

In this example, the variable char_list is a list of 18 character strings, each of length 8.

The shape of an array is a list of the number of elements in each dimension. A 9 x 7 array has shape (9,7); the array char_list declared above has shape (18); and the array declared by

integer, dimension (9, 0:99, -99:99) :: iii

has shape (9,100,199). When only one number is given in a dimension declaration in place of a subscript range, it is used as the upper subscript bound and the lower bound is 1.

The shape of a scalar is a list with no elements in it. The shape of a scalar or array can be computed using the shape intrinsic function.

The declaration

real, dimension (:, :), allocatable :: a, b

indicates that the arrays A and B have two dimensions (rank 2). The colons mean that the extents along each dimension will be established later, when an allocate statement is executed.

A similar declaration using colons may occur for a dummy argument of a procedure, indicating that the shape of the dummy array is to be taken from the actual argument used when the procedure is called. This sort of dummy argument is called an assumed-shape array.

subroutine s (d)
   integer, dimension (:, :, :) :: d

The allocatable attribute must not be used for an array argument, but a pointer attribute may be (see chapter 8).

The declaration of arrays also may use values of other dummy arguments to establish extents; such arrays are called automatic arrays. For example, the statements

subroutine s2 (dummy_list, n, dummy_array)
   real, dimension (:) :: dummy_list
   real, dimension (size (dummy_list)) :: local_list
   real, dimension (n, n) :: dummy_array, local_array
   real, dimension (2 * n + 1) :: longer_local_list

declare that the size of dummy_list is to be the same as the size of the corresponding actual argument, that the array local_list is to be the same size as dummy_list, and that dummy_array and local_array are both to be two-dimensional arrays with n x n elements. The last declaration shows that some arithmetic on other dummy arguments is permitted in calculating array bounds; these expressions may include references to certain intrinsic functions, such as size.

In the main program, an array must either be declared with constant fixed bounds or be declared allocatable and be given bounds by the execution of an allocate statement (4.1.5). In the first case, our lost and stolen card program might contain the declaration

integer, dimension (8262) :: lost_card

This is not satisfactory if the number of lost cards changes frequently. In this situation, perhaps the best solution is to declare the array to have a sufficiently large upper bound so that there will always be sufficient space to hold the card numbers. Because the upper bound is fixed, there must be a variable whose value is the actual number of cards lost. Assuming that the list of lost credit cards is stored in a file connected to the standard input unit (UNIT=*), the following program fragment reads, counts, and prints the complete list of lost card numbers. The read statement has an iostat keyword argument whose value is set to zero if no error occurs and is set to a negative number if there is an attempt to read beyond the last data item in the file. The slightly different form of the read statement necessitated by the use of iostat is described in detail in Section 9.3, as is the iostat specifier.

integer, dimension (20000) :: lost_card
integer :: number_of_lost_cards, i, iostat_var

do i = 1, 20000
   read (unit = *, fmt = *, iostat = iostat_var)  &
         lost_card (i)
   if (iostat_var < 0) then
      number_of_lost_cards = i - 1
      exit
   end if
end do
   . . .
print "(4i8)", lost_card (1:number_of_lost_cards)

Although the array lost_card is declared to have room for 20,000 entries, the print statement limits output to only those lost card numbers that actually were read from the file by specifying a range of subscripts 1:number_of_lost_cards (see Section 4.1.6 for more details about this notation).

4.1.4 Array Constructors

Rather than assign array values one by one, it is convenient to give an array a set of values using an array constructor. An array constructor is a sequence of scalar values defined along one dimension only. An array constructor is a list of values, separated by commas and delimited by the pair of two-character symbols ``(/'' and ``/)''. There are three possible forms for the array constructor values:

  1. A scalar expression as in
    X (1:4) = (/ 1.2, 3.5, 1.1, 1.5 /)
    
  2. An array expression as in
    X (1:4) = (/ A (I, 1:2), A (I+1, 2:3) /)
    
  3. An implied do loop as in
    x (1:4) = (/ (sqrt (real (i)), i = 1, 4) /)
    

If there are no values specified in an array constructor, the resulting array is zero sized. The values of the components must have the same type and type parameters (kind and length). The rank of an array constructor is always one; however, the reshape intrinsic function can be used to define rank-two and greater arrays from the array constructor values. For example,

reshape ( (/ 1, 2, 3, 4, 5, 6 /), (/ 2, 3 /) )

is the 2 x 3 array

     1  3  5
     2  4  6

An implied do list is a list of expressions, followed by something that is like an iterative control in a do statement. The whole thing is contained in parentheses. It represents a list of values obtained by writing each member of the list once for each value of the do variable replaced by a value. For example, the implied do list in the array constructor above

(sqrt (real (i)), i = 1, 4)

is the same as the list

sqrt (real (1)), sqrt (real (2)),  &
sqrt (real (3)), sqrt (real (4))

The following print statement illustrates another use of an implied do list.

print *, (a (i, i), i = 1, n)

4.1.5 Dynamic Arrays

In Fortran 77, all the storage that was required during execution of a program could be determined and allocated by the compiler. This is known as static storage allocation, as opposed to dynamic storage allocation, which means that storage may be allocated or deallocated during execution of the program.

With static allocation, the size of each array must be declared at compile time, usually as the largest size anticipated in any execution of the program. With dynamic storage allocation, the program can wait until it knows during execution exactly what size array is needed and then allocate only that much space. Memory also can be deallocated dynamically, so that the storage used for a large array early in the program can be reused for other large arrays later in the program after the values in the first array are no longer needed.

For example, instead of relying on an end-of-file condition when reading in the list of lost cards, it is possible to keep the numbers stored in a file with the number of lost cards as the first value in the file, such as

8262
2718281
7389056
1098612
5459815
1484131
   .
   .
   .
1383596

The program can then read the first number, allocate the correct amount of space for the array, and read the lost card numbers.

integer, dimension (:), allocatable :: lost_card
integer :: number_of_lost_cards
   . . .
! The first number in the file is
! the number of lost card numbers in the
! rest of the file.
read *, number_of_lost_cards
allocate (lost_card (number_of_lost_cards))

! Read the numbers of the lost cards
read "(i7)", lost_card
   . . .

In the declaration of the array lost_card, the colon is used to indicate the rank (number of dimensions) of the array, but the bounds are not pinned down until the allocate statement is executed. Because the programmer doesn't know how many lost cards there will be, there is no way to tell the compiler that information. During execution, the system must be able to create an array of any reasonable size after reading from the input data file the value of the variable number_of_lost_cards. The deallocate statement may be used to free the allocated storage.

Pointers, discussed in Chapter 8, provide an additional and more general facility for constructing data structures with variable sizes.

4.1.6 Array Sections

In the following statement, used in the example that tests for an end-of-file condition, a section of the array lost_card is printed.

print "(4i8)", lost_card (1:number_of_lost_cards)

On many occasions such as the one above, only a portion of the elements of an array is needed for a computation. It is possible to refer to a selected portion of an array, called an array section. A parent array is an aggregate of array elements, from which a section may be selected.

In the following example

real a (10)
   . . .
a (2:5) = 1.0

the parent array a has 10 elements. The array section consists of elements a(2), a(3), a(4), and a(5). The section is an array itself and the value 1.0 is assigned to all four of the elements in a(2:5).

In addition to the ordinary subscript that can select a subobject of an array, there are two other mechanisms for selecting certain elements along a particular dimension of an array. One is a subscript triplet, an example of which was shown above, and the other is a vector subscript.

The syntactic form of a subscript triplet is

[ expression ] : [ expression ] [ : expression ]

where each set of brackets encloses an optional item and each expression must produce a scalar integer value. The first expression gives a lower bound, the second an upper bound, and the third a stride. If the lower bound is omitted, the lower bound that was declared or allocated is used. (Note that an assumed-shape dummy array is treated as if it were declared with lower bound 1.) If the upper bound is omitted, the upper bound that was declared or allocated is used. The stride is the increment between the elements in the section referenced by the triplet notation. If omitted, it is assumed to be one. For example, if V is a one-dimensional array (list) of numbers:

v (0:4)

represents elements v(0), v(1), v(2), v(3), and v(4) and

v (3:7:2)

represents elements v(3), v(5), and v(7).

Each expression in the subscript triplet must be scalar. The values of any of the expressions in triplet notation may be negative. The stride must not be zero. If the stride is positive, the section is from the first subscript up to the second in steps of the stride. If the stride is negative, the section is from the first subscript down to the second, decrementing by the stride.

Another way of selecting a section of an array is to use a vector subscript. A vector subscript is an integer array expression of rank one. For example, if iv is a list of three integers, 3, 7, and 2, and x is a list of 9 real numbers 1.1, 2.2, ..., 9.9, the value of x (iv) is the list of three numbers 3.3, 7.7, and 2.2-the third, seventh, and second elements of x.

Ordinary subscripts, triplets, and vector subscripts may be mixed in selecting an array section from a parent array. An array section may be empty.

Consider a more complicated example. If b were declared in a type statement as

real b (10, 10, 5)

then b (1:4:3, 6:8:2, 3) is a section of b, consisting of four elements:

b (1, 6, 3)   b (1, 8, 3)
b (4, 6, 3)   b (4, 8, 3)

The stride along the first dimension is 3; therefore, the notation references the first subscripts 1 and 4. The stride in the second dimension is 2, so the second subscript varies by 2 and takes on values 6 and 8. In the third dimension of b, there is no triplet notation, so the third subscript is 3 for all elements of the section. The section would be one that has a shape of (2, 2), that is, it is two dimensional, with extents 2 and 2.

To give an example using both triplet notation and a vector subscript, suppose again that b is declared as above:

real b (10, 10, 5)

then b(8:9, 5, (/ 4, 5, 4/) ) is a 2 x 3 array consisting of the six values:

b (8, 5, 4)   b (8, 5, 5)   b (8, 5, 4)
b (9, 5, 4)   b (9, 5, 5)   b (9, 5, 4)

If vs is a list of three integers, and vs = (/ 4, 5, 4 /), the expression b(8:9, 5, vs) would have the same value. The expression b(8:9, 5, vs) cannot occur on the left side of an assignment because of the duplication of elements of b.

4.1.7 Array Assignment

Array assignment is permitted under two circumstances: when the array expression on the right has exactly the same shape as the array on the left, or when the expression on the right is a scalar. Note that, for example, if a is a 9 x 9 array, the section a (2:4, 5:8) is the same shape as a (3:5, 1:4), so the assignment

a (2:4, 5:8) = a (3:5, 1:4)

is valid, but the assignment

a (1:4, 1:3) = a (1:3, 1:4)

is not valid because even though there are 12 elements in the array on each side of the assignment, the left side has shape 4 x 3 and the right side has shape 3 x 4.

When a scalar is assigned to an array, the value of the scalar is assigned to every element of the array. Thus, for example, the statement

m (k+1:n, k) = 0

sets the elements m (k+1, k), m (k+2, k), ..., m (n, k) to zero.

4.1.8 The where Statement and Construct

The where statement may be used to assign values to only those elements of an array where a logical condition is true. For example, the following statement sets the elements of b to zero in those positions where the corresponding element of a is negative. The other elements of b are unchanged. a and b must be arrays of the same shape.

where (a < 0) b = 0

The logical condition in parentheses is an array of logical values conformable to each array in the assignment statement. In the example above, comparison of an array of values with a scalar produces the array of logical values.

The where construct permits any number of array assignments to be done under control of the same logical array, and the elsewhere statement within a where construct permits array assignments to be done where the logical expression is false. The following statements assign to the array a the quotient of the corresponding elements of b and c in those cases where the element of c is not zero. In the positions where the element of c is zero, the corresponding element of a is set to zero and the zero elements of c are set to 1.

where (c /= 0) ! c/=0 is a logical array.
   a = b / c   ! a and b must conform to c.
elsewhere
   a = 0       ! The elements of a are set to 0
               ! where they have not been set to b/c.
   c = 1       ! The 0 elements of c are set to 1.
end where

Within a where statement or where construct, only array assignments are permitted. The shape of all arrays in the assignment statements must conform to the shape of the logical expression following the keyword where. The assignments are executed in the order they are written-first those in the where block, then those in the elsewhere block. where constructs may not be nested.

4.1.9 Intrinsic Operators

All of the intrinsic operators and many of the intrinsic functions may be applied to arrays, operating independently on each element of the array. For example, the expression abs (a (k:n, k)) results in a one-dimensional array of n-k+1 nonnegative real values. A binary operation, such as *, may be applied only to two arrays of the same shape, in which case it multiplies corresponding elements of the two arrays. The assignment statement

a (k, k:n+1) = a (k, k:n+1) / pivot

divides each element of a (k, k:n+1) by the real scalar value pivot. In essence, a scalar value may be considered an array of the appropriate size with all its entries equal to the value of the scalar.

4.1.10 Element Renumbering in Expressions

An important point to remember about array expressions is that the elements in an expression no longer have the same subscripts as the elements in the arrays that make up the expression. They are renumbered with 1 as the lower bound in each dimension. Thus, it is legal to add y (0:7) + z (-7:0), which results in an array whose eight values are considered to have subscripts 1, 2, 3, .., 8.

The renumbering must be taken into account when referring back to the original array. Suppose v is a one-dimensional integer array that is given an initial value with the declaration:

integer, dimension (0:6), parameter ::  &
      v = (/ 3, 7, 0, -2, 2, 6, -1 /)

The intrinsic function maxloc returns a list of integers giving the position of the largest element of an array. maxloc (v) is (/ 2 /) because position 2 of the list v contains the largest number, 7, even though it is v (1) that has the value 7. Also, maxloc (v (2:6)) is the list (/ 4 /) because the largest entry, 6, occurs in the fourth position in the section v (2:6).

There is also an intrinsic function, minloc, whose value is the list of subscripts of a smallest element of an array. For example, if a =

     1   8   0
     5  -1   7
     3   9  -2

the value of minloc (a) is (/3, 3/) because a (3, 3) is the smallest element of the array.

4.1.11 Exercises

  1. Write a statement that declares values to be an array of 100 real values with subscripts ranging from -100 to -1.
  2. Use an array constructor to assign the squares of the first 100 positive integers to a list of integers named squares. For example, squares (5) = 25.
  3. If a chess or checkers board is declared by
    character (len = 1), dimension (8, 8) :: board
    

    the statement

    board = "W"
    

    assigns the color white to all 64 positions. Write a statement or statements that assigns ``B'' to the 32 black positions. Assume that board (1, 1) is to be white so that the board is as shown in Figure 4-1.

       W B W B W B W B
       B W B W B W B W
       W B W B W B W B
       B W B W B W B W
       W B W B W B W B
       B W B W B W B W
       W B W B W B W B
       B W B W B W B W
    

    Figure 4-1 A chess board.

  4. Suppose list is a one-dimensional array that contains n < max_size real numbers in ascending order. Write a subroutine insert (list, n, max_size, new_entry) that adds the number new_entry to the list in the appropriate place to keep the entire list sorted.
  5. Write a function that finds the angle between two three-dimensional real vectors. If V = (v1,v2,v3), the magnitude of V is |V| = sqrt (V . V) where . is the vector dot product. The cosine of the angle between V1 and V2 is given by
                     V1 . V2
    cos (theta) =  _____________
    
                    |V1| |V2|
    
    acos (arccosine) may be used to find an angle with a given cosine.