The PRIME-FACTORS procedure terminates

Course links

The following procedure takes any positive integer as argument and returns a list of its prime factors.

;;; prime-factors: construct and return a list of the
;;; prime factors of a given positive integer

;;; Given:
;;;   NUMBER, an exact integer.

;;; Result:
;;;   FACTORS, a list of exact integers.

;;; Precondition:
;;;   NUMBER is positive.

;;; Postconditions:
;;;   (1) Every element of FACTORS is a prime natural number.
;;;   (2) The product of the elements of FACTORS is NUMBER.

(define prime-factors
  (lambda (number)
    (if (not (and (integer? number)
                  (exact? number)
                  (positive? number)))
        (error "prime-factors: requires an exact positive integer")
        (prime-factors-kernel number 2))))

;;; prime-factors-kernel: construct and return a list of the
;;; prime factors of a given positive integer that are greater
;;; than or equal to another given positive integer

;;; Given:
;;;   NUMBER and DIVISOR, both exact integers.

;;; Result:
;;;   FACTORS, a list of exact integers.

;;; Preconditions:
;;;   (1) NUMBER is positive.
;;;   (2) DIVISOR is greater than or equal to 2.
;;;   (3) NUMBER is not divisible by any positive integer
;;;       greater than or equal to 2 but less than DIVISOR.

;;; Postconditions:
;;;   (1) Every element of FACTORS is a prime natural number
;;;       greater than or equal to DIVISOR.
;;;   (2) The product of the elements of FACTORS is NUMBER.

