アドプロ 2018.04.30

制御構造 (1)

前回,基本的なプログラムによって,とりあえずタートルを動かしてみた. その結果,命令(制御関数)のひとつひとつに反応して, タートルが状態を変化させることがわかったハズである.

様々な命令をたくさん羅列すれば, どんなに複雑な図形でも描くことができる. しかし,同じ命令を何度も羅列するのは高コストである.

コードの記述に時間がかかるのは,最初の1度だけではない. 後々度々に必要となる修正・改良の作業も大変になる. まぁ,やっつけ仕事の場合は別だが... (とは思っても,実際にはやっつけきれず, 後々まで苦労を引きずることも多い.)

そこで,本授業では,より効率的な(複雑な図形をより少ない命令で描く) 方法を探って行く.

本授業では,効率化の具体的な方策として, 制御構造と関数とを利用する.

制御構造には, 連接(羅列,順次), 選択(条件判断), 反復(繰り返し)の3種類がある.

今回は,反復(繰り返し,ループ)による効率化を理解しよう. 例えば,ある動作を 100 回繰り返す場合, その動作に対応する一連の命令を人間が 100 回も書く のは明らかに無駄である. 効率的なプログラミングとしては, その一連の命令を 1 回だけ書き, 繰り返し回数を指定するだけでよい.

要するに,人間が命令を何度も繰り返すのではなく,コンピュータに繰り返させる.

C言語の場合, for 文または while 文を利用して, 反復処理を記述する.

作業に移る前に,今回も, 作業用ディレクトリの準備をお忘れなく. また,C言語プログラムのコンパイルと実行の方法について, 前回の説明を再確認しておこう.


非効率な羅列的プログラミング

例として,多角形の描画を考える. まずは,6角形を羅列的にプログラミングしてみよう. 一定距離の前進と 60 度の回転を6回実行すれば6角形になる.

ソースファイル polygon.c:(効率の悪い例

#include "kame3d.h"

int main()
{
	Init("Kame3D Polygon");

	Goto(-1.0, -1.732);

	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);
	Move(2.0); Turn(60.0);

	Play();
	return (0);
}

この例では, まったく同じコードMove(2.0); Turn(60.0);」を 6回も羅列している.

「たったの6回だけ」ではない. 「仏の顔も3度まで」だ. 人間の忍耐(羅列)の限度は,3回程度.

また,このコードを改造し頂点(角)の個数を増やせば,円に近づけることも可能である. しかし,そのためにソースコードをどれだけ肥大化させなければならないだろうか?

もちろん,現代の PC では, コピー&ペーストの手作業の繰り返しによって, 何行でも簡単に羅列できるので,そうしたくなるのが人情だが, それをやってはいけない. 後で,変更したいとき等,余計な時間・労力がかかることになるし, 不注意による間違いも混入しやすくなってしまう. 人間が働くのではなく, コンピュータを働かせること. 人間は手よりも,頭の方を多く使うべきだ.


反復による効率化

では,次の通り, for を利用して, polygon.c を書き換えよう:(効率の良い例

#include "kame3d.h"

int main()
{
	int  i;				// 反復回数のカウンタ

	Init("Kame3D Polygon");

	Goto(-1.0, -1.732);

	for (i = 0; i < 6; i++) {	// ここから6回反復
		Move(2.0); Turn(60.0);
	}				// ここまで

	Play();
	return (0);
}

実行結果は最初のものと変わらないのに, ソースコードを非常にコンパクトに記述できた. また,その効果は,反復回数が多いほど,より強力になる. 反復回数を変えて試してみよう.

回数に応じて,当然,回転角度も変える必要がある. 回数が n の場合,回転角度を 360.0/n にすればよい. 例えば:
int n = 12;	// この値を変えるだけで任意の多角形を描ける
...
for (...; i < n; ...) {
	...
	Turn(360.0/n);
}
注意:整数演算 360/n だと,割り切れる場合を除き, 計算結果が不正確となってしまう.この例では,実数演算 360.0/n としている.

なお,for ループの標準的な使い方は次の通り:

int  カウンタ;

for (カウンタ = 0; カウンタ < 反復回数; カウンタ++) {
	繰り返したい処理
	...
}

このループを実行すると, カウンタの値が 0, 1, 2, ..., 反復回数-1 まで増加しながら, ループ内の処理(命令のグループ)が反復される. カウンタの値が反復回数以上になったとき, 反復は終了する.

なお,for ではなく,while を使っても同様な処理が可能: (だがこれは,オススメできない.

int  カウンタ = 0;

while (カウンタ < 反復回数)  {
	繰り返したい処理
	...
	カウンタ++;
}

for と while の使い分けの基本方針:

while の適切な利用方法については,次回,解説したい.


練習問題

polygon.c を元にして, 反復処理で何か素敵な図形を描け. (前回作成した図形を複数個ならべてみるとか.)

ヒント: for ループを二重・三重にしたり, ループ内に色々な命令を詰め込んだり, 移動距離や回転角度をカウンタ値から計算したりすると, 面白い図形ができあがる. どんな命令(制御関数)があったか, 前回の説明を再確認しよう.

サンプルソースファイル:

これらのソースファイルについては, 本日の作業用ディレクトリにダウンロードして使うとよい.

ダウンロード方法:

リンクを右クリック→ [名前を付けてリンク先を保存...]

ファイルダウンロードを賢く活用するための firefox の設定:
[編集]→[設定]→[一般]→v ファイルごとに保存先を指定する
(標準では,保存先フォルダが「ダウンロード」とかに設定にされているかもしれない.)
なお,制御構造(連接,選択,反復)の考え方を理解するには, アルゴロジック で練習するとよいだろう. (演習室の Linux では利用できないようなので,他の環境で.)

規則性・類似性による効率化

羅列的な悪い例:

	Move(0.2); Turn(30.0);
	Move(0.4); Turn(30.0);
	Move(0.6); Turn(30.0);
	...
	Move(2.0); Turn(30.0);

これは,「まったく同じ」ではないが, 「同じような」コードの羅列だ.

改善案 (1):

	int	i;

	for (i = 1; i <= 10; i++) {
		Move(i*0.2);		// 異なる部分を規則性によりコンパクトに表現
		Turn(30.0);
	}

改善案 (2):

	int	i;
	double	d[10] = {0.2, 0.4, 0.6, ..., 2.0};	// 異なる部分だけを羅列

	for (i = 0; i < 10; i++) {
		Move(d[i]);
		Turn(30.0);
	}

要するに,コード同士の規則性(パターン)を見つけ出し, それを一般化できれば, 羅列的な複数のコードを一つにまとめることができる.

数学に例えると,数列のすべての項を羅列するよりも, 一般項の数式ひとつだけで表現すれば楽だよね.

本日の課題

for 文を2個以上利用して, 規則性のある独自の図案を効率良く描くような タートルグラフィックスプログラムを自由に作成せよ.

担当教員へレポートを送信せよ:

SS画像作成方法は前回と同様.


(c) 2018, yanagawa@kushiro-ct.ac.jp