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.
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.
We introduce the use of arrays with an example involving credit card numbers.
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.
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 x_{3}
.
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_cardThe 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.
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).
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:
X (1:4) = (/ 1.2, 3.5, 1.1, 1.5 /)
X (1:4) = (/ A (I, 1:2), A (I+1, 2:3) /)
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)
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.
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
.
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.
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.
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.
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.
values
to be an array of 100 real values
with subscripts ranging from -100 to -1.
squares
.
For example, squares (5)
= 25.
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.
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.
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.