roy > naoya > 基礎プログラミングI > (3)制御構造[1]

(3) 制御構造[1]

[1] 制御構造とは何か

プログラムは1行目から順番に実行される。通常はこれで問題ないが、場合によっては困ることもある。いくつか例をあげてみよう。

  1. 計算問題を出す。正解していれば「正解です!」と表示し、間違っていれば「不正解です!」と表示する。
  2. 商品の金額を順番に入力し、全ての商品の合計金額を計算する。

条件判断

1番を図式的に(フローチャートで)表現すると以下のようになる。◇の箇所で条件を満たしているかどうか判定が行われ、満たしている場合と満たしていない場合では異なる動作を行っている。

上記の例はいずれも分岐が必要になる(フローチャートで表現)

プログラムでこれを表現する場合、左右に分けて書くことができないので、上下に分割して書く。

正解している場合
  正解です!と表示
そうでない場合
  不正解です!と表示

プログラムは上から進んでくるが、この部分では条件に応じて「正解です!」が表示されたり「不正解です!」が表示されたりする。順番に1行ずつ実行されて両方のメッセージが表示されることはない。

通常は上から順に実行するプログラムの処理の流れを変更する構造を制御構造という。制御構造のうち、条件によって行う処理を変える記法を条件判断という。

繰り返し

2番はスーパーやコンビニのレジのプログラムに相当する。この場合、お客さんが購入する商品数は一定ではないため、合計を出すために必要な足し算の回数は毎回変化する。プログラムを書く際は、合計金額を最初0円としておき、個々の商品の金額を随時合計金額に加算していくことになる。

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

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

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

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

このように、一定の条件を満たす間、同一処理を繰り返し行う制御構造の記法を繰り返しという。

制御構造のまとめ

  • 上から順に実行するプログラムの流れを変えるものを制御構造という。
  • 条件に応じて異なる処理をさせることを条件判断という。
  • 条件を満たす間同一処理を反復させることを繰り返しという。

[2] 繰り返し(1):while-end

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

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

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

簡単なプログラムを見てみよう。これは6回のテストの合計得点を求めるプログラムである(add.rb)。

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

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)

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

sime{c11xxxx}% chmod +x add.rb[Return]
sime{c11xxxx}% ./add.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 <= 6である。これはnumber≦6という意味であり、numberが6以下の間は繰り返し処理を行うことになる。

while-end内は4行あり、それぞれ以下の処理が行われている。

  • 1行目:「○回目のテストの得点は?」と表示。○にはnumberを入れ込む
  • 2行目:キーボードからの入力を整数変換してscoreに代入
  • 3行目:totalの現在の値にscoreを加算
  • 4行目:numberの現在の値に1を加算

3行目、4行目の+=は左辺に右辺を加算するという意味の代入演算子である。繰り返しを行うたびにnumberの値が1ずつ増加していき、numberが6を超えた時点で、繰り返し継続条件を満たさなくなるため、繰り返しから抜け、最終行のprintfが実行される。

numberの初期値を1、繰り返し継続条件をnumber <= 6とすることで、結果的に6回の繰り返しが行われる

while文のまとめ

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

[3] 繰り返し(2):for-end

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

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

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

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

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文で書くこともできる。

[4] 繰り返し(3):その他の繰り返し[補足]

>>More

[5] 代入演算子

Rubyでは=は右辺と左辺が等しいという意味ではなく、左辺に右辺を代入するという意味になる。変数に値を代入する際に利用するのが代入演算子である。算術演算子と=を組み合わせることで様々な働きを持たせることができる。例えば+=は左辺に右辺を加算するという意味になる。

data = 10 というように初期値10が与えられていた場合、data += 5とすると、10に5が加算され15となる。

代入演算子 意味
= 通常代入z=10(zに10を代入する)
+= 加算代入z+=10(zの現在の値に10を加算する)
-= 減算代入z-=10(zの現在の値から10を引く)
*= 乗算代入z*=5(zの現在の値を5倍する)
/= 除算代入z/=10(zの現在の値を1/10にする)
%= 剰余代入z%=5(zの現在の値を5で割った余りを新しいzとする)
**= べき乗代入z**=5(zの現在の値を5乗した値を新しいzとする)

以下のプログラムは変数xに初期値として0を与え、その後代入演算子を用いて様々な値を代入していきながらprintfでx内の値がどのように変化していくかを確認するものである(assign.rb)。

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

x = 0
printf("xの値は%dです\n",x)
x = 10
printf("xの値は%dです\n",x)
x += 5
printf("xの値は%dです\n",x)
x *= 2
printf("xの値は%dです\n",x)
x %= 4
printf("xの値は%dです\n",x)

