前回の授業では、選択というプログラムの制御構造を紹介しました。 今日は、 反復 ( iteration ) について説明します。 反復構造を表す文は、 繰返し文 ( repetitive statement ) 、または ループ文 ( loop statement ) とよばれます。
反復構造にはいくつか種類がありますが、「何々が成り立つ間、何々を繰り返す」というものが基本です。
この種の反復構造には
while文
(
while statement
)
を使います。
while
文は以下の形をとります。
while (condition) { statement; ... }
これで、条件
condition
が成り立っている間、文
statement
;
...
が繰り返し実行されます。
ここで、Good afternoon!と10回出力するプログラムを考えます。 もちろん、
System.out.println("Good afternoon!"); System.out.println("Good afternoon!"); System.out.println("Good afternoon!"); ... System.out.println("Good afternoon!");
と書けば10回出力できますが、これと同じことを
while
文で表すわけです。
はじめは数え上げ方式です。 これは、すでに繰り返した回数を覚えておく方法です。 つまり、変数 i に0を代入しておき、Good afternoon!と一回出力するごとに i の値を1増やすということを、 i の値が10未満の間繰り返します。
/* 1*/ class TenAfternoons { // こんにちは10回 /* 2*/ public static void main (String[] args) { /* 3*/ int i; // 繰り返した回数 /* 4*/ i = 0; /* 5*/ while (i < 10) { // iが10未満の間繰り返す /* 6*/ System.out.println("Good afternoon!"); /* 7*/ i++; /* 8*/ } /* 9*/ } /* 10*/ }
asiaa1:~/comp2b b08a001$ java TenAfternoons Good afternoon! Good afternoon! Good afternoon! Good afternoon! Good afternoon! Good afternoon! Good afternoon! Good afternoon! Good afternoon! Good afternoon! asiaa1:~/comp2b b08a001$
5行目から8行目までの
while
文は次のように実行されます。
while
文の実行を終える。次はカウントダウン方式です。 これは、これから繰り返す回数を覚えておく方法です。 つまり、変数 i に10を代入しておき、Good afternoon!と一回出力するごとに i の値を1減らすということを、 i の値が正の数の間繰り返します。
/* 1*/ class TenAfternoons2 { // こんにちは10回2 /* 2*/ public static void main (String[] args) { /* 3*/ int i; // 繰り返す回数 /* 4*/ i = 10; /* 5*/ while (i > 0) { // iが正の数の間繰り返す /* 6*/ System.out.println("Good afternoon!"); /* 7*/ i--; /* 8*/ } /* 9*/ } /* 10*/ }
5行目から8行目までの
while
文は次のように実行されます。
while
文の実行を終える。数え上げ方式とカウントダウン方式のどちらがよいかは、問題によって変わります。 数え上げ方式のほうがよく使われますが、カウントダウン方式が適切な場合もあります。
さて、上記のプログラムで、7行目を書き忘れたとしましょう。
/* 1*/ class InfiniteAfternoons { // こんにちは無限回 /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ i = 10; /* 5*/ while (i > 0) { // 永遠に条件が成立 /* 6*/ System.out.println("Good afternoon!"); /* 7*/ // i--; /* 8*/ } /* 9*/ } /* 10*/ }
このプログラムを実行すると、変数 i の値は10のままです。 したがって、条件 i > 0 がずっと成り立ち、永遠にGood afternoon!と出力され続けることになります。
このような、いつまでも続く繰り返しを、 無限ループ ( infinite loop ) とよびます。 反復構造を使うときは、無限ループにならないように気をつける必要があります。 なお、誤って無限ループに陥ったときは、Ctrl-Cで強制終了してください。
また、繰り返しの回数を0にすると、0回繰り返す、つまり、一度も実行されなくなります。
/* 1*/ class NoAfternoons { // こんにちは0回 /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ i = 0; /* 5*/ while (i < 0) { // 最初から条件が不成立 /* 6*/ System.out.println("Good afternoon!"); /* 7*/ i++; /* 8*/ } /* 9*/ } /* 10*/ }
このプログラムを実行すると、条件 i < 0 は初めから成り立たないので、何も出力されないのです。
反復構造の中には、繰り返す回数が明確なものがあります。
この場合、
ループ制御変数
(
loop control variable
)
とよばれる変数を増加(あるいは減少)させながら繰り返すのが一般的です。
for文
(
for statement
)
を用いると、このような反復構造をコンパクトに書き表すことができます。
for
文は次のような形をとります。
for (statement1; condition; statement2) { statement3; ... }
これで、まず文
statement1
が一度だけ実行されます。
そして条件
condition
が成り立っている間、文
statement3
;
...
と
statement2
が繰り返し実行されます。
while
文を使ってこの
for
文を書くと、次のようになります。
statement1; while (condition) { statement3; ... statement2; }
for
文を用いると、Good afternoon!と10回出力するプログラムは次のように書けます。
数え上げ方式については、
/* 1*/ class TenAfternoons3 { // こんにちは10回3 /* 2*/ public static void main (String[] args) { /* 3*/ int i; // ループ制御変数 /* 4*/ for (i = 0; i < 10; i++) { // 10回繰り返す /* 5*/ System.out.println("Good afternoon!"); /* 6*/ } /* 7*/ } /* 8*/ }
です。 カウントダウン方式は、
/* 1*/ class TenAfternoons4 { // こんにちは10回4 /* 2*/ public static void main (String[] args) { /* 3*/ int i; // ループ制御変数 /* 4*/ for (i = 10; i > 0; i--) { // 10回繰り返す /* 5*/ System.out.println("Good afternoon!"); /* 6*/ } /* 7*/ } /* 8*/ }
となります。
while
文と
for
文には本質的な差はありません。
繰返しの回数がはっきりしているときには
for
文、そうでないときには
while
文を使うのがよいでしょう。
ループ制御変数は、整数型にしてください。 実数型にすると、誤差の影響で、繰り返しの回数を間違える可能性があります。
/* 1*/ class ForIntTest { // for文で整数を使うテスト /* 2*/ public static void main (String[] args) { /* 3*/ int i; // 整数型 /* 4*/ for (i = 0; i < 10; i++) { // 10 回繰り返す /* 5*/ System.out.println(i); /* 6*/ } /* 7*/ } /* 8*/ }
asiaa1:~/comp2b b08a001$ java ForIntTest 0 1 2 3 4 5 6 7 8 9 asiaa1:~/comp2b b08a001$
/* 1*/ class ForDoubleTest { // for文で実数を使うテスト /* 2*/ public static void main (String[] args) { /* 3*/ double d; // 実数型 /* 4*/ for (d = 0.0; d < 1.0; d += 0.1) { // 11回繰り返してしまう /* 5*/ System.out.println(d); /* 6*/ } /* 7*/ } /* 8*/ }
asiaa1:~/comp2b b08a001$ java ForDoubleTest 0.0 0.1 0.2 0.30000000000000004 0.4 0.5 0.6 0.7 0.7999999999999999 0.8999999999999999 0.9999999999999999 asiaa1:~/comp2b b08a001$
なお、ループ制御変数は、 i , j , k などを使うのが一般的です。 変数名 i は、整数(integer)から来ているようです。
反復の使用例として、はじめに 1 + 2 + ... + 100 の計算を行います。
この問題は、レジの計算をまねることで解決できます。 つまり、それまでの合計を表す変数 sum を用意して、初期値を 0 とします。 そして、 sum の値を 1 増やし、2 増やし、…と繰り返し、100 増やした時点の sum の値が答になるわけです。 したがって、
sum += 1; sum += 2; ... sum += 100;
と同じことを
for
文で表します。
プログラムは次の通りです。
/* 1*/ class Summation100 { // 合計100 /* 2*/ public static void main (String[] args) { /* 3*/ int i, sum = 0; /* 4*/ for (i = 1; i <= 100; i++) { // 100回繰り返す /* 5*/ sum += i; /* 6*/ } /* 7*/ System.out.println(sum); /* 8*/ } /* 9*/ }
asiaa1:~/comp2b b08a001$ java Summation100 5050 asiaa1:~/comp2b b08a001$
次の例は、360 の約数をすべて求めるものです。
360 の約数が、1 から 360 までの間にあることは、数学的に分かっています。 したがって、1 から 360 までの数一つ一つに対して、360 がその数で割り切れるかどうかを判断すれば、約数がもれなく見つけられます。 つまり、
if (360 % 1 == 0) { System.out.println(1); } if (360 % 2 == 0) { System.out.println(2); } ... if (360 % 360 == 0) { System.out.println(360); }
と同じことを
for
文で表します。
プログラムは次の通りです。
/* 1*/ class Divisors360 { // 約数360 /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ for (i = 1; i <= 360; i++) { // 360回繰り返す /* 5*/ if (360 % i == 0) { /* 6*/ System.out.println(i); /* 7*/ } /* 8*/ } /* 9*/ } /* 10*/ }
asiaa1:~/comp2b b08a001$ java Divisors360 1 2 3 4 5 6 8 9 10 12 15 18 20 24 30 36 40 45 60 72 90 120 180 360 asiaa1:~/comp2b b08a001$
アプレットの例として、長方形を描きます。
以前、次のような図形を描きました。
/* 1*/ import java.applet.*; /* 2*/ import java.awt.*; /* 3*/ /* 4*/ public class BlackRect extends Applet { // 黒い長方形 /* 5*/ public void paint (Graphics g) { /* 6*/ g.setColor(Color.black); /* 7*/ g.fillRect(20, 40, 60, 80); /* 8*/ } /* 9*/ }
塗り潰した長方形を描くために、
g.fillRect(
...
);
を使っています。
ここではこれを用いず、自分で正方形を塗り潰すことにします。
長方形は、上から順にすきまなく水平線を引いていけば、塗り潰すことができます。 この場合は、
g.fillRect(20, 40, 60, 80);
の代わりに
g.drawLine(20, 40, 79, 40); g.drawLine(20, 41, 79, 41); g.drawLine(20, 42, 79, 42); ... g.drawLine(20, 119, 79, 119);
と同じことを実行すればよいわけです。
(
g.drawLine(20, 40, 80, 40);
だと、幅61ピクセルになってしまいます。)
プログラムは次の通りです。
/* 1*/ import java.applet.*; /* 2*/ import java.awt.*; /* 3*/ /* 4*/ public class FillRect extends Applet { // 塗り潰した長方形 /* 5*/ public void paint (Graphics g) { /* 6*/ int i; /* 7*/ for (i = 0; i < 80; i++) { // 80回繰り返す /* 8*/ g.drawLine(20, 40 + i, 79, 40 + i); /* 9*/ } /* 10*/ } /* 11*/ }
この方法を応用すると、色々な図形が描けるようになります。 例えば、次のような直角三角形を考えます。
この図形は、
g.drawLine(50, 50, 51, 50); g.drawLine(50, 51, 53, 51); g.drawLine(50, 52, 55, 52); ... g.drawLine(50, 149, 249, 149);
と同じことを実行すれば描けます。
プログラムは次の通りです。
/* 1*/ import java.applet.*; /* 2*/ import java.awt.*; /* 3*/ /* 4*/ public class RightTriangle extends Applet { // 直角三角形 /* 5*/ public void paint (Graphics g) { /* 6*/ int i; /* 7*/ for (i = 0; i < 100; i++) { // 100回繰り返す /* 8*/ g.drawLine(50, 50 + i, 51 + 2 * i, 50 + i); /* 9*/ } /* 10*/ } /* 11*/ }
以下の二等辺三角形を
for
文を用いて描きます。
この図形は、
g.drawLine(150, 50, 151, 50); g.drawLine(149, 51, 152, 51); g.drawLine(148, 52, 153, 52); ... g.drawLine(51, 149, 250, 149);
と同じことを実行すれば描けます。 この描き方に対応するアルゴリズムを考え、プログラムを作成してください。
余力のある人は、平行四辺形、ひし形など、自分で考えた図形を
for
文を用いて描いてください。
今日の演習7の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b08a001@cis.twcu.ac.jpなど)とし、宛先はkonishi@cis.twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(6月4日)を明記してください。