生物生産科学科 担当 國分 尚(5月28日、6月4日、6月11日)
Rubyプログラムは文の集まりである。他の多くのプログラミング言語と違い、Rubyの文は最後に何かの記号を付ける必要はない。ただし、複数の文を1行に書く時はセミコロンで区切る。
i = 1
a = 0; b = "text"
print "Hello world\n"
print 'Hello world\n'
c = 5 / 6
print c,"\n"
c = 5.0 / 6.0
print c,"\n"
この例でもわかるように数値はそのままの数字を書き、文字列は " " (ダブルクォーテーション)、または ' ' (シングルクォーテーション)でくくる。" " と ' ' には違った意味があるが、主に使うのは " " である。数値で気をつける必要があるのは整数と実数の違いで、小数点のない数値は整数として判断される。
練習3.上の例を実行して""と’’のちがい、小数点の有無に対する挙動に慣れること。また、上の割り算で、片方だけに小数点を付けるとどうなるか。
Rubyでは = は代入の記号であり、数学の等号とは異なる。
a = 5
は a という変数に 5 を代入している。二つの値が同じかどうかを調べるには==を使う。
a == 5
はaに代入されているのが5であれば真 (true)、そうでなければ偽 (false)である。
プログラミング言語で変数と呼ぶものは数学の変数とは異なる。簡単に言ってしまうとある値が入っている入れ物となる。(Rubyの場合、厳密には変数にはオブジェクトへの参照が入っている。)
RubyはCやPascal等と違って変数の型と言うものがなく、宣言をする必要もない。従ってプログラムに変数宣言部という特別な構造はない。しかし、次に出てくるローカル変数は初めに必ず代入をしないと使えないし(代入が暗示的な宣言になっている)、プログラムの見通しをよくするためにもあとで使う変数にプログラムの先頭であらかじめ初期値を代入しておくのは悪いことではない。
変数にはローカル変数、グローバル変数、インスタンス変数、定数の4種類があり、次のように名前の付け方で区別する。
このうちグローバル変数は一般には使われない。また、インスタンス変数はオブジェクト指向プログラミング以外には使わない。定数は最初に一度だけ代入することができるが、その後は値を変えることはできない。
一般に、プログラミング言語では扱うデータが「型」と呼ばれる種類に厳密に分かれている。整数型、実数(浮動小数点)型、文字列型、論理型などがあり、変数も型が決まっている。例えば a という変数が整数型と指定されると整数以外のデータを入れようとするとエラーになる。
Rubyではこれらはオブジェクトの種類(クラス)と呼ぶ。例えば123という数字は他の言語では整数型のデータだが、Rubyでは整数クラスのオブジェクトである。すべてのオブジェクトはObjectという種類のオブジェクトでもあるので、Rubyの変数にはどんな種類のオブジェクトを代入してもよい。
a = 25
print a, "\n"
a = "twenty-five"
print a, "\n"
Rubyの特徴の1つに多倍長整数がある。普通のプログラミング言語では扱える整数の大きさには制限があり、±32767または0から65535であることが多い。しかし、Rubyでは整数の大きさはメモリの容量のみに制限され、例えば400の階乗のような大きな数も問題なく扱える。
練習4.次のプログラムfact.rbを作成し、実行してみる。
プログラム2.fact.rb(まつもと・石塚, 1999)
def fact(n)
return 1 if n ==0 # 0の階乗は1である
f = 1
while n>0
f *= n # f = f * nと同じ
n -= 1 # n = n - 1と同じ
end
return f
end
print fact(ARGV[0].to_i), "\n"
ファイルを作成後、
keyaki% ruby fact.rb 10
3628800
と10の階乗が正しく計算されるのを確認したら
keyaki% ruby fact.rb 400
のようにすると400の階乗が出力される。この値は宇宙の直径と電子の直径の比よりも遥かに大きい。
プログラミング言語で配列と呼ぶものも数学の配列とは異なる。簡単に言ってしまうと番号がついた変数のことで、この番号を別の変数を使って指定することで読み書きできるようになっている。
配列は a[0] とか youbi[d] の様な形をしている。
最初の例はaと言う名前の配列の0番目の要素、次の例はyoubiという名前の配列のd番目の要素と言うことで、dの値によって違う要素を指定できる。配列は下に出てくる繰り返しと組み合わせて使うことが多い。
プログラム3.dayofweek.rb
youbi = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
print "Today is ",youbi[Time.now.wday],".\n"
#Time.now.wdayは今日が日曜なら0、金曜なら5という整数である
ハッシュ(連想配列)は配列と似ているが、要素を番号で指定するのでなく名前で指定するようになっている。使い方がわかると大変有効なものだが、ここでは紹介だけにとどめる。例えば p_rec["name"] というのはp_recという名前のハッシュのnameという要素を示す。
プログラム4.meibo.rb
def printPerson(p_rec)
print "学籍番号: ", p_rec["id"],"\n"
print "名前: ", p_rec["name"],"\n"
print "出身地: ", p_rec["pob"],"\n"
print "性別: "
if p_rec["sex"] = "M"
print "男\n"
else
print "女\n"
end
end
meibo=[] # 空の配列を作る
meibo[0] = {"id" => "04H1111X", "name" => "田中一郎", "pob" => "新潟県", "sex" => "M"} # 配列の0番目のデータとして{}内のハッシュを指定
meibo[1] = {"id" => "04H1112P", "name" => "新井素子", "pob" => "埼玉県", "sex" => "F"}
for person in meibo
printPerson(person)
end
などという使い方が考えられる。いちいちこんなことをしなくてもid、name、age、sexという変数を使ったらいいのではないかと考える人は鋭い。ぜひともRubyプログラミングの参考書や一般的なプログラミングの本を調べて理由を考えてください。
連続した文は上から順番に実行する。上のdaikei.rb等を参照。
ある条件によって処理を変えたい時に使う。最も簡単には
if 条件 条件に当てはまる時の処理 end
のように書く。もっと一般的には
if 条件1 条件1に該当する時の処理 elsif 条件2 条件2に該当する時の処理 ... elsif 条件n 条件nに該当する時の処理 else どの条件にも当てはまらない時の処理 end
と言う形になる。この時に注意が必要なのは、条件1が成立した場合、その時の処理が終わったあとにはendの次の文を実行し、elsif以下の条件に当てはまるかどうかの判断は行わない、ということである。条件1に関わりなく条件2の判断をしたい場合は
if 条件1 条件1に該当する時の処理 end if 条件2 条件2に該当する時の処理 end
と書く必要がある。
プログラム5.greeting.rb
hh = Time.now.hour #Time.now.hourは現在時刻の時の部分
if hh >= 4 and hh <= 11
print "おはようございます\n"
elsif hh > 11 and hh <= 18
print "こんにちは\n"
elsif hh > 18
print "こんばんは\n"
else
print "こんな時間に起きていると体を壊しますよ\n"
end
夜中の1時頃このプログラムを実行して「大きなお世話だ」と言いたくなる人は適当に改造すること。
ある条件が成立している間、同じ処理を繰り返す必要がしばしばある。これを行うのが繰り返しで、ループとも呼ぶ。Rubyは繰り返しの書き方が何通りもあるが、基本は次のようになる。
while 条件 条件が成立している時の処理 条件の成立に影響を与える処理 end
よくあるプログラム上の間違い(bug,バグと呼ぶ)は繰り返しの処理の中で条件の成立に影響を与える処理を書き忘れることである。この場合運が悪いと繰り返しの中から抜け出せなくなる。これを無限ループと呼び、最も頻繁に出くわすバグの一つである。
文章ではよくわからないので例を挙げると
プログラム6.sum10.rb
s = 0; i = 1
while i <= 10
s = s + i
i = i + 1 #カウンタを増やす
end
print s,"\n"
これは1から10までの和を求めるごくつまらないプログラムである。ところが、ここでカウンタを増やす文を書き忘れてしまうとプログラムはいつまでたっても終了しない。(このような事態に陥った場合はC-cをタイプするとプログラムを中止できる。)繰り返しの部分がこのように単純な場合はまず問題ないが、繰り返しの処理が複雑になってくると間違いが入り込む確率も上がることに注意する。
まとまりのある処理を値だけ変えて何度も行う必要があるとき、またはその処理を状況に応じて実行したりしなかったりする場合、一般にサブルーチン(または手続き、関数)と言うものを使う。Rubyの場合これをメソッドとよび、単なるサブルーチンとは性質が異なるが、オブジェクト指向プログラミングを意識しない限り普通のプログラミング言語のサブルーチンと違いはない。これまでの例題の中にも既に出て来ていて、プログラムX,fact.rbの中の
def fact(n) .... end
の部分がメソッドである。10の階乗でも400の階乗でもアルゴリズムは同じで、与える値を変える仕組みさえあればよい。これを行うのがメソッドである。
メソッドはdef ... で定義した部分では実行されず、他の部分から呼び出された時に初めて実行される。
ここまでの情報をふまえてアルゴリズム3をRubyプログラムに書き直してみる。プログラミングではまず部品を完成させてから全体を作る方法と、全体の流れを作っておいてから細かい部分を作っていく方法があるが、今回の場合は後者は成立しないので(素数かどうかの判断ができないとプログラムが永久に終了しない)、まず素数の判定部分をメソッドとして作成し、それをテストしてみる。
プロブラム7.test_sosuu.rb
def sosuu?(x)
# 素数の判定を行う
# 入力条件: xは3以上の奇数
# 出力条件: 素数ならtrue、素数でなければfalseを返す
i = 3
while (i <= x/3) and (x % i != 0) #x % i はxをiで割った余り
i = i + 2 # i += 2 とも書ける
end
if i > x/3
# 成立すればxは素数(割り切れた場合にはiは必ずx/3以下である)
return true
else
return false
end
end
print "n="
n=STDIN.gets.to_i
if sosuu?(n)
print n," is a prime number.\n"
else
print n," is not a prime number.\n"
end
練習5.test_sosuu.rbを実行して11が素数であり、9はそうでないことを確認し、そのほかのいくつかの数について素数の判定をする。このアルゴリズムは3以上の奇数について正しく働くが、偶数に対して判断するとどうなるか。
素数の判断に間違いがなければ、全体のプログラムに進む。これは次のようになる。ただし、全員が一斉に1000番目の素数を求めようとするとホストに負荷がかかりすぎる可能性があるので、100番目に変更してある。(一人だけで実行した時は1000番目で約4秒かかった。単純計算で100人同時だと400秒かかることになる。)このような場合、この1000とか100とかの数を後から簡単に変更できるようにプログラムを書くことは非常に重要である。下の例ではたった1か所を変更するだけでいいことに注意する。
プログラム8.sosuu.rb
def sosuu?(x)
i = 3
while (i <= x/3) and (x % i != 0)
i = i + 2
end
if i > x/3
return true
else
return false
end
end
n = 3
c = 2
last = 2
target = 100
while c <= target
if sosuu?(n) # nは素数か?
last = n
c = c + 1 # c += 1 とも書ける
end
n = n + 2 # n += 2 とも書ける
end
print target, "th prime is ", last, ".\n"
練習6.sosuu.rbを実行して100番目の素数が541になることを確認する。また、プログラムを変更して何通りかのn番目の素数を求める。
課題1.sosuu.rbを変更してキーボードから任意の自然数を入力し(プログラム1.daikei.rb、プログラム7.test_sosuu.rb参照)、n番目の素数を求めることができるようにする。または、プログラム2.fact.rbのやり方を参考にしてコマンドラインからnを指定するようにしてもよい。1番目(2)と2番目(3)についても動くだろうか。
課題2.上の課題1のプログラムを変更し、1番目の素数からn番目まですべての値をc: lastの形で出力すること。出力例を示す。
出力例 1: 2 1th prime is 2. 1: 2 2: 3 3: 5 4: 7 5: 11 6: 13 6th prime is 13.
上の出力例で"1th prime is 2."となっているが、これでは大学生の作ったプログラムの出力としては恥ずかしいのでここも変更したい。targetを10で割った余り (target % 10) が1、2、3、それ以外の場合分けをすればよいだろう。(少し考えればこれでも十分でないことはわかるだろう。外国語の数字の扱いは大変である。ヒント:11はどうなる?)
この課題、または次の章の課題3、4のどれか1つ作成したプログラムと変更した部分についての説明をeメールで國分(hkokubun@faculty.chiba-u.jp)に提出すること。どうしてもプログラムができない時はアルゴリズム3を変更してアルゴリズムを提出すること。期日は7月30日(金)とする。
オブジェクト指向プログラミングにおいてオブジェクトとは簡単に言うとデータとそれを操作するアルゴリズム(メソッド)をまとめたものである。オブジェクトは自分自身にどんなデータが格納されていて、それをどう扱うかを「知って」いる。例えばプログラム1の中にSTDIN.getsという表現が出てくるが、これはSTDINというオブジェクトのgetsというメソッドを使うことを意味し、「STDINにgetsメッセージを送る」と表現する。STDINは標準入力といわれるIOクラスの特別なオブジェクトであり、プログラムが使われた状況によってキーボードであったりファイルであったりするが、STDINオブジェクトはこれを知っていて、getsというメッセージをもらうと自分自身のおかれている状況に従って適切な処理を行い、呼び出したプログラムに1行分のデータを答えとして送り返す。
また、jouhen.to_fはjouhenという変数が示すオブジェクトにto_fというメッセージを送っている。プログラム1の場合はjouhenにはキーボードから入力した文字列がオブジェクトとして入っているので、文字列に対するto_fメッセージということになり、文字列オブジェクトは自分自身を数値に変換した値を答える。
2004年5月14日作成、2004年8月24日更新
國分 尚