このプログラムを実行すると以下の結果が得られる。

sime{c11xxxx}% chmod +x assign.rb[Return]
sime{c11xxxx}% ./assign.rb[Return]
xの値は0です
xの値は10です
xの値は15です
xの値は30です
xの値は2です

代入演算子のまとめ

  • 変数への代入を行う際に用いる演算子を代入演算子という。
  • =は左辺に右辺を代入するという意味を持つ
  • その他に+=, -=, *=, /=, **=, %=がある(働きは上の表参照)

[6] 論理演算子

while文において繰り返し継続条件を記述する際に<=が登場したが、これらは右辺と左辺を比較するために利用していた。このように条件を記載するときに用いるのが論理演算子である。「以上」「以下」といった表現に加え、複数の条件を指定する際の「かつ」(論理積)や「または」(論理和)などもある。

論理演算子 意味
== 左辺と右辺が等しい
< 左辺が右辺よりも小さい
<= 左辺が右辺以下
> 左辺が右辺よりも大きい
>= 左辺が右辺以上
&& かつ(論理積)
|| または(論理和)
! 否定
not 否定

「以上」や「以下」では不等号と等号を組み合わせるが、不等号の後に等号を書くのが正しい順番である。すなわち>=<=が正しく=>や=<は間違いである。以下には、論理演算子を用いた条件記述例を示す。

if x >= 1     #->xが1以上であれば
  x -= 1      #->xから1を引く
end
if x >= 1 && x <= 10   #->xが1以上かつ10以下であれば
  x += 1      #->xに1を加える
end
if x > 1 || x < 10 #->xが1より大きい、またはxが10未満であれば
  x *= 2      #->xを2倍する
end
if !(x <= 1 && x >= 10) #->1以下または10以上でなければ
  x -= 4           #->xから4を引く
end

論理演算子のまとめ

  • 制御構造の条件を記載するときに利用する演算子を論理演算子という。
  • ==は右辺と左辺が等しいという意味を持つ。
  • その他に<, <=, >, >=, &&, ||, !, notがある(働きは上の表参照)

[7] 出席課題

add.rbを書き換え、7日間の食費を順番に入力すると残額を計算して表示するプログラムを書いてみよう。初期の所持金は10000円とする(shokuhi.rb)。

add.rbを別名で保存してから修正を行うと良い。Ctrl+x Ctrl+wで名前をつけて保存になる。programディレクトリにshokuhi.rbという名称で保存をしてから修正を始めよう。

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

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

[8] getsメソッドを用いたファイル読み込み

先ほどは6回のテストの得点や、7日分の食費をキーボードから入力していた。この場合、途中で入力間違いがあると、最初からやり直しになってしまう。データが100個もある場合には大変な手間である。そこで、あらかじめデータをファイルで作成しておき、ファイル内のデータを読み込みながら処理をする方法について確認してみよう。

まずは、10個のデータの合計を求めてみよう。以下のデータをemacsでnumber.txtという名称で保存しよう。今回は結果が分かりやすいよう、1~10までの値を記載したものとする。

1
2
3
4
5
6
7
8
9
10

次に、以下のプログラムをfile_sum.rbという名称で作成、保存しよう。

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

sum = 0

while line = gets
  p line
  sum += line.to_i
end

printf("合計は%dです。\n",sum)

このプログラムを実行してみよう。今回は、number.txtというファイルを読み込んで処理を行うため、実行方法がこれまでとは若干異なる。

sime{c11xxxx}% chmod +x file_sum.rb[Return]
sime{c11xxxx}% ./file_sum.rb number.txt[Return]
"1\n"
"2\n"
"3\n"
"4\n"
"5\n"
"6\n"
"7\n"
"8\n"
"9\n"
"10\n"
合計は55です。

ruby プログラム名 読み込むファイル名[Return]という形式で実行していることが分かるだろうか。このようにプログラム名の後ろにファイル名を指定すると、そのファイルからデータを読み込んで処理をする。この点を踏まえ、プログラムを詳しく見ていこう。

while line=gets

getsはキーボードから入力した値を文字列として読み込むメソッドとして利用してきた。しかし、今回はプログラムを実行しても入力は求められなかったはずである。getsメソッドはキーボードからの入力を受け取る以外に、ファイル内容の読み込みも行うことができる。より厳密に言うと、プログラム実行時にファイルを指定した場合はファイルから読み込みを行い、指定しなかった場合はキーボードからの入力を受け取る。今回は、実行時にnumber.txtというファイルを指定しているので、このファイルから読み込みを行っている。

