Introducing arrays
4.6 Subscript triplets
Before proceeding further, for future reference it will be useful to define in general terms the notation using colons that was described above to specify array sections. A section of an array specified by array-name is defined by
array-name (section-subscript, section-subscript, &
section-subscript, …)
there being one section-subscript for every dimension of the array.
What is a section-subscript? A section-subscript is either a subscript-triplet or a single integer. (A third possibility, a “vector subscript”, is introduced in Chapter 9.) If a section-subscript is an integer, it could be either a constant or a variable or an integer-valued expression, but it must be a scalar.
However, to form an array section, it would not be permissible for every section-subscript to be an integer. If they were all integers, the expression would be a valid one but, instead of representing an array section, it would simply represent a single element of the array and would be a scalar. On the other hand, as long as at least one section-subscript is a subscripttriplet, then we will be forming an array section and it is
important to realize that an array section is itself a kind of array. The array section has a rank equal to the number of subscript-triplets among the section-subscripts.
What is a subscript-triplet? It is subscript1: subscript2: stride
a construction in which only the first colon is compulsory and may not be omitted. The effect of the subscript-triplet is to pick out the elements of the array whose indices (in this particular dimension) run from subscript1 to no more than subscript2 in increments of stride. Note that subscript1, subscripts and stride must be integers (constants, variables or expressions). If subscript1 is omitted, the effect is as if it were equal to the index at the lower bound of the array in this dimension. If subscript2 is omitted, it is as if the upper bound were specified. If stride is omitted, it is as if it were equal to 1.
Note that subscript2 would normally be greater than subscript1, but it could be the other way round if stride were negative, in which case the selected array elements would be reversed in order from the original array. Note also that an array section will contain no elements (but will still exist!) if there is a subscript-triplet that generates no valid index. This would happen, for example, if stride were positive but subscript2 were less than subscript1.
If quintessence is a rank-five array, quintessence(:,:,:,:,:)
is no different from quintessence but
quintessence(:, 1, : ,6, :) is a rank-three array section and
quintessence(1,1, 9, 6,2)
is just one element of quintessence, and is a scalar. In quintessence(:,:,:,:,2:1:1)
the fifth subscript-triplet is 2:1:1, which generates no indices because you never get to 1 by adding units to 2; so this array section is a rank-five array with no elements at all!
4.7
Character substrings and arrays of character strings
The subscript triplet notation is very similar to a notation that may be used to pick out a part of a character string. If lines (10) is an array of ten character strings, what will the processor understand by lines (1:5)?
The answer is, it means the first five elements of the array lines, in accordance with all that has been said above. On the other hand, lines (1) (1:5) would actually represent the first five characters of the first element of the array!
The situation is that if character-name represents any scalar character-string, then character-name (position1 : position?)
represents the character string formed by taking the sequence of characters within character-name starting from position position1 and going forward to position2. It is a “substring” of character- name. If position1 is omitted, it is taken to be 1, and if positions is not given, it is taken to have its maximum value, i.e. the length of the string character-name. If L is the length of character-name, we must have
1 ≤ position1 ≤ position2 ≤ L unless
position1 > position2
in which case the substring exists but contains no characters, i.e. it has zero length. The pair position1 : position2
is known as a “substring range”.
So, the substring c ( i : j ) would be equal to C(i) //c(i+1) //c(i+2)//…//c(j–1)//c(j) if j is sufficiently greater than i.
Now, if we pick up the example given at the start of this section, lines(1:4) (1:5)
is an array section consisting of four substrings, i.e. the first five characters from each of the first four elements of the array lines. On the other hand,
lines (1:5) (1:4)
is an array section consisting of five substrings each of four characters. Symbolically, the notation for a subobject of a rank-one character array is
character-array-name (section-subscript) (substring-range)
and if the rank is greater than one there must be that number of section-subscripts. If substring-range is omitted then we simply have a section of the array as defined in Section 4.6. The section- subscripts may not be omitted, i.e.
character-array-name (substring-range) is not legal, but the desired effect may be got by writing
character-array-name (:,:,:,…,:) (substring-range) where the number of colons is the rank of character-array-name.
The following example may make the notation clear:
CHARACTER (30) :: words (2000), test (25) CHARACTER(10) :: shorter (2000) , check .
. .
test = words(176:200)
shorter = words(1:2000)(1:10) check = words(99)(1:10)
The first two of these assignment statements give values to arrays. The third gives a value to the scalar character string check. The last two statements could equally well be written
shorter = words(:)(:10) check = words(99)(:10)
since an omitted index is taken to have the lowest or highest value possible, according to whether it is before or after the colon. Similar rules apply to multidimensional arrays, as in
CHARACTER(30) :: words (30, 60,200), halfpage (30, 30) .
. .
CHARACTER :: capitals(30,60,200) halfpage = words(:,:30,57)
capitals = words(:,:,:)(:1)
Array sections and substrings can also be used in array constructors, as in CHARACTER, PARAMETER :: digits(0:9) = (/ ("01235456789"&
(k:k), k=1,10) /)
CHARACTER, PARAMETER :: octals(0:7) = digits(:7) This syntax is made clearer in Chapter 8. As a final example, if we have
CHARACTERS) :: concept(3) = (/&
"origin","cattle","potato"/)
then concept(2:)(4:5) is equal to (/"t1", "at"/). Incidentally, an array constructor is taken to have the same type as the first element in it, and a character-type array constructor consists of strings whose lengths are all the same as those of the first element. So, the character constant
(/"or", "cattle", "potato"/)
is exactly equivalent to (/"or", "ca", "po"/), because the longer elements will be truncated just as they would be if they were given as values to length-two strings by assignment statements.
Remember that the substring notation may not be applied to an expression other than a constant or a variable. So,
a//b(i:j)
concatenates a string with a substring; it does not take a substring of the concatenation of two strings. And it is not permissible to specify a substring of a substring directly as in
p = q(1:m)(n:)
which could actually mean something quite different, as we will see in the next chapter when arrays are explained in more detail.
4.8 Masks
The concept of a “mask” arises in the WHERE statement, to be explained below, and in several of the intrinsic functions commonly used with arrays.
A mask is a logical array, i.e. an array whose elements are data items of LOGICAL type. The word
“mask” is appropriate because arrays of this type are most often used to mask arrays of other types, i.e. to identify a subset of their elements on which some other operation is to be carried out.
Suppose, for example, that a and b are REAL arrays of the same shape. Then a>b is a logical expression, whose value is .TRUE, whenever a>b, and it is itself an array of the same shape as a and b. It is a logical array, i.e. a mask.
Logical arrays can be manipulated by assignment statements like any other type of array, e.g.
pickneg = a<0.0
evens = (/.FALSE.,.TRUE.,.FALSE.,.TRUE.,.FALSE.,.TRUE./) 4.9
WHERE
Normally, an array assignment statement a=b will set each element of a equal to the corresponding element of b. However, the assignment may be made conditional with a statement such as
WHERE (b>a) a = b or
WHERE (a<0.0) a=0.0; a=SQRT(a)
The expression b>a is a mask of the same shape as the arrays a and b, and a<0 . 0 has the same shape as a.
The logical array b>a could be expressed in array constructor notation as (/ b(1)>a(1), b(2>a(2), b(3)>a(3), …. /)
The general syntax of the WHERE statement is WHERE (m) a = b
Here, m must be of logical type: it could be a variable, or a constant, or an expression. It is necessary for a to be an array (otherwise, we could have had IF instead of WHERE). If m, a and b are all arrays, they must have the same shapes. The assignment a=b then takes place conditionally, element by element, according to whether the corresponding element of m is true or false. The logical object m could be a scalar, in which case the statement is equivalent to
IF (m) a = b
If b is a scalar and m and a are arrays, then the value of b is given to all the elements of a that correspond to true elements of m.
Here is another simple example:
REAL :: angles(500)
WHERE (angles>360.0) angles = MOD(angles, 360.0)
Going beyond the simple WHERE statement, there is a WHERE construct by which a number of conditional array assignment statements may be tied together. If x, x1 and xr are real arrays of the same shape, we could have:
WHERE (x>0.0) x1 = LOG(x) xr = SQRT(x) ELSEWHERE x1 = –99.0 xr = 0.0 END WHERE
This WHERE construct consists of an opening statement with the keyword WHERE followed just by a bracketed logical array; then there is a sequence of array assignment statements; then there can be an ELSEWHERE followed by assignment statements conditional on the inverse of the original logical array.
Finally, the construct is terminated by an END WHERE statement. The WHERE construct forms a unit and must not be interspersed with other types of statement. The WHERE construct may only contain assignment statements and not (for example) WRITE statements.
The WHERE statement, and assignment statements within a WHERE construct, are often called
“masked” array assignments.
Fortran 95 feature not in Fortran 90
WHERE constructs may be nested, and may be named, in the same way as IF constructs. An example is Outer: WHERE (out)
a = b c = d
Inner: WHERE (narrow) e = f
Innermost: WHERE (inside) x = y
END WHERE Innermost g = h
END WHERE Inner i = j
WHERE (outlook) k = 1 END WHERE Outer
This consists of three WHERE constructs nested inside one another, and a WHERE statement that is within the outer WHERE construct. For simplicity no ELSEWHERE statements are included in this example, but in principle they could also occur.
In Fortran 95, a WHERE construct may contain a series of masked ELSE WHERE statements, like ELSE IF statements in the IF construct, as well as a non-masked ELSEWHERE that is analogous to ELSE. (Notice that, although Fortran does not distinguish between ELSE WHERE and ELSEWHERE, it is as well to follow the English language and use ELSE WHERE when another mask follows it, but ELSEWHERE for an unmasked catch-all final part of the construct).
In a Fortran 95 WHERE statement or WHERE construct, if the data is of a userdefined derived type, then the assignment must be an “elemental” assignment defined by an elemental subroutine (Section 11.3).
4.10
Arrays and intrinsic functions
Arrays can make use of many of Fortran’s intrinsic functions, and in this chapter we have already encountered the SQRT and LOG functions being applied to arrays. Functions that can be applied to arrays are known as “elemental” functions. Fortran’s numeric, mathematical and character functions are all elemental, and so are the bit manipulation functions (Appendix B). However, inquiry functions and so-called “transformational” functions that operate on whole arrays are not elemental. Examples of transformational functions are
ALL(masA) ANY(mask) COUNT(mask) MAXVAL(array) MINVAL(array) PRODUCT(array)
SUM (array)
where the arguments are one-dimensional arrays but the results returned are scalars. The use of multidimensional arrays with these functions is slightly more complicated and additional arguments may be applicable.
The names of these functions make it easy to remember what they do. The argument of ALL must be a logical array, and the result is a scalar of logical type and is true if and only if all elements of the argument are true. The function ANY likewise returns a true result if any of its elements is true. The function COUNT has an integer value equal to the number of true elements in its argument. MAXVAL and MINVAL operate on arrays that may be of real or integer type, and the results are the highest (MAXVAL) or lowest (MINVAL) values to be found among the elements. So,
MINVAL ((/5, 99,0,-5, 1/))
is equal to –5. The functions PRODUCT and SUM operate on arrays that may be real, integer or complex and the result is the product of all the elements or the sum of them. Thus,
PRODUCT((/(0.0,1.0),(0.0,1.0)/)) is equal to (–1.0,0.0).
Note that MAXVAL, MINVAL, PRODUCT and SUM have values that are of the same data type as their arguments. For example,
SUM((/1,–1,1,–1/))
is equal to the integer 0 because the argument is an integer array. Because these functions are not specific to a single data type they are called “generic” functions.
4.11 Exercises 4.B 4.B
1
What are the substrings
(i) "mulligatawny" (7:8) (ii) "mulligatawny" (1:4) (iii) "mulligatawny" (6:6) (iv) "mulligatawny" (10:8) 4.B
2
Write type declaration statements to declare
(i) A zero-length string called null;
(ii) Three strings, each of length 24, called s1, s2 and s3;
(iii) A character-string constant named me whose value is your surname;
(iv) A named character constant of length 1 called bs whose value is the backslash character (\).
4.B 3
Write statements, which should be as concise as possible, to set up the following one-dimensional arrays:
(i)(/.TRUE., .FALSE., .TRUE., .FALSE., …, (repeated for 50 pairs) /);
(ii)(/"a","ab","abr","abra", …, "abracadabra"/);
(iii) The array razamatazz but with its elements in reverse order and omitting every third element, where razamatazz is the name of a size-30 vector.
4.B 4
Write a program to read a list of ten real numbers (real parts) and then another list of the same length (imaginary parts) and then to form the array of complex numbers comprised by the corresponding real and imaginary parts. Then calculate which of the complex numbers has the greatest magnitude, and write it. (Note that CMPLX and ABS are elemental functions.)
4.B 5
If the array s is given by
CHARACTER (5) : : S (6)
s = (/"light","trick","witch","hazel","beach","shore"/) use structure constructors to write down the values of
(i) s(1:2) (ii) s(5:) (iii) s(4:3 :–1)
(iv) s(1:2) (3:4) (v) s(1) (5:5) (vi) s(5:1:–1)(5:)
For example, the answer to (vi) is (/"ch", "e1", "ch", "ck", "ht"/).
4.B 6
Write a program to input a string of up to 80 characters that will be interpreted as a line of text. Obtain the words from this string (a “word” being any sequence of letters with non-letter characters at both ends of the sequence) and search the words for palindromes. A palindrome is a word that reads the same in both directions, like “deed”. The program should write out any palindromes that it finds.
4B.
7
Write code to convert a sequence of x, y, z coordinate triplets into the corresponding sequence of radial coordinates (r, θ, φ), and then write out the radial coordinates of the point nearest to the origin. (Hint:
look up MAXLOC in Appendix F.) 4.B
8
Use a WHERE statement to halve all the elements of an integer array that are even numbers.
4.B 9
Write a program to input a string of up to 20 characters and replace any blank in the string (but not trailing blanks) by an asterisk. Then write out the string, excluding trailing blanks.
4.B 10
Write code to take a string that is a series of words (separated by blanks) and reduce it to a string made of their capital letters (e.g. "North Dakota" being reduced to "ND").
4.B 11
Use a WHERE construct to manipulate three two-dimensional square arrays (i.e. matrices) of real type called a, b and c. The object is to double the values of the elements of a that correspond in position to negative elements of b, unless the corresponding element of c is also negative, in which case it is the element of b that is doubled in value; and subsequently it is necessary to replace each element of b by the greater of the corresponding elements then to be found in a and c.
4.B 12
Use a WHERE construct to look at an integer-valued array called nibble and form a real-valued array called chew. The elements of chew are to be the square roots of the elements of nibble where the latter are positive and odd, but the cube roots in all other cases.
4.B 13
Given an array of 100 real numbers, called data, write a WHERE construct to halve each element of data that exceeds the average value of all of the elements initially in data, and to double those which are less than the average.
4.B 14
Write another WHERE construct, again looking at the array data as in the previous example, but halving each element that is closer in value to the largest than to the smallest, and doubling those closer in value to the smallest.
4.B 15
Again looking at the array data of the previous two examples, write a statement which, if any element of data is negative, will add to each element a number (the same for each element) sufficient to bring the value of the lowest element up to zero.