TL;DR : あなたが行っていた最適化されていない 2 つのことは、平方根で停止しないことと、見つかった最小の要素を分割しないことです。
これは、 HaskellElephant による回答に示されている (2 番目の) 因数分解コードの少しの派生です。私たちはあなたのコードから始めます:
f1 n = [ x | x <- [2..n], rem n x == 0]
n3 = 600851475143
Prelude> f1 n3
[71,839,1471,6857,59569,104441,486847Interrupted.
したがって、妥当な時間内に終了せず、生成される数の一部は素数ではありません... しかし、リスト内包表記に素数チェックを追加する代わりに、71が素数であることに注意してください。によって生成される最初の数値f1 n
はの最小の約数でありn
、素数です。そうでない場合は、その最小の約数を最初に見つけます。これは矛盾です。
したがって、それを分割して、新たに簡約された数の素因数を検索し続けることができます。
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
30 分で終了できなかったものが、1 秒未満で終了するようになりました (典型的な高性能ボックスで):
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
、最初の の試みは終了するまでに約 1 週間かかる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
2 つの部分に分割し、それぞれを他の関数に融合して、やや単純化したコードにします。また、常に2から最小の除数を検索するように、 を最初からやり直すことはもうありません。f12
-- 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)
これは、高階関数による保護された (共) 再帰を表し、機能的には上記のコードと同等です。以下はスムーズに実行されるようになり、ボーナスとして双子の素数のペアを見つけることさえできます。 iterate
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,3中断されました。