roy > naoya > 基礎プログラミングI·情報検索 > (4)制御構造[2]

(4) 05/12の授業内容:制御構造[2]

繰り返しの有効性

プログラムの流れを変える働きをするものを制御構造とよび、前回の授業では条件判断を取り上げた。制御構造にはもう1つ繰り返しがある。繰り返しについて考えるにあたり、まず次のプログラムを作ることを考えてみる。

バーコードリーダーを使ってバーコードを読み取り、商品名と金額を取得する。レジの「合計」ボタンを押すと合計金額が計算され、表示される。

お客さんが購入する商品の個数は一定ではないため、合計を出すために必要な足し算の回数は毎回変化する。プログラムを書く際は、合計金額を最初0円としておき、バーコードリーダーを介して取得される金額を随時合計金額に加算していくことになる。

このプログラムの流れをフローチャートを用いて図式的に表現してみよう。左側がプログラムを上から順番に実行していく方法、右側が今回登場する繰り返しを用いた表現方法である。

繰り返しをしないと同じことを何度もプログラムに書かなければならない

左側から見ると、まず合計金額の初期値を0円としている。次にバーコードを読み取って金額を取得し、その金額を合計金額に加算する。これで1つ目の商品の金額が合計に加算されたことになる。次に2つ目の商品のバーコードを読取り、合計金額に加算する。「バーコードを読み取る」「合計金額に加算する」という処理が繰り返し登場していることがわかる。ここでは2回分しか書いていないが、実際には3個以上の商品を購入する場合もありうるのでこれでは足りない。最大で何回分準備しておけばよいのかは難しい判断だが、ここでは念のため100個分準備しておけば安心と考え、100回同じことを書いている。

右側は繰り返しを用いたプログラムの考え方である。ここでは「バーコードを読み取る」「合計金額に加算する」の後で◇があり、ここで合計ボタンを押したかどうか判定が行われる。押した場合はお会計に進み、押していない場合は上に戻り、次の商品の金額を合計に加算する。このような書き方をすると、バーコードからの金額の取得と合計への加算をプログラム内で1回書いておけばよいことになり、シンプルになる。

制御構造における繰り返し

  • 同じ処理を繰り返し行う場合に、繰り返し表現を用いると1回書けばよい。

繰り返し:while-end

繰り返しを表現する際に主に用いられるのはwhile-end(while文)である。以下がwhile文の基本構造となる。

while 繰り返しを継続する条件
  繰り返し行う処理
end

whileの横に繰り返しを継続する条件を記載する。その条件を満たす間はwhileとendの間に書かれた行を繰り返し実行する。最後には必ずendをつける。

簡単なプログラムを見てみよう(add.rb)。

#!/usr/koeki/bin/ruby

sum = 0
i = 1
while i <= 5
   sum += i
   i += 1
end
printf("1から5の合計は%dです。\n",sum)

これは1から5まで足した結果を求めるプログラムであり、実行すると以下の結果が得られる。

irsv{c10xxxx}% ruby add.rb[Return]
1から5の合計は15です。

このプログラムでは冒頭で、sumに0、iに1を代入している。その後while-endが登場するが、whileの横に書かれた繰り返しを継続する条件は「i <= 5」、whileとendの間にある繰り返し行う処理は「sum += i」「i += 1」の2つとなっている。では、このプログラムでどのように1から5までの足し算をしているのだろうか。

以下は、add.rbを説明するにあたり左端に行番号を付加したものである。この行番号に基づいてプログラムの流れを示す。

#!/usr/koeki/bin/ruby

