项目euler 10 - [haskell]为什么这么低效?

时间:2013-12-02 15:34:57

标签: haskell primes sieve-of-eratosthenes

好吧,所以我选择了项目euler,当我使用java时我离开了,我遇到了问题10.我现在使用Haskell,我觉得学习一些haskell是好的,因为我还是非常初学者。

http://projecteuler.net/problem=10

我仍然使用java编码的朋友想出了一个非常直接的方法来实现eratosthenes的筛选:

http://puu.sh/5zQoU.png

我尝试实现一个更好看的(以及我认为会更高效的)Haskell函数来查找高达2,000,000的所有素数。 我来到这个非常优雅,但显然非常低效的功能:

primeSieveV2 :: [Integer] -> [Integer]
primeSieveV2 [] = []
primeSieveV2 (x:xs) = x:primeSieveV2( (filter (\n -> ( mod n x ) /= 0) xs) )

现在我不确定为什么我的功能比他慢得多(他声称他的作品在5ms内),如果我的任何东西应该更快,因为我只检查一次复合(当它们是从列表中删除时)虽然他可以对它们进行多次检查。

任何帮助?

3 个答案:

答案 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

相关问题