有一个计算斐波那契数的标准技巧可以很容易地适应您的问题。斐波那契数的朴素定义是:
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
开头的两个s 当然是基本情况。这里还有其他关于 SO 的好问题可以更详细地解释这项技术,你应该研究这段代码和那些答案,直到你觉得你理解它是如何工作的以及为什么它非常快。
如有必要,可以Int -> Integer
使用(!!)
.
让我们尝试将此技术应用于您的函数。与计算斐波那契数一样,您需要前一个值和倒数第二个值;并且还需要当前索引。这可以通过包含[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)