NumberSieves-0.1.2: Math/Sieve/Factor.hs
{-# LANGUAGE BangPatterns #-}
-----------------------------------------------------------------------------
-- |
-- Module : Math.Sieve.Factor
-- Copyright : (c) 2009-2012 Leon P Smith
-- License : BSD3
--
-- Maintainer : leon@melding-monads.com
-- Stability : experimental
--
-- This is an array-based generalization of the Sieve of Eratosthenes that
-- associates a prime divisor to each number in the sieve. This is useful
-- if you want to factor a large quantity of small numbers
--
-- This code contains two simple optimizations: even numbers are not
-- represented in the array, reducing both time and space by 50%.
-- Secondly, the smallest prime divisor is sieved, and the prime numbers
-- are represented by @0@ in the array instead of themselves. This allows
-- the divisors to be stored in half the number of bits, reducing space
-- consumption by another 50%.
--
-- Currently, this sieve is limited to numbers less than 2^32, and consumes
-- one byte per number in the sieve on average. Thus if you want to find
-- the smallest divisor of every number up to 2^32, you will need 4 GB
-- of storage.
--
-----------------------------------------------------------------------------
module Math.Sieve.Factor
( FactorSieve()
, findFactor
, sieveBound
, isPrime
, factor
, sieve
) where
import Control.Monad
import Control.Monad.ST
import Data.Array.ST
import Data.Array.MArray
import Data.Array.Unboxed
import Data.Bits
import Data.Word
-- Note that if you want to sieve numbers beyond 2^32, you probably do not
-- want to simply change this type to (UArray Word64 Word32), as this would
-- result in 4 GB of wasted space. Increasing the limit in a sensible
-- fashion would require the use of multiple arrays with heterogenous types.
newtype FactorSieve = FactorSieve (UArray Word32 Word16)
instance Show FactorSieve where
show fs = "<<FactorSieve " ++ show (sieveBound fs) ++ ">>"
instance Eq FactorSieve where
a == b = sieveBound' a == sieveBound' b
instance Ord FactorSieve where
compare a b = compare (sieveBound' a) (sieveBound' b)
-- | Returns the upper limit of a sieve.
sieveBound :: Integral a => FactorSieve -> a
sieveBound (FactorSieve arr) = fromIntegral (shiftL (snd (bounds arr)) 1)
sieveBound' (FactorSieve arr) = (snd (bounds arr))
-- | Is a number prime?
isPrime :: Integral a => FactorSieve -> a -> Bool
isPrime (FactorSieve arr) n
| even n = n == 2
| n <= 1 = False
| otherwise = arr ! ix (fromIntegral n) == 0
-- | Returns the smallest prime divisor of a given number in the sieve.
findFactor :: Integral a => FactorSieve -> a -> a
findFactor (FactorSieve arr) n
| even n = 2
| d == 0 = n
| otherwise = fromIntegral d
where
d = arr ! ix (fromIntegral n)
ix n = shiftR n 1
for min max step f = loop min
where
loop !x
| x <= max = f x >> loop (x + step)
| otherwise = return ()
flsqrt x = floor (sqrt (fromIntegral x))
-- | Finds the smallest prime divisor of every number up to a given limit.
sieve :: (Show a, Integral a) => a -> FactorSieve
sieve limit
| not (0 <= intlim && intlim < 2^32)
= error ("Math.Sieve.Factor.sieve: out of bounds: " ++ show limit)
| otherwise
= FactorSieve $ runSTUArray $ do
arr <- newArray (ix 3, lim) 0
for 3 (flsqrt limit) 2
(\i -> do
i' <- readArray arr (ix i)
when (i' == 0)
(for (ix (i*i)) lim i
(\i' -> do
i'' <- readArray arr i'
when (i'' == 0)
(writeArray arr i' (fromIntegral i)))))
return arr
where
intlim = fromIntegral limit :: Integer
lim = ix (fromIntegral limit - 1) :: Word32
-- FIXME: clean up the definition of 'factor', but maintain speed
-- | Factors a number completely using a sieve, e.g.
--
-- > factor (sieve 1000) 360 == [(2,3),(3,2),(5,1)]
factor :: Integral a => FactorSieve -> a -> [(a,a)]
factor (FactorSieve arr) n = start (fromIntegral n)
where
p x y = ((,) $! fromIntegral x) $! fromIntegral y
start n
| even n = loop0 (shiftR n 1) 1
| otherwise = loop1 n
loop0 !n !i
| even n = loop0 (shiftR n 1) (i + 1)
| otherwise = p 2 i : loop1 n
loop1 !n
| n == 1 = []
| d == 0 = [p n 1]
| otherwise = loop1' (n `div` d) d 1
where
d = fromIntegral (arr ! ix n)
loop1' !n !d !i
| d' == 0 = if n == d then [p d (i+1)] else [p d i, p n 1]
| d' == d = loop1' (n `div` d ) d (i+1)
| otherwise = p d i : loop1' (n `div` d') d' 1
where
d' = fromIntegral (arr ! ix n)