我对Euler Project#3的解决方案太慢了

时间:2013-07-30 11:15:05

标签: haskell primes prime-factoring

我是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

为什么这么慢?我做了一件非常错误的事情,或者这种任务是否正常?

5 个答案:

答案 0 :(得分:10)

使用您的解决方案,可能有大约6000亿个数字。正如德尔南所指出的那样,每次检查数字都不会产生太大影响,我们必须限制候选人的数量。

您的解决方案似乎也不正确。 59569 = 71 * 839不是吗?这个问题 只询问主要因素。请注意,71839在您的列表中,所以您是 做正确的事。事实上,你正试图找到所有因素。

我认为你最简单的效果就是在继续之前将因素分开。

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

这看起来似乎是一种明显的优化,但它依赖于以下事实:如果ab分为ca,则b是互为的a然后c/beuler3 = 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*b1 < 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)