1:sum = 0
2:i = 1
3:while i <= 5 #=> iの値が5以下の間は以下の2行を繰り返し実施する
4:   sum += i
5:   i += 1
6:end
7:printf("1から5の合計は%dです。\n",sum)
  1. 1行目:sumに0が代入される。
  2. 2行目:iに1が代入される
  3. 3行目:iは1なので5以下である。このため以下の2行を実施する。
  4. 4行目:sumiを加算代入する。sumは1となる。
  5. 5行目:iに1を加算代入する。iは2となる。
  6. 6行目:endに到達したのでとりあえず3行目に戻る。
  7. 3行目:iは2なので5以下である。このため以下の2行を実施する。
  8. 4行目:sumiを加算代入する。sumは3となる。
  9. 5行目:iに1を加算代入する。iは3となる。
  10. 6行目:endに到達したのでとりあえず3行目に戻る。
  11. 3行目:iは3なので5以下である。このため以下の2行を実施する。
  12. 4行目:sumiを加算代入する。sumは6となる。
  13. 5行目:iに1を加算代入する。iは4となる。
  14. 6行目:endに到達したのでとりあえず3行目に戻る。
  15. 3行目:iは4なので5以下である。このため以下の2行を実施する。
  16. 4行目:sumiを加算代入する。sumは10となる。
  17. 5行目:iに1を加算代入する。iは5となる。
  18. 6行目:endに到達したのでとりあえず3行目に戻る。
  19. 3行目:iは5なので5以下である。このため以下の2行を実施する。
  20. 4行目:sumiを加算代入する。sumは15となる。
  21. 5行目:iに1を加算代入する。iは6となる。
  22. 6行目:endに到達したのでとりあえず3行目に戻る。
  23. 3行目:iは6なので5以下という条件を満たさない。このため繰り返しを終了する。
  24. 7行目:「1から5の合計は15です。」と表示される。

繰り返しを行う中で、iの値を1ずつ増加させている(5行目)。そしてiの値に基づいて繰り返し継続の可否を決定している(3行目)。

もう1つ例を見てみよう。今度は6回のテストの合計得点を求めるプログラムである(test.rb)。

#!/usr/koeki/bin/ruby

total = 0
number = 1

print"6回のテストの合計得点を求めます。\n"

while number <= 6
   printf("%d回目のテストの得点は?\n", number)
   score = gets.chomp!.to_i
   total += score
   number += 1
end
printf("6回のテストの合計は%dです。\n",total)

これを実行すると次の結果が得られる。

irsv{c10xxxx}% ruby test.rb[Return]
6回のテストの合計得点を求めます。
1回目のテストの得点は?
30[Return]
2回目のテストの得点は?
45[Return]
3回目のテストの得点は?
25[Return]
4回目のテストの得点は?
30[Return]
5回目のテストの得点は?
50[Return]
6回目のテストの得点は?
40[Return]
6回のテストの合計は220点です。

ここではwhile-end内でnumberの値を1ずつ増加させ、whileの横に書かれた条件であるnumber <= 6を満たす間繰り返し処理を行わせている。

while-end間にはprintfメソッドやgetsメソッドが用いられており、繰り返しを行うたびに、メッセージの出力やキーボードからの入力の受け取りが行われている。

while文のまとめ

  • 次の基本構造を持つ。
while 繰り返しを継続する条件
  繰り返し行う処理
end
  • 最後にはendを必ずつける。

その他の繰り返し表現

while文以外にもいくつかの繰り返し表現がある。詳細はその他の繰り返し表現のページを参照されたい。ここでは、whileに次いで利用頻度の高いfor-end(for文)のみ紹介する。

for文は様々な使い方ができ、もう少し後の回(具体的には配列を学んだ後)に役に立つ表現方法である。ここでは最も簡単なfor文の使い方を示す。

for 変数 in 開始時の数値..終了時の数値 do
   繰り返したい処理
end

これがfor文の基本構造である。forの横に記載した変数にin以下の値を順次代入しながらその下に書かれた処理を繰り返し実行する。for-endを用いた足し算プログラムの例を示す。

#!/usr/koeki/bin/ruby

sum = 0
for i in 1..5
   sum += i
end
printf("合計は%dです。\n",sum)

上記のプログラムでは、iに1から5まで1ずつ増やしながら順番に代入していき、その下にあるsum += iという処理を繰り返し実行することになる。iの値は1、2、3、4、5と変化していくため、これに合わせてsumの値は1、3、6、10、15と変化する。1から5までの足し算なのでwhile文で書くこともできる。

出席課題

add.rbを参考に1から10までの積(1×2×・・・×10)を求めるプログラムを書いてみよう。最初から書くのは大変なのでadd.rbを修正してみよう。while文で書けば良いが、for文に挑戦しても良い。

