【问题标题】:Haskell: Code running too slowHaskell:代码运行太慢
【发布时间】:2017-01-28 19:15:21
【问题描述】:

我有一个计算 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 这样的简单数字的输出也需要一段时间才能返回。

有什么优化思路??

【问题讨论】:

标签: performance haskell recursion motzkin-numbers


【解决方案1】:

感谢大家的回复。根据我对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的答案有什么想法吗?

【讨论】:

  • 您需要为您的mwave 函数使用Integer 类型。 Int 不支持 100 位数字。
  • 你想要Integer; Int 在这里不好(试试mwave !! 43)。见haskell.org/hoogle/?hoogle=%5Ba%5D+-%3E+Integer+-%3E+a
  • @WillNess 当我更改mwave 的签名时,它给出了错误:Couldn't match expected type 'Integer' with actual type Int'
  • 是的,!! 是罪魁祸首,它的类型是:....(试试Prelude> :t (!!))。而genericIndex 的类型是:....你试过我上面提供的链接吗?
  • 在这种情况下,给它一个大的类型,并在你的代码中撒上fromIntegrals,以获得良好的效果。 IE。而不是a !! na !! (fromIntegral n)。或者更好的是,给它Int -> Integer签名并写(3 * fromIntegral n - 3) * mwave (n - 2) ...n 将是 Int(并且 !! 期望 Int 作为其第二个参数),但 mwave n 的结果将是 Integer,因此 fromIntegral n 将是 Integer 并且计算将进行类型检查。
【解决方案2】:

有一个计算斐波那契数的标准技巧可以轻松适应您的问题。斐波那契数的朴素定义是:

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)

这乍一看可能有点令人费解;这里zipWithfibList 参数作为前两个索引的递归,而tail fibList 参数作为前一个索引的递归,这给了我们fib (n-2)fib (n-1) 值。开头的两个1s 当然是基本情况。 other good questions here on 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)

【讨论】:

    【解决方案3】:

    在 n=30 的情况下,您需要计算 wave 29wave 28,而这又需要计算两次 wave 28wave 27wave 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) 的值。

    【讨论】:

    • 我认为您在 div 之前的最后一行缺少括号(div 应该适用于条款的总和)。
    【解决方案4】:

    这是一个记忆的版本

    wave = ((1:1:map waveCalc [2..]) !!)
        where waveCalc n = ( (2*n+1)*wave (n-1) + (3*n-3)*wave (n-2) ) `div` (n+2)
    

    【讨论】:

      猜你喜欢
      • 2017-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多