getsメソッドがキーボードからの入力を受け取る際、値入力後に[Return]を押す。これがプログラム内では改行文字である\nとして扱われることは前回確認した。つまり、getsメソッドは\nを値を読み込む際の区切りとしてとらえる。number.txtは1行に1データずつ書かれており、行末で改行している。それゆえ、1行を1つのデータとして読み込み、ここでは変数lineに代入している。

while line=getsはデータを読みこめる間は繰り返し処理を行えという意味になる。number.txtは10行あるので10回繰り返され、11回目になると読み込むデータがなくなるので繰り返しから抜ける。

p line

この行では、lineの中にどのような値が代入されているか確認している。実行結果を見ると、"1\n"や"2\n"のように、""がつき末尾に\nがついている。getsは値を文字列として受け取るメソッドであるため""がついており、\nを区切りとして読み込むため、末尾には\nがついている。

この行は、足し算を行うというプログラムの当初の目的と照らし合わせれば不要な行である。しかし、pメソッドを用いて変数内の値を確認することで、意図したとおり値が代入されているか、確認をしながらプログラムを書き進めることが可能となる。

仮に、思ったとおりに値が代入されていないのであれば、どこかで書き間違いがあることになる。最初から間違いのないプログラムを書くことは困難であるため、このようにpメソッドを使って値を表示させながらプログラムを完成させる方法は、しばしば利用される。なお、完成時にはこの行は除去して構わない

sum += line.to_i

sumlineの値を加算代入している。line内の値を加算するにあたり、to_iを付けて整数に変換している。pメソッドで確認したとおり、line内の値は文字列であるため、このままでは初期値が0のsumに加算することはできない。整数変換することで初めて加算処理が可能となる。

[9] 大規模データの統計処理

ファイル読み込みの方法を用いて、もう少し規模の大きなデータの処理を行ってみよう。例えば、毎年1月に行われる大学入試センター試験は55万人程度の受験生が受験をする。センター試験では各科目の平均点が計算されているが、こうした計算はどうすればできるだろうか。

電卓や手計算は不可能なので、エクセルを初めとする表計算ソフトを使うことを考えてみよう。まずは以下のファイルを右クリックし、「Save Link As」を指定してprogramディレクトリに保存してみよう

rand.csv

emacsでこのファイルを開いてみると、数字が縦にずらっと並んでいる。これは学食を利用したことがある人を対象に、過去1年間で何杯カレーうどんを注文したかを示す架空データであると考えてほしい。なんと10万人分のデータを集めることができたとする。全ての合計を求め、カレーうどんが1年間で合計何杯売れたかを調べたい。

43
49
76
18
43
26
:以降10万人目まで

なお、このファイルはcsvという拡張子がついている。中身はただのテキストファイルであるが、csvという拡張子をつけると、エクセルやCalcなどの表計算ソフトで開くことができるようになる。csvはComma Separeted Valuesの略である。1行に複数のデータを書く場合は、カンマで区切って記述すると、表計算ソフトで読み込んだ際にカンマを区切りとして、各データを個別のセルに入れてくれる。

表計算ソフトを用いた10万人のデータの分析

では、このデータを表計算ソフトで開いてみよう。この教室ではエクセルは使えないので、OpenOffice Calcを使用する。Calcはルートメニューから起動することもできるが、ここではktermから起動する方法を確認してみよう。

sime{c11xxxx}% ooo3&[Return]

このように入力すると、OpenOfficeが起動する。その後「ファイルを開く」からrand.csvを指定しよう

sime{c11xxxx}% ooo3 rand.csv&[Return]    {program}

rand.csvを保存したprogramディレクトリがカレントディレクトリである場合は、ooo3の後ろにファイル名を指定することで、直接Calcでファイルを開くことができる。いずれの場合も最後に&をつけること。&を付け忘れるとOpenOfficeを終了するまでktermが使えなくなる。

いずれかの方法で、rand.csvを開くと、以下のようなファイル変換メニューが表示される。特に何も変更せずOKをクリック。

Calcでcsvファイルを開くと変換メニューが表示される。特に何もせずOK

すると、次のようなメッセージが表示される。

最大行数を超えたため、最大行数以降の行はインポートできないと表示

エクセルやCalcのワークシートは65536行目までしかない。それゆえ、65537個目以降のデータは読み込むことができない。つまり、表計算ソフトでは大規模なデータを取り扱うことはできない。

1行に2つのデータを入力すれば5万行になり、ワークシートが不足することはないが、1行に2つのデータにするためには、rand.datをemacs等のテキストエディタで開き、上半分と下半分をそれぞれ別のファイル名で保存しておき、それぞれを表計算ソフトで開いた上で合体させる必要がある。実際にこの作業を行ってみるとわかるが、かなり面倒くさい。

