Haskell 我对Euler项目3的解决方案太慢了
我是Haskell的新手,正在处理Euler项目的问题。我解决问题3的速度太慢了。起初,我试着这样做:Haskell 我对Euler项目3的解决方案太慢了,haskell,primes,prime-factoring,Haskell,Primes,Prime Factoring,我是Haskell的新手,正在处理Euler项目的问题。我解决问题3的速度太慢了。起初,我试着这样做: -- Problem 3 -- The prime factors of 13195 are 5, 7, 13 and 29. -- What is the largest prime factor of the number 600851475143 ? problem3 = max [ x | x <- [1..n], (mod n x) == 0, n /= x] whe
-- Problem 3
-- The prime factors of 13195 are 5, 7, 13 and 29.
-- What is the largest prime factor of the number 600851475143 ?
problem3 = max [ x | x <- [1..n], (mod n x) == 0, n /= x]
where n = 600851475143
30分钟后,列表仍在处理中,输出如下所示
[1,71839147168575956910444148684712341695753023100866478762594940084633716151937
为什么这么慢?我是做错了什么,还是这类任务很正常?一个数字的完全分解对于大数字来说可能需要很长时间。对于Project Euler问题,暴力解决方案(这是)通常不足以在你的一生中找到答案 提示:你不需要找到所有的主要因素,只需要找到最大的一个。它做了很多工作!(它也会给你错误的答案,但这是一个单独的问题!) 有几种非常快速的方法可以让你先考虑一下这个问题,从而加快速度:
- 您正在将函数应用于所有数字
,并检查每一个数字,以确保它不是1..n
。相反,您可以只检查所有数字n
,然后跳过1..n-1
不同的检查(尽管它们很小)n
- 答案是奇数,因此您可以通过从
中选择1..(n-1)/2
而不是2x
快速过滤掉任何偶数x
- 如果你仔细想想,所有的因素都是成对出现的,所以你实际上可以从
(或者1..sqrt(n)
中搜索,如果你忽略偶数的话)并在每个步骤中输出成对的数字1..sqrt(n)/2
与此函数的性能无关,但值得注意的是,您在这里实现的将找到一个数的所有因子,而您想要的只是最大的素数因子。因此,要么您必须测试每个因子的素数性(这也会很慢)或者你可以一步实现这两个。你可能想看看“筛子”,最简单的是埃拉托斯坦的筛子,以及如何实现它们。用你的解决方案,有大约6000亿个可能的数字。正如德尔南所指出的,加快每一个数字的检查不会有多大区别,我们必须限制t他有许多候选人 您的解决方案似乎也不正确。
59569=71*839
不是吗?问题是
只要求基本因子。请注意,71
和839
在您的列表中,所以您是
做正确的事情。事实上,你正在努力找到所有的因素
我认为最引人注目的效果是,在继续之前,你只需把这个因素除掉
euler3 = go 2 600851475143
where
go cand num
| cand == num = [num]
| cand `isFactorOf` num = cand : go cand (num `div` cand)
| otherwise = go (cand + 1) num
isFactorOf a b = b `mod` a == 0
这似乎是一个明显的优化,但它依赖于这样一个事实,即如果a
和b
都除以c
,并且a
与b
是互质,那么a
除以c/b
如果你想做更多的事情,常用的“只检查到平方根”技巧已经被使用了
此处已提到。同样的技巧也可以应用于此问题,但遗憾的是,在这种情况下,性能增益没有显示出来:
euler3 = go 2 600851475143
where
go cand num
| cand*cand > num = [num]
| cand `isFactorOf` num = cand : go cand (num `div` cand)
| otherwise = go (cand + 1) num
isFactorOf a b = b `mod` a == 0
这里,当一个候选数大于剩余数的平方根(num
)时,我们知道num
必须是一个素数,因此是原始数的素数因子
编号(600851475143
)
只考虑素数就可以删除更多的候选者,
但是这稍微高级一些,因为您需要做出一个合理的性能测试
生成素数的方法。有关方法,请参见。TL;DR:您所做的两件非最佳的事情是:不在平方根处停止,也不按发现的那样划分每个最小的因子
下面是中所示的(第二)因式分解代码的一点派生。我们从您的代码开始:
f1 n = [ x | x <- [2..n], rem n x == 0]
n3 = 600851475143
Prelude> f1 n3
[71,839,1471,6857,59569,104441,486847Interrupted.
(错误,因为f1 1=[])。我们完成了!(6857是答案,在这里…。让我们总结一下:
takeUntil p xs = foldr (\x r -> if p x then [x] else x:r) [] xs
pfactors1 n = map fst . takeUntil ((==1).snd) . f2 $ n -- prime factors of n
尝试我们新发明的解决方案
Prelude> map pfactors1 [n3..]
[[71,839,1471,6857],[2,2,2,3,3,1259Interrupted.
突然,我们遇到了一道新的低效率墙,关于没有小除数的数字。但是如果
n=a*b
和1
首先,我们应该找到给定数的所有因子。但我们知道,偶数不能是素数(除数2外)。因此,要解Euler项目#3,我们不需要所有因子,只需要奇数因子:
getOddFactors num = [ x | x <- [3,5..num], num `rem` x == 0 ]
因此,解决办法是:
ghci> largestPrimeFactor 600851475143
6857
(1.22 secs, 110646064 bytes)
我明白了。回到绘图板。有趣的事实:你接触了大约6000亿个数字。即使每个数字只占用一个CPU时钟周期(这是完全不现实的)在目前可用的时钟频率最高的CPU上,这仍然需要几十秒。因此,是的,这种方法是不可行的。这是迄今为止最好的答案,因为它显著减少了运行时间。我建议在第一个保护条件下改为选中cand*cand>num
。这将实现常见的“只检查平方根”技巧(不实际取平方根)。您还可以跟踪要添加的奇数(1+3=2^2,1+3+5=3^2,1+3+5+7=4^2)将平方运算从乘法减少为几次加法。@Olathe,谢谢你的建议。我添加了一段关于它的内容。不幸的是,这一实例的性能差异并不明显。让我们重构,euler3 n=go 2 n where…
。现在,尝试[euler3 x]x@WillNess在这个基准上,“只检查到sqrt”技巧至关重要。与分钟相比,差异不到一秒。抱歉,更改了对您的测试;它应该是take 7[euler3 x | x
f12 n = [ x | x <- takeWhile ((<= n).(^2)) [2..n], rem n x == 0] ++ [n]
f22 n = tail $ iterate (\(_,m)-> (\f->(f, quot m f)) . head $ f12 m) (1,n)
pfactors2 n = map fst . takeUntil ((==1).snd) . f22 $ n
Prelude> f12 n3
[71,839,1471,6857,59569,104441,486847,600851475143]
Prelude> f12 (n3+6)
[600851475149]
Prelude> f22 n3
[(71,8462696833),(839,10086647),(1471,6857),(6857,1),(1,1),(1,1),(1,1),(1,1),(1,
1),(1,1),(1,1),(1,1),(1,1),(1,1),(1,1),(1,1),(1,1),(1,1)Interrupted
-- smallest factor of n, starting from d. directly jump from sqrt n to n.
smf (d,n) = head $ [ (x, quot n x) | x <- takeWhile ((<=n).(^2)) [d..]
, rem n x == 0] ++ [(n,1)]
pfactors n = map fst . takeUntil ((==1).snd) . tail . iterate smf $ (2,n)
Prelude Saga> map pfactors [n3..]
[[71,839,1471,6857],[2,2,2,3,3,1259,6628403],[5,120170295029],[2,13,37,227,27514
79],[3,7,7,11,163,2279657],[2,2,41,3663728507],[600851475149],[2,3,5,5,19,31,680
0809],[600851475151],[2,2,2,2,37553217197],[3,3,3,211,105468049],[2,7,11161,3845
351],[5,67,881,2035853],[2,2,3Interrupted.
getOddFactors num = [ x | x <- [3,5..num], num `rem` x == 0 ]
getOddFactors num = [ x | x <- [3, 5..(floor.sqrt.fromIntegral) num],
num `rem` x == 0 ]
isPrime number = [ x | x <- [3..(floor.sqrt.fromIntegral) number],
number `rem` x == 0] == []
largestPrimeFactor = head . filter isPrime . reverse . getOddDivisors
ghci> largestPrimeFactor 600851475143
6857
(1.22 secs, 110646064 bytes)