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

(4) 05/14の授業内容:制御構造[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{naoya}% 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{naoya}% 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点。提出要領は下記の通り。

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールのSubject:ruby04
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降にプログラムおよび実行結果を貼りつける。その他説明を加えても良い。

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

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

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

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

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

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

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

このような場合にはwhile trueを用いる。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{naoya}% 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の次の行に進む。

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キーを押すと自動的に適切な位置にインデントをしてくれる。便利なので確認しておこう。

問題はプログラム中に黄色で示した、while trueとif-endである。

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

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

レポート課題

register.rbを改良し、以下のバイト代を計算するプログラムを作成せよ(kadai2.rb)。

時給を入力後、毎日のアルバイトの時間を順に入力していくと、最後に合計時間とアルバイト代を計算して出力する。

whileの横には繰り返しを継続する具体的な条件を書いても良いし、while trueを使っても良い。while trueの場合は何日分でも入力することができるが、繰り返し継続条件を具体的に指定する場合は、何回(何日分)入力できるのかをあらかじめ決めておく必要がある。この場合の日数は任意とするが、少なくとも2回以上繰り返しが行われるようにする。

いずれの場合も、printメソッドやprintfメソッドを使って、初めてそのプログラムを実行したユーザでも使い方がわかるような文章を提示するよう心がけよう。せっかく内部は良いプログラムであったとしても、ユーザが操作方法がわかりづらいと感じれば使ってもらえない。この点についても趣向を凝らしてみよう。

  • 提出先:naoya@e.koeki-u.ac.jp
  • 提出期限:5/20(日)23:59
  • メールのSubject:kadai02
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
  1. 作成したプログラム
  2. プログラムの実行結果
  3. プログラムの説明
  4. 感想や気づいた点

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

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

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

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