(define prime-factors-kernel
  (lambda (number divisor)
    (cond ((= number 1) '())
          ((zero? (remainder number divisor))
           (cons divisor
                 (prime-factors-kernel (quotient number divisor)
                                       divisor)))
          (else (prime-factors-kernel number (+ divisor 1))))))

We want to prove that any invocation of prime-factors eventually terminates. Since the body of the definition of prime-factors consists entirely of a call to the prime-factors-kernel procedure with arguments that can be evaluated immediately, the invocation of prime-factors terminates if, and only if, this initial invocation of prime-factors-kernel terminates.

Step 1: If the value of number is 1, the invocation of prime-factors-kernel terminates.

Suppose that the value of number is 1. Then the only steps in the evaluation of the body of prime-factors-kernel are the evaluation of (= number 1) and of '(). Since the arguments in (= number 1) can be evaluated immediately, and the = procedure always terminates, and '() can be evaluated immediately, the invocation of prime-factors-kernel terminates in this case. No recursive calls are needed.

Step 2: If the values of number and divisor are equal (and greater than 1), the invocation of prime-factors-kernel terminates.

Suppose that the values of number and divisor are equal (and greater than 1). Then the only steps in the evaluation of the body of prime-factors-kernel are the evaluation of (= number 1), of (zero? (remainder number divisor)), and of (cons divisor (prime-factors-kernel (quotient number divisor) divisor)). Number and divisor can be evaluated immediately, and the remainder, zero?, and quotient procedures always terminate (without error, since divisor is known to be non-zero), so the only possible problem is the evaluation of the recursive procedure call. However, since number and divisor are equal, (quotient number divisor) is 1, so in the recursive procedure call the first argument is 1. Hence, by step 1, it will terminate. Hence the original invocation of prime-factors-kernel terminates in this case as well.

Step 3: In every invocation of prime-factors-kernel, either the value of number is 1, or the values of number and divisor are equal, or the value of number is greater than the value of divisor; and in addition no integer greater than 1 and less than the value of divisor evenly divides number.

First, consider the original call to prime-factors-kernel -- the one in the body of the definition of prime-factors. The first argument must be either less than, equal to, or greater than the second. But the second argument is 2; so the first argument is either less than 2, or greater than or equal to the second argument. But the first argument is, by precondition, a positive integer, and the only positive integer less than 2 is 1. So the first argument is either 1, or else equal to or greater than the second argument.

Moreover, there are no integers greater than 1 and less than 2, so obviously no such integer divides the first argument.

Next, suppose that we have started an evaluation of the body of prime-factors-kernel with the knowledge that either the value of number is 1, or the values of number and divisor are equal, or the value of number is greater than the value of divisor; and in addition no integer greater than 1 and less than the value of divisor evenly divides number.

Consider, then, the recursive call to prime-factors-kernel in the second cond-clause in the definition of that procedure. If we reach that call, the value of number cannot be 1, since then we would have selected the action in the first cond-clause. So the value of number is greater than or equal to the value of divisor. If the two values are equal, then in the recursive call the first argument, the value of (quotient number divisor), is 1. On the other hand, if the value of number is greater than the value of divisor, then the value of (quotient number divisor) is greater than the value of divisor, since otherwise the value of (quotient number divisor) would be an integer greater than 1 and less than divisor that evenly divides the value of number, contrary to hypothesis. So in the recursive invocation either the first argument is 1 or it is equal to or greater than the second argument.

Moreover, any exact divisor of the first argument (the value of (quotient number divisor), remember) is also a divisor of number. Since no integer greater than 1 and less than divisor evenly divides the value of number, no such integer evenly divides the first argument in the recursive invocation of prime-factors-kernel either.

Next, consider the recursive invocation of prime-factors-kernel in the third cond-clause. If we reach that call, we know that the value of number is strictly greater than the value of divisor, since if the value of number were 1, we would have selected the action in the first cond-clause, and if the values of number and divisor were equal, we would have selected the action in the second cond-clause. In the recursive application, the second argument is one greater than the value of divisor; but this is still less than or equal to the first argument, since the value of number is strictly greater than the value of divisor.

Also, in the recursive application, no integer greater than 1 and less than divisor evenly divides the value of number, by hypothesis; and the value of divisor itself does not evenly divide the value of number (since if it did, we would have selected the action in the second cond-clause and never reached the third one). So no integer greater than 1 and less than the value of (+ divisor 1) evenly divides the value of number. In other words, in the recursive invocation, no number greater than 1 and less than the second argument evenly divides the first argument.

Now, since the original invocation of prime-factors-kernel meets the complicated condition stated in the header for this step, and since, as we have proven, any recursive invocation meets the same condition if undertaken as part of an invocation that itself meets that condition, every invocation of prime-factors-kernel meets that condition.

Step 4: Any invocation of prime-factors-kernel that meets the condition stated in step 3 terminates.

Let d be the result of subtracting the value of divisor from the value of number. We know from step 3 that either the value of number is 1, in which case the invocation terminates as shown in step 1, or d is zero, in which case the invocation terminates as shown in step 2, or d is positive.

Suppose, then, that any invocation in which d is less than or equal to some natural number k terminates, and consider a case in which d is k + 1. Now, either the value of divisor evenly divides the value of number or it does not.

If it does, then the only steps in the evaluation of the body of the definition of prime-factors-kernel are the evaluation of (= number 1), of (zero? (remainder number divisor)), and of (cons divisor (prime-factors-kernel (quotient number divisor) divisor)). As in step 2, all of these expressions obviously terminate except the recursive call. But in that recursive call the result of subtracting the second argument from the first is less than d, that is, it is k or less. So, by hypothesis, this recursive call also terminates.

On the other hand, if divisor does not evenly divide number, then the steps in the evaluation of the body of the definition of prime-factors-kernel are the evaluation of (= number 1), of (zero? (remainder number divisor)), and of (prime-factors-kernel number (+ divisor 1)). Again, as in step 2, only the recursive call is a problem. But in the recursive call the difference between the first and second arguments is one less than d -- that is, it is k. So, again by hypothesis, the recursive call also terminates.

Thus we have shown that the proposition holds whenever d is negative or zero and also for any positive integer such that it holds for all lesser natural numbers. But this means that it holds for any integer value d, by mathematical induction. So every invocation of prime-factors-kernel that meets the condition stated in step 3 terminates.

Step 5: Every invocation of prime-factors-kernel terminates.

Since every invocation of prime-factors-kernel that meets the condition stated in step 3 terminates, and since every invocation of prime-factors-kernel meets that condition, every invocation of prime-factors-kernel terminates.