(8) 11/27の授業内容:メソッドを自分で作る

導入

歌の歌詞について考えてみよう(本当の歌詞を出すと著作権の侵害となりJASRACに怒られるので架空の歌詞とする)。多くの歌ではサビの部分は共通している。このため歌詞カードを見ると、2回目や3回目に登場するときは「*繰り返し」とだけ書かれていることが多い。

1.うんたらかんたら
  うんたらかんたら
  たりらり〜〜〜

 *ららららら〜
  れれれれれ〜

2.なんたらかんたら
  なんたらかんたら
  てれてれ〜〜〜

 *繰り返し

もちろん、サビの部分で同じことを繰り返し書いても構わない。しかし、「*繰り返し」の方が効率的であるし、行数も短くて済む。プログラムにおいても効率化を考えて、同じことを繰り返し書くかわりに以下のような構造をとることがある。あらかじめ冒頭に繰り返し部を記載しておき、プログラムの本文中で、必要に応じて呼び出すというものである。

上部に繰り返し部を配置し、必要に応じて呼び出す

これにより、同じことを何度も書く必要がなくなる。冒頭で呼び出されるプログラムを記載しておき、必要に応じて呼び出すために用いるのがdef-endである。def-endを用いる場面として以下をあげることができる。

  • プログラム内で同じ処理が何度も繰り返し出現する
  • 複数のプログラムで同じ処理をする。2回目以降は冒頭に書いてあるものをコピーすれば、その部分は毎回書かなくても良くなる

def-endの基本的な用法

def-endの基本構造は以下の通りとなる。

def メソッド名(引数リスト)
  定義本体
end

簡単な例をみてみよう。

#!/usr/koeki/bin/ruby

def plus(x)
  return x + 10
end

printf ("10に10を足すと%dになります\n", plus(10))
printf ("20に10を足すと%dになります\n", plus(20))

def-endで定義されたplusをメソッドと呼ぶ。このプログラムでは冒頭でplusメソッドを定義し、その下で実際にplusメソッドを呼び出して活用している。

def plus(x)とあるが、このうち(x)は引数リストと呼ぶ。これはplusメソッドを実行するためには引数が1つ必要であるということを意味する。メソッドと引数の関係をまとめると以下のようになる。

  • def xxx():xxxメソッドは引数なしで実行可能
  • def yyy(a):yyyメソッドを実行するためには引数が1つ必要
  • def zzz(j,k):zzzメソッドを実行するためには引数が2つ必要
  • def sss(x,y,z):sssメソッドを実行するためには引数が3つ必要

引数として指定する変数名は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(20))

printfを用い、%dに表示する変数として、plus(10)およびplus(20)と書いている。ここでplusメソッドを呼び出している。plusメソッドは引数を1つ必要とするメソッドであるため、引数として10を与えている。

plusメソッドが呼び出されるとdef-endの部分に移動する。引数として10を与えた場合はxに10が代入されplusメソッドが実行される。つまり10+10が行われ、その結果の20が呼び出した場所に返される。

続けて幾つかの例を見ていきながらdef-endの用法について確認してみよう。

消費税を計算するメソッド:shohizei(shohizei.rb)

#!/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の行に返される。

消費税を計算するメソッド:tax(tax.rb)

#!/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)で呼び出せば、計算をして結果も表示してくれることとなる。

出席課題

次のプログラムをdef-endを使って書き換えてみよう。平均点を算出するメソッドの名前はaveとすること。

#!/usr/koeki/bin/ruby

print "前期の試験の得点を入力してください:"
first = gets.chomp!.to_f
print "後期の試験の得点を入力してください:"
second = gets.chomp!.to_f

average = (first + second) / 2

printf ("平均点は%5.2f点です。\n", average)

制限時間は10分。出席点は2点。提出要領は下記の通り。

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールのSubject:ruby08
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降に作成したプログラム、実行結果を貼り付ける。変更点を記載する余裕がある場合は、これも書いてみること。

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

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

def-end利用上の注意

ここでは、def-endを利用する上での注意点をまとめる。一部はこれまでの説明で出てきたものであり、新たに登場するものもある。後者についてはこの後で説明するが、注意点については複数の場所に記載されるとわかりづらくなるため、ここで一括して示す。

  1. 呼び出す前に定義をする:def-endで定義をしたメソッドを呼び出す場合、呼び出す前に定義されている必要がある。定義が下に書かれていても呼び出せない。
  2. メソッドの返り値は原則として最終行となる:def-endでメソッドを定義する場合、def-end内に複数行を書くことができる。この場合呼び出した場所に返す(返り値)のは、原則として最終行となる。
  3. 返り値であることを示すreturnはあってもなくても良い:原則として返り値は最終行となるため、これが返り値であるということを示すreturnはあってもなくても良い。
  4. if文を用いた場合は返り値は最終行になるとは限らない:例外的にif文などの条件判断をdef-end内に書いた場合、条件に応じて返り値が異なる。この場合のみ最終行以外が返り値となることがある。
  5. 呼び出し方と返り値の関係を考える:単に計算した結果を返すだけのメソッドなのか(例:shohizeiメソッド)、計算をしてその結果を表示してくれるメソッドなのか(taxメソッド)など、メソッドの持つ働きに応じて呼び出し方を変える必要がある。
  6. メソッドに引数として配列を渡すことができる:メソッドの中で配列を用いた処理を記載しておけば、引数は通常の変数でなくても構わない。
  7. メソッドを呼び出す際の引数は0〜∞まで指定できる:以下でプログラムのサンプルを示す。
  8. 引数にはデフォルト値を指定することができる:引数を必要とするメソッドを呼び出す際に、引数を与えなければエラーになるが、デフォルトの値を指定しておくとその値が自動的に代入される。

