Haskell 哈斯克尔:更快的素数求和
免责声明:我正在研究Euler问题9 我把一些相当大的数字加起来,所有的素数从1到2000 求这些素数的和需要永远。我使用的是haskell内置函数“sum” 例如:Haskell 哈斯克尔:更快的素数求和,haskell,primes,Haskell,Primes,免责声明:我正在研究Euler问题9 我把一些相当大的数字加起来,所有的素数从1到2000 求这些素数的和需要永远。我使用的是haskell内置函数“sum” 例如: sum listOfPrimes 还有其他更快的选择吗 --我的主生成器是代码中的慢速链接 听起来你的问题不是求和,而是生成它们。您对时间列表的实现是什么 这篇论文可能很有趣:我写了一篇“埃拉托斯坦筛”: 使用此功能,打印大约需要25秒。sum$takeWhile(我希望您使用的是ghc-O2,而不是ghci,对吗?您的问题将出
sum listOfPrimes
还有其他更快的选择吗
--我的主生成器是代码中的慢速链接 听起来你的问题不是求和,而是生成它们。您对时间列表的实现是什么 这篇论文可能很有趣:我写了一篇“埃拉托斯坦筛”:
使用此功能,打印
大约需要25秒。sum$takeWhile(我希望您使用的是ghc-O2,而不是ghci,对吗?您的问题将出现在下一代,而不是求和
一种更快的方法是使用基于流融合的序列,它可以更好地优化。对于常规列表:
import Data.List
import qualified Data.Map as M
primes :: [Integer]
primes = mkPrimes 2 M.empty
where
mkPrimes n m = case (M.null m, M.findMin m) of
(False, (n', skips)) | n == n' ->
mkPrimes (succ n) (addSkips n (M.deleteMin m) skips)
_ -> n : mkPrimes (succ n) (addSkip n m n)
addSkip n m s = M.alter (Just . maybe [s] (s:)) (n+s) m
addSkips = foldl' . addSkip
-- fuse:
main = print (sum (takeWhile (<= 2000000) primes))
切换到流,因此sum.takeWhile保险丝:
import qualified Data.List.Stream as S
main = print (S.sum (S.takeWhile (<= 2000000) primes))
但你的问题将是素代,我们可以看到,如果我们完全放弃求和,用最后一个替换求和:
$ time ./A
1999993
./A 9.65s user 0.12s system 99% cpu 9.768 total
因此,请找到一个更好的素数生成器。:-)
最后,有一个关于快速素数生成器的黑客库:
利用它,我们的时间变得:
$ cabal install primes
$ cabal install stream-fusion
$ cat A.hs
import qualified Data.List.Stream as S
import Data.Numbers.Primes
main = print . S.sum . S.takeWhile (<= 2000000) $ primes
$ ghc -O2 -fvia-C -optc-O3 A.hs --make
$ time ./A
142913828922
./A 0.62s user 0.07s system 99% cpu 0.694 total
$cabal安装底漆
$cabal安装流融合
$cat A.hs
将符合条件的Data.List.Stream作为S导入
导入数据。数字。素数
main=打印。苏姆。S.takeWhile(函数中缓慢的部分肯定是生成素数,而不是生成sum
函数。生成素数的一个好方法是:
isprime :: (Integral i) => i -> Bool
isprime n = isprime_ n primes
where isprime_ n (p:ps)
| p*p > n = True
| n `mod` p == 0 = False
| otherwise = isprime_ n ps
primes :: (Integral i) => [i]
primes = 2 : filter isprime [3,5..]
我认为它可读性很强,尽管可能有点令人惊讶,因为它使用了递归和primes
列表的惰性。它也相当快,尽管可以以可读性为代价进行进一步的优化。因为你没有链接到它,|非常酷的东西,我没有意识到它被很好地打包在Hackage。嘿,我以前在Haskell中没有见过这种特殊的算法。非常酷,它比我的更简单,感觉也更像Haskell-y。这实际上相当于低效算法。它仍然根据每个素数测试每个数字。@newacc:它使用“低效”算法,但比“高效”算法更快版本,至少在这种情况下是这样。“高效”算法需要做大量的地图修改,并不断地在越来越大的地图上迭代。这不一定比几个素数好。确切地说。它感觉就像传统的primes=sieve[2..]一样简单和干净,其中sieve(p:xs)=p:[x | x这是一个(几乎)最优试算法(OTD)筛,比“传统的”“低效”算法(即Turnerps=sv[2..)的筛,其中sv(p:xs)=p:sv[x | x0]更有效
,因为它停止了对n
的平方根的测试——由低于该数字本身的所有素数进行的特纳测试。OTD的理论时间复杂度为O(k^1.5/(log k)^0.5)
(根据经验通常被视为~k^1.45
),而对特纳测试的时间复杂度为O(k^2)
,在k
中生产素数。因此,在生产一百万素数时,OTD将~2000x…4000x
(或更多)比Turner的筛子快。我同意!那个筛子帮了我很大的忙!非常好、清晰而且好!三个标准改进-仅赔率、推迟向地图添加素数以及双素数馈送-使它在2mln(测试)下运行速度加快10.6倍,在2000万(预测)下运行速度加快13.4倍。因此,运行时间将变为~1.9s。我已将更改后的代码添加到。:@WillNess Best reply:-)为什么,非常感谢。:…忘了提到它现在在接近恒定的空间(1..2MB)中运行。但这是意料之中的。
$ time ./A
142913828922
./A 9.60s user 0.13s system 99% cpu 9.795 total
$ time ./A
1999993
./A 9.65s user 0.12s system 99% cpu 9.768 total
$ cabal install primes
$ cabal install stream-fusion
$ cat A.hs
import qualified Data.List.Stream as S
import Data.Numbers.Primes
main = print . S.sum . S.takeWhile (<= 2000000) $ primes
$ ghc -O2 -fvia-C -optc-O3 A.hs --make
$ time ./A
142913828922
./A 0.62s user 0.07s system 99% cpu 0.694 total
isprime :: (Integral i) => i -> Bool
isprime n = isprime_ n primes
where isprime_ n (p:ps)
| p*p > n = True
| n `mod` p == 0 = False
| otherwise = isprime_ n ps
primes :: (Integral i) => [i]
primes = 2 : filter isprime [3,5..]