Upload
-
View
911
Download
0
Embed Size (px)
Citation preview
再帰、漸化式、差分方程式と アルゴリズム
OGATA Tetsuji (@xtetsuji) 2015/12/03 Gaiaxグループエンジニア勉強会 #20
自己紹介
• 尾形 鉄次 (OGATA Tetsuji) a.k.a. @xtetsuji
• Blog: http://post.tetsuji.jp/
• mod_perl 芸人
• RND インフラチーム (2015/06/10~)
• 社内勉強会初デビュー
インフラチーム?• Perl は社会人になってから10年ほど書いているけれど、もともとは大学院時代に数学科のメールサーバを作ってくれと頼まれたのがIT系の始まり→インフラだ
• 学生時代はさほどプログラミングできなかったし、興味も無かったけれど、なんとかIT業界で生きてます
• 学生時代はメールとLaTeXの生活で、逆に数学とコンピュータが結びついていなかったのを最近リハビリ中
最近の社内のアウトプットで 結構印象に残ったもの
単純だけど奥が深い数学
• フィボナッチ数列
• カプレカ数
どっちも再帰的
• フィボナッチ数列もカプレカ数も、手続きがあってアルゴリズム感満点な割にシンプルで良い
• その他の古典的なものはハノイの塔とか
• フィボナッチ数列は高校数学では漸化式と言ったりする
• ときどき差分方程式とも呼ばれたりして、微分方程式の数値解析などにも顔を出したり
Fn+2 = Fn+1 + Fn
n 2 N, F1 = F2 = 1
#!/usr/bin/perl
use strict;use warnings;
sub fib { my $n = shift; if ( $n <= 0 ) { die; } if ( $n == 1 || $n == 2 ) { return 1; } else { return fib($n-1) + fib($n-2); }}
print fib(7); # 13
再帰呼び出しの良い教材
じゃ、100項目はいくつかな?
あれ?結果が返ってこない!
手元では、50項目でも 10分で処理が終わらない
#!/usr/bin/perl
use strict;use warnings;
my $n = shift || die "input number";my $call_count = 0;
sub fib { $call_count++; my $n = shift; if ( $n <= 0 ) { die; } print " " x ($n - 1) . ".\n"; if ( $n == 1 || $n == 2 ) { return 1; } else { return fib($n-1) + fib($n-2); }}
print fib($n) . "\n";print "call_count=$call_count\n";
関数の呼び出し回数が多い
• $call_count すなわち fib の呼び出し回数が多い
• $n に対応する $call_count の増加量は元のフィボナッチ数列と同程度に巨大になる
• call_count($n+2) = call_count($n+1)+call_count($n)+1
再帰関数の呼び出し回数
• 再帰の実装はシンプルになっても、呼び出し回数が甚大になるとツラい
• 今回は回数だけが問題だけれども、再帰が深すぎると deep recursion で例外になることも
• 今回はサブルーチンに副作用がないまさに関数なわけで、無駄だらけじゃない?
メモ化の手法
• 他の条件によらず x に対して y = func(x) が固定なら、func(x) の計算結果をキャッシュすればいい
• このことをメモ化 (memoize) という
• 関数型言語的に言えば、参照透過性とかナントカ
#!/usr/bin/perl
use strict;use warnings;
use Memoize;memoize('fib');
my $n = shift || die "input number";my $call_count = 0;
sub fib { $call_count++; my $n = shift; die if $n <= 0; print " " x ($n - 1). ".\n"; if ( $n == 1 || $n == 2 ) { return 1; } else { return fib($n-1) + fib($n-2); }}
print fib($n) . "\n";print "call_count=$call_count\n";
速~いヽ(=´▽`=)ノ
Memoizeモジュール
• 参照透過性のある関数であれば、メモリと相談の上で Memoize をすると、一度計算した引数で関数を二度目以降実行すると、キャッシュの結果を返すと効率的
• Memoize モジュールの適用が透過的なのでキャッシュ変数を用意したりしなくてよいのが便利
Function.prototype.memoized = function(key){ this._values = this._values || {}; return this._values[key] !== undefined ? this._values[key] : this._values[key] = this.apply(this, arguments);};
Function.prototype.memoize = function(){ var fn = this; return function(){ return fn.memoized.apply(fn, arguments); };};
// 用例var fib = (function(n) { if ( n == 1 || n == 2 ) return 1; return fib(n-1) + fib(n-2);}).memoize();
console.log(fib(100));
JavaScript Ninja の極意 (ジョン・レシグ著) より
一発で導出する式はないの?
• どんな巨大な n でも一発で fib(n) が求められる式?
• 再帰などの反復手続きによらない閉じた式または一般項
• 母関数論などを使うと導けます(詳細割愛)
Fn =1p5
( 1 +
p5
2
!n
� 1�
p5
2
!n)
さらに
• 小数点以下の誤差を丸めるようにすれば、以下の公式まで簡略化するらしい(Wikipediaより)
Fn =
$1p5
1 +
p5
2
!n
+1
2
%
#!/usr/bin/perl
use strict;use warnings;
use Benchmark qw(:all);use Memoize;memoize('fib_r');
sub fib_r { my $n = shift; if ( $n == 1 || $n == 2 ) { return 1; } return fib_r($n-1)+fib_r($n-2);}
sub fib_t { my $n = shift; return int(sqrt(5)*((1+sqrt(5))/2)**$n + 1/2);}
timethese(1_000_000, { recursion => sub { fib_r(100); }, theorem => sub { fib_t(100); },});
$ perl bench-fib.pl Benchmark: timing 1000000 iterations of recursion, theorem... recursion: 3 wallclock secs ( 2.73 usr + 0.01 sys = 2.74 CPU) @ 364963.50/s (n=1000000) theorem: 1 wallclock secs ( 0.42 usr + 0.00 sys = 0.42 CPU) @ 2380952.38/s (n=1000000)
速~いヽ(=´▽`=)ノ
一般項、最高~
カプレカ数
カプレカ数:定義1• 正の整数を2乗し、それが偶数桁 2n 桁である場合は先頭 n 桁と末尾 n 桁に分け、奇数桁 2n + 1 桁である場合は先頭 n 桁と末尾 n + 1 桁に分けて和を取る。この操作によって元の値に等しくなる数をカプレカ数と呼ぶ。
• 例えば、2972 = 88209 であるが、これを前の2桁 88 と後ろの3桁 209 に分けて足すと、88 + 209 = 297 となるので、297 はカプレカ数である。
• この定義でのカプレカ数は、小さな順に
• 1, 9, 45, 55, 99, 297, 703, 999, 2223, 2728, 4879, 4950, 5050, 5292, …(オンライン整数列大辞典の数列 A006886)
• である。(Wikipediaより)
文字列操作と数値操作
• 「偶数桁 2n 桁である場合は先頭 n 桁と末尾 n 桁に分け、奇数桁 2n + 1 桁である場合は先頭 n 桁と末尾 n + 1 桁に分けて和を取る」
• 文字列操作なら length と substr などを使う
• 数値操作なら、桁数を求める関数や床関数を使っていく
数値操作の役立つ君
• 桁数関数:
• 上n桁を求める関数:
• 下n桁を求める関数:
keta(x) = blog10 xc+ 1
An
(x) =j
x
10keta(x)�n
k
Bn
(x) = x� 10nAketa(x)�n
(x)
数値操作の役立つ君
• これだけでも文字列操作から離れられる
• とはいえ明示的な条件分岐・制御構造は必要
• 条件分岐・制御構造も「閉じた式」にできない?
• 面白そうなのでやってみる
数値操作の役立つ君
• 偶数なら1、奇数なら0:
• 奇数なら1、偶数なら0:
• 公式:
even(x) =
���cos⇡x
2
���
odd(x) =
���sin⇡x
2
���
even(x) + odd(x) = 1
数値操作の役立つ君
• 定義1を書き換えるとこうなる
• xの桁が2nの場合
• xの桁が2n+1の場合
• 先ほどの even と odd 関数があるので、これもさらにまとめられるぞ!
An(x2) + Bn+1(x
2) = x
An(x2) + Bn(x
2) = x
定義1の閉じた式を求めて
(Aketa(x2)(x2) + Bketa(x2)(x
2))even(x
2)
+ (A keta(x2)�12
(x
2) + B keta(x2)�1
2 +1(x
2))odd(x
2)
= x
ちょっとボリュームある…
定義1の閉じた式を求めて
• この方程式を満たす x を求めれば定義1のカプレカ数が求まる…のか?
• さらに手元で計算したけれど、余白がどんどん無くなる
• 一般項が求まらない、または非常に難しい形になる場合もあるので、不必要なら妥協する場合もある
• 面白いから計算練習として計算するというのも良い
高校数学が分からない?
• 今回の漸化式や数列は高校数学の範囲
• 大学レベルの数学の勉強会はちらほらあるものの、高校数学をやり直せる勉強会が今まで無かった
• 今まで?
ITエンジニアのための 高校数学勉強会 #1
http://highschoolmath.connpass.com/event/23792/
高校数学をやり直す
• 今まで構想を温めていた高校数学をやり直す勉強会
• 第1回目は12月9日、会場はモバイルファクトリーさん
• 数学 I II A B を丁寧にかつ俯瞰して扱います
• 理系の数学である数学IIIと数学活用の取り扱いも検討中
• 興味ありましたらぜひお越しください
モバイルファクトリーさんを 会場に選んだ理由?
ホワイトボードが 超書きやすいんですよ!
🍻おしまい🍣