[comp.lang.prolog] Deriving isprime/1

arun@bach.ces.cwru.edu (Arun Lakhotia) (12/21/88)

I just derived a program segment that checks if a number is prime from
some pieces code posted by Thomas Sj|land. The code was posted in
reference to finding perfect numbers, where a perfect number can be
stated in terms of (merseme) prime numbers.

The final program (Program 4) was derived in three steps from Program
1. The latter program (except for isprime/1) has been extracted from
Thomas Sj|land's code. The motivation for the transformation was to
improve efficiency. The actual transformation steps applied were
guided by intuition.

%%
%% program: 1
%%

isprime(X) :- divisors(X, [1]).

% divisors(I, D) -- D is a list of divisors of I (excluding I itself).

divisors(I, D) :- J is I//2+1, upto(J, L), divisors(I, L, D).

divisors(_I, [], []).
divisors(I, [H|T], L) :- 
	(divides(I, H) -> L=[H|R] ; L=R), 
	divisors(I, T, R).

% upto(N, L) -- L is a list of integers from 1..N-1

upto(N, L) :- upto(N, 1, L).

upto(N, N, []).
upto(N, I, [I|T]) :- I<N, I1 is I+1 , upto(N, I1, T).

divides(I, D) :- I is I//D*D.

%%
%% end of program 1
%%

DISCUSSION

Program 1 first constructs a list of 1..I//2 integers and then extracts
from this list the numbers that are divisors of I. The process of
creating the list of 1..I//2 is extraneous and may be gotten rid of.
This is done by first unfolding `upto(J, L)',  introducing a definition
clause 

     divisors(M, N, I, L, D) :- upto(N, I, L), divisors(I, L, D).

and creating a divisors/5 using Tamaki-Sato's fold/unfold procedure.
Next `recognizing' that the fourth argument 'L' is of no use and
mapping all predicates divisors/5 to divisors/4 as follows

     definition(M, N, I, L, D) --> definition(M, N, I, D).

This could have been done in the previous step itself, but then the
variable 'L' would have become local and that sometimes creates
problems when folding.

%%
%% program: 2
%%
isprime(X) :- divisors(X, [1]).

divisors(M, D) :- J is M//2+1, divisors(M, J, 1, D).

divisors(M, N, N, []).
divisors(M, N, I, L) :- 
	I < N, 
	I1 is I+1, 
      (divides(M, I) -> L=[I|R] ; L=R), divisors(M, N, I1, R).

divides(I, D) :- I is I//D*D.

%%
%% end of program 2
%%

DISCUSSION

Program 2 checks if a number is prime by first getting the list of all
divisors of the number, and then checking if that list contains just a
single element '1'. Clearly the process of finding all the divisors, 
is a waste as all we need to make sure is their is no other divisor
but 1.

The following program is modified to check the value of the divisor
immediately after it is found. It better be 1 or else isprime_x/3
fails. Also realizing that we do not have any need for the list of
divisors we simply get rid of that argument.

I do not know how to explain this step in terms of known
transformation. Would appreciate input towards that.

%%
%% program 3
%%

isprime(M) :-  J is M//2+1, isprime_x(M, J, 1).

isprime_x(M, N, N).
isprime_x(M, N, I) :- 
	I < N, 
	I1 is I+1, 
      (divides(M, I) ->I=1; true), isprime_x(M, N, I1).

divides(I, D) :- I is I//D*D.

%%
%% end of program 3
%%

DISCUSSION

The above program checks for prime-ness of a number N by testing if
any of the numbers from 1..N//2+1 is its divisor. It does so even if
the number is even. This is clearly redundant and can be avoided by
checking early enough if the given number is even (other than 2).

Also checking whether an even number is a divisor for an odd number is
extraneous. This may be avoided by incrementing the sequence in steps
of 2 instead of 1.

The following program uses these observations.

%%
%% program 4
%%

isprime(2).
isprime(M) :- 
    \+ divides(M, 2), 
    J is M//2+1, 
    isprime_x(M, J, 1).

isprime_x(M, N, I) :-
        N - I =< 1.
isprime_x(M, N, I) :- 
	I =< N, 
        (divides(M, I) -> I = 1 ; true), 
	I1 is I+2, 
        isprime_x(M, N, I1).

divides(I, D) :- I is I//D*D.

%%
%% end of program 4
%%