Well, your question presents an interesting case of under-specification that can be advantageous, delaying the actual specification -- not just implementation as usual -- of subroutines used by your piece of code.
Here we have sievehelper
which uses the non-implemented non-specified filterfunc
and not-divisible-by
. Despite their suggestive names these function can do anything, as long as they work, when used together, and make the function using them, sievehelper
, also do its work as intended. Delayed specification implementation for the win!
(meaning, keep specifications as general as possible for as long as you can, and use discovered requirements while making the more and more specific -- and possibly competing -- implementations).
So let's first see what can be intended with the sievehelper
as given. What does it do? Assuming the obvious meaning of the two subroutines, it seems to be intended to perform a one-step filtering of its working sequence, culling it from any multiples of its "head" element, the one in its car
position.
It would signal the stopping condition by returning ()
. That stopping condition is a*a > z
, for the input list of [a,b,c,...,z]
.
Otherwise, it does not perform the looping, but just the one step of it. Your sieve
doesn't account for that at all, so will need to be changed to continually call this helper, performing step after step as is usual in sieving algorithms, until the square of the first element is bigger than the last element in the working sequence, when it is indeed safe to stop the culling as all the multiples in the sequence will have already been removed from it as multiples of the smaller primes ...... provided that those smaller primes were present in the initial sequence.
So this discovered requirement falls on the third non-implemented subrouting in use, makelist
. You do mention that it must create the list of sequential natural numbers from 2
to n
, and now we understand why we needed it to do so.
So then, in order to iterate through the different versions of the input list as each divisor is filtered out from it in turn, using your sievehelper
definition as given, your sieve
function must be changed as e.g.
(define sieve
(lambda (n)
(let ((init (makelist n)))
(let loop ((this init )
(next (sievehelper init)))
(if (null? next)
this
(cons (car this)
(loop next
(sievehelper next))))))))
(The above code incorporates the fix by ad-absurdum from the followup Q&A entry, to the error that this code originally contained).
This code comes from the perspective of working with a pair -- the current sequence, and its next iteration. On each step the next version of the list is found after all the multiples of its head element are removed from it by sievehelper
(including the head element itself, the next found prime) -- or the empty list is instead returned to signal the end of processing when all the numbers in the list are known to already be prime, by construction.
Trying it out in Racket:
> (sieve 50)
'(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47)
To run it, further definitions had to be made:
(define filterfunc filter)
(define (not-divisible-by n) ; NB! this critically depends on
(let ((m n)) ; filterfunc working over its
(lambda (x) ; argument list _in order_ NB!
(let ((ret (not (= x m))))
(if (>= x m) (set! m (+ m n)) #f)
ret))))
(define (makelist n)
(range 2 (+ 1 n))) ; in Racket
Defining the not-divisible-by
as we did here, enumerating the multiples of each prime internally by iterated additions, makes it be the sieve of Eratosthenes indeed. And stopping early (at the square root of the limit), as in your original code, keeps its time complexity on the sane side, closer to optimal.