How to optimize this Haskell code summing up the primes in sublinear time?

后端 未结 4 1423
迷失自我
迷失自我 2021-02-18 23:11

Problem 10 from Project Euler is to find the sum of all the primes below given n.

I solved it simply by summing up the primes generated by the sieve of Eratosth

4条回答
  •  失恋的感觉
    2021-02-18 23:32

    This code of mine evaluates the sum to 2⋅10^9 in 0.3 seconds and the sum to 10^12 (18435588552550705911377) in 19.6 seconds (if given sufficient RAM).

    import Control.DeepSeq 
    import qualified Control.Monad as ControlMonad
    import qualified Data.Array as Array
    import qualified Data.Array.ST as ArrayST
    import qualified Data.Array.Base as ArrayBase
    
    primeLucy :: (Integer -> Integer) -> (Integer -> Integer) -> Integer -> (Integer->Integer)
    primeLucy f sf n = g
      where
        r = fromIntegral $ integerSquareRoot n
        ni = fromIntegral n
        loop from to c = let go i = ControlMonad.when (to<=i) (c i >> go (i-1)) in go from
    
        k = ArrayST.runSTArray $ do
          k <- ArrayST.newListArray (-r,r) $ force $
            [sf (div n (toInteger i)) - sf 1|i<-[r,r-1..1]] ++
            [0] ++
            [sf (toInteger i) - sf 1|i<-[1..r]]
          ControlMonad.forM_ (takeWhile (<=r) primes) $ \p -> do
            l <- ArrayST.readArray k (p-1)
            let q = force $ f (toInteger p)
    
            let adjust = \i j -> do { v <- ArrayBase.unsafeRead k (i+r); w <- ArrayBase.unsafeRead k (j+r); ArrayBase.unsafeWrite k (i+r) $!! v+q*(l-w) }
    
            loop (-1)         (-div r p)              $ \i -> adjust i (i*p)
            loop (-div r p-1) (-min r (div ni (p*p))) $ \i -> adjust i (div (-ni) (i*p))
            loop r            (p*p)                   $ \i -> adjust i (div i p)
    
          return k
    
        g :: Integer -> Integer
        g m
          | m >= 1 && m <= integerSquareRoot n                       = k Array.! (fromIntegral m)
          | m >= integerSquareRoot n && m <= n && div n (div n m)==m = k Array.! (fromIntegral (negate (div n m)))
          | otherwise = error $ "Function not precalculated for value " ++ show m
    
    primeSum :: Integer -> Integer
    primeSum n = (primeLucy id (\m -> div (m*m+m) 2) n) n
    

    If your integerSquareRoot function is buggy (as reportedly some are), you can replace it here with floor . sqrt . fromIntegral.

    Explanation:

    As the name suggests it is based upon a generalization of the famous method by "Lucy Hedgehog" eventually discovered by the original poster.

    It allows you to calculate many sums of the form (with p prime) without enumerating all the primes up to N and in time O(N^0.75).

    Its inputs are the function f (i.e., id if you want the prime sum), its summatory function over all the integers (i.e., in that case the sum of the first m integers or div (m*m+m) 2), and N.

    PrimeLucy returns a lookup function (with p prime) restricted to certain values of n: .

提交回复
热议问题