一方、10万件を越えるようなデータは、入試データだけでなく商品の売上データや顧客データ、商品管理データベースなどいくらでもある。こうしたデータは表計算ソフトでは処理しきれない。

プログラムを用いた10万人のデータの処理

プログラムを用いた場合、先ほどのfile_sum.rbを使えば処理をすることができる。以下は、先ほどのfile_sum.rbであるが、p lineの行でpの手前に#をつけコメント扱いにしている。また、最終行では今回のプログラムに合致するよう""内のメッセージを変更している。

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

sum = 0

while line = gets
  #p line
  sum += line.to_i
end

printf("合計%d杯です。\n",sum)

とりあえず実行してみよう。rand.csvを読み込んで実施するので、以下のように読み込むファイルを指定しながら実行する。

sime{c11xxxx}% chmod +x file_sum.rb[Return]
sime{c11xxxx}% ./file_sum.rb rand.csv[Return]
合計5046022杯です。

あっという間に結果が出たことが確認できただろうか。本当に計算をしっかり行っているのか疑うくらいの速度である。今回は、p lineの行をコメント扱いにして実行しないようにしたが、#をとってこの行も実行させてみると、多少時間はかかるようになるが処理を行っていることが見て取れる。

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

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

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

sum = 0

while line = gets
  p line
  sum += line.to_i
end

printf("合計%d杯です。\n",sum)
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

sum = 0

while line = gets
p line
sum += line.to_i
end

printf("合計%d杯です。\n",sum)

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

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

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

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

[11] レポート課題

課題

1番、2番いずれも実施しなさい(report2a.rb、report2b.rb)。難しい場合は1番のみでも可。

  1. 8回分のレポートの得点を順番に入力すると、合計得点が表示されるプログラム。レポート点は6.5点というように小数を含む(ここまでだと5点満点)
  2. population.csvは平成22年1月末日時点での酒田市民全員の年齢を示したデータである。ファイルを開くと最初に0が747個続くが、これは0歳が747人であることをあらわしている。このデータを読み込み、平成22年1月末日時点での酒田市民の平均年齢を調べなさい(小数点以下第1位まで)(1番、2番共に実施すると8点満点)

作成上の条件

  • while-endを使用すること
  • printやprintfメソッドを用いてメッセージを表示する際、初めてそのプログラムを実行したユーザでも使い方が分かるような文章にする(1番)
  • 2番については、合計ではなく平均値を求める必要がある。合計は、授業中に取り上げたプログラムで求められるので、このプログラムを改良して平均が求められるようにする。改良のポイントは次の通り
    • 合計を人数で割れば平均が求められる。while-end内で初期値が0の変数の値を1ずつ増加すれば、繰り返しから抜けた時点でその変数には繰り返しの回数(=人数)が代入されている
    • pメソッドを使って変数内の値を表示させながら、値が思ったとおりの変化をしているか確認しながら作業をすすめると良い
    • 平均値は小数点を含む値になる。計算過程や結果表示における値の型について考えること

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

sime{c11xxxx}% chmod +x report2a.rb[Return]
sime{c11xxxx}% ./report2a.rb[Return]
1回目のレポートの得点を入力してください
7.5[Return]
2回目のレポートの得点を入力してください
6[Return]
      :
合計得点は82.5点です。

2番については実行しても答えがあっているかわからないため、ExcelやCalcなどの表計算ソフトを用いて平均値を計算して結果を確認しても良い。確認した場合はこのファイルもレポートに添付して送れば加点する。なお、population.csvはデータ数が多く、このまま表計算ソフトで開くと下のほうのデータが削除されてしまうため、emacs等のテキストエディタで開いて下半分を別のファイルに保存してからそれぞれのファイルを開いて計算を行うなどの対応が必要である(大規模データを表計算ソフトで処理する際にはこのような面倒な手続きが必要になるということを確認するよい機会になるので余力のある人は是非挑戦してみること)。

なお、csv形式は計算式は保存できない形式であり、正しく計算式が入力されているかを確認できない。作業終了後は「名前を付けて保存(別名で保存)」を選び、Calcであればods形式で、Excelであればxls形式で保存し、このファイルを提出すること。

提出要領

  • 提出先:課題提出用メールアドレス
  • 提出期限:第1提出期限、第2提出期限を設定
  • メールのSubject:report02
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
    1. 作成したプログラム(1)
    2. プログラム(1)の実行結果
    3. プログラム(1)の説明
    4. 作成したプログラム(2)
    5. プログラム(2)の実行結果
    6. プログラム(2)の説明
    7. 感想
    8. 参考文献
    9. 添付ファイル(report2a.rb, report2b.rb, population.ods又はpopupation.xls)

採点要領

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