問題に簡単に適応できるフィボナッチ数を計算するための標準的なトリックがあります。フィボナッチ数の単純な定義は次のとおりです。
fibFunction :: Int -> Integer
fibFunction 0 = 1
fibFunction 1 = 1
fibFunction n = fibFunction (n-2) + fibFunction (n-1)
ただし、これは非常にコストがかかります。再帰のすべての葉が1
, iffib x = y
であるため、再帰呼び出しを実行する必要がありy
ます! フィボナッチ数は指数関数的に増加するため、これは悪い状態です。しかし、動的計画法を使用すると、2 つの再帰呼び出しで必要な計算を共有できます。このための楽しいワンライナーは次のようになります。
fibList :: [Integer]
fibList = 1 : 1 : zipWith (+) fibList (tail fibList)
これは、最初は少し不可解に見えるかもしれません。ここでfibList
引数 tozipWith
は 2 つのインデックス前の再帰として機能し、引数は 1 つのインデックス前の再帰として機能し、とtail fibList
の両方の値を与えます。最初の 2 つの s はもちろん基本ケースです。SO には、このテクニックをさらに詳しく説明する良い質問が他にもあります。このコードとその答えを、このコードがどのように機能し、なぜ非常に高速であるかを理解できるまで学習する必要があります。fib (n-2)
fib (n-1)
1
Int -> Integer
必要に応じて、を使用してこれから型シグネチャを回復でき(!!)
ます。
この手法を関数に適用してみましょう。フィボナッチ数の計算と同様に、前の値と最後から 2 番目の値が必要です。さらに、現在のインデックスが必要です。[2..]
これは、 への呼び出しに含めることで実行できますzipWith
。これがどのように見えるかです:
waves :: [Integer]
waves = 1 : 1 : zipWith3 thisWave [2..] waves (tail waves) where
thisWave n back2 back1 = ((3 * n - 3) * back2 + (2 * n + 1) * back1) `div` (n + 2)
以前と同様に、関数のバージョンを(!!)
orで復元できgenericIndex
ます (本当にInteger
インデックスが必要な場合)。ghci で同じ関数を計算することを確認できます (ただし、より速く、より少ないメモリを使用します)。
> :set +s
> map wave [0..30]
[1,1,2,4,9,21,51,127,323,835,2188,5798,15511,41835,113634,310572,853467,2356779,6536382,18199284,50852019,142547559,400763223,1129760415,3192727797,9043402501,25669818476,73007772802,208023278209,593742784829,1697385471211]
(6.00 secs, 3,334,097,776 bytes)
> take 31 waves
[1,1,2,4,9,21,51,127,323,835,2188,5798,15511,41835,113634,310572,853467,2356779,6536382,18199284,50852019,142547559,400763223,1129760415,3192727797,9043402501,25669818476,73007772802,208023278209,593742784829,1697385471211]
(0.00 secs, 300,696 bytes)