制限時間は10分。うまくいかない場合は、できたところまででよいのでメールで解答を送信すること。出席点は2点。提出要領は下記の通り。

  • 提出先:課題提出用メールアドレス
  • メールのSubject:ruby04
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降にプログラムおよび実行結果を貼りつける。その他説明を加えても良い。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:Mewによるメールの送り方はMewコマンドを参照

while-endの特殊な用法(while true)

本日の冒頭で話したバーコードを読み取るレジのプログラムを思い出そう。ここでは複数商品の会計をすることができるように、以下の部分を繰り返して実行していた。

  • バーコードを読取り金額を取得する。
  • その金額を合計金額に加算する。
  • 合計ボタンを押したらお会計、押さなければ次の商品のバーコードを読み取る

これをwhile-endを使って書くことを考えよう。これまではwhile-end内で特定の変数の値を一定の値ずつ増加させ、その変数の値があらかじめ指定した値を超えた時点で繰り返しが終了するようにしていた。

しかし、今回はこのような方略を採用することはできない。合計ボタンを押したときに繰り返しを終了しなければならないからである。つまりユーザーからの入力によって繰り返しを終了する必要があり、あらかじめ繰り返しの回数を定めておくことができない。

このような場合にはwhile trueを用いる。trueとは条件を満たすという意味である。したがってwhile trueは、whileの横の繰り返し継続条件が「常に条件を満たす」ということになり、永久に繰り返しを継続せよという構文になる。このままでは本当に永久にプログラムが継続するため、while-end内に別途繰り返し終了条件を設ける。まずはwhile trueを用いたプログラム(register.rb)を確認してみよう。

#!/usr/koeki/bin/ruby

sum = 0

while true
  print"金額を入力してください(お会計は0と入力):\n "
  price = gets.chomp!.to_i
  if price == 0 then
    break
  end
  sum += price
end

printf("ありがとうございます。お会計 %d 円になります。\n", sum)
irsv{c10xxxx}% ruby register.rb[Return]
金額を入力してください(お会計は0と入力):
200[Return]
金額を入力してください(お会計は0と入力):
100[Return]
金額を入力してください(お会計は0と入力):
350[Return]
金額を入力してください(お会計は0と入力):
0[Return]
ありがとうございます。お会計650円になります。

while-end内では、printメソッドでメッセージを表示し、getsメソッドを使ってユーザーからの金額の入力を受け取りpriceに代入している。そして、priceの値をsumに加算代入することで、繰り返しを行うたびにsumの値は増加する。

新しく登場したのはプログラム中に黄色で示した、while trueとif-endである。

  • while true:trueは条件を満たすという意味。while tureとすると常に条件を満たすことになり、永久に繰り返しが行われる。このままでは本当に終了しないので、while-end内に別途繰り返しを終了する条件を入れる。具体的にはif文を使用し、if文の中にbreakを入れる。
  • break:繰り返しから抜ける命令。このプログラムではpriceに代入された値が0であれば(=ユーザが0を入力したら)、breakが実行される。breakが実行されるとwhile-endの繰り返しから抜け出し、endの下のprintfの行に進む(for-endやuntil-endでも使用することができる)。

breakと同じように、while-end、for-end、until-endで使用する琴ができる繰り返し制御の記法を参考までに以下に示す。

  • break:繰り返しの実行を中止し、繰り返しから抜ける
  • redo:その回の繰り返しを無効化し、その回の繰り返しの先頭からやりなおす
  • next:その回の繰り返しを無効化し、次の繰り返しに突入する

redoについては以下のように不適切な入力に対する再入力の促しとして使用できる。

while true
  print"1、2、3のいずれかを入力:\n "
  number = gets.chomp!.to_i
  if number < 1 || number > 3 then
    print"1、2、3のいずれかを入力してください\n"
    redo
  end
  (略)
end

printf("ありがとうございます。お会計 %d 円になります。\n", sum)

while trueについてのまとめ

  • ユーザの入力に基づいて繰り返しを終了する場合などにはwhileの横に繰り返しを継続する条件を書くかわりにwhile tureとする。
  • while trueとすると永久に繰り返しが継続し、終了しない。
  • while-end内にif文を設け、if文の中にbreakを入れる。breakは繰り返しから抜ける命令で、if文に記載された条件を満たす場合に繰り返しを終了することになる。

