IT pass HikiWiki - [itbase2017]Fortran 実習 繰り返し Diff

  • Added parts are displayed like this.
  • Deleted parts are displayed like this.

{{toc}}

プログラムでは, 同じ処理を何度も何度も何度も何度も繰り返して実行することが良くあります.
例えば, 長期間にわたる観測データを処理したいときには,
数値の数だけプログラムを実行することはできません.
そのような時に必要なのが繰り返しの処理です.
そして, このような同じ作業の繰り返しは計算機の得意分野でもあります.

Fortran では, 繰り返しの方法がいくつか提供されています.
まずは, 最も基本的な繰り返しの方法である do 文 (do ループ) を説明します.

= do 文

do 文は例えば下のように書きます.

  do <ループ変数> = <繰り返し開始点>, <繰り返し終了点> (, <繰り返しの間隔>)

    実行文

  end do

<繰り返しの間隔> は省略可能なため, 括弧を付けてあります.

<繰り返し開始点>, <繰り返し終了点>, <繰り返しの間隔> の説明には,
具体的なプログラム例を見てもらう方が早いでしょう.

下のようなプログラムを doloop.f90 というファイル名で作成して実行してみましょう.

  program doloop

    implicit none

    integer :: i

    do i = 1, 20
      write( 6, * ) i
    end do

  end program doloop

上の例では,
<ループ変数> が i で,
<繰り返し開始点> が 1,
<繰り返し終了点> が 20 です.
<繰り返しの間隔> は省略されています.
<繰り返しの間隔> が省略された場合には 1 とみなされます.

このプログラムをコンパイルして作った実行ファイル doloop を
実行すると下のようになります.

  $ ./doloop
           1
           2
           3
           4
           ...
          17
          18
          19
          20

つまり, <ループ変数> の i が 1 から 1 ずつ増えて 20 になるまで
do 文の中の実行文が繰り返して実行されます.
上の例では, 繰り返しの中にある write 文で, <ループ変数> が 1 から 20 まで
1 ずつ増えながら実行されていることがわかります.

= 多重ループ

do 文は例えば下のように多重にすることもできます.

下のようなプログラムを doloop2.f90 というファイル名で作成して実行してみましょう.

  program doloop2

    implicit none

    integer :: i, j

    do j = 1, 3
      do i = 1, 2
        write( 6, * ) i, j
      end do
    end do

  end program doloop2

二重になった do 文がどのような順番で実行されるか確認できましたか?


= 総和の計算

繰り返しは様々な場面で使いますが, 最もよく使う例の一つは,
和を計算することです.

下のようなプログラムを dosummation.f90 というファイル名で作成して実行してみましょう.

  program dosummation

    implicit none

    integer :: i
    integer :: num              ! 変数 num を用意

    num = 0                     ! 変数 num を初期化

    do i = 1, 10
      num = num + i             ! 繰り返しながら i の値を num に足す
      write( 6, * ) i, num
    end do

    write( 6, * ) "Summation: ", num

  end program dosummation

このプログラムでは, 1 から 10 までの整数の合計を計算しています.

do 文による繰り返しで, i を 1 ずつ増やしながら, その値を

      num = num + i

で, num にひとつずつ足しています.
そして, do 文による繰り返しが終わった時には, 1 から 10 までの総和が num に
入っているのです.

ここで重要なことは, do 文の前に

    num = 0

とすることで, 合計を入れる予定の変数, num, にゼロを入れて
おく (初期化しておく) ことです.
一般に, 変数が宣言されたあとに, それに値を代入するまでは,
変数にどんな値が入っているかはわかりません.
どのような値が入っているかは, コンパイラの種類などに依っていて,
場合によっては, 予想もしないとんでもない数値が入っている可能性
もあります. (コンパイラによっては, 宣言された変数を初期化してくれる
(例えば数値であればゼロを入れてくれる)こともありますが, これは
余計な処理になりますので, プログラムを遅くする原因になることも
あり, 好まれないこともあります.)
従って, 確実に 1 から 10 の総和を num で計算するには, 使う前に
num にゼロを代入しておく必要があります.


= 繰り返し途中での中止

do 文で繰り返ししている途中で, 計算の状況に応じて繰り返しを
やめたい (do ループから抜けたい) こともあります.
そのような時には exit 文を使うことができます.

