roy > naoya > 基礎プログラミングII[月2] > (6)メソッド
歌の歌詞について考えてみよう(本当の歌詞を出すと著作権の侵害となりJASRACに怒られるので架空の歌詞とする)。多くの歌ではサビの部分は共通している。このため歌詞カードを見ると、2回目や3回目に登場するときは「*繰り返し」とだけ書かれていることが多い。
1.うんたらかんたら うんたらかんたら たりらり〜〜〜 *ららららら〜 れれれれれ〜 2.なんたらかんたら なんたらかんたら てれてれ〜〜〜 *繰り返し
もちろん、サビの部分で同じことを繰り返し書いても良い。しかし、「*繰り返し」の方が効率的であるし、行数も短くて済む。プログラムにおいても効率化を考えて、同じことを繰り返し書くかわりに以下のような構造をとることがある。あらかじめ冒頭に繰り返し部を記載しておき、プログラムの本文中で、必要に応じて呼び出すというものである。
これにより、同じことを何度も書く必要がなくなる。冒頭で呼び出されるプログラムを記載しておき、必要に応じて呼び出すために用いるのがdef-endである。def-endを用いる場面として以下をあげることができる。
def-endの基本構造は以下の通りとなる。
def-endの基本構造
def メソッド名(引数リスト) 定義本体 end
簡単な例をみてみよう。
#!/usr/koeki/bin/ruby def plus(x) return x + 10 end number = 20 printf("10に10を足すと%dになります\n", plus(10)) printf("20に10を足すと%dになります\n", plus(number))
def-endで定義されたplusをメソッドと呼ぶ。このプログラムでは冒頭でplusメソッドを定義し、その下で実際にplusメソッドを呼び出して活用している。
def plus(x)とあるが、このうち(x)は引数リストと呼ぶ。これはplusメソッドを実行するためには引数が1つ必要であるということを意味する。メソッドと引数の関係をまとめると以下のようになる。
メソッドの定義と引数の関係
引数として指定する変数名はxでもyでもaでもbでも何でも構わない。
このプログラムで使われているplusメソッドに話を戻そう。def-end内を見ると、次のように書かれている。
def plus(x) return x + 10 end
このメソッドは引数を1つ必要とし、具体的には2行目にあるreturn x + 10を実行する。returnは呼び出されたところに返すという意味なので、呼び出されたときに与えられた引数xに10を足した値を返すということになる。
今度は、このプログラムの下部を見てみよう。
printf("10に10を足すと%dになります\n", plus(10)) printf("20に10を足すと%dになります\n", plus(number))
printfを用い、%dに表示する変数として、plus(10)およびplus(number)と書いている。ここでplusメソッドを呼び出している。plusメソッドは引数を1つ必要とするメソッドであるため、引数として10やnumberを与えている。numberには20が代入されている。
plusメソッドが呼び出されるとdef-endの部分に移動する。引数として10を与えた場合はxに10が代入されplusメソッドが実行される。つまり10+10や20+10が行われ、その結果の20や30が呼び出した場所に返される。
def-end内ではplusメソッドの引数にxを使用しているが、def-endの下でplusメソッドを呼び出す際は引数として10やnumberを与えている。xはdef-end内でのみ使用される変数であり、呼び出す際に与えられた引数が代入される。よって、def-end内の変数とdef-endの下の変数名が一致していなくても問題ない。
続けて幾つかの例を見ていきながらdef-endの用法について確認してみよう。
#!/usr/koeki/bin/ruby def shohizei(a) a * 1.05 end print"金額を入力してください" item = gets.chomp!.to_i printf("税込み金額は%d円です\n", shohizei(item))
shohizeiメソッドは引数を1つ必要とするメソッドであり、1.05倍した値を返す。ここではa * 1.05となっており、returnがついていない。returnは呼び出し元に返すという意味だが、省略可能である。
shohizeiメソッドを呼び出しているのは最後のprintfの行であり、printf("税込み金額は%d円です\n", shohizei(item))という形で呼び出している。itemはユーザが入力した金額が代入されており、これを引数として指定することで、shohizeiメソッドは1.05倍した結果の値を返してくれる。
shohizeiメソッドが呼び出されると、aにitemが代入され1.05倍される。その結果がprintfの行に返される。
#!/usr/koeki/bin/ruby def tax(x) price = x * 1.05 printf("税込み価格は%d円です\n",price) end STDERR.print "金額を入力してください" item = gets.chomp!.to_i tax(item)
これも消費税を求めるメソッドを作成した例である。ただしtaxメソッドはshohizeiメソッドと比較して、def-end内の行数が1行増えている。shohizeiメソッドは単に与えられた引数を1.05倍して返すだけであったが、taxメソッドは与えられた引数を1.05倍し、printfで結果を表示してくれる。この場合、呼び出した場所へ返されるのは最終行のprintfの行となる。
さらに、taxメソッドを呼び出す際の方法も異なっている。上記のプログラムでは、単にtax(item)とだけ記している。shohizeiメソッドは計算をした結果の数字を返してくれるだけであるため、その数字を表示できるような形で(すなわちprintfを使って)呼び出す必要がある。一方、taxメソッドではメソッドの働き自体にprintfによる表示が含まれるため、単にtax(item)で呼び出せば、計算をして結果も表示してくれることとなる。
tax.rbを改良し、金額を入力すると1割引の価格を計算して表示するプログラムを作成しなさい。なお、1割引の価格の計算と金額の表示をdef-end内で行うこと。メソッドの名称はdiscountとする。
制限時間は10分。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
ここでは、def-endを利用する上での注意点をまとめる。一部はこれまでの説明で出てきたものであり、新たに登場するものもある。後者についてはこの後で説明するが、注意点については複数の場所に記載されるとわかりづらくなるため、ここで一括して示す。
def-end使用上の注意
#!/usr/koeki/bin/ruby
def hello()
print"こんにちは!\n"
end
hello
引数が不要な場合は、引数リストのカッコ内は空にする。helloメソッドはprint文でメッセージを表示するので、呼び出す際も単にhelloをすれば結果が表示される。
#!/usr/koeki/bin/ruby
def triangle(x,y)
menseki = x * y / 2
printf("面積は%4.2f平方センチメートルです\n",menseki)
end
STDERR.print"底辺をcm単位で入力してください"
teihen = gets.chomp!.to_f
STDERR.print"高さをcm単位で入力してください"
takasa = gets.chomp!.to_f
triangle(teihen, takasa)
triangleメソッドは2つの値を引数として与えると、計算をするだけではなくprintfで結果を表示する。このため呼び出す際も単にtriangle(teihen, takasa)とすればよい。
#!/usr/koeki/bin/ruby
def sankaku(x,y)
x * y / 2
end
STDERR.print"底辺をcm単位で入力してください"
teihen = gets.chomp!.to_f
STDERR.print"高さをcm単位で入力してください"
takasa = gets.chomp!.to_f
result = sankaku(teihen, takasa)
printf("面積は%4.2f平方センチメートルです\n",result)
sankakuメソッドも2つの値を引数として受け取り面積を求めるものである。ただし返り値は計算をした結果の数字であるため、呼び出し方もtriangleメソッドとは異なっている。sankakuメソッドを呼び出して得られた返り値をresultに代入し、printfでresultを表示している。
#!/usr/koeki/bin/ruby
def min(x,y)
if x < y
return x #returnは省略可能
else
return y #returnは省略可能
end
end
print"好きな数字を入力してください"
no1 = gets.chomp!.to_i
print"もう1つ好きな数字を入力してください"
no2 = gets.chomp!.to_i
printf("%dの方が小さいです\n", min(no1,no2))
minメソッドは2つの値を引数として受け取り、小さい方の値を返す。if文を用い、条件に応じた処理が行われるが、この場合は最終行が返り値とならない。最終行が返り値にならない場合でもreturnは省略することができる。
#!/usr/koeki/bin/ruby def total(yen) #yenは数値の入っている配列 sum = 0 x = 0 while x < yen.length-1 #.lengthで配列の中のデータの個数を調べる sum += yen[x] x += 1 end sum #最終行が返り値となるので単にsumと書いている end bokin = [] i = 0 while true STDERR.print"募金お願いします(0で終了)" bokin[i] = gets.chomp!.to_i if bokin[i] == 0 then break end i += 1 end printf("募金の合計は%d円でした\n",total(bokin))
プログラム下部ではbokinを配列として定義し、while-endの繰り返しを行う中で次々にbokinに値を代入している。最後にtotal(bokin)で配列のbokinを引数としてtotalメソッドに引き渡している。defの横はtotal(yen)と書いており、yenはdef-end内では配列として扱われている。配列と引数とする場合でもtotal(yen)の部分に特別な書き方はない。yenを配列扱いとして以後処理すればよい。
引数が必要なメソッドにおいて、引数が省略された場合に適用される値(デフォルト値)を指定しておくことができる。
#!/usr/koeki/bin/ruby
def sankaku(x,y=10)
x * y / 2
end
printf("面積は%4.2f平方センチメートルです\n", sankaku(5))
printf("面積は%4.2f平方センチメートルです\n", sankaku(5,8))
sankakuメソッドは2つの引数を受け取り、面積を計算してその値を返すメソッドである。本来2つの引数が必要であり、sankaku(5,8)のように2つの引数を与えるのが通常の呼び出し方である。しかし、このプログラムではsankaku(5)で呼び出している。
一方、def-endではsankaku(x,y=10)としてyに最初から10というデフォルト値を与えている。このため、sankaku(5)で呼び出した場合にはxに5が代入され、yは10が使用される。sankaku(5,8)で呼び出すとxには5、yには8が代入される。
メソッドを呼び出す際に与える引数は、先頭から順番に与えられる。上記の例でsankaku(5)とすると、5は必ずxに代入される。このため2つの引数を用いるメソッドを定義する際に、先頭の引数にデフォルト値を与え、2つ目の引数にデフォルト値を与えない場合、sankaku(5)で呼び出すとxに5が代入され、yには値がないことになる。この場合は実行するとエラーとなる。引数が2つの場合デフォルト値の与え方は以下の3種類があるが、×の場合はうまく動かないことがある。
メソッドへのデフォルト値の与え方
○ def sankaku(x=10, y=8) ○ def sankaku(x, y=8) × def sankaku(x=10, y)
単一のプログラムの中で複数のメソッドを定義することができるし、一つのメソッドをプログラムの中で何度も使用することもできる。
(fight.rb)
#!/usr/koeki/bin/ruby hp1 = 30 #自分のヒットポイント hp2 = 30 #敵のヒットポイント time = 1 #sleepの待ち時間 #wazaメソッド(引数不要) #damageメソッドから呼び出される。 #3つの技からランダムにどれかが選ばれ、呼び出し元に返す。 def waza() sound = ["難しい宿題!","賞味期限の過ぎた牛乳!","オラオラオラオラッ!"] srand x = rand(3) return sound[x] end #damageメソッド(引数不要) #wazaメソッドで選ばれた技の名称と、1〜5のダメージをprintfで表示し #1〜5のダメージ(a)を呼び出し元に返す def damage() srand a = rand(5)+1 #1〜5がランダムに選ばれaに代入 printf(" %s %dのダメージを与えた\n",waza,a) return a end #hanteiメソッド(引数2つ必要) #自分と敵のヒットポイントを引数として受け取り #勝敗や戦闘の継続の判定をする def hantei(a,b) if a <= 0 && b > 0 print"あなたの負け\n" return 1 elsif a > 0 && b <= 0 print"あなたの勝ち\n" return 1 elsif a <= 0 && b <= 0 print"相打ちだ\n" return 1 else return 0 end end while true print"\nあなたの攻撃:\n" sleep(time) hp2 -= damage sleep(time) print"敵の攻撃:\n" sleep(time) hp1 -= damage sleep(time) printf("\n YOU:%d ENEMY:%d\n",hp1, hp2) if hantei(hp1,hp2)==1 exit(0) end sleep(time) end
pan{c10xxxx}% ruby fight.rb[Return] あなたの攻撃: 難しい宿題! 1のダメージを与えた 敵の攻撃: オラオラオラオラッ! 3のダメージを与えた YOU:27 ENEMY:29 あなたの攻撃: オラオラオラオラッ! 1のダメージを与えた 敵の攻撃: オラオラオラオラッ! 2のダメージを与えた YOU:25 ENEMY:28 : (以下略)
このプログラムは、みどりのように、表示された文字色の名前と表示色が異なる場合に、色の名前(ここでは「みどり」)と解答する時間が、双方が一致する場合と比べて遅れること(これをストループ効果という)を体験するプログラムである(stroop.rb)。
#!/usr/koeki/bin/ruby name = ["あか","みどり","あお","きいろ","しろ"] number = [31, 32, 34, 33, 37] text = [] color = [] result = [] def randomize_color(n) #10試行分の文字色(表示色)をランダムに作成 array = [] 10.times do srand i = rand(n) #0〜(n-1)の値をランダムに返しiに代入 array << i end return array end def check(start, stop, a, b) #正答の場合は反応時間を返し、誤答の場合はerrorと返す reaction_time = stop - start if a == b - 1 return reaction_time else return "error" end end def cls() sleep(1) print"\e[2J" #画面消去 printf("\e[%d;%dH", 0, 1) #カーソルを左上に移動 end cls system 'banner STROOP EFFECT' cls print"実験を開始すると、ディスプレイ左上に「あか」や「みどり」など、 色の名前が表示されます。 \e[33mみどり \e[mのように色の名前と表示色が異なる場合もあれば \e[31mあか \e[mのように色の名前と表示色が一致する場合もあります。 いずれの場合も表示色ではなく、\e[4m文字で書かれたの色の名前を回答して下さい。\e[m 例えば \e[34mみどり \e[mの場合は、「みどり」が答えになります。 回答は、「みどり」のように色の名前を入力するのではなく、 それぞれの色に割り当てられたキーを押して反応していただきます。 キーの割り当ては、 あか:1、みどり:2、あお:3、きいろ:4、しろ:5 となります。\n この実験では出現する色の種類を2種類から5種類まで自由に選択することができます。 例えば、3種類にすると、色の名前、表示色ともに「あか」「みどり」「あお」の 3種類となります。4種類にすると「きいろ」が追加されます。\n\n" while true print"何色にしますか。2から5で選んで下さい:" choice = gets.chomp!.to_i if choice < 2 || choice >5 print"入力できるのは2から5の整数のみです\n" redo else break end end print"全部で10試行行います。頑張って下さい。\n" color = randomize_color(choice) text = randomize_color(choice) 0.upto(9) do |j| cls start = Time.now #タイマースタート printf("\e[%dm%s\e[m\n",number[color[j]], name[text[j]]) answer = gets.chomp!.to_i stop = Time.now #タイマーストップ result << check(start, stop, text[j], answer) end p result
このプログラムで使用されている画面消去や文字色の変更、大きな文字の出し方、乱数発生などについては前期の自由課題の際に説明をしたプログラムを楽しくするためのテクニックのページを参照すること。
どちらかを選んで実施せよ。
問題1(7〜10点満点):def-endを用いて面白いプログラムを作ってみよう。randやsleep、Time.nowなどを使ってみても良い。fight.rbやstroop.rbレベルで10点満点、このWebページ内の他のサンプルプログラムレベルだと7点満点で採点する。複数のメソッドを定義し、プログラムを実行する中で複数回呼び出して活用すると得点が高くなる。
問題2(8点満点以上):stroop.rbに、文字色と表示色が異なる場合と一致する場合の反応時間の平均値と誤答数を表示するメソッドを追加し、10試行実施後の結果表示の際にこのメソッドを呼び出して、綺麗に結果を表示できるようにせよ。その他の部分をさらに改良しても良い。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照