I am trying to create a function prime-factors
that returns the prime factors of a number. To do so, I created is-prime
function, and prime-factors-helper
that will do a recursive check of the prime factors.
(defun is-prime (n &optional (d (- n 1)))
(if (/= n 1) (or (= d 1)
(and (/= (rem n d) 0)
(is-prime n (- d 1)))) ()))
(defun prime-factors-helper (x n)
(if (is-prime x)
(list x)
(if (is-prime n)
(if (AND (= (mod x n) 0) (<= n (/ x 2)))
(cons n (prime-factors-helper (/ x n) n))
(prime-factors-helper x (+ 1 n)))
(prime-factors-helper x (+ 1 n)))))
(defun prime-factors (x)
(prime-factors-helper x 2))
Question
I have a problem of optimisation. When I have a big number such as 123456789
, I get this error message Stack overflow (stack size 261120)
. I believe because since the correct answer is (3 3 3607 3803)
, my program once it constructs the list with the two first elements (3 3)
, it will take so long to find the next prime factor. How can I optimise my code?
CL-USER 53 > (prime-factors 512)
(2 2 2 2 2 2 2 2 2)
CL-USER 54 > (prime-factors 123456789)
Stack overflow (stack size 261120).
1 (abort) Return to level 0.
2 Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options
copied from https://codereview.stackexchange.com/a/189932/20936:
There are several problems with your code.
Style
is-prime
is C/Java style. Lispers useprimep
orprime-number-p
.zerop
is clearer than(= 0 ...)
.Stack overflow
is-prime
is tail-recursive, so if you compile it, it should become a simple loop and there should be no stack issues.However, do not rush with it yet.
Algorithm
Number of iterations
Let us use
trace
to see the problems:You do 17 iterations when only
(isqrt 17) = 4
iterations are necessary.Recalculations
Now compile
is-prime
to turn recursion into a loop and see:You are checking the primality of the same numbers several times!
Extra optimization
primep
can find a divisor, not just check primality.Optimized algorithm
Note that one final optimization is possible - memoization of
compositep
:or, better yet, of
prime-decomposition
:note the use or
append
instead ofnconc
.Another interesting optimization is changing the iteration in
compositep
from descending to ascending. This should speedup it up considerably as it would terminate early more often: