有一种计算斐波那契数列的标准技巧,可以轻松地适应你的问题。斐波那契数列的朴素定义是:
fibFunction :: Int -> Integer
fibFunction 0 = 1
fibFunction 1 = 1
fibFunction n = fibFunction (n-2) + fibFunction (n-1)
然而这非常昂贵:由于递归的所有叶子都是1
,如果fib x = y
,那么我们必须执行y
次递归调用!由于斐波那契数呈指数增长,这是一个糟糕的状态。但是使用动态规划,我们可以共享两个递归调用中所需的计算。这个令人愉悦的一行代码看起来像这样:
fibList :: [Integer]
fibList = 1 : 1 : zipWith (+) fibList (tail fibList)
这一开始可能看起来有些令人困惑;在这里,fibList
参数传递给zipWith
函数作为回溯两个位置的递归,而tail fibList
参数则作为回溯一个位置的递归,这样我们就得到了fib (n-2)
和fib (n-1)
的值。开头的两个1
当然是基本案例。这里有其他好的SO问题对这个技巧进行了进一步的解释,你应该学习这段代码和那些答案,直到你理解它如何工作以及为什么这非常快。
如果需要,可以使用(!!)
从中恢复Int -> Integer
类型的签名。
让我们尝试将这一技巧应用到您的函数中。与计算斐波那契数列一样,您需要前面两个值;此外还需要当前索引。可以通过在调用zipWith
时包含[2..]
来实现。以下是代码的实现:
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)
与之前一样,可以使用(!!)
或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)