hmt-0.20: Music/Theory/Math/Prime.hs
-- | Prime number related functions.
module Music.Theory.Math.Prime where
import Data.List {- base -}
import Data.Maybe {- base -}
import Data.Ratio {- base -}
import qualified Data.Numbers.Primes as Primes {- primes -}
import qualified Music.Theory.Function as Function {- hmt -}
import qualified Music.Theory.List as List {- hmt -}
import qualified Music.Theory.Math as Math {- hmt -}
import qualified Music.Theory.Unicode as Unicode {- hmt -}
-- | Alias for 'Primes.primes'.
--
-- > take 12 primes_list == [2,3,5,7,11,13,17,19,23,29,31,37]
primes_list :: Integral i => [i]
primes_list = Primes.primes
-- | Give zero-index of prime, or Nothing if value is not prime.
--
-- > map prime_k [2,3,5,7,11,13,17,19,23,29,31,37] == map Just [0 .. 11]
-- > map prime_k [1,4,6,8,9,10,12,14,15,16,18,20,21,22] == replicate 14 Nothing
prime_k :: Integral a => a -> Maybe Int
prime_k i = if Primes.isPrime i then Just (List.findIndex_err (== i) primes_list) else Nothing
-- | 'maybe' 'error' of 'prime_k'
--
-- > prime_k_err 13 == 5
prime_k_err :: Integral a => a -> Int
prime_k_err = fromMaybe (error "prime_k: not prime?") . prime_k
{- | Generate list of factors of /n/ from /x/.
> factor primes_list 315 == [3,3,5,7]
> Primes.primeFactors 315 == [3,3,5,7]
As a special case 1 gives the empty list.
> factor primes_list 1 == []
> Primes.primeFactors 1 == []
-}
factor :: Integral i => [i] -> i -> [i]
factor x n =
case x of
[] -> error "factor: null primes_list input"
i:x' -> if n < i
then [] -- ie. prime factors of 1...
else if i * i > n
then [n]
else if rem n i == 0
then i : factor x (quot n i)
else factor x' n
-- | 'factor' of 'primes_list'.
--
-- > map prime_factors [-1,0,1] == [[],[],[]]
-- > map prime_factors [1,4,231,315] == [[],[2,2],[3,7,11],[3,3,5,7]]
-- > map Primes.primeFactors [1,4,231,315] == [[],[2,2],[3,7,11],[3,3,5,7]]
prime_factors :: Integral i => i -> [i]
prime_factors = factor primes_list
-- | 'maximum' of 'prime_factors'
--
-- > map prime_limit [243,125] == [3,5]
-- > map prime_limit [0,1] == [1,1]
prime_limit :: Integral i => i -> i
prime_limit x = if x < 2 then 1 else maximum (prime_factors x)
-- | Collect number of occurences of each element of a sorted list.
--
-- > multiplicities [1,1,1,2,2,3] == [(1,3),(2,2),(3,1)]
multiplicities :: Eq t => [t] -> [(t,Int)]
multiplicities = List.generic_histogram_by (==) Nothing
-- | Pretty printer for histogram (multiplicites).
--
-- > multiplicities_pp [(3,2),(5,1),(7,1)] == "3×2 5×1 7×1"
multiplicities_pp :: Show t => [(t,Int)] -> String
multiplicities_pp =
let f (x,y) = show x ++ "×" ++ show y
in unwords . map f
-- | 'multiplicities' of 'Primes.primeFactors'.
--
-- > prime_factors_m 1 == []
-- > prime_factors_m 315 == [(3,2),(5,1),(7,1)]
prime_factors_m :: Integral i => i -> [(i,Int)]
prime_factors_m = multiplicities . Primes.primeFactors
-- | 'multiplicities_pp' of 'prime_factors_m'.
prime_factors_m_pp :: (Show i,Integral i) => i -> String
prime_factors_m_pp = multiplicities_pp . prime_factors_m
-- | Prime factors of /n/ and /d/.
rat_prime_factors :: Integral i => (i,i) -> ([i],[i])
rat_prime_factors = Function.bimap1 Primes.primeFactors
-- | 'Ratio' variant of 'rat_prime_factors'
rational_prime_factors :: Integral i => Ratio i -> ([i],[i])
rational_prime_factors = rat_prime_factors . Math.rational_nd
{- | Variant that writes factors of numerator as positive and factors for denominator as negative.
Sorted by absolute value.
> rat_prime_factors_sgn (3 * 5 * 7 * 11,1) == [3,5,7,11]
> rat_prime_factors_sgn (3 * 5,7 * 11) == [3,5,-7,-11]
> rat_prime_factors_sgn (3 * 7,5) == [3,-5,7]
-}
rat_prime_factors_sgn :: Integral i => (i,i) -> [i]
rat_prime_factors_sgn r = let (n,d) = rat_prime_factors r in sortOn abs (n ++ map negate d)
-- | Rational variant.
--
-- > rational_prime_factors_sgn (2 * 2 * 2 * 1/3 * 1/3 * 1/3 * 1/3 * 5) == [2,2,2,-3,-3,-3,-3,5]
rational_prime_factors_sgn :: Integral i => Ratio i -> [i]
rational_prime_factors_sgn = rat_prime_factors_sgn . Math.rational_nd
-- | The largest prime factor of n/d.
rat_prime_limit :: Integral i => (i,i) -> i
rat_prime_limit = uncurry max . Function.bimap1 prime_limit
-- | The largest prime factor of /n/.
--
-- > rational_prime_limit (243/125) == 5
rational_prime_limit :: Integral i => Ratio i -> i
rational_prime_limit = rat_prime_limit . Math.rational_nd
-- | Merge function for 'rat_prime_factors_m'
rat_pf_merge :: Ord t => [(t,Int)] -> [(t,Int)] -> [(t,Int)]
rat_pf_merge p q =
case (p,q) of
(_,[]) -> p
([],_) -> map (\(i,j) -> (i,-j)) q
((a,b):p',(c,d):q') ->
if a < c
then (a,b) : rat_pf_merge p' q
else if a > c
then (c,-d) : rat_pf_merge p q'
else if b /= d
then (a,b-d) : rat_pf_merge p' q'
else rat_pf_merge p' q'
{- | Collect the prime factors in a rational number given as a
numerator/ denominator pair (n,m). Prime factors are listed in
ascending order with their positive or negative multiplicities,
depending on whether the prime factor occurs in the numerator or the
denominator (after cancelling out common factors).
> rat_prime_factors_m (1,1) == []
> rat_prime_factors_m (16,15) == [(2,4),(3,-1),(5,-1)]
> rat_prime_factors_m (10,9) == [(2,1),(3,-2),(5,1)]
> rat_prime_factors_m (81,64) == [(2,-6),(3,4)]
> rat_prime_factors_m (27,16) == [(2,-4),(3,3)]
> rat_prime_factors_m (12,7) == [(2,2),(3,1),(7,-1)]
> rat_prime_factors_m (5,31) == [(5,1),(31,-1)]
-}
rat_prime_factors_m :: Integral i => (i,i) -> [(i,Int)]
rat_prime_factors_m (n,d) = rat_pf_merge (prime_factors_m n) (prime_factors_m d)
-- | 'Ratio' variant of 'rat_prime_factors_m'
rational_prime_factors_m :: Integral i => Ratio i -> [(i,Int)]
rational_prime_factors_m = rat_prime_factors_m . Math.rational_nd
-- | Variant of 'rat_prime_factors_m' giving results in a list.
--
-- > rat_prime_factors_l (1,1) == []
-- > rat_prime_factors_l (2^5,9) == [5,-2]
-- > rat_prime_factors_l (2*2*3,7) == [2,1,0,-1]
-- > rat_prime_factors_l (3*3,11*13) == [0,2,0,0,-1,-1]
rat_prime_factors_l :: Integral i => (i,i) -> [Int]
rat_prime_factors_l x =
case rat_prime_factors_m x of
[] -> []
r -> let lm = maximum (map fst r)
in map (\i -> fromMaybe 0 (lookup i r)) (List.take_until (== lm) primes_list)
-- | 'Ratio' variant of 'rat_prime_factors_l'
--
-- > map rational_prime_factors_l [1/31,256/243] == [[0,0,0,0,0,0,0,0,0,0,-1],[8,-5]]
rational_prime_factors_l :: Integral i => Ratio i -> [Int]
rational_prime_factors_l = rat_prime_factors_l . Math.rational_nd
-- | Variant of 'rational_prime_factors_l' padding table to /k/ places.
-- It is an error for /k/ to indicate a prime less than the limit of /x/.
--
-- > map (rat_prime_factors_t 6) [(5,13),(12,7)] == [[0,0,1,0,0,-1],[2,1,0,-1,0,0]]
-- > rat_prime_factors_t 3 (9,7) == undefined
rat_prime_factors_t :: (Integral i,Show i) => Int -> (i,i) -> [Int]
rat_prime_factors_t k = List.pad_right_err 0 k . rat_prime_factors_l
-- | 'Ratio' variant of 'rat_prime_factors_t'
rational_prime_factors_t :: (Integral i,Show i) => Int -> Ratio i -> [Int]
rational_prime_factors_t n = rat_prime_factors_t n . Math.rational_nd
-- | Condense factors list to include only indicated places.
-- It is an error if a deleted factor has a non-zero entry in the table.
--
-- > rat_prime_factors_l (12,7) == [2,1,0,-1]
-- > rat_prime_factors_c [2,3,5,7] (12,7) == [2,1,0,-1]
-- > rat_prime_factors_c [2,3,7] (12,7) == [2,1,-1]
rat_prime_factors_c :: (Integral i,Show i) => [i] -> (i,i) -> [Int]
rat_prime_factors_c fc r =
let t = rat_prime_factors_l r
k = map prime_k_err fc
f (ix,e) = if ix `notElem` k
then (if e > 0 then error "rat_prime_factors_c: non-empty factor" else Nothing)
else Just e
in mapMaybe f (zip [0..] t)
-- | 'Ratio' variant of 'rat_prime_factors_t'
--
-- > map (rational_prime_factors_c [3,5,31]) [3,5,31]
rational_prime_factors_c :: (Integral i,Show i) => [i] -> Ratio i -> [Int]
rational_prime_factors_c fc = rat_prime_factors_c fc . Math.rational_nd
-- | Pretty printer for prime factors. sup=superscript ol=overline
prime_factors_pp :: [Integer] -> String
prime_factors_pp = intercalate [Unicode.middle_dot] . map show
{- | Pretty printer for prime factors. sup=superscript ol=overline
> prime_factors_pp_sup_ol True [2,2,-3,5] == "2²·3̅·5"
> prime_factors_pp_sup_ol False [-2,-2,-2,3,3,5,5,5,5] == "-2³·3²·5⁴"
-}
prime_factors_pp_sup_ol :: Bool -> [Integer] -> String
prime_factors_pp_sup_ol ol =
let mk x = if x < 0 && ol then Unicode.overline (show (- x)) else show x
f x = let x0 = head x
n = length x
in if n == 1 then mk x0 else mk x0 ++ Unicode.int_show_superscript n
in intercalate [Unicode.middle_dot] . map f . group