上の dosummation.f90 を編集し, 下のように exit 文を追記して実行してみましょう.

  program dosummation

    implicit none

    integer :: i
    integer :: num              ! 変数 num を用意

    num = 0                     ! 変数 num を初期化

    do i = 1, 10
      num = num + i             ! 繰り返しながら i の値を num に足す
      write( 6, * ) i, num
      if ( num >= 11 ) exit     ! 追記した exit 文
    end do

    write( 6, * ) "Summation: ", num

  end program dosummation

プログラムの動作はどのように変わったでしょうか?
exit 文を加えたことで, 和が 11 以上になったら
それ以上の計算をやめていることがわかりますか?


= 繰り返し回数が予め決まっていない時の繰り返し

上で示した do 文の使い方は基本形で,

  do <ループ変数> = <繰り返し開始点>, <繰り返し終了点> (, <繰り返しの間隔>)

    実行文

  end do

のように, 繰り返し回数が分かる書き方でした.
しかし, プログラムで行う繰り返しには, 何回繰り返せばよいのかが
事前にはわからないこともあります.
そのような時に使う方法を二つ紹介しておきます.

== do while 文

do while 文は do 文に条件分岐を組み合わせた命令で, 下のように使います.

  do while ( 条件文 )

    実行文

  end do

この文の意味は, 条件文が満たされている間は繰り返す, です.
do while 文を使ったプログラムの例を下に示します.

下のようなプログラムを dowhile.f90 というファイル名で作成して実行してみましょう.

  program dowhile

    implicit none

    integer :: i
    integer :: num              ! 変数 num を用意

    num = 0                     ! 変数 num を初期化

    i = 1                       ! 繰り返し変数 i を初期化

    do while ( num < 11 )       ! 条件が満たされている間は繰り返す
      num = num + i             ! 繰り返しながら i の値を num に足す
      write( 6, * ) i, num
      i = i + 1                 ! 繰り返し回数を更新
    end do

    write( 6, * ) "Summation: ", num

  end program dowhile

このプログラムでは, num が 11 よりも小さい間は繰り返しが実行されます.
(逆に言えば, num が 11 以上になると繰り返しが終了します. 結果的に,
このプログラムは上に示した exit を用いたプログラムと同じように動作します.)
このように, 繰り返し回数が事前にわからない時にも, 繰り返し中の
条件分岐で繰り返し回数をプログラムの実行中に決めることができるわけです.

=== 「無限ループ」についての注意

ただし, このプログラムには重要な注意点があります.
それは, 繰り返しの回数が事前にわからないとは言え, いつかは繰り返しが
終わるプログラムにしておくことです.

例えば, もし

    do while ( num < 11 )       ! 条件が満たされている間は繰り返す

の文を間違って

    do while ( num /= 11 )       ! 条件が満たされている間は繰り返す

と書き間違えると, このプログラムは終わりません.
なぜならば, num は 11 にはならないからで,
いつまでも繰り返しが終わらない「無限ループ」になってしまうからです.
(ちなみに, do while ( num /= 10 ) ならば終わります.)

プログラミングは間違えるものなので, いくらでも間違えながら
少しずつ直していけば良いでしょう.
また, 無限ループになっても, 計算機が壊れることはまずありませんので
安心して練習すると良いでしょう.
しかし, 気を付ける意識がないと改善できませんので, 頭には入れておきましょう.

また, 非常に重要なことは, 無限ループになった時にプログラムを
止める方法を知っておくことです.
これまでの実習で既に説明・体験した通り, コマンドの強制終了は C-c でした.
同じ方法をプログラムについても使うことができます.
(コマンドもプログラムですから同じです.)
例えば, 上で説明した

    do while ( num /= 11 )       ! 条件が満たされている間は繰り返す

としたプログラムでも, 実行中に C-c を押すと (しばらくして) プログラムは
停止します.

  $ ./dowhile
           1           1
           2           3
           3           6
           4          10
           5          15
          ...                      <- C-c を押す.
          ...
          ...
       12768    81517296
       12769    81530065
       12770    81542835
       12771    81555606
       12772    81568378
  ^C$

計算機の処理は速いので, C-c を押したときには計算機内では
既にかなり処理が進んでいて, ターミナルの表示はすぐには止まりませんが,
しばらくすると停止します.
(心配であれば, C-c を 2 度, 3 度を押してみても良いでしょう.)


