加速 Haskell 中的分区计算
我正在尝试解决欧拉问题 78,它基本上要求 分区函数所在的第一个数字 p(n) 可被 1000000 整除。
我使用基于五边形数的欧拉递归公式(此处计算为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
似乎产生了正确的结果,但它太慢了。有没有办法加快计算速度,或者我是否需要完全不同的方法?
I'm trying to solve Euler problem 78, which basically asks for the first number where the partition function p(n) is divisible by 1000000.
I use Euler's recursive fomula based on pentagonal numbers (calculated here in pents
together with the proper sign). Here is my code:
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])
While ps
seems to produce the correct results, it is too slow. Is there a way to speed the calculation up, or do I need a completely different approach?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
xs!! n
具有线性复杂度。您应该尝试使用对数或恒定访问数据结构。编辑:这是我通过复制 类似的一个快速实现augustss:
在 ghci 中,我没有观察到您的列表版本有明显的加速。运气不好!
编辑:
事实上,按照 Chris Kuklewicz 建议的
-O2
,该解决方案比n=5000
的解决方案快八倍。结合哈马尔对模 10^6 求和的见解,我得到了一个足够快的解决方案(在我的机器上大约 10 秒内找到希望正确的答案):(我破坏了 psCache 抽象,所以你应该使用 psArr 而不是
psOpt
; 这确保了对psArr
的不同调用将重用相同的记忆数组,这在您编写find ((==) 时很有用。 0) ...)
...好吧,我认为最好不要发布完整的解决方案。)感谢大家的额外建议。
xs !! n
has a linear complexity. You should rather try using a logarithmic or constant-access data structure.Edit : here is a quick implementation I came up with by copying a similar one by augustss :
In ghci, I observe no spectacular speedup over your list version. No luck !
Edit :
Indeed, with
-O2
as suggested by Chris Kuklewicz, this solution is eight times faster than your forn=5000
. Combined with Hammar insight of doing sums modulo 10^6, I get a solution that is fast enough (find the hopefully correct answer in about 10 seconds on my machine):(I broke the psCache abstraction, so you should use
psArr
instead ofpsOpt
; this ensures that different call topsArr
will reuse the same memoized array. This is useful when you writefind ((== 0) . ...)
... well, I thought it was better not to publish the complete solution.)Thanks to all for the additional advice.
好吧,一个观察结果是,由于您只对
map (`mod` 10^6) ps
感兴趣,因此您可能能够避免对巨大的数字进行计算。Well, one observation is that since you're only interested in
map (`mod` 10^6) ps
, you might be able to avoid having to do calculations on huge numbers.我还没有做过欧拉问题,但通常在欧拉问题中都有一个巧妙的技巧来加速计算。
当我看到这一点时:
我不禁想到一定有比调用
sum 更聪明的方法。每次计算
。ps
的元素时都会映射 getP现在我看看它......先执行求和,然后乘法不是比对每个元素执行乘法(在
getP
内部)然后再执行乘法更快吗?总结?通常我会更深入地研究并提供工作代码;但这是一个欧拉问题(不想破坏它!),所以我就在这里停下来谈谈这些想法。
I haven't done that Euler problem, but usually with Euler problems there is a clever trick to speed up the calculation.
When I see this:
I can't help but think there must be a smarter way than to invoke
sum . map getP
every time you compute an element ofps
.Now that I look at it...wouldn't it be faster to perform the sum first, and then multiply, rather than performing the multiply (inside of
getP
) for every element and then summing?Normally I'd take a deeper look and provide working code; but it's an Euler problem (wouldn't want to spoil it!), so I'll just stop here with these few thoughts.
受到你的问题的启发,我用你的方法用 Haskell 解决了 Euler 78。所以我可以给你一些性能提示。
你的缓存列表应该是好的。
选择一些大数 maxN 来限制您的搜索 (pn)。
计划是使用 (Array Int64 Integer) 来存储 (pn) 的结果,下限为 0,上限为 maxN。这需要根据'p'定义数组,并根据数组'p'定义数组,它们是相互递归定义的:
将(pn)重新定义为(pArray n)以在数组A中查找对'p'的递归调用。
使用新的 pArray 和 Data.Array.IArray.listArray 来创建数组 A。
一定要使用“ghc -O2”进行编译。此处运行时间为 13 秒。
Inspired by your question, I have used your approach to solve Euler 78 with Haskell. So I can give you some performance hints.
Your cached list of pents should be good as it is.
Pick some large number maxN to bound your search for (p n).
The plan is to use an (Array Int64 Integer) to memorize the result of (p n), with lower bound 0 and upper bound maxN. This requires defining the array in terms of 'p' and 'p' in terms of the array, they are mutually recursively defined:
Redefine (p n) to (pArray n) to look up recursive calls to 'p' in the array A.
Use your new pArray with Data.Array.IArray.listArray to create the array A.
Definitely compile with 'ghc -O2'. This runs in 13 seconds here.