roy > naoya > 基礎プログラミングII・情報表現[月1] > (8)メソッドを自分で作る
ある事柄の定義にそれ自身が登場することを再帰という。例えば、今日の授業は何回目かと考えた場合、「前回の回数に1を足したものである」と定義することができる。このような定義は「去年は20歳だったから今年は21歳」、「昨日は15日だったから今日は16日」というように様々な場面で登場する。
この定義にしたがう場合、回数を明らかにする定義の中に、再度回数が出現することになる。これだといつまでたっても今回の回数が明らかにならないような気がするが、「それ以上前に授業がない場合は1回目とする」という定義を付け加えれば、必ず回数は確定する。
複雑な問題を解く場合に、再帰を用いた方が簡単になる場合がある。
1からNまでの足し算を例に挙げ、再起について考えてみよう。
例えば、5までの足し算の場合、1+2+3+4+5とか5+4+3+2+1と書く。Nまでの足し算の場合はN+(N-1)+(N-2)+...+2+1となる。
再帰的な方法で1からNまでの計算の方法を定義してみよう。
1からNまでの足し算の再帰的定義
1からNまでの足し算とは、1からN-1まで足した結果にNを加えたものである。ただし、Nが1の場合は1である。
これをプログラムで書くと下記の通りになる(recursive-sum.rb)
#!/usr/koeki/bin/ruby/ def sum(n) if n == 1 1 else n + sum(n-1) end end STDERR.print "好きな数字を入力してください" number = gets.chomp!.to_i printf ("1から%dまで足すと%dになります。\n",number,sum(number))
問題となるのはdef-endの定義内容である。特に下記の行に着目しよう。
n + sum(n-1)
sumというメソッドの定義の中で、再度sumが呼び出されている。
number=4とした場合の、このプログラムの動きを確認してみよう
階乗の計算を使って、再起についてもう少し考えてみよう。
階乗の計算とは n! で表記され n!=n*(n-1)*(n-2)*・・・2*1で表現される。例えば5の階乗であれば、5!=5*4*3*2*1となる。
再帰的な方法で階乗の計算の方法を定義してみよう。
階乗計算の再帰的定義
Nの階乗とは N-1の階乗とNを掛けたものである。ただし、1の階乗は1である
これをプログラムで書くと下記の通りになる(factorial.rb)
#!/usr/koeki/bin/ruby/ def factorial(n) if n <= 1 1 else n * factorial(n-1) end end STDERR.print "好きな数字を入力してください" number = gets.chomp!.to_i printf ("%dの階乗は%dです\n",number,factorial(number))
問題となるのはdef-endの定義内容である。特に下記の行に着目しよう。
n * factorial(n-1)
factorialというメソッドの定義の中で、再度factorialが呼び出されている。
number=4とした場合の、このプログラムの動きを確認してみよう
プログラムの流れは上記の通りである。階乗の計算は比較的単純であるため、こんな複雑なことをしなくてもプログラムを書くことができそうな気がする。しかし、再帰的な方法は、複雑な処理を繰り返し行う場合には大きな力を発揮する。
再帰を使用する際のポイントは下記の通りとなる。
再起使用時のポイント
大小関係が定められたデータを、大きい順(降順)や小さい順(昇順)に並べ替える作業をソートという。Rubyにはsortメソッドが備わっており、ソート自体を実行するプログラムを書く必要はない。しかし、実際にはソートは奥の深いテーマであり、いかに効率的に実行するかという観点より様々な方法が提案されている。
n個の要素から任意の要素aを取り出す。残りの要素についてaよりも大きければ右側、小さければ左側に配置する。先頭の要素を基準として左右に振り分ける作業をクイックソートと呼ぶ。クイックソートを繰り返すことで、左右に振り分けられる値が0個もしくは1個となる。この時点で並び替えは終了となる。
この図では最初に先頭の要素である6を取り出している。そして6を基準として、6よりも大きければ右側に配置し、小さければ左側に配置している。次に左右に配置された要素について、それぞれ先頭(左は3、右は8)を基準として同様にクイックソートを行っている。
2回目のクイックソートを行った時点で、8の左側の山は値が「7」の1つのみとなっている。1つの場合はこれ以上の並び替えはできないため、これで順番が確定になる。3の左右、8の右側についてクイックソートを継続する。
3回目のクイックソートを行うと左から2つの山については順番が確定となる。一番右側の山のみまだ順番が確定していない。
一番右側の山について4回目のクイックソートを実行する。これですべての順番が確定する。
これをプログラムで書くと下記のとおりになる(quicksort.rb)。
#!/usr/koeki/bin/ruby def quicksort(data) # 配列(順番がばらばらなデータ)を受け取る if data.length <= 1 # データが1個以下なら data # そのものを返して並べ換え完了 else left = [] # left(左側配列)を新規作成 left = Array.new でも可 right = [] # right(右側配列)を新規作成 right = Array.new でも可 k = data.shift # 配列dataの先頭を取り出してkにする(破壊的操作) for x in data # 残ったdata全てに対して繰り返す if x < k # kより小さかったら left << x # 左側に追加(<<は配列末尾への破壊的要素追加、pushと同じ) else right << x # 右側に追加(同上) end # ifに対するend end # forに対するend quicksort(left) + [k] + quicksort(right) # quicksort(left) と k と quicksort(right) を合体したものが最終結果 # kは要素なので[]で括って配列にする end # ifに対するend end # defに対するend def ketsugo(a) # 配列を受け取ってjoinメソッドで結合して返す printf("[%s]\n", a.join(", ")) end # defに対するend array = [] while true # 配列に好きな数値を順次代入 STDERR.print "好きな数字を入力(終了はq)" datum = gets.chomp! if datum == "q" then break end array.push(datum.to_i) # pushで配列arrayの末尾に追加(<<でも同じ) end print "もとの順番\n" ketsugo(array) # ketsugoメソッドの呼び出し print "並べ替え後\n" ketsugo(quicksort(array)) # ketsugoメソッドの呼び出し # 引数はquicksortを呼び出した結果
irsv{c10xxxx}% ruby quick_sort.rb[Return] 好きな数字を入力(終了はq)400 好きな数字を入力(終了はq)204 好きな数字を入力(終了はq)346 好きな数字を入力(終了はq)958 好きな数字を入力(終了はq)225 好きな数字を入力(終了はq)367 好きな数字を入力(終了はq)784 好きな数字を入力(終了はq)q もとの順番 [400,204,346,958,225,367,784] 並べ替え後 [204,225,346,367,400,784,958]
10進数を2進数や16進数に変換する方法は前期に取り上げたが、この方法で10進数を他の進数に変換することもできる。以下の図を確認してみよう。
この図は前期の図とは若干形が異なっている。前期の図では商が変換したい進数未満(例えば2進数なら2未満、16進数なら16未満)になるまで割り続け、最終的な商から初めて余りを下から上に向かって読んでいくとした。
この図では、割り算の回数を1回増やし、商が0になるまで割り算を続けている。こうすると余りの部分のみ下から上に向かって読んでいけばよい。行っている作業は同じであるが、今回の図では余りだけを読めばよいので、プログラムで実装する場合には都合が良い(作りやすい)。
1、2いずれかについて解答せよ。余裕がある人はいずれも解答せよ。
制限時間は10分。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
以下のうちいずれかを選んで解答する。
問題1(8点満点):これまでに作成したプログラムの中から1つを選び、自分のWebページで公開する。htmlの文法の正しさに基づき採点する。なお作成するのはhtmlファイル1つであり、その中にプログラムの概要とプログラムを記載する。デザインや概要の文章を工夫して、プログラムを実行してみたくなるようなページ作りを心がける。なお、htmlファイル作成にあたってはtext-decorationやline-heightなどのCSSのプロパティを最低でも6種類は使用すること。
問題2(8点満点):問3を再帰を使わずに書く。def-endも使用しなくても良い。
問題3(10点満点):キーボードから入力した任意の10進数Xを、Y進数に変換するプログラムを再帰を用いて書け。Yもキーボードから入力するが、2〜9の整数のみ入力を受け付けるようにする(つまり2進数から9進数までの変換ができるようにする)。
問題1の場合
問題2、3の場合
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照