フィボナッチがあまりに遅いのでなんとかします.
昨日のフィボナッチはあまりに遅いのです.辛抱できません.遅い理由は簡単で,同じことを何度も何度もしてるからで,Perlの場合はハッシュのクロージャでキャッシュしてました.けど,Haskellじゃそういうことはきっとできません*1.
どうして同じことを繰り返すのかを考えたら,要は「上から計算するから駄目」なんだろうと思い至りました.人間が計算する場合,
0 1 0+1=1 1+1=2 1+2=3 2+3=5
と下から計算していくはずです.再帰ではこれが逆です.上から降りていけばいつかは下限につきあたって停まるので,ある意味,これは自然です.逆に「下から上っていく」と上限はありません.けど,Haskellは遅延評価パワーで無限リストを扱えるので,「フィボナッチ」の無限列を下から作って,与えた添え字に対応する要素をひっぱりだせばいいはずです.実際は遅延評価で,添え字までのリストしか構築されないはずです.つまり,
main = putStr.show.(!!) fiblist.read.head =<< getArgs
となるようなフィボナッチ数からなるリストfiblistを構築すれば解決です.fiblistは漸化式で作ります.
| 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... 0 | 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
フィボナッチ数列をならべて,次に一個ずらして並べてみました.そして,縦に足してみると,やはりフィボナッチ数列がでてきます.しかも下から順番にいけば,同じものを何度も計算しないはず.そして,二つのリストをとって,それから新しいリストを作るにはzip系の関数をつかうわけで,その中のzipWithを使います.
import System fiblist :: [Integer] fiblist = 0:1:zipWith (+) fiblist (tail fiblist) main = putStr.show.(!!) fiblist.read.head =<< getArgs
こうなりました.zipWith (+) でfiblistの最初と,fiblistの二番目からのリストを足し算して,それをfiblistに連結します.つまり
0 1 zipWith (+) [0 1 ...] [1 ...] = 0 1 (0+1) zipWith (+)[1 (0+1) ...] [(0+1) ..] = 0 1 1 zipWith (+) [1 1 ...] [ 1 ...] = 0 1 1 (1+1) zipWith (+) [1 (1+1) ... ] [(1+1) ...] = 0 1 1 2 zipWith (+) [1 2 ...] [2 ...] = ....
と計算してくれて,必要なところまで計算してとまるはずです.
実際,コンパイルすると,
>ghc fib.hs -o fib >fib 1000 4346655768693745643568852767504062580256466051737178040248 1729089536555417949051890403879840079255169295922593080322 6347752096896232398733224711616429964409065331879382989696 49928516003704476137795166849228875
一瞬で終わります.遅延評価パワー炸裂が実感できました.面白い(^^)
*1:少なくとも,私にはできません(泣笑)