== 繰り返し回数を指定しない do 文

do 文の使い方は,

  do <ループ変数> = <繰り返し開始点>, <繰り返し終了点> (, <繰り返しの間隔>)

    実行文

  end do

と説明しました.
上では, <繰り返し間隔> は省略できると説明しましたが,
実は, <ループ変数>, <繰り返し開始点>, <繰り返し終了点> をすべて
省略することもできます.
つまり,

  do

    実行文

  end do

となります.

しかし, これは do 文による繰り返しの中の実行文に exit がない場合に
確実に無限ループになりますので, 使い方には注意が必要です.
また, この do 文でできることは概ね do while 文で代替できるでしょう.
しかし, この do 文はこれはこれですっきりして使いやすい部分もあり,
適宜注意しながら使うと良いでしょう.


= do ループのラベル

do 文には, 下のようにラベルを付けることができます.

  <ラベル>: do <ループ変数> = <繰り返し開始点>, <繰り返し終了点> (, <繰り返しの間隔>)

    実行文

  end do <ラベル>

このラベルは, do 文が入れ子になっている時にも使えます.

ラベルは省略可能で, 書かなくても動作します.
しかし, 例えば下のような利点があります.
* どの do とどの end do が対応しているのか把握しやすくなる,
  * do 文の中の実行文が長くなった時に便利
  * do 文が多重に入れ子になった時に便利
* exit などでどの do ループを抜けるのかを指定することができる.

下のようなプログラムを dolabel.f90 というファイル名で作成して実行してみましょう.

  program dolabel

    implicit none

    integer :: i, j

    write( 6, '(a)' ) '(i,j)'

    iloop: do i = 1, 3
      jloop: do j = 1, 3

        if ( ( i == 2 ) .and. ( j == 3 ) ) then   ! (i,j) = (2,3) になったら繰り返し終了
          exit iloop
        end if

        write( 6, '(a,i1,a,i1,a)' ) '(', i, ',', j, ')'

      end do jloop
    end do iloop

  end program dolabel

このプログラムでは, iloop と jloop というラベルの付いた二つの do 文で
二重の繰り返しをしています.
そして, (i,j) = (2,3) の時に exit することで繰り返しを抜けていますが,
抜けるのは iloop の do 文です.

上のプログラムの

  exit iloop

を消した場合にどのように動作が変わるのか確認しましょう.
また,

  exit iloop



  exit jloop

に変えるとどのように動作が変わるのか確認しましょう.


= 出力と一緒に使う繰り返し

出力文に繰り返しを組み合わせて使うことができます.

下のようなプログラムを dowrite.f90 というファイル名で作成して実行してみましょう.

  program dowrite

    implicit none

    integer, parameter :: im = 2, jm = 3   ! 配列の大きさの宣言
    integer            :: i, j
    integer            :: array(im,jm)     ! 2x3 の配列の宣言

    do j = 1, jm                           ! 配列の値の準備
      do i = 1, im
        array(i,j) = i + 10*j
      end do
    end do

    do i = 1, im
      write( 6, * ) ( array(i,j), j = 1, jm ) ! それぞれの i に対して j を 1 から jm まで繰り返す
    end do

  end program dowrite

このプログラムをコンパイルして作った実行ファイル dowrite を
実行すると下のようになるでしょう.

  $ ./dowrite
          11          21          31
          12          22          32

上のプログラムの i のループのように do 文での繰り返しで write 文を
実行すると, 1 行ずつ縦に数値が出力されますが,
write 文に繰り返しを組み合わせると (上のプログラムの j のループ)
横に数値を出力することができます.


= 練習問題 1

上で説明した doloop.f90 を変更して,
<繰り返し間隔> を省略しない場合を試してみましょう.
上に示した doloop.f90 の do 文を下のように変更して実行してみましょう.

    do i = 1, 20, 5

繰り返し間隔が指定の通りになったことが確認できますか?

また, <繰り返し間隔> は負の値にすることもできます.
例えば, do 文を下のように変更してみましょう.

    do i = 20, 1, -1

結果はどうなりましたか?


= 練習問題 2

1 から 40 までの 4 の倍数を大きい方から小さい方に向かって順に
表示するプログラムを作りなさい.


= 練習問題 3

10! (10 の階乗) を計算するプログラムを作りなさい.