好吧,所以我选择了项目euler,当我使用java时我离开了,我遇到了问题10.我现在使用Haskell,我觉得学习一些haskell是好的,因为我还是非常初学者。
http://projecteuler.net/problem=10
我仍然使用java编码的朋友想出了一个非常直接的方法来实现eratosthenes的筛选:
我尝试实现一个更好看的(以及我认为会更高效的)Haskell函数来查找高达2,000,000的所有素数。 我来到这个非常优雅,但显然非常低效的功能:
primeSieveV2 :: [Integer] -> [Integer]
primeSieveV2 [] = []
primeSieveV2 (x:xs) = x:primeSieveV2( (filter (\n -> ( mod n x ) /= 0) xs) )
现在我不确定为什么我的功能比他慢得多(他声称他的作品在5ms内),如果我的任何东西应该更快,因为我只检查一次复合(当它们是从列表中删除时)虽然他可以对它们进行多次检查。
任何帮助?
答案 0 :(得分:7)
你这里实际上没有筛子。在Haskell你可以写一个筛子
import Data.Vector.Unboxed hiding (forM_)
import Data.Vector.Unboxed.Mutable
import Control.Monad.ST (runST)
import Control.Monad (forM_, when)
import Prelude hiding (read)
sieve :: Int -> Vector Bool
sieve n = runST $ do
vec <- new (n + 1) -- Create the mutable vector
set vec True -- Set all the elements to True
forM_ [2..n] $ \ i -> do -- Loop for i from 2 to n
val <- read vec i -- read the value at i
when val $ -- if the value is true, set all it's multiples to false
forM_ [2*i, 3*i .. n] $ \j -> write vec j False
freeze vec -- return the immutable vector
main = print . ifoldl' summer 0 $ sieve 2000000
where summer s i b = if b then i + s else s
通过使用可变的未装箱的矢量来“欺骗”,但它非常快速
$ ghc -O2 primes.hs
$ time ./primes
142913828923
real: 0.238 s
这比我对奥古斯都解决方案的基准测试快了约5倍。
答案 1 :(得分:5)
要在Haskell中有效地实现筛选,您可能需要以Java方式执行(即,分配一个可变数组并对其进行修改)。
我只喜欢生成素数:
primes = 2 : filter (isPrime primes) [3,5 ..]
where isPrime (p:ps) x = p*p > x || x `rem` p /= 0 && isPrime ps x
然后你可以打印所有质数素数的总和&lt; 2000000
main = print $ sum $ takeWhile (< 2000000) primes
您可以通过添加类型签名primes :: [Int]
来加快速度。
但它也适用于Integer
,并且它也为您提供了正确的总和(32位Int不会)。
有关详细信息,请参阅The Genuine Sieve of Eratosthenes。
答案 2 :(得分:1)
代码的时间复杂度为 n 2 (在 n 生成的素数中)。生产超过前10到2万个素数是不切实际的。
该代码的主要问题不在于它使用rem
,而是它过早地启动了过滤器,因此创建了太多的过滤器。通过小调整来解决这个问题:
{-# LANGUAGE PatternGuards #-}
primes = 2 : sieve primes [3..]
sieve (p:ps) xs | (h,t) <- span (< p*p) xs = h ++ sieve ps [x | x <- t, rem x p /= 0]
-- sieve ps (filter (\x->rem x p/=0) t)
main = print $ sum $ takeWhile (< 100000) primes
这可以通过约 n 1/2 (在 n 质数中产生)来提高时间复杂度并使其获得极大的加速:它获得比100,000
快75倍。您的 28秒 应该 ~0.4秒。但是,您可能在GHCi中将其作为解释代码进行了测试,而不是编译。将其标记为 1)为:: [Int]
并使用-O2
标志进行编译使其再次加速~40倍,因此它将 ~0.01秒 即可。使用此代码到达2,000,000
需要大约90倍的时间,高达 ~1秒 的预计运行时间。
1)请务必在sum $ map (fromIntegral :: Int -> Integer) $ takeWhile ...
中使用main
。
另见:http://en.wikipedia.org/wiki/Analysis_of_algorithms#Empirical_orders_of_growth