メソッドのサンプル(引数が異なる場合)

引数が0の場合(単にこんにちはと表示するだけのメソッド:hello)

#!/usr/koeki/bin/ruby

def hello()
  print "こんにちは!\n"
end

hello

引数が不要な場合は、引数リストのカッコ内は空にする。helloメソッドはprint文でメッセージを表示するので、呼び出す際も単にhelloをすれば結果が表示される。

引数が2の場合(三角形の面積を求めるメソッド:triangle)

#!/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)とすればよい。

引数が2の場合(三角形の面積を求めるメソッド:sankaku)

#!/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
printf ("面積は%4.2f平方センチメートルです\n",sankaku(teihen, takasa))

sankakuメソッドも2つの値を引数として受け取り面積を求めるものである。ただし返り値は計算をした結果の数字であるため、呼び出し方もtriangleメソッドとは異なっている。printfで結果を表示する行の中でメソッドを呼び出し、その返り値を%fに入れ込んで表示するというような方法がとられている。

メソッドのサンプル(メソッド内にifがある場合·引数として配列を指定する場合)

返り値が最後行とならない場合(大小比較を行うメソッド:min)

#!/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は省略することができる。

メソッドが配列を受け取る場合(募金の合計額を出すメソッド:total)

#!/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) 

プログラムを楽しくするテクニック

プログラムを楽しくするために様々なテクニックがある。いくつか例をあげてみよう。

乱数を発生する

#!/usr/koeki/bin/ruby

janken = ["グー","チョキ","パー"]

srand
comp=rand(3)

STDERR.print "じゃんけんぽん!(0: ぐー,1: ちょき,2: ぱー) :"
user = gets.chomp!.to_i

printf "あなたは %s でした \n", janken[comp]
printf "わたしは %s でした。\n", janken[user]
  • rand(3):3未満の整数(0、1、2)からランダムな値を返す
  • srand:乱数発生の種を作る。これがないとrandで毎回同じ値が出てきてしまう。randの上の行に必ず記載する。

少し間を空ける

#!/usr/koeki/bin/ruby

srand
rocket = rand(2)

print "打ち上げ10秒前\n"
sleep(1)

9.downto(1) do |i|
  printf ("%s秒前\n",i)
  sleep(1)
end

if rocket == 1
  print "打ち上げ成功!!\n"
else
  print "ドカーン!!\n"
end
  • sleep(1):次の行に進むまでに1秒間停止する。1未満の値を入れることもできる。

時刻を取得する

#! /usr/koeki/bin/ruby
list = ["赤","黄","青"]

print "あなたの反応時間を計測します。準備は良いですか(y or n)"
junbi = gets.chomp!

if junbi == "y"
  sleep(1)
  print "赤と表示されたら1、黄なら2、青なら3を押してください。\n"
else
  print "そうですかさようなら\n"
  exit(1)
end

sleep(1)

srand
color = rand(3)
print "\n"
printf ("%s\n",list[color])

start = Time.now

choice = gets.chomp.to_i

reaction_time = Time.now - start
printf ("表示されたのは%s、あなたの回答は%s\n",list[color],list[choice-1])

if color == choice-1
  print "正解です\n"
else
  print "はずれです\n"
end

printf ("反応時間は%s秒でした\n",reaction_time)
  • Time.now:現在時刻を返す。2地点で現在時刻を取得し、差を調べると経過時間を調べることができる。

レポート課題

問題(8点満点):def-endを用いて面白いプログラムを作ってみよう。randやsleep、Time.nowなどを使ってみても良い。


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

  • 採点基準:期限内提出点(2点)、メールの体裁(1点)、プログラム(2点)、プログラムの説明(2点)、独自性(1点〜)
  • プログラムの説明:def-end内に記述したメソッドの働きについて。また、どのような形でメソッドを呼び出し、メソッドが返り値として何を返しているのかについて。
  • 独自性:他の先生を含めてWebページに公開されているプログラムをベースとした場合、みんなで協力して全く同じプログラムを作成した場合は独自性なしとする。自分なりのテーマ設定をしてみよう。面白さや凝り具合に応じて独自性の配点を加点する(=8点満点を超える場合がある)。
  • 説明に関して、文章の意味がわかりづらい場合や、Webページを単にコピー&ペーストしたものは減点することがある。一度読み直してから提出すること。
  • 驚異的に良くできているレポートについては満点を超える得点をつけることがある。
  • よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。

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

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

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