(4) 5/8の授業内容:制御構造

プログラム実行時の流れ

プログラムを作成すると、通常は上から順番に処理が実行される。しかし場合によっては順番に処理をされると都合が悪いことがある。いくつか例をあげてみよう。

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

例2:銀行ATMで振り込み作業を行った。この際、口座の残高が振り込み金額以上であれば振込みを実施するが、以下の場合には金額が不足している旨を表示し、振込みは実施しない。

まずは、例1をプログラムで書くことを考えよう。お客さんが購入する商品の個数は一定ではないため、合計を出すために何回足し算をすればよいかは毎回変化する。プログラムを作る上での考え方としては、合計金額を最初0円としておき、バーコードリーダーから入力される金額を随時合計金額に足していくということになる。最高でも100個くらいしか購入しないだろうという予測をたてて100回分足し算ができるような準備をしておくこともできるが、これだとプログラム内に同じ内容が100回も登場することになり、なんとなく美しくない。こんな場合は、「合計」ボタンが押されるまで、新しく入力される金額を合計金額に繰り返し足していくとすれば、100回も同じことを書く必要がなくなる。これは前回の授業で用いたwhile-endである。

例2では場合わけが行われている。振り込み金額と口座の残高の大小比較を行い、振込み金額の方が大きければ振込みを実施しないということになる。これをプログラムで書き、上から順番に実施したらどういうことになるだろうか。振込みを実施しない行と実施する行のどちらも実施されてしまうことになり、口座の残高がいくらであっても結局振り込みは行われてしまう。この場合、条件1を満たす場合はAを実行してBは実行しない、条件2を満たす場合はBを実行してAは実行しないという構造が必要になる。

プログラムを上から順番に実行することを逐次処理というが、逐次処理だけでは表現できないことがある。

制御構造とは

逐次処理のみでは表現できないという問題を解決するのが制御構造である。制御構造とはプログラムの実行される順番を変更するものであり、下記のように分類することができる。

  • 繰り返し:ある条件が成り立つ間、○○を繰り返し実行する(例1に該当)。
  • 条件判断:ある条件が成り立つ場合は××を、そうでない場合は△△を実行する(例2に該当)。

繰り返し(1):while-end

whileの横に繰り返しを行う条件を記載する。その条件を満たす間はwhileとendの間に書かれた行を繰り返し実行する。while文と呼ぶことも多い。

#!/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 ("sumの値は最終的に%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行目:「sumの値は最終的に15になりました」と表示される。

繰り返し(2):until-end

while-endでは、whileの横に書かれた条件を満たす間は繰り返し処理を実行するが、until-endではuntilの横に書かれた条件を満たしていない場合に処理を繰り返し実行する。

#!/usr/koeki/bin/ruby

sum = 0
i = 1
until sum >= 20
   sum += i
   i += 1
end
printf ("sumの値は最終的に%dになりました\n",sum)

上記の例では、untilの横に記載された条件はsum >= 20である。つまり「sumが20以上である」という条件を満たさない場合はその下の処理を繰り返し実施することになる。言い換えれば「sumが20以上になるまで」処理を繰り返すということになる。これはwhile-endを使用して書くことも可能である。

繰り返し(3):for-end

for-endは様々な使い方ができるため、重宝する記法である。6回目の授業で「配列」を学ぶが、それ以降頻繁に登場するようになる。現時点では1種類の使い方だけ示す。当面は繰り返し表現ではwhile-endしか使用しないので、今は無理に覚えなくともよい。

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

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

#!/usr/koeki/bin/ruby

sum = 0
for i in 1..5
   sum += i
end
printf ("sumの値は最終的に%dになりました\n",sum)

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

繰り返し(4):times

単純に一定回数繰り返す場合に用いる。以下のどちらの方法でもよい。

#!/usr/koeki/bin/ruby

4.times {
  print "Hello\n"
}
#!/usr/koeki/bin/ruby

