我目前正在尝试优化我在Projet Euler上解决问题14的解决方案。我非常喜欢Haskell,并认为它非常适合这些问题,以下是我尝试过的三种不同的解决方案:
import Data.List (unfoldr, maximumBy)
import Data.Maybe (fromJust, isNothing)
import Data.Ord (comparing)
import Control.Parallel
next :: Integer -> Maybe (Integer)
next 1 = Nothing
next n
| even n = Just (div n 2)
| odd n = Just (3 * n + 1)
get_sequence :: Integer -> [Integer]
get_sequence n = n : unfoldr (pack . next) n
where pack n = if isNothing n then Nothing else Just (fromJust n, fromJust n)
get_sequence_length :: Integer -> Integer
get_sequence_length n
| isNothing (next n) = 1
| otherwise = 1 + (get_sequence_length $ fromJust (next n))
-- 8 seconds
main1 = print $ maximumBy (comparing length) $ map get_sequence [1..1000000]
-- 5 seconds
main2 = print $ maximum $ map (\n -> (get_sequence_length n, n)) [1..1000000]
-- Never finishes
main3 = print solution
where
s1 = maximumBy (comparing length) $ map get_sequence [1..500000]
s2 = maximumBy (comparing length) $ map get_sequence [500001..10000000]
solution = (s1 `par` s2) `pseq` max s1 s2
现在,如果您看一下实际问题,就会发现有很多缓存的潜力,因为大多数新序列将包含先前已计算过的子序列。
相比之下,我也用C写了一个版本:
使用缓存的运行时间:0.03秒
不使用缓存的运行时间:0.3秒 这简直太疯狂了!当然,缓存将时间减少了10倍,但即使没有缓存,它仍然比我的Haskell代码快至少17倍。
我的代码有什么问题? 为什么Haskell不为我缓存函数调用?由于函数是纯的,缓存不应该很简单吗,只是一个可用内存的问题?
我的第三个并行版本有什么问题?为什么它无法完成?
关于Haskell作为一种语言,编译器是否自动并行化某些代码(折叠、映射等),还是必须始终明确地使用Control.Parallel来完成?
编辑:我偶然发现了this类似的问题。他们提到他的函数不是尾递归的。我的get_sequence_length是尾递归的吗?如果不是,我该如何使其成为尾递归? 编辑2:
致Daniel:
非常感谢您的回复,真的很棒。我尝试了您的改进,发现有一些严重问题。
我使用Windows 7 (64位),3.3 GHZ四核处理器和8GB RAM测试。第一件事就是按照你说的将所有的Integer替换为Int,但每当我运行任何主程序时都会用光内存,即使+RTS kSize -RTS设置的非常高。
最终我找到了this(stackoverflow太神奇了……), 这意味着由于Windows上的所有Haskell程序都是以32位运行的,因此Ints会溢出从而导致无限递归,简直不可思议……
我在Linux虚拟机中运行了测试(具有64位ghc),结果相似。
main3
中多了一个零... - Daniel Wagner