コンピュータ基礎および演習II

例題

繰り返しプログラムの構造

繰り返しにはいくつかの書き方がありますが、 今回は while 文をとりあげます。 while 文の文法的な説明は「絵本」p.48、 および「エッセンシャル」pp.107〜108のとおりです。

繰り返しを用いたプログラムでは、 文法的な知識として覚えることはそれほど多くありません。 それよりも、プログラムの書き方についての感覚をつかむことの方が重要です。 また、変数をどう使えば良いかも要点になります。

プログラムの例を読むことによって、 繰り返しを使ったプログラミングの感覚をつかむのです。

- 例題 1 : 1 から n までの合計値を求める

繰り返しのあるプログラムはプログラムの同じ場所を 何回も実行することになります。 同じ場所を通るたびに変数の値が少しずつ変わり、 最終的に結果が求まります。 ここで、各変数の値はある規則にしたがって変化していくことを きちんと理解する必要があります。

次の例題を用いて、プログラムの理解の仕方を説明しましょう。 ファイル名は SumOneToN.java としています。

このプログラムは指定された整数 n について、 1 から n までの和を出力するものです。たとえば n に 10 を代入しておくと、 55 と出力します。

while 文に入って s = s + 1;を行う直前を (a) 点、 s = s + 1;を行い、i = i + 1;を行う直前を (b) 点、 i = i + 1;の直後を (c) 点とします。

このプログラムの理解の仕方として、以下の二とおりの方法を説明します。

方法1 - トレース

これは、プログラムの実行時を想定して、変数の値がどう変わっていくか、 if 文の場合はどちらの分岐を実行するのか、繰り返しの場合は何回まわるか、 等に注目してプログラムの動きを忠実に追っていく方法です。

例題のプログラムで n が 3 だったときを想定して、 プログラムの動きを追ってみましょう。

実行中の場所 sの値 iの値
s = 0;の後 0
i = 1;の後 0 1
while (i <= n) 繰り返しを続ける
s = s + 1;の後 1 1
i = i + 1;の後 1 2
while (i <= n) 繰り返しを続ける
s = s + 1;の後 3 2
i = i + 1;の後 3 3
while (i <= n) 繰り返しを続ける
s = s + 1;の後 6 3
i = i + 1;の後 6 4
while (i <= n) 繰り返しを打ち切る

このように、 確かに 1 から n までの和が求まることが納得できます。

トレースは最も単純で明快な方法と言えます。 例を使ってプログラムを理解する方法は、初心者には一番分かりやすいでしょう。 しかし、プログラムが複雑になり多数の変数や条件が絡み合ってくると、 この方法での理解は限界があります。

方法2 - 変数の意味を言葉で述べる

プログラムの理解の鍵は、変数の意味をしっかり把握することです。 「プログラムのこの地点では、この変数にこういう値が入っている。」 と言えるようにするのです。たとえば、前回の x, y, z の最大値を求めるプログラムも、 この原則を適用すると理解の助けになったはずです。

例題のプログラムでは、繰り返しの中に 2 つの変数 si が現れます。 変数 i は 1 から n まで 1 ずつ増えていきます。 これはすぐに分かります。 1 から n までの和を求めるのですから、 1 から n までの値を 1 回ずつ登場させるのは当然です。

変数 s の方はどうでしょう。 「s の値は 1 から i-1 までの和が入っている。」 と言えます。

厳密には、(a) 地点と (c) 地点では 1 から i-1 までの和であり、 (b) 地点では 1 から i までの和です。 また、少し考えると while 文から抜けたときの i の値は、 n+1 であることがわかります。 ここでも s が 1 から i-1 の和という意味が成立しています。 i を n+1 で置き換えると、 最終的には s は 1 から n までの和ということになり、 所期の目的が達成できていることが分かります。

もっとおおざっぱに、 i を 1 ずつ増やしながら途中までの和を求め、 その値を s に代入している、というのでも十分かもしれません。

このように、変数に入っている値の意味を言葉で言えるようにすることが重要です。 プログラムを書くときは、この考え方を逆に利用します。 最初に、 プログラムのある地点で、 変数にこんな意味の値が入っているようにしようと決め、 その値を変数に入れるにはどんな計算をすれば良いかを考えるのです。

- 例題 2 : 素数かどうか判定するプログラム

このプログラムは n が素数か否か判定するプログラムです。 ファイル名は IsPrime.java としています。

素数かどうかの判定にはいろいろな方法がありますが、 ここでは、 n を 2, 3, 4, … の順に n-1 まで割ったとき、 すべての数で割り切れない場合、素数と判定することにします。

このプログラムで注目することは、 次の 2 つ条件を満たしているとき繰り返しをつづけるということです。

