この文書の URL は http://www.cc.kyoto-su.ac.jp/~mtkg/lecture/comp_B/2012/05.html です。
配列とは
数学では行列 A の要素を表すのに添字を使って A2,3 (行列 A の (2, 3) 要素)や Ai,j (行列 A の (i, j) 要素)といった書き方をします。 Fortran の「配列」というデータ構造を使うと、プログラムの中でこれとまったく同じ書き方をすることができます。 FORTRAN 77 から Fortran 90 への改訂では配列機能が大幅に強化され、便利になりました。
配列の宣言
配列を定義するには、配列名、次元の数 (rank)、各次元の寸法 (bound) を型宣言文で宣言します。
型名 :: 配列要素名 (d1, d2, ..., dn)
di
は寸法宣言子で添字の範囲を [lower:]upper
の形式で指定します。
lower
は寸法の下限、
upper
は上限です。
lower
は省略可能で、省略すると 1 が指定されたものとみなされます。
以下に配列宣言の例を示します。
integer :: a(10) ! a(1:10) と同じ integer :: b(0:9), c(-2:2) real(8) :: g(3, 3) ! g(1:3, 1:3) と同じ real(8) :: h(0:2, 1:3)
添字の範囲はそれぞれ次のようになります。
a(1), a(2), a(3), …, a(10) b(0), b(1), b(2), …, b(9) c(-2), c(-1), c(0), c(1), c(2) g(1, 1), g(1, 2), g(1, 3) g(2, 1), g(2, 2), g(2, 3) g(3, 1), g(3, 2), g(3, 3) h(0, 1), h(0, 2), h(0, 3) h(1, 1), h(1, 2), h(1, 3) h(2, 1), h(2, 2), h(2, 3)
配列の参照と部分配列
配列の要素を参照するには整数式で位置(添字)を指定します。 もちろん、添字は配列宣言子の下限以上かつ上限以下でなくてはいけません。 (範囲外の要素にアクセスするとエラーになります。)
a(3) b(i+1) g(i, j)
Fortran 90 以降では個々の要素だけでなく、ある範囲を参照することができます。 これを「部分配列」といいます。 各次元の範囲は次のように指定します。
[lower]:[upper][:stride]
lower
または upper
が省略されると、配列宣言に用いられた下限と上限が指定されたものとみなされます。
stride
が省略された場合は 1 が仮定されます。
stride
には負の整数を指定することもできます。
a(1:5) ! a(1), a(2), ..., a(5) を要素とする1次元配列 a(1:10:2) ! a(1), a(3), ..., a(9) を要素とする1次元配列 a(:) ! 1次元配列 a の全体 g(1, 1:3) ! g(1,1), g(1,2), g(1,3) を要素とする1次元配列 h(0:1, 2:3) ! h(0,2), h(0,3), h(1,2), ..., h(1,3) を要素とする2次元配列
配列定数
1次元の配列定数は配列生成子で作ります。
配列生成子は (/
と /)
で要素を囲んだものです。
各要素はカンマ ,
で区切ります。
(/ 1, 2, 3, 4, 5 /) (/ 1.0d+0, 1.0d+1, 1.0d+2, 1.0d+3, 1.0d+4 /)
2次元以上の配列定数を作成するには、1次元の配列定数を reshape
関数で変形します。
説明は省略します。
do 形並び (implied-do list)
do 形並びを利用すると、配列定数をプログラム中で作成したり、配列の初期化を行うことができます。 一般に do 形並びは
(dlist, 整数型変数=e1, e2 [, e3])
のように書かれます。
dlist
は変数名、配列要素名、または do 形並びをコンマで区切って並べたものです。
e1
, e2
, e3
は do 文の初期値パラメータ、終値パラメータ、増分パラメータと同じです。
配列定数の作成
do 形並びを使うと配列定数を作ることができます。
i
を整数型変数とします。
(/ (i, i=1, 5) /) ! (/ 1, 2, 3, 4, 5 /) (/ (0.1*(i+10), i=1, 10, 2) /) ! (/ 1.1, 1.3, 1.5, 1.7, 1.9 /) (/ ((i, i=1, 3), j=1, 2) /) ! (/ 1, 2, 3, 1, 2, 3 /)
入出力文
do 形並びは read 文や write 文の入出力並びとして使うことができます。
read(*,*) (v(i), i = 1, 5) do i = 1, 10 write(*,*) (x(i, j), j = 1, 10) end do
は次のように要素を並べたものと同じです。
read(*,*) v(1), v(2), v(3), v(4), v(5) do i = 1, 10 write(*,*) x(i, 1), x(i, 2), x(i, 3), ..., x(i, 10) end do
Fortran 90 では部分配列を使って次のように書くことができます。
read(*,*) v(1:5) do i = 1, 10 write(*,*) x(i, 1:10) end do
配列の初期化
配列の初期化にはいくつかの方法があります。
すべての要素を同じ値に設定する場合
変数宣言に =
を用いて初期値を指定すると、すべての要素に同じ値が設定されます。
integer :: nx(10) = 0 ! すべての要素を 0 に設定 real(8) :: mx(3, 3) = 1.0d+0 ! すべての要素を 1.0d+0 に設定
配列定数で初期化する
配列の各要素に異なる初期値を設定したい場合は配列定数を使います。
integer :: nx(5) = (/ 0, 1, 2, 3, 4 /) real(8) :: vx(0:2) = (/ 1.0d+0, 3.0d+0, 5.0d+0 /)
配列定数の要素を指定するのに do 形並びを利用することができます。 do 形並びのための作業変数(整数型変数)の宣言が必要です。
integer :: i integer :: ny(5) = (/ i*5, i = 1, 5 /) ! ny(1) = 5, ny(2) = 10, ..., ny(5) = 25
data 文で初期化する
data 文を使って各要素に異なる値を設定することもできます。
integer :: nz(5) data nz/1, 10, 100, 1000, 10000/ ! nz(1) = 1, nz(2) = 10, ..., nz(5) = 10000
2次元以上の配列を初期化する場合は要素の並べ方に注意しましょう。
! A = ( 1 2 3 ) で初期化する ! ( 4 5 6 ) integer :: ma(2, 3) data ma / 1, 4, 2, 5, 3, 6 /
並べ方については次回の「配列要素順序」で説明します。
練習問題
次のプログラムを作成して、配列が正しく初期化されたことを確認しましょう。
program work0501 implicit none integer :: i integer :: nx(5) = 10 integer :: ny(5) = (/ 1, 2, 3, 4, 5 /) integer :: nz(5) = (/ i**2, i = 1, 5 /) integer :: ma(2, 3) data ma / 1, 4, 2, 5, 3, 6 / do i = 1, 5 write(*,*) nx(i), ny(i), nz(i) end do do i = 1, 2 write(*,*) ma(i, 1:3) ! write(*,*) ma(i, 1), ma(i, 2), ma(i, 3) と同じ end do stop end program work0501
配列に値を読み込む
配列に値を読み込み、それを逆順に並び替えてみましょう。
program work0502 implicit none integer :: nx(5), i, tmp write(*,*) 'Input 5 integers:' read(*,*) nx(1:5) ! nx(1) と nx(5), nx(2) と nx(4) の値を交換する do i = 1, 2 tmp = nx(i) nx(i) = nx(6-i) nx(6-i) = tmp end do do i = 1, 5 write(*,*) nx(i) end do stop end program work0502
実行すると次のようになります。
% ./a.out Input 5 integers: 1 3 4 6 9 9 6 4 3 1
部分配列と入出力文
部分配列を read 文や write 文とともに使うことができます。
read(*,*) nx(1:5)
は
read(*,*) nx(1), nx(2), nx(3), nx(4), nx(5)
と同じ意味になります。 do 形並びを使って
read(*,*) (nx(i), i = 1, 5) ! FORTRAN 77 までの書き方
と書くこともできますが、Fortran 90 以降では部分配列を使った方がよいでしょう。
値の交換方法
x と y の値を入れ替えるのに
x = y y = x
としてはいけません(どういうことが起こるか考えてみましょう)。 値を交換する場合は作業変数をひとつ用意して
tmp = x ! x の値を作業変数に保存 x = y ! x に y の値を代入 y = tmp ! 保存しておいた x の値を y に代入
とする必要があります。
配列の基本演算
Fortran 90 以降では加減乗除やべき乗、多くの組込み関数が配列に対応しています。 ただし、配列に関する計算では
- 配列は形状(階数と寸法)が同じでなければならない
- 配列とスカラーの演算では、すべての要素に対して同じ計算が行われる
ことに注意が必要です。
配列演算
配列を次のように宣言しておきます。
real(8) :: a(10, 10), b(10, 10) ! 10x10 行列 real(8) :: x(5), y(5) ! 5 次元ベクトル
配列全体やその一部(部分配列)を対象とした計算が可能です。
- a ! a の全要素に -1 をかける: - a(i, j) a * b ! a と b の対応する要素をかける: a(i, j) * b(i, j) x + 1 ! x の全要素に 1 を加える: x(i) + 1 a**2 ! a の全要素を2乗する: a(i, j)**2 1 / x + a(1, 2:6) ! 1/x(i) + a(1, i+1) (i=1, 2, ..., 5) a == b ! a(i, j) == b(i, j) がすべての i, j について成り立てば真、 ! そうでなければ偽 abs(x) ! abs(x(i)) sin(a) * cos(b) ! sin(a(i, j)) * cos(b(i, j))
a * b
は行列の積ではありません。
注意しましょう。
また 1 / x
や a * b
と書くと x や a, b がスカラーなのか配列なのか、これを見ただけでは判断できません。
配列であることを強調するために 1 / x(:)
, a(:,:) * b(:,:)
と書く流儀もあります。
配列代入
配列演算の結果を同じ形状の配列に代入することができます。
a = 0 ! a の全要素に 0 を代入する: a(i, j) = 0 a = a + 1 ! a の全要素に 1 を加える: a(i, j) = a(i, j) + 1 a(5, 6:10) = x ! a(5, 6:10) に x(1:5) をコピーする: a(5, i+5) = x(i) y = sin(x) ! y(i) = sin(x(i))
配列要素の対応
配列要素の対応は添字の番号ではなく各次元での位置(何番目の要素であるか)で行われます。 例として
real(8) :: a(10, 5), b(0:9, 0:4), c(11:21, 11:15)
という配列を考えましょう。 これらの配列は、添字の範囲は異なりますが、すべて同じ形状になっており、配列演算・配列代入が可能です。
c = a + b
という配列演算では次のように要素が対応します。
do j = 1, 5 do i = 1, 5 c(i+10, j+10) = a(i, j) + b(i-1, j-1) end do end do
ただし、実際の計算順序(どの要素から計算が計算が行われるか)は不定です。
配列代入の注意点
配列代入は「右辺がすべて評価されてから行われる」ことに注意が必要です。
integer :: nx(5) = (/ 1, 2, 3, 4, 5 /) nx(2:5) = nx(1:4)
を実行すると配列 nx
は (/ 1, 1, 2, 3, 4 /)
となります。
この配列代入文を
do i = 2, 5 nx(i) = nx(i-1) end do
と理解してはいけません。 do ループの場合、代入は逐次的に行われるため、
nx(2) = nx(1) nx(3) = nx(2) nx(4) = nx(3) nx(5) = nx(4)
が上から順に評価され、最終的に nx
は (/ 1, 1, 1, 1, 1 /)
となります。
練習問題
「配列代入の注意点」を確認するためのプログラムを作成せよ。
最大値を見つける
次の例は、キーボードから5つの整数を入力し、その最大値を求めるプログラムです。
program work0503 implicit none integer :: nx(5), i, tmp write(*,*) 'Input 5 integers:' read(*,*) nx(1:5) ! 最大値を見つける tmp = nx(1) do i = 2, 5 if (nx(i) > tmp) tmp = nx(i) end do write(*,*) tmp stop end program work0503
まず tmp
に nx(1)
の値を代入しておきます。
do ループを使って nx(2)
, nx(3)
, ..., nx(5)
との大小比較を行い、
tmp
より大きい値が見つかったら tmp = nx(i)
によって tmp
を更新します。
組込み関数 maxval
Fortran 90 以降では、組込み関数 maxval(配列名)
を使って配列の最大値を求めることができます。
tmp = maxval(nx) ! nx(1:5) の最大値
最小値を見つける組込み関数 minval
も用意されています。
配列の総和
組込み関数 sum
を使うと配列の要素の和を求めることができます。
配列は何次元配列でも構いません。
キーボードから5つの整数を入力し、その総和を求めるプログラムを作ってみましょう。
program work0504 implicit none integer :: nx(5) write(*,*) 'Input 5 integers:' read(*,*) nx(1:5) write(*,*) sum(nx) stop end program work0504
この場合、
sum(nx)
は次の計算と等価です。
integer :: nx(5), i, tmp : tmp = 0 do i = 1, 5 tmp = tmp + nx(i) end do
sum
の引数に部分配列を与えると、配列の一部の和を求めることができます。
integer :: data(100, 100) : sum(data) ! すべての要素の和 sum(data(1, 1:10)) ! 1 行 1-10 列の和 sum(data(1:10, 5:10)) ! 1-10 行・5-10 列の和
本日の課題
1. 最小値を見つける
キーボードから5つの整数を入力し、その最小値を求めるプログラムを作成せよ。 do ループを利用すること。
2. 最小値・最大値を見つける
キーボードから5つの整数を入力し、その最小値と最大値を求めるプログラムを作成せよ。
組込み関数 minval
, maxval
を利用すること。
3. 平均を求める
キーボードから5つの整数を入力し、その平均値を求めるプログラムを作成せよ。
組込み関数 sum
を利用すること。
チャレンジ問題
1. 内積
2つの4次元ベクトル x, y をキーボードから読み込み、内積 x.y を計算するプログラムを作成せよ。 x, y は real(8) 型の配列とする。
2. パスカルの三角形
以下の下三角行列はパスカルの三角形と呼ばれる。
1 0 0 0 … 0 1 1 0 : : 1 2 1 0 1 3 3 1 0 1 4 6 4 1 0 : 1 5 10 10 5 1 0 1 6 15 20 15 6 1
この行列を配列 p(0:9, 0:9)
に作れ。
上三角行列はすべて 0 とし、第 0 列と対角要素をすべて 1 とすると、他の要素はすべて真上と左上の要素の和になる。
添字が 1 ではなく 0 から始まることに注意すること。
3. 数列の和
フィボナッチ数列の第1項から第30項までの和を求めるプログラムを作成せよ。 初項と第2項は f1 = 1, f2 = 1 とする。
4. Newton 法
キーボードから実数 a を与え、方程式 tan(x) = a をNewton 法で解くプログラムを作成せよ。 漸化式は次で与えられる。 初期値 X0 は 0 とせよ。
Xn+1 = Xn - (tan(Xn) - a) * cos(Xn)**2