我目前正致力于project euler problem 14。
我使用编码不佳的程序解决了它,没有记忆,运行 386 5秒(参见编辑)。
这是:
step :: (Integer, Int) -> Integer -> (Integer, Int)
step (i, m) n | nextValue > m = (n, nextValue)
| otherwise = (i, m)
where nextValue = syr n 1
syr :: Integer -> Int -> Int
syr 1 acc = acc
syr x acc | even x = syr (x `div` 2) (acc + 1)
| otherwise = syr (3 * x + 1) (acc + 1)
p14 = foldl step (0, 0) [500000..999999]
我的问题是关于这个问题的线程中的几条评论,其中提到的程序的执行时间为&lt; 1秒如下(C代码,项目euler论坛用户的信用 ix < / em> 代码 - 注意:我没有检查执行时间实际上是如上所述):
#include <stdio.h>
int main(int argc, char **argv) {
int longest = 0;
int terms = 0;
int i;
unsigned long j;
for (i = 1; i <= 1000000; i++) {
j = i;
int this_terms = 1;
while (j != 1) {
this_terms++;
if (this_terms > terms) {
terms = this_terms;
longest = i;
}
if (j % 2 == 0) {
j = j / 2;
} else {
j = 3 * j + 1;
}
}
}
printf("longest: %d (%d)\n", longest, terms);
return 0;
}
对我来说,在谈论算法时,这些程序是一样的。
所以我想知道为什么会有这么大的差异?或者我们的两种算法之间是否有任何基本的区别,可以证明x6因素在性能上是合理的?
顺便说一下,我现在正试图用memoization实现这个算法,但是对我来说有点迷失,用命令式语言实现起来更容易(而且我还没有操纵monad所以我不能使用这个范例)。所以,如果你有任何适合初学者学习备忘录的好教程,我会很高兴(我遇到的那些不够详细或不在我的联盟中)。注意:我通过Prolog来进行声明式范例,并且仍处于发现Haskell的早期阶段,所以我可能会错过重要的事情。
注2:欢迎任何关于我的代码的一般建议。
编辑:感谢delnan的帮助,我编译了程序,它现在运行5秒钟,所以我现在主要寻找关于memoization的提示(即使仍然欢迎有关现有x6差距的想法)。
答案 0 :(得分:9)
在使用优化进行编译之后,C程序仍存在一些差异
div
,而C程序使用机器划分(截断)[但任何自尊的C编译器将其转换为移位,这样使它更快],那将是{{1在哈斯克尔;这使得运行时间减少了大约15%。quot
秒。如果您的GHC(Windows以外的64位操作系统)中有64位Integer
,请将Int
替换为Integer
。这样可以将运行时间缩短约3倍。如果您使用的是32位系统,那么运气不好,GHC不会在那里使用本机64位指令,这些操作是作为C调用实现的,但仍然很慢。对于备忘录,您可以将其外包给hackage上的一个记忆包,我唯一记得的是data-memocombinators,但还有其他一些。或者你可以自己做,例如保留以前计算的值的地图 - 这在Int
monad中效果最好,
State
但这可能不会获得太多(即使你已经添加了必要的严格性)。问题是import Control.Monad.State.Strict
import qualified Data.Map as Map
import Data.Map (Map, singleton)
type Memo = Map Integer Int
syr :: Integer -> State Memo Int
syr n = do
mb <- gets (Map.lookup n)
case mb of
Just l -> return l
Nothing -> do
let m = if even n then n `quot` 2 else 3*n+1
l <- syr m
let l' = l+1
modify (Map.insert n l')
return l'
solve :: Integer -> Int -> Integer -> State Memo (Integer,Int)
solve maxi len start
| len > 1000000 = return (maxi,len)
| otherwise = do
l <- syr start
if len < l
then solve start l (start+1)
else solve maxi len (start+1)
p14 :: (Integer,Int)
p14 = evalState (solve 0 0 500000) (singleton 1 1)
中的查找不是太便宜而且插入相对昂贵。
另一种方法是为查找保留一个可变数组。代码变得更加复杂,因为您必须为要缓存的值选择合理的上限(应该不比起始值的界限大很多)并处理落在备忘范围之外的序列部分。但是数组查找和写入速度很快。如果你有64位Map
s,下面的代码运行得非常快,这里需要0.03s,限制为100万,0.33s限制为1000万,相应(尽可能合理) can)C代码以0.018分别运行。 0.2S。
Int
答案 1 :(得分:4)
好吧,C程序使用unsigned long
,但Integer
可以存储任意大整数(它是bignum)。如果您导入Data.Word
,则可以使用Word
,这是一个机器字大小的无符号整数。
用Integer
替换Word
并使用ghc -O2
和gcc -O3
后,C程序在0.72秒内运行,而Haskell程序在1.92秒内运行。 2.6x也不错。但是,ghc -O2
并不总是有用,而且这是它没有的程序之一!像你一样只使用-O
,将运行时间缩短到1.90秒。
我尝试用div
替换quot
(它使用与C相同的除法类型;它们只在负输入上有所不同),但奇怪的是它实际上使Haskell程序对我来说运行得稍慢。
你应该能够在this previous Stack Overflow question的帮助下加快syr
功能。我回答了同样的Project Euler问题。
答案 2 :(得分:2)
在我当前的系统(32位Core2Duo)上,您的Haskell代码(包括答案中给出的所有优化)需要0.8s
进行编译并运行1.2s
。
您可以将运行时转移到编译时,并将运行时间减少到接近零。
module Euler14 where
import Data.Word
import Language.Haskell.TH
terms :: Word -> Word
terms n = countTerms n 0
where
countTerms 1 acc = acc + 1
countTerms n acc | even n = countTerms (n `div` 2) (acc + 1)
| otherwise = countTerms (3 * n + 1) (acc + 1)
longestT :: Word -> Word -> (Word, Word)
longestT mi mx = find mi mx (0, 0)
where
find mi mx (ct,cn) | mi == mx = if ct > terms mi then (ct,cn) else (terms mi, mi)
| otherwise = find (mi + 1) mx
(if ct > terms mi then (ct,cn) else (terms mi, mi))
longest :: Word -> Word -> ExpQ
longest mi mx = return $ TupE [LitE (IntegerL (fromIntegral a)),
LitE (IntegerL (fromIntegral b))]
where
(a,b) = longestT mi mx
和
{-# LANGUAGE TemplateHaskell #-}
import Euler14
main = print $(longest 500000 999999)
在我的系统上,编译它需要2.3s
,但运行时间会降至0.003s
。编译时功能执行(CTFE)是您在C / C ++中无法做到的。我所知道的唯一支持CTFE的编程语言是D programming language。只是为了完成,C代码需要0.1s
来编译并0.7s
来运行。