############################################################################
#
#	File:     factors.icn
#
#	Subject:  Procedures related to factors and prime numbers
#
#	Authors:  Ralph E. Griswold and Gregg M. Townsend
#
#	Date:     January 23, 2002
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  This file contains procedures related to factorization and prime
#  numbers.
#
#	divisors(n)	generates the divisors of n.
#
#	divisorl(n)	returns a list of the divisors of n.
#
#	factorial(n)	returns n!.  It fails if n is less than 0.
#
#	factors(i, j)	returns a list containing the prime factors of i
#			limited to maximum value j; default, no limit.
#
#	genfactors(i, j)
#			like factors(), except factors are generated as
#			they are found.
#
#	gfactorial(n, i)
#			generalized factorial; n x (n - i) x (n - 2i) x ...
#
#	ispower(i, j)	succeeds and returns root if i is k^j
#
#	isprime(n)	succeeds if n is a prime.
#
#	nxtprime(n)	returns the next prime number beyond n.
#
#	pfactors(i)	returns a list containing the primes that divide i.
#
#	prdecomp(i)	returns a list of exponents for the prime
#			decomposition of i.
#
#	prime()		generates the primes.
#
#	primel()	generates the primes from a precompiled list.
#
#	primorial(i,j)	product of primes j <= i; j defaults to 1.
#
#	sfactors(i, j)	as factors(i, j), except output is in string form
#			with exponents for repeated factors
#
#	squarefree(i)	succeeds if the factors of i are distinct
#
############################################################################
#
#  Notes:  Some of these procedures are not fast enough for extensive work.
#  Factoring is believed to be a hard problem. factors() should only be
#  used for small numbers.
#
############################################################################
#
#  Requires:  Large-integer arithmetic; prime.lst for primel() and primorial().
#
############################################################################
#
#  Links:  io, numbers
#
############################################################################

link io
link numbers

procedure divisors(n)			#: generate the divisors of n
   local d, dlist

   dlist := []
   every d := seq() do {
      if d * d >= n then
         break
      if n % d = 0 then {
         push(dlist, d)
         suspend d
         }
      }
   if d * d = n then
      suspend d
   suspend n / !dlist

end

procedure divisorl(n)			#: return list of divisors of n
   local divs
   every put(divs := [], divisors(n))
   return divs
end

procedure factorial(n)			#: return n! (n factorial)
   local i

   n := integer(n) | runerr(101, n)

   if n < 0 then fail

   i := 1

   every i *:= 1 to n

   return i

end

procedure factors(i, j)			#: return list of factors
   local facts

   every put(facts := [], genfactors(i, j))
   return facts

end

procedure genfactors(i, j)		#: generate prime factors of integer
   local p

   i := integer(i) | runerr(101, i)
   /j := i

   every p := prime() do {
      if p > j | p * p > i then break
      while i % p = 0 do {
         suspend p
         i /:= p
         }
      if i = 1 then break
      }
   if i > 1 then suspend i

end

procedure gfactorial(n, i)		#: generalized factorial
   local j

   n := integer(n) | runerr(101, n)
   i := integer(i) | 1

   if n < 0 then fail
   if i < 1 then fail

   j := n

   while n > i do {
      n -:= i
      j *:= n
      } 

   return j

end

procedure pfactors(i)			#: primes that divide integer
   local facts, p

   i := integer(i) | runerr(101, i)
   facts := []
   every p := prime() do {
      if p > i then break
      if i % p = 0 then {
         put(facts, p)
         while i % p = 0 do
            i /:= p
         }
      }

   return facts

end

procedure ispower(i, j)			#: test for integer power
   local k, n

   k := (n := round(i ^ (1.0 / j))) ^ j
   if k = i then return n else fail

end

#  NOTE:  The following method for testing primality, called Baby Division,
#  is about the worst possible.  It is inappropriate for all but small
#  numbers.

procedure isprime(n)			#: test for primality
   local p

   n := integer(n) | runerr(101, n)
   if n <= 1 then fail		# 1 is not a prime
   every p := prime() do {
      if p * p > n then return n
      if n % p = 0 then fail
      }

end

procedure nxtprime(n)			#: next prime beyond n
   local d
   static step, div

   initial {
      step := [1,6,5,4,3,2,1,4,3,2,1,2,1,4,3,2,1,2,1,4,3,2,1,6,5,4,3,2,1,2]
      div := [7]			# list of known primes
      }

   n := integer(n) | runerr(101, n)
   if n < 7 then			# handle small primes specially
      return n < (2 | 3 | 5 | 7)

   repeat {
      n +:= step[n % 30 + 1]		# step past multiples of 2, 3, 5
      every (d := !div) | |put(div, d := nxtprime(d)) do {  # get test divisors
         if n % d = 0 then		# if composite, try a larger candidate
            break
         if d * d > n then		# if not divisible up to sqrt, is prime
            return n
         }
      }

end

procedure prdecomp(i)			#: prime decomposition
   local decomp, count, p

   decomp := []
   every p := prime() do {
      count := 0
      while i % p = 0 do {
         count +:= 1
         i /:= p
         }
      put(decomp, count)
      if i = 1 then break
      }

   return decomp

end

procedure prime()			#: generate primes
   local i, k

   suspend 2 | ((i := seq(3, 2)) & (not(i = (k := (3 to sqrt(i) by 2)) *
      (i / k))) & i)

end

procedure primel()			#: primes from list
   local pfile

   pfile := dopen("primes.lst") | stop("*** cannot open primes.lst")

   suspend !pfile

end

procedure primorial(i, j)		#: product of primes
   local m, k, mark

   /j := 1

   m := 1
   mark := &null			# to check for completeness

   every k := primel() do {		# limited by prime list
      if k <= j then next
      if k <= i then m *:= k
      else {
         mark := 1
         break
         }
      }

   if \mark then return m else fail	# fail if list is too short

end

procedure sfactors(i, j)		#: return factors in string form
   local facts, result, term, nterm, count

   facts := factors(i, j)

   result := ""

   term := get(facts)			# will be at least one
   count := 1
   
   while nterm := get(facts) do {
      if term = nterm then count +:= 1
      else {
         if count > 1 then result ||:= " " || term || "^" || count
         else result ||:= " " || term
         count := 1
         term := nterm
         }
      }

   if count > 1 then result ||:= " " || term || "^" || count
   else result ||:= " " || term

   return result[2:0]

end

procedure squarefree(n)			#: test for square-free number
   local facts

   facts := factors(n)

   if *facts = *set(facts) then return n else fail

end
