我是Haskell的新手并且在修改Euler Project问题。我对问题#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]
where n = 600851475143
然后我将其更改为返回所有x
而不仅仅是最大的那个。
problem3 = [ x | x <- [1..n], (mod n x) == 0, n /= x]
where n = 600851475143
30分钟后,列表仍在处理中,输出如下所示
[1,71,839,1471,6857,59569,104441,486847,1234169,5753023,10086647,87625999,408464633,716151937
为什么这么慢?我做了一件非常错误的事情,或者这种任务是否正常?
答案 0 :(得分:10)
使用您的解决方案,可能有大约6000亿个数字。正如德尔南所指出的那样,每次检查数字都不会产生太大影响,我们必须限制候选人的数量。
您的解决方案似乎也不正确。 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
必须是素数,因此是原始因素的主要因素
号码({{1}})。
只考虑素数就可以删除更多的候选人, 但这有点先进,因为你需要做出合理的表现 生成素数的方法。有关这方面的信息,请参见this page。
答案 1 :(得分:3)
它做了很多工作! (它也会给你错误的答案,但这是一个单独的问题!)
有一些非常快捷的方法可以通过先考虑问题加快速度:
1..n
应用您的功能,并检查其中的每一个以确保它不是n
。相反,您可以浏览所有数字1..n-1
并跳过n
不同的检查(虽然它们很小)。1..(n-1)/2
并检查2x
而不是x
来快速过滤掉任何偶数。1..sqrt(n)
(或1..sqrt(n)/2
搜索,如果您忽略偶数)并在每一步中输出数字对。 与此功能的性能无关,但值得注意的是,您在此处实施的内容将找到数字的所有因素,而您想要的只是最大的素数因素。因此,要么你必须测试每个除数的素数(这将再次变慢),或者你可以一步实现这两个除数。你可能想看看'sieves',最简单的是Eratosthenes的Sieve,以及你如何实现它们。
答案 2 :(得分:1)
数字的完全因子分解对于大数字来说可能需要很长时间。对于Project Euler问题,蛮力解决方案(这是)通常不足以在您的生命中找到答案。
提示:您不需要找到所有素因子,只是最大因素。
答案 3 :(得分:0)
TL; DR :您在非最佳状态下做的两件事是:不停留在平方根,而不是将每个最小因子分开,因为它们找到了。
这是the answer by HaskellElephant中显示的(第二)分解代码的一点推导。我们从您的代码开始:
f1 n = [ x | x <- [2..n], rem n x == 0]
n3 = 600851475143
Prelude> f1 n3
[71,839,1471,6857,59569,104441,486847Interrupted.
所以它没有在任何合理的时间内完成,并且它产生的一些数字不是 prime ...但是不是在列表理解中添加素性检查,让我们注意到71 是素数。 f1 n
生成的第一个数字是 n
的最小除数 ,因此 是素数 即可。如果不是,我们首先找到其最小的除数 - 这是一个矛盾。
因此,我们可以将其除去,并continue searching作为新减少数量的素因子:
f2 n = tail $ iterate (\(_,m)-> (\f->(f, quot m f)) . head $ f1 m) (1,n)
Prelude> f2 n3
[(71,8462696833),(839,10086647),(1471,6857),(6857,1),(*** Exception: Prelude.hea
d: empty list
(错误,因为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 < a <= b
,那么a*a <= a*b == n
,那么仅测试数字的 平方根 就足够了,找到它最小的除数。
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]
根本不需要sqrt n3
以上的所有除数。我们无条件地将n
本身添加为f12
中的最后一个除数,因此它能够处理素数:
Prelude> f12 (n3+6)
[600851475149]
自n3 / sqrt n3 = sqrt n3 ~= 775146
以来,您f1 n3
的原始尝试应该已经 大约一周 来完成。这种优化的重要性在于停止在平方根上。
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
我们显然已经将“Prelude.head:empty list”错误交换为非终止 - 但高效行为。
最后,我们将f22
分为两部分并将它们分别融合到其他函数中,以获得稍微简化的代码。此外,我们不会重新开始,就像f12
一样,不断寻找 2 中的最小除数:
-- 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)
这通过higher-order function iterate
表示保护(共同)递归,并且在功能上等同于上面提到的代码。以下现在运行顺利,我们甚至可以在那里找到一对twin primes奖励:
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.
答案 4 :(得分:0)
这是我对Euler Project#3的解决方案。我的Macbook Air只需1.22秒。
首先,我们应该找到给定数字的所有因素。但我们知道,偶数不能是素数(第2号除外)。因此,要解决欧拉项目#3,我们不需要全部,只需要奇怪的因素:
getOddFactors num = [ x | x <- [3,5..num], num `rem` x == 0 ]
但我们可以优化这个功能。如果我们计划找到 num 大于 sqrt num 的因子,我们应该有另一个小于 sqrt num 的因子 - 这些可能我们已经找到的因素。因此,我们可以通过 sqrt num :
来限制可能因素的列表 getOddFactors num = [ x | x <- [3, 5..(floor.sqrt.fromIntegral) num],
num `rem` x == 0 ]
接下来我们想知道 num 的哪些奇数因子是素数:
isPrime number = [ x | x <- [3..(floor.sqrt.fromIntegral) number],
number `rem` x == 0] == []
接下来,我们可以使用函数 isPrime 过滤 num 的奇数因子,以查找 num 的所有素因子。但是为了使用Haskell的懒惰来优化我们的解决方案,我们将函数 filter isPrime 应用于num的奇数因子的反向列表。一旦我们的函数找到第一个值为素数,Haskell就会停止计算并返回解决方案:
largestPrimeFactor = head . filter isPrime . reverse . getOddDivisors
因此,解决方案是:
ghci> largestPrimeFactor 600851475143
6857
(1.22 secs, 110646064 bytes)