再帰、漸化式、差分方程式と
アルゴリズム
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_countn";
関数の呼び出し回数が多い
• $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_countn";
速∼いヽ(=´▽`=)ノ
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 =
1
p
5
(
1 +
p
5
2
!n
1
p
5
2
!n)
さらに
• 小数点以下の誤差を丸めるようにすれば、以下の公式ま
で簡略化するらしい(Wikipediaより)
Fn =
$
1
p
5
1 +
p
5
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 10n
Aketa(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(x2
) = x
An(x2
) + Bn(x2
) = x
定義1の閉じた式を求めて
(Aketa(x2)(x2
) + Bketa(x2)(x2
))even(x2
)
+ (Aketa(x2) 1
2
(x2
) + Bketa(x2) 1
2 +1
(x2
))odd(x2
)
= x
ちょっとボリュームある…
定義1の閉じた式を求めて
• この方程式を満たす x を求めれば定義1のカプレカ数が
求まる…のか?
• さらに手元で計算したけれど、余白がどんどん無くなる
• 一般項が求まらない、または非常に難しい形になる場合
もあるので、不必要なら妥協する場合もある
• 面白いから計算練習として計算するというのも良い
高校数学が分からない?
• 今回の漸化式や数列は高校数学の範囲
• 大学レベルの数学の勉強会はちらほらあるものの、高校
数学をやり直せる勉強会が今まで無かった
• 今まで?
ITエンジニアのための
高校数学勉強会 #1
http://highschoolmath.connpass.com/event/23792/
高校数学をやり直す
• 今まで構想を温めていた高校数学をやり直す勉強会
• 第1回目は12月9日、会場はモバイルファクトリーさん
• 数学 I II A B を丁寧にかつ俯瞰して扱います
• 理系の数学である数学IIIと数学活用の取り扱いも検討中
• 興味ありましたらぜひお越しください
モバイルファクトリーさんを
会場に選んだ理由?
ホワイトボードが
超書きやすいんですよ!
🍻おしまい🍣

再帰、漸化式、差分方程式とアルゴリズム Gx#20