[英]Haskell: Code running too slow
我有一个代码,用于计算Motzkin数字:
module Main where
-- Program execution begins here
main :: IO ()
main = interact (unlines . (map show) . map wave . (map read) . words)
-- Compute Motzkin number
wave :: Integer -> Integer
wave 0 = 1
wave 1 = 1
wave n = ((3 * n - 3) * wave (n - 2) + (2 * n + 1) * wave (n - 1)) `div` (n + 2)
但即使是30
的简单数字的输出也需要一段时间才能返回。
任何优化的想法?
计算Fibonacci数字有一个标准技巧,可以很容易地适应您的问题。 Fibonacci数字的天真定义是:
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
类型签名。
让我们尝试将此技术应用于您的功能。 与计算Fibonacci数一样,您需要前一个和倒数第二个值; 另外还需要当前的指数。 这可以通过在zipWith
调用中包含[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)
和以前一样,可以使用(!!)
或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)
当n = 30时,你需要计算wave 29
和wave 28
,这反过来需要计算wave 28
, wave 27
两次和wave 26
等等,这很快就会达到数十亿。
您可以使用与计算斐波那契数字相同的技巧:
wave 0 = 1
wave 1 = 1
wave n = helper 1 1 2
where
helper x y k | k <n = helper y z (k+1)
| otherwise = z
where z = ((3*k-3) * x + (2*k+1) * y) `div` (k+2)
这在线性时间内运行,并且辅助器对于每k
都有wave (k-2)
和wave (k-1)
。
这是一个memoized版本
wave = ((1:1:map waveCalc [2..]) !!)
where waveCalc n = ( (2*n+1)*wave (n-1) + (3*n-3)*wave (n-2) ) `div` (n+2)
感谢大家的回应。 根据我对Memoization
理解,我重新编写了代码:
mwave :: Int -> Int
mwave = (map wave [0..] !!)
where wave 0 = 1
wave 1 = 1
wave n = ((3 * n - 3) * mwave (n - 2) + (2 * n + 1) * mwave (n - 1)) `div` (n + 2)
digits :: Int -> Int
digits n = (mwave n) `mod` 10^(100::Int)
关于如何输出模数10 ^ 100的任何想法?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.