ある事柄の定義にそれ自身が登場することを再帰という。例えば、今日の授業は何回目かと考えた場合、「前回の回数に1を足したものである」と定義することができる。このような定義は「去年は20歳だったから今年は21歳」、「昨日は15日だったから今日は16日」というように様々な場面で登場する。
この定義にしたがう場合、回数を明らかにする定義の中に、再度回数が出現することになる。これだといつまでたっても今回の回数が明らかにならないような気がするが、「それ以上前に授業がない場合は1回目とする」という定義を付け加えれば、必ず回数は確定する。
複雑な問題を解く場合に、再帰を用いた方が簡単になる場合がある。
階乗の計算を使って、再起についてもう少し考えてみよう。
階乗の計算とは 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を呼び出した結果
%ruby quick_sort.rb 好きな数字を入力(終了は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]
n個の要素を想定した場合に、一番最後の値であるA[n]を基準として、A[n-1]、A[n-2]、...と大小比較をしていく。添え字が小さいほうに小さい値が保持されるようにする。例えばA[n]とA[n-1]の大小比較を行った場合、A[n-1]の方が小さければそのまま、A[n]の方が小さければA[n]とA[n-1]を入れ替え、A[n-1]に小さい値が入るようにする。次にA[n-1]とA[n-2]の大小比較をする。今度はA[n-2]に小さい値が入るようにする。これを順番に繰り返しながら先頭のA[1]まで比較を行う。これにより最も小さい値がA[1]に入る。
A[1]に最も小さい値が入ったら、2回目はA[n]から開始してA[2]まで大小比較を行う。2回目の大小比較を行った結果としてA[2]に2番目に小さい値が入ることになる。これを繰り返すことで、最終的に小さい順の並び替えが完了する。
最大値をmとするn個の要素がある場合に、これらの数字を記憶する領域(バケット)をm個用意する。10という数値は10番目のバケット、iはi番目のバケットに放り込むというように、対応するバケットに放り込む。これを最後まで繰り返し、最後に1番のバケットから順番に取り出していくと小さい順に並ぶ。20個のデータで、最小1、最大100万というようなセットの場合、20個の並び替えに100万個のバケットが必要になる。
[3,1,7,2]の並び替えを行う場合、並び替え後の配列をbucketとすると、bucket[3]=3, bucket[1]=1というように、代入する値とインデックスを同じ値にして代入をする。この結果としてbucketの中は[nil,1,2,3,nil,nil,nil,7]となる。値が存在しないところは空っぽであるという意味のnilが表示される。あとはnilを取り除けば並び替えが完了となる。
バケットソートの応用。各要素が全てk桁のm進数(例えば3桁の10進数)であらわされるときに有効なアルゴリズム。まず1桁目についてバケットソートを行い、続けて2桁目についてバケットソートを行う。この際は1回目のバケット番号の小さい順に値を保持する。同様に繰り返し、最後のk桁目のバケットソートを行い、k-1桁目のバケット番号の小さい順に並べれば小さい順に並ぶ。
GIMPはAdobe Photoshopと同じペイント系のソフトである。GIMPを使用すると簡単にロゴを作成することができる。
GIMPを起動する前に、様々な英語フォント(書体)を追加する。これは必須ではないが、標準フォントのみではバリエーションに欠けてしまう。フォントの追加は addfont コマンドを使う(101/102教 室の場合)。
irsv{C10xxxx}% addfont[Return]
ktermからgimpと入力して起動する。
irsv{C10xxxx}% gimp &[Return]
& を付けないと、GIMPを終了するまでそのktermで作業ができなくなる。
起動後に表示される「GIMP今日の技」(以下参照)はいろいろなテクニックを紹介しているのみで、特にロゴを作るうえでは不要なので「閉じる」で閉じる。。
起動したGIMPの「道具箱」ウィンドウ(ウィンドウタイトルがThe GIMPとなっているもの)より
ファイル(F) → 終了で終了となる。
GIMPを使用していると時折固まることがある。この場合GIMPを起動したktermからkillコマンドを利用して強制終了する。
irsv{C10xxxx}% kill -9 %gimp
これでもうまくいかない場合は、GIMPのどれかのウィンドウの左上のボタンを押し「Destroy」を選択する。この際、保存していないファイルがあり、なおかつ保存ができるようであれば事前に全てしておくこと。固まったままのGIMPを放置するとシステムの負荷が高くなり、他の利用者にも迷惑をかけるので必ず対処する。わからない場合は教員やTAを呼ぶこと。
GIMPの道具箱ウィンドウで「拡張(X) → Script-Fu → ロゴ」と選ぶ とロゴを作成するウィンドウが現れる。ロゴには「立体縁取り」や「エイリアン発光」など様々な種類があるが、ここではとりあえず「霜」(下の図では切れていて見えていないが)を選んだものとして説明を続ける。
霜を選ぶと以下のウィンドウが出現する。他を選んでも同じようなウィンドウが出現する。ここで文字とフォントサイズとフォントを変更する。文字は自分で作りたいロゴに使用する言葉とする。フォントサイズは一度作成してから適宜変更してみると良い。フォントの変更は複雑なので以下で詳細に説明する。
フォントを変更するためには上のウィンドウでフォントの右のボタンを押す。すると以下のようなフォント選択ウィンドウが出現するので左側のフォントのリストから自由にフォントを選ぶ。日本語の場合は101/102教室で利用できるのはricoh, kochi, aqua, yozの4種類となる。フォントを選ぶと右側のフォントスタイルでさらに斜体や太字を選ぶことができる場合がある。必要に応じてこれもいずれかを選択する。設定したら「了解」を押す。
元のウィンドウに戻るので、さらに「了解」を押すとロゴが生成される。
作成したロゴを右クリックするとメニューが出現する。そこから「ファイル」→「保存」を選ぶ。保存をするときの形式はPNGを選ぶ。ファイル名をつけ、保存する場所を適宜選択して「了解」を押す。すると、エクスポートするかなど幾つかの質問が行われるが、全てYesに相当するボタンをクリックすると保存が完了する。
その他、 「GIMPの使い方」というキーワードでgoogle検索するとたくさん参考となるページが出てくるのでこれを読めばほとんどの説明は得られるだろう。
全体の大きさを変えることができる。画像の上で右クリック、「画像」→「拡大縮小」を選ぶ。幅と高さがXとYで表示されているので、適宜比率を変化させればよい。
GIMPでロゴを作成し、PNG形式で保存して添付ファイルで送る。ロゴ自体の完成度については問わないものとする。正しく添付ファイルを送ることができるかどうかを見る。ロゴのテーマも何でも良い。
制限時間は10分。出席点は2点だが、添付ファイルがない場合は1点とする。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
以下のうちいずれかを選んで解答する。
問題1(6点満点):GIMPを使用して基礎プログラミング(もしくは情報表現)をあらわす素敵なロゴを作る。6点満点だが極めてよくできている場合は芸術点がさらに加算される場合がある。
問題2(8点満点):バゲットソートを再帰を使わずに書く。def-endも使用しなくても良い。
問題3(10点満点):バブルソートを再帰を用いて書く。
問題1の場合
問題2、3の場合
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照