6.times do
  print "Hello\n"
end

仮に下のプログラムをtimes.rbという名称で保存し実行すると、以下のようにHelloが6回表示される。。

irsv{naoya}%ruby times.rb
Hello
Hello
Hello
Hello
Hello
Hello

出席課題

以下のプログラム(上記until-endの箇所で取り上げたものと同じ)をwhile-endを使用して書き直す。

#!/usr/koeki/bin/ruby

sum = 0
i = 1
until sum >= 20
   sum += i
   i += 1
end
printf ("sumの値は最終的に%dになりました\n",sum)

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

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールのSubject:学籍番号-ruby04
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降に修正したプログラムを貼り付け、その下にどこをどのように修正したかを記載する。

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

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

条件判断(1):if-then-elsif-else-end

if-then-elsif-else-endと長い名称になっているが、一般にはif文とよぶ。場合わけを行う際に使用するもので、基本的には以下の構造をとる。

if 条件A then   #条件Aを満たせば(thenは省略しても良い)
  処理A         #処理Aを実施する
elsif 条件B     #条件Aを満たさないが条件Bを満たせば
  処理B         #処理Bを実施する
else              #条件ABともに満たさない場合は
  処理C         #処理Cを実施する
end

この構造を把握するために、下記のプログラムをif.rbという名称で保存し、実行してみよう。

#!/usr/koeki/bin/ruby

print "1〜100の間で好きな数字を入力してください\n"
number = gets.chomp!.to_i

if number >= 66 then
   print "結構大きいのが好きなんですね\n"
elsif number >=33
   print "真ん中あたりが無難ですよね\n"
else
   print "小さいのが好みなんですか\n"
end

ifやelsifの横の条件を変更すれば結果が変化することが確認できる。このプログラムには新しいメソッド(命令)が3つ出てきている。いずれもこれから頻繁に使用するので覚えよう。

gets:キーボードから入力された値を文字列として読み込むメソッド。ただし使い方によってはキーボードからの入力を読み込むのではなく、ファイルからデータを読み込ませることもできる(これは「ファイルの入出力」の回で扱う)。注意しなければならないのは、文字列として読み込むということである。100と入力すれば見かけ上は「ひゃく」に見えるが、文字列なので正確には「いちぜろぜろ」である。このためこのままでは足し算や引き算ができない。

chomp!:キーボードから入力する際は必ず最後に[Return]キーを押すが、これも\nというデータとして扱われてしまう(\nのことを改行文字という)。chomp!は末尾に改行文字がある場合に取り除くメソッドである。最後に!のつかないchompもあり、若干意味が異なる。ここでは混乱を避けるため改行文字を取り除くのはchomp!であると覚えよう。

to_i:文字列を整数に変換するメソッド。小数点以下は切捨てになる。

メソッドは.(ピリオド)でつなぐことができる。この3つのメソッドをつなげることで、上記のプログラムでは、キーボードから入力したデータを整数にしてnumberに代入することが可能になる。

つまり、「number = gets.chomp!.to_i」は、キーボードから文字列を読み込み、改行文字を取り除き、整数に変換した上でnumberに代入するという意味になる。

条件判断(2):unless-else-end

ifの場合はif以下の条件を満たす場合に処理を実行するが、unlessでは条件を満たさない場合に処理を実行する。until-endをwhile-endで書き直せるように、unless文もif文で書くことができる(ので無理に覚えなくて良い)。

#!/usr/koeki/bin/ruby

print "1〜100の間で好きな数字を入力してください\n"
a = gets.chomp!.to_i
print "もう1回1〜100の間で好きな数字を入力してください\n"
b = gets.chomp!.to_i

unless a > b
   print "最初の方が2回目より大きくないわね\n"
else
   print "最初の方が2回目より大きいわね\n"
end

条件判断(3):case-when-when-else-end

