packages feed

ideas-0.6: src/Domain/Math/Data/PrimeFactors.hs

-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed 
-- under the terms of the GNU General Public License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Domain.Math.Data.PrimeFactors
   ( PrimeFactors
   , factors, multiplicity, coprime
   , square, power, splitPower
   , primes
   ) where

import qualified Data.IntMap as IM

-------------------------------------------------------------
-- Representation

-- Invariants:
-- * Keys in map are prime numbers only (exception: representation of 0)
-- * Elements in map are positive (non-zero)
-- * Zero is represented by [(0,1)] (since 0^1 equals 0)
-- * The number can be negative, in which case we use the factors of 
--   its absolute value
data PrimeFactors = PF Integer Factors 

type Factors = IM.IntMap Int

-------------------------------------------------------------
-- Conversion to and from factors

toFactors :: Integer -> Factors
toFactors n
   | n > 0     = rec primes n
   | n < 0     = rec primes (-n)
   | otherwise = IM.singleton 0 1
 where
   rec [] n       = IM.singleton (fromIntegral n) 1
   rec (p:ps) n
      | n <= 1    = IM.empty
      | otherwise = f 0 n
    where
      p2 = fromIntegral p
      f i m
         | r == 0    = f (i+1) q
         | i >  0    = IM.insert p i (rec ps m)
         | otherwise = rec ps m
       where
         (q, r) = quotRem m p2


fromFactors :: Factors -> Integer
fromFactors = product . map f . IM.toList
 where f (a, i) = fromIntegral a ^ fromIntegral i

-- For practical reasons, the list of prime numbers is cut-off after 
-- 1000 elements (last primes gives 7919).
primes :: [Int] 
primes = take 1000 $ rec [2..]
 where
   rec (x:xs) = x : rec (filter (\y -> y `mod` x /= 0) xs)
   rec []     = error "PrimeFactors: empty list"

-------------------------------------------------------------
-- Type class instances

instance Show PrimeFactors where
   show (PF a m) = show a ++ " (factors = " ++ show (IM.toList m) ++ ")"

instance Eq PrimeFactors where
    PF a _ == PF b _ = a==b

instance Ord PrimeFactors where
   PF a _ `compare` PF b _ = a `compare` b
   
instance Num PrimeFactors where
   PF a m1 + PF b m2
      | a==0         = PF b m2 -- prevent recomputing prime factors
      | b==0         = PF a m1
      | otherwise    = fromInteger (a+b)
   a - b             = a + negate b
   PF a m1 * PF b m2
      | a==0 || b==0 = 0
      | otherwise    = PF (a*b) (IM.unionWith (+) m1 m2)
   negate (PF a m)   = PF (negate a) m
   abs    (PF a m)   = PF (abs a) m
   signum (PF a _)   = fromInteger (signum a)
   fromInteger n     = PF n (toFactors n)

instance Enum PrimeFactors where
   toEnum   = fromIntegral
   fromEnum = fromIntegral . toInteger
   
instance Real PrimeFactors where
   toRational = toRational . toInteger
   
instance Integral PrimeFactors where
   toInteger (PF a _) = a
   quotRem = quotRemPF
   
-------------------------------------------------------------
-- Utility functions

factors :: PrimeFactors -> [(Int, Int)]
factors (PF _ m) = IM.toList m

multiplicity :: Int -> PrimeFactors -> Int
multiplicity i (PF _ m) = IM.findWithDefault 0 i m

-- no prime in common
coprime :: PrimeFactors -> PrimeFactors -> Bool
coprime (PF _ m1) (PF _ m2) = IM.null (IM.intersection m1 m2)

square :: PrimeFactors -> PrimeFactors
square = (`power` 2)

power :: PrimeFactors -> Int -> PrimeFactors
power (PF a m) i = PF (a^i) (IM.map (*i) m)

-- splitPower i a = (b,c)  
--  => b^i * c = a
splitPower :: Int -> PrimeFactors -> (PrimeFactors, PrimeFactors)
splitPower i (PF a m) = (PF b p1, PF c p2)
 where 
   pairs = IM.map (`quotRem` i) m
   p1    = IM.filter (>0) (fmap fst pairs)
   p2    = IM.filter (>0) (fmap snd pairs)
   b     = fromFactors p1
   c     = a `div` (b^i)
   
quotRemPF :: PrimeFactors -> PrimeFactors -> (PrimeFactors, PrimeFactors) 
quotRemPF (PF a m1) (PF b m2)
   | b==0 = error "division by zero" 
   | a==0 = (0,0)
   | otherwise = sign $
        case (IM.null up, IM.null dn) of
           (True,  True)  -> (1, 0)
           (False, True)  -> (PF (fromFactors up) up, 0)
           (True,  False) -> (0, PF a m1)
           _              -> (fromInteger qn, fromInteger rn)
 where
   (up, dn) = IM.partition (>0) $ IM.filter (/=0) $ IM.unionWith (+) m1 (IM.map negate m2)
   (qn, rn) = fromFactors up `quotRem` fromFactors (IM.map negate dn)
   sign (q, r) = ( fromInteger (signum a*signum b) * q
                 , fromInteger (signum a) * r
                 )