Webページでは数式を表すためにLaTeX表記が使えるMathJaxを利用しています。
WebブラウザにはSafari/Chrome/Firefoxを使って下さい(IEでは表示できないようです。)
再帰再訪--計算時間
Fibonacci数列
Fibonacci数列 $\{ f(n)\}_{n=0,1,2,\dots}$ はよく知られている数列宇で次で定義される。 $n$番目のFibonacci数を $fib(n)$ と書くと次のような漸化式が成立する。
\begin{align*} fib(0) & = 1,\\ fib(1) &= 1,\\ fib(n) & = fib(n-1) + fib(n-2), \quad n \geqq 2. \end{align*}次のスクリプト fibonacci.py では、関数 fibonacci_recur(n) では$n$番目のFibonacci数を返す関数を再帰的に定義し、キーボードから非負整数文字列を整数 n として fibonacci(n) を計算するに要する時間[sec]と併せてFibonacci値をプリントしている。
#!/usr/bin/env python # -*- coding: utf-8 -*- import time def fibonacci_recur(n): if n == 0: return 1 elif n == 1: return 1 else: return fibonacci_recur(n - 1) + fibonacci_recur(n - 2) n = int(raw_input("Calculating n-th Fibonacci numbers: n(> 0) = ")) start_time = time.time() fib = fibonacci_recur(n) end_time = time.time() print "Recursive version: used time = :", end_time - start_time print "\tFibonacci number = ", fib
$ ./fibonacci.py Calculating n-th Fibonacci number: n(> 0) = 20 For version: used time = : 4.57763671875e-05 [sec] Fibonacci number = 10946 Recursive version: used time = : 0.0022919178009 [sec] Fibonacci number = 10946
関数引数
Pythonでは関数名を関数の引数として渡して処理することが可能である。 これを関数引数という。
たとえば、つぎのようなスクリプトが可能である。 関数 used_time を2つの仮引数 procedure と n を渡すようにして定義する。 timeモジュールをインポートしているので、procedure(n) の実行を測定し、その経過時間 end_time - start_time を返すというのが関数 used_time(procedure, n) の働きである。 つまり、used_time(procedure, n) は関数 procedure が procedure(n) に要する処理時間を返す関数である。
import time def used_time(procedure, n): start_time = time.time() result = procedure(n) end_time = time.time() return end_time - start_time
この関数 used_time(procedure, n) を使えば、2つの関数 fibonacci_for と fibonacci_recur が 1番目から n番目(n> 1)のFibonacci数を計算するに要する時間を次のように並べて書き出すことができる。 この n はキーボードから入力した正整数文字列から与えている。
n = int(raw_input("Calculating n-th Fibonacci number: n(> 0) = ")) for i in range(1, n + 1): print i, used_time(fibonacci_for, i), '\t',used_time(fibonacci_recur, i)
次の実行結果は、キーボードから正整数 n を与えて、1番目からn番目までのFibonacci数に関して、2つの関数 fibonacci_for と fibonacci_recur を使って i 番目の値を計算するために必要な時間を並べて出力するスクリプト calc_time_fibonacci.py の実行結果である。
./calc_time_fibonacci.py Calculating n-th Fibonacci number: n(> 0) = 30 1 3.09944152832e-06 9.53674316406e-07 2 2.14576721191e-06 1.19209289551e-06 ........... ........... 24 1.50203704834e-05 0.0149919986725 25 7.86781311035e-06 0.0260441303253 26 8.10623168945e-06 0.040717124939 27 8.10623168945e-06 0.0683250427246 28 9.05990600586e-06 0.104562997818 29 8.10623168945e-06 0.16804599762 30 8.10623168945e-06 0.269570112228

次は、calc_time_fibonacci.py を修正して、laptime_fibonacci.csv を書き込みモードで開いて、2津の関すの実行時間をカンマ(,)区切りで、最後に改行 '\n' を加えて書き出すようにした。 つまり、1行のデータ要素がカンマ(,)で区切られている(このような形式をファイルをcsv(comma separated value)ファイルという。 Excelなどの表計算で加工処理できる)。 fh.write( ) は文字列として書き出すように数値を str を使って文字列に変更している。
fh = open('laptime_fibonacci.csv','w') n = int(raw_input("Calculating n-th Fibonacci numbers: n(> 0) = ")) for i in range(1, n + 1): # print i, used_time(fibonacci_for, i), '\t',used_time(fibonacci_recur, i) fh.write(str(used_time(fibonacci_for, i)) + ','+ str(used_time(fibonacci_recur, i))+'\n') fh.close()

再帰を使わずにfor文でFibonacci数を計算した計算時間(青色)に比べて、再帰的定義を使って計算した計算時間(赤色)は $n$ はが増えると急速に増加していることがわかる。

ベクトルの$L_1$ノルム
$n$個の成分を持つベクトル $v=(v_0, v_1, \dots, v_{n-1})$の$L_1$のノルム $\Vert v\Vert_1$ は次のようである。
\[ \Vert v\Vert_1 =|v_0| + |v_1| +\dots + |v_{n-1}|. \]Pythonではベクトル$v$をリスト num_list = [$v_0$, $v_1$, $\dots$, $v_{n-1}$] で表すことができ、その$L_1$ノルムを返す関数 one_norm_recur[num_list] を考えよう。 数値リスト num_list は空でなく1以上の要素を持つと仮定する。
リスト num_list が2つ以上の要素(長さ2以上)を持つ場合、リストの先頭(0番目)から末尾(len(num_list -1 番目)における中間位置を mid = int(math.floor(len(num_list) / 2)) で求めておくと、リスト前半のリストは num_list[: mid] 、リスト後半のリストは num_list[mid: ] である。 浮動小数 x の x を越えない最大の整数(数学ではGauss記号 $[x]$で表記)返すFloor関数 math.floor() を使うために math モジュールをインポートしている。
したがって、ベクトル num_list が長さ2以上のリストであるとき、$L_1$ノルム one_norm_recur[num_list] は、上の定義よりそれぞれの部分リストの$L_1$ノルムの和 one_norm_recur(num_list[: mid]) + one_norm_recur(num_list[mid: ]) である。 この事実を使って、 $L_1$ノルムを返す関数 one_norm_recur[num_list] は次のように再帰的に定義できる。
import math def one_norm_recur(num_list): if len(num_list) == 1: return math.fabs(num_list[0]) else: mid = int(math.floor(len(num_list) / 2)) return one_norm_recur(num_list[: mid]) + one_norm_recur(num_list[mid: ])
正整数 $n$ をキーボードから入力して、0.0 から 1.0 までの乱数を使って $1\leqq i\leqq n$個からなるリストを生成し、その$L_1$ノルムを求めるに要した計算時間を2つ1行に書き出すスクリプト one_norm.py を書きなさい。 次はその実行結果の一部である。
$ ./one_norm.py Calculating 1 norms of random vectors with n (> 0) elements = 200 1.00135803223e-05 2.69412994385e-05 2.86102294922e-06 1.8835067749e-05 3.09944152832e-06 1.28746032715e-05 3.09944152832e-06 1.4066696167e-05 ........ ........ 2.31266021729e-05 0.000375032424927 2.28881835938e-05 0.000353097915649 3.48091125488e-05 0.000358819961548 1.59740447998e-05 0.000349998474121
さらにこの結果を各行のデータがカンマ(,)区切りであるようにファイル laptime_one_norm.csv に書き出すように修正して、その様子をグラフに表示してみなさい。