unless文で紹介したサンプルプログラムのように比較したい変数が2つではなく、1つであり、その値によって場合わけを行う場合、case文を使用することができる。case文ではなくif文やunless文でももちろん記述できるが、場合わけを行う上で条件が多い場合にはcase文を用いた方が簡単に書ける場合が多い。

#!/usr/koeki/bin/ruby

print "1から10の好きな数字を入力してください\n"
a = gets.chomp!.to_i

case a
when 1, 2, 3
   print "small\n"
when 4, 5, 6
   print "medium\n"
when 7, 8, 9, 10
   print "big\n"
else
   print "範囲を超えました\n"
end

上記の例ではwhenを3つ使って条件を記述しているが、条件の数が増えた場合whenを無制限に増やすことができる。

繰り返しと条件判断を組み合わせたプログラムを作ろう

まずは下記のプログラムをregister.rbという名前をつけて保存し実行してみよう。

#!/usr/koeki/bin/ruby

sum = 0
item = 0

while true
  print "金額を入力してください(終了はq): "
  price = gets.chomp!
    if price == "q" 
      then break
    end
  sum += price.to_i
  item += 1
  printf ("現時点の購入商品の個数は%d個、合計は %d 円です\n", item, sum)
end

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

このプログラムはキーボードから入力された金額を加算していき、購入商品の個数と現時点での合計金額を毎回表示するプログラムである。キーボードから金額を入力するかわりにqと入力すると購入終了で、お会計金額が表示される。以下では、このプログラムで新しく出てきた表現について示す。

while true:while-endで繰り返しを記述する場合、whileの横に繰り返しを続ける条件を記載できない場合がある。register.rbでは、繰り返しを続けるかどうかは、ユーザーがqを入力したかどうかによる。ユーザーの入力を受け付ける行はwhileの下にあるため、whileの行では繰り返しの判断ができない。この場合、whileの横にtrueをつけることができる。これは永久に繰り返すという意味になる。while trueとした場合は、while-end内に別途終了条件を設ける。具体的にはif文を使用し、if文の中にbreakを入れる。

break:繰り返しの途中で処理を中断する命令。このプログラムではキーボードからqが入力された場合にbreakが実行される。breakが実行されるとwhile-endの繰り返しから抜け出し、endの次の行に進む。

先ほどはgets.chomp!.to_iと3つのメソッドを並べて使用していたが、このプログラムではto_iの位置が異なる。なぜ上のプログラムと同じように3つを並べないのか考えてみよう。このプログラムではキーボードから入力されるのは数字のみではない。qが入力される可能性もある。price = gets.chomp!.to_iとすると、キーボードからの入力を整数に変換してpriceに代入してしまう。qを整数に変換すると0になってしまうので都合が悪い。このため、sum += price.to_iでsumに加算代入する際に整数に変換している。

レポート課題

register.rbを改良し、以下のいずれかのプログラムを作成する。(1)の方が簡単で、(2)は若干難しい。どちらを作成しても良い。

  1. 現在はqを入力すると終了になっているが、購入金額の合計が1万円を超えた場合に終了し、お会計金額を表示するように変更する(この場合、while trueでも書けるし、これまでのようにwhileの横に繰り返し条件を記述することもできる。どちらで書いても良い)。
  2. 1〜50のランダムな数字を生成し、その数字を当てるプログラムを作成する。キーボードから任意の数字を入力し、それが生成された数字と同じであれば「正解」と表示し、異なれば「違う」と表示する。挑戦できる回数は10回とする。

(2)は(1)の応用である。ランダムな数字を生成するためには、register.rbのプログラムの冒頭(sum=0の上)に、digit = rand(50)+1と書けばよい。randはランダムな数字を生成するメソッドで、rand(50)とすると0〜49のうちのどれかがランダムに選ばれる。digit = rand(50)+1のように+1をすることで1〜50となり、この値がdigitという変数に代入されていることになる。もちろんdigitの部分は自分で自由に変更しても良い。

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

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

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

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

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