As a warmup to obtaining full factorizations, let's look at a
straightforward primality test. We'll assume that the argument to the
predicate prime?
is an integer nnot less than 2. If
n is even, it's prime if and only if it is 2; otherwise, we can try
out odd divisors until we either (a) exceed the square root of n or
(b) obtain a remainder of zero:
Now for the full problem: Given a positive integer, construct a (monotonically nondecreasing) list of its prime factors. It will be convenient to work through the potential divisors in ascending order, just as in(define prime? (lambda (n) (if (even? n) (= n 2) (let loop ((trial-divisor 3)) (or (< n (* trial-divisor trial-divisor)) (and (not (zero? (remainder n trial-divisor))) (loop (+ trial-divisor 2))))))))
prime?
, except that now we need to keep any divisors
that we find on a list (and simultaneously to divide them out of the number
we're factoring). Just as in prime?
, it's useful to handle 2
as a special case -- essentially, pre-processing the number by dividing out
all the twos before entering the main loop.
Both the elimination of the twos and the main loop are complicated enough
to be separated off as locally defined procedures, here called
extract-twos
and extract-odd-factors
; the
factor
procedure itself simply calls
extract-twos
, checks to see whether the residual quotient is 1
(in which case the factorization is complete), and, if not, calls
extract-odd-factors
to finish the job.
The(define factor (let ((extract-twos (lambda (n) (let loop ((two-list '()) (rest n)) (if (even? rest) (loop (cons 2 two-list) (quotient rest 2)) (cons rest two-list))))) (extract-odd-factors (lambda (partial-factorization) (let loop ((so-far (cdr partial-factorization)) (odd-product (car partial-factorization)) (trial-divisor 3)) (cond ((< odd-product (* trial-divisor trial-divisor)) (reverse (cons odd-product so-far))) ((zero? (remainder odd-product trial-divisor)) (loop (cons trial-divisor so-far) (quotient odd-product trial-divisor) trial-divisor)) (else (loop so-far odd-product (+ trial-divisor 2)))))))) (lambda (n) (let ((partial-factorization (extract-twos n))) (if (= (car partial-factorization) 1) (cdr partial-factorization) (extract-odd-factors partial-factorization))))))
extract-twos
procedure has to return, in effect, two
results -- the residual quotient (the odd number that is left when all of
the twos have been divided out of the original integer) and the list of
factors so far accumulated (all equal to 2, of course). The conventional
way for a procedure to return two values is to return a pair with one value
as its car and the other as its cdr, and that is the approach used here.
The resulting pair can be thought of as a ``partial factorization'' -- for
instance, (extract-twos 180)
returns (45 . (2 2)),
which can also be written as (45 2 2).
The extract-odd-factors
procedure is a simple adaptation of
the main loop of prime?
, accumulating any divisors that it
finds and continuing until all possible divisors have been identified.
Note that the value of trial-divisor
cannot increase unless
the procedure has established that it does not divide the value of
odd-product
, since it might be necessary to divide out the
same odd prime more than once.
The two sub-procedures should be local to factor
, but it is
preferable to construct them only once, when factor
is
defined, rather than every time factor
is called. Placing
them in a let
-expression that encloses the
lambda
-expression achieves this goal.
This document is available on the World Wide Web as
http://www.math.grin.edu/~stone/events/scheme-workshop/factoring.html