このプログラムでは 1 つ目の条件を while 文の評価式に書いています。 また、 2 つ目の条件を if 文として書き、条件が成立したとき、 break; 文で繰り返しを打ち切るようにしています。 このように、繰り返しの途中で処理を打ち切りたいケースでは、 break; 文を用いると処理の流れを簡潔に記述することができます。

boolean 型の変数 isPrime は、 素数か否かの結果をいったん入れておく変数です。 最初は false にしておき、素数だとわかると (n2 から n-1 まで割り切れないと) true にします。 最後の if 文で isPrime の値によって出力を変えています。

- 例題 3 : 二重の繰り返しでグラフを描く

y = x2 のグラフを描きます。 ただし、普通のグラフの描き方を 90 度回転したものです。 グラフのようなものはグラフィック表示にすることが望ましいのですが、 今のところ文字による表示法しか学んでいません。 そこで、文字を並べることによってグラフらしきものを表示することにします。

このプログラムのファイル名は Graph.java としています。

このプログラムは while 文が二重になっています。 while 文の中に while 文が含まれています。

外側の while 文では、 x の値を -8 から 8 まで 1 ずつ増やしながら繰り返します。 x の値が 9 になったら繰り返しを終了します。

内側の while 文は、 iy より小さい間、 i の値を 0 から 1 ずつ増やしていき、 iy と等しくなった時点で終了になります。 例えば y の値が 3 だったとき、 i の値は 0, 1, 2 と増えていき 3 になった時点で終了します。 要するに、この while 文の中を y の値と同じ回数だけ繰り返すということです。

この繰り返しのたびに System.out.print(" ") を実行しているので、 y の値の数と同じ個数の空白を出力することになります。 これによって、繰り返しが終った後に表示している * は、 画面左端からの距離が y の位置に表示されます。 * の表示には System.out.println を用いているため改行されるので、 x を 1 増やしたときの y の値に相当する * は、 次の行に表示されるようになります。

さて、ここで内側の while 文に注目してみます。

i = 0;
while (i < 回数) {
    処理
    i = i + 1;
}

このプログラムの意味は、 while 文の中の「処理」を「回数」ぶん繰り返すということです。 このような書き方は実際のプログラミングでもよく使うパターンです。 覚えておきましょう。

- 例題 4 : カンマ区切り書式付き出力

1 から n までの数をカンマ「,」区切りで並べて表示するプログラムを考えます。 例えば n を 10 とすると、次のような出力になります。

1,2,3,4,5,6,7,8,9,10

これまでの例題が理解できた人なら、「10回繰り返せばいいんでしょ? 簡単簡単!」 と思うかもしれません。でもよく見てみましょう。カンマは9個しかありませんよ?

方法1 - 前判定

この問題の解答は何通りも考えられますが、 while文を使って素直に書くと例えば以下のプログラムになります。 ファイル名は SeparatedByComma1.java としています。

class SeparatedByComma1 {
    public static void main(String[] args) {
	int i = 1;
	int n = 10;
	
	while(i < n) {
	   System.out.print(i);
	   System.out.print(",");
	   i++;
	}
	System.out.print(i);
	System.out.println();
    }
}

このプログラムでは、n-1 回目の出力までは数字の後にカンマを出力し、 n 回目だけは数字のみ出力します。

このプログラムは正しく動きますが、問題点が1つあります。 それは、数字を出力する処理 System.out.print(i) が2箇所に存在する点です。 もしプログラムの出力を以下のように変更したくなったとしましょう。

i=1,i=2,i=3,i=4,i=5,i=6,i=7,i=8,i=9,i=10

これは数字を出力する部分を System.out.print("i=" + i) に変更するだけで実現できるのですが、 2箇所を修正しなくてはならないというのは間違いの原因となります。

方法2 - 繰り返しの途中で終了条件を判定

break をうまく使った以下のプログラムでは、方法1の問題点を解決しています。 ファイル名は SeparatedByComma2.java としています。

class SeparatedByComma2 {
    public static void main(String[] args) {
	int i = 1;
	int n = 10;
	
	while(true) {
	    System.out.print(i);
	    if(i == n)
		break;
	    System.out.print(",");
	    i++;
	}
	System.out.println();
    }
}

数字を出力する System.out.print(i) は1箇所のみとなっています。

この例題では

という2つの処理を繰り返しますが、 それぞれの処理が同じ回数繰り返されるわけではありません。 このような場合、繰り返しの最初(while文)や最後(do while文) で繰り返しの終了条件を判定していたのでは、繰り返しの回数は等しくなり、 うまくいきません。繰り返しを終了する場所を工夫する必要があります。