加速Haskell中分区的计算

时间:2011-04-25 14:34:51

标签: performance haskell math number-theory

我正在尝试解决Euler问题78,它基本上要求第一个数字partition function p(n)可以被1000000整除。

我使用基于五边形数字的Euler递归公式(在pents中与正确的符号一起计算)。这是我的代码:

ps = 1 : map p [1..] where
  p n = sum $ map getP $ takeWhile ((<= n).fst) pents where
    getP (pent,sign) = sign * (ps !! (n-pent)) 

pents = zip (map (\n -> (3*n-1)*n `div` 2) $ [1..] >>= (\x -> [x,-x]))
            (cycle [1,1,-1,-1])

虽然ps似乎产生了正确的结果,但它太慢了。有没有办法加速计算,还是我需要一种完全不同的方法?

3 个答案:

答案 0 :(得分:4)

xs !! n具有线性复杂性。您应该尝试使用对数或常量访问数据结构。

编辑:这是我通过复制a similar one by augustss

提出的快速实现
psOpt x = psArr x
  where psCall 0 = 1
        psCall n = sum $ map getP $ takeWhile ((<= n).fst) pents where
          getP (pent,sign) = sign * (psArr (n-pent))
        psArr n = if n > ncache then psCall n else psCache ! n
        psCache = listArray (0,ncache) $ map psCall [0..ncache]

在ghci中,我发现你的列表版本没有惊人的加速。没有运气!

修改: 事实上,正如Chris Kuklewicz所建议的-O2,这个解决方案比n=5000的解决方案快8倍。结合Hammar对模数为10 ^ 6的总和的洞察力,我得到了一个足够快的解决方案(在我的机器上大约10秒内找到希望正确的答案):

import Data.List (find)
import Data.Array 

ps = 1 : map p [1..] where
  p n = sum $ map getP $ takeWhile ((<= n).fst) pents where
    getP (pent,sign) = sign * (ps !! (n-pent)) 

summod li = foldl (\a b -> (a + b) `mod` 10^6) 0 li

ps' = 1 : map p [1..] where
  p n = summod $ map getP $ takeWhile ((<= n).fst) pents where
    getP (pent,sign) = sign * (ps !! (n-pent)) 

ncache = 1000000

psCall 0 = 1
psCall n = summod $ map getP $ takeWhile ((<= n).fst) pents
  where getP (pent,sign) = sign * (psArr (n-pent))
psArr n = if n > ncache then psCall n else psCache ! n
psCache = listArray (0,ncache) $ map psCall [0..ncache]

pents = zip (map (\n -> ((3*n-1)*n `div` 2) `mod` 10^6) $ [1..] >>= (\x -> [x,-x]))
            (cycle [1,1,-1,-1])

(我打破了psCache抽象,所以你应该使用psArr而不是psOpt;这可以确保对psArr的不同调用将重用相同的memoized数组。这对你很有用写find ((== 0) . ...) ......好吧,我认为最好不要发布完整的解决方案。)

感谢大家的额外建议。

答案 1 :(得分:1)

我没有做过欧拉问题,但通常在欧拉问题上有一个聪明的伎俩可以加速计算。

当我看到这个时:

sum $ map getP $ takeWhile ((<=n).fst) pents

我不禁想到,每次计算sum . map getP元素时都必须有一个更聪明的方法来调用ps

现在我看一下......首先执行总和,然后乘法,而不是为每个元素和执行乘法(在getP内)不是更快总结?

通常情况下,我会深入了解并提供正常工作的代码;但这是一个欧拉问题(不想破坏它!),所以我会在这里停留一些想法。

答案 2 :(得分:1)

受你的问题的启发,我用你的方法用Haskell解决了Euler 78。所以我可以给你一些表现提示。

您的缓存列表应该很好。

选择一些大数字maxN来绑定搜索(p n)。

计划是使用(Array Int64 Integer)来记忆(p n)的结果,其下限为0,上限为maxN。这需要根据数组以'p'和'p'的形式定义数组,它们是相互递归定义的:

重新定义(p n)到(pArray n)以查找数组A中对'p'的递归调用。

使用新的pArray和Data.Array.IArray.listArray创建数组A.

绝对用'ghc -O2'编译。这在13秒内运行。