インデントをして見やすくしよう

register.rbを見ると、なぜか字下げ(インデント)されている箇所がある。while-end内やif-end内がインデントされている。インデントをすることでwhile文やif文がどこで始まりどこで終わっているかわかりやすくなる。以下には同一のプログラムを(1)インデントをした場合、(2)インデントをしない場合の2パターンで示す。(1)インデントをした場合の方が見やすいのは明らかである。

#!/usr/koeki/bin/ruby

sum = 0

while true
  print"金額を入力してください(お会計は0と入力):\n "
  price = gets.chomp!.to_i
  if price == 0 then
    break
  end
  sum += price
end

printf("ありがとうございます。お会計 %d 円になります。\n", sum)
#!/usr/koeki/bin/ruby

sum = 0

while true
print"金額を入力してください(お会計は0と入力):\n "
price = gets.chomp!.to_i
if price == 0
then break
end
sum += price
end

printf("ありがとうございます。お会計 %d 円になります。\n", sum)

もう少し長いプログラムを書くようになると、while文の中にwhile文が登場したり、さらにif文が登場したりして、endを複数使用するようになる。endがないとプログラムはうまく動かない。インデントをする習慣をつけるとendの抜け落ちに気づきやすくなる。

インデントを行う場合、スペースキーを何回か押しても良いが、Tabキーを押すと自動的に適切な位置にインデントをしてくれる。便利なので確認しておこう。

プログラムのインデントについて

  • インデントをすると見やすさが向上し、誤りも軽減できる。
  • スペースキーを複数回押すのではなく、Tabキーを押すと自動的に適切な位置にインデントしてくれる。

レポート課題

register.rbを改良し、家計簿をつける以下のプログラムを作成せよ(kadai2.rb)(8点満点)。

今月(または今週)の収入を入力後、毎日の支出を順に入力していくと、最後に支出合計と収支(何円の黒字または赤字か)を計算して出力する。

作成上の条件

  • while-endを使用すること
  • whileの横に具体的に繰り返し継続条件を記載しても良いし、while trueを用いても良い
  • whileの横に繰り返し継続条件を記載する場合、繰り返し回数は7回(1週間分)とすること
  • while trueを用いる場合は何回でも繰り返せるが、実行時は7回入力すること
  • printやprintfメソッドを用いて、初めてそのプログラムを実行したユーザでも使い方が分かるような文章を提示するよう心がけること
  • 収支の表示は条件によってメッセージが異なる(赤字と黒字の場合)。if-endを用いて記載すること
  • 収支は赤字と黒字の2種類のメッセージにとどめず、赤字の程度や黒字の程度によってより多くのメッセージを表示できるようにすることが望ましい

実行結果のイメージは以下の通り(黄色がユーザの入力)。

irsv{c10xxxx}% ruby kadai2.rb[Return]
今月(今週)の収入入力のうながし
金額の入力
1日目の支出入力のうながし
金額の入力
2日目の支出入力のうながし
金額の入力
      :
支出合計の表示(例:今週の支出は○○円でした)
収支の表示(例:100円の赤字です。1000円の黒字です)

  • 提出先:課題提出用メールアドレス
  • 提出期限:5/18(日)23:00
  • メールのSubject:kadai02
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
  1. 作成したプログラム
  2. プログラムの実行結果
  3. プログラムの説明
  4. 感想や気づいた点

  • 採点基準:期限内提出点(2点)、メールの体裁(1点)、プログラム(2点)、プログラムの説明(2点)、趣向を凝らしているか(1点)
  • プログラムの説明は、今日新しく出てきたwhile文を中心に行えば良いが、何が重要か判断できない場合は1行ずつ説明してもよい。
  • 趣向については、print、printfのメッセージのわかりやすさ、if-endの条件の複雑さで評価する。
  • わかりにくい説明や、Webページを単にコピー&ペーストしただけの説明は減点することがある。一度読み直してから提出すること。
  • 驚異的に良くできているレポートについては満点の8点を超える得点をつけることがある。
  • よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする

Tips:Mewによるメールの送り方はMewコマンドを参照