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

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