As part of some Eulerian travails, I'm trying to code a Sieve of Eratosthenes with a factorization wheel. My code so far is:
(defun ring (&rest content)
"Returns a circular list containing the elements in content.
The returned list starts with the first element of content."
(setf (cdr (last content)) content))
(defun factorization-wheel (lst)
"Returns a circular list containing a factorization
wheel using the list of prime numbers in lst"
(let ((circumference (apply #'* lst)))
(loop for i from 1 to circumference
unless (some #'(lambda (x) (zerop (mod i x))) lst)
collect i into wheel
finally (return (apply #'ring
(maplist
#'(lambda (x) ; Takes exception to long lists (?!)
(if (cdr x)
(- (cadr x) (car x))
(- circumference (car x) -1)))
wheel))))))
(defun eratosthenes (n &optional (wheel (ring 4 2)))
"Returns primes up to n calculated using
a Sieve of Eratosthenes and a factorization wheel"
(let* ((candidates (loop with s = 1
for i in wheel
collect (setf s (+ i s))
until (> s n))))
(maplist #'(lambda (x)
(if (> (expt (car x) 2) n)
(return-from eratosthenes candidates))
(delete-if
#'(lambda (y) (zerop (mod y (car x))))
(cdr x)))
candidates)))
I got the following result for wheels longer than 6 elements. I didn't really understand why:
21 > (factorization-wheel '(2 3 5 7 11 13))
(16 2 4 6 2 6 4 2 4 6 6 2 6 4 2 6 4 6 8 4 ...)
21 > (factorization-wheel '(2 3 5 7 11 13 17))
> Error: Too many arguments.
> While executing: FACTORIZATION-WHEEL, in process listener(1).
The algorithm seems to be working OK otherwise and churns out primes with wheels having 6 or fewer elements.
Apparently apply
or ring
turn up their noses when long lists are passed to them.
But shouldn't the list count as a single argument? I admit I'm thoroughly flummoxed. Any input is appreciated.
ANSI Common Lisp allows implementations to constrain the maximum number of arguments which can be passed to a function. This limit is given by call-arguments-limit can be as small as 50.
For functions which behave like algebraic group operators obeying the
associative property (+
, list
, and others), we can get around the limit by using reduce
to decimate the input list while treating the function as binary.
For instance to add a large list of numbers: (reduce #'+ list)
rather than (apply #'+ list)
.
reduce
In Common Lisp, reduce
will appear to work even if the list is empty. Few other languages give you this, and it doesn't actually come from reduce
: it won't work for all functions. But with +
we can write (reduce #'+ nil)
and it calculates zero, just like (apply #'+ nil)
.
Why is that? Because the +
function can be called with zero arguments, and when called with zero arguments, it yields the identity element for the additive group: 0
. This dovetails with the reduce
function.
In some other languages the fold
or reduce
function must be given an initial seed value (like 0
), or else a nonempty list. If it is given neither, it is an error.
The Common Lisp reduce
, if it is given an empty list and no :initial-value
, will call the kernel function with no arguments, and use the return value as the initial value. Since that value is then the only value (the list is empty), that value is returned.
Watch out for functions with special rules for the leftmost argument. For instance:
(apply #'- '(1)) -> -1 ;; same as (- 1), unary minus semantics.
(reduce #'- '(1)) -> 1 ;; what?
What's going on is that when reduce
is given a one-element list, it just returns the element without calling the function.
Basically it is founded on the mathematical assumption mentioned above that if no :initial-value
is supplied then f
is expected to support (f) -> i
, where i
is some identity element in relation to f
, so that (f i x) -> x
. This is used as the initial value when reducing the singleton list, (reduce #'f (list x)) -> (f (f) x) -> (f i x) -> x
.
The -
function doesn't obey these rules. (- a 0)
means "subtract zero from a
" and so yields a
, whereas (- a)
is the additive inverse of a
, probably for purely pragmatic, notational reasons (namely, not making Lisp programmers write (- 0 a)
just to flip a sign, just for the sake of having -
behave more consistently under reduce
and apply
). The -
function also may not be called with zero arguments.
If we want to take a list of numbers and subtract them all from some value x
, the pattern for that is:
(reduce #'- list-of-numbers :initial-value x)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With