3

I've been searching the web for an implementation of the Sieve of Eratosthenes in scheme and although I came up with a lot of content, none of them seemed to have made it like I need it to be done.

The problem is most algorithms either use a static end or use iteration. This paired with my lack of knowledge of the language led me to ask all of you for help.

I need an implementation of the Sieve that takes in one argument (number to Sieve until), uses only recursion and has a list of "cons" of a number with a #t (true) or #f (false).

So essentially the algorithm would go as such:

  1. Make list from 2 - inputed number with each number starting as true
  2. Recursively go through and mark each number that is divisible by 2 false
  3. Then go on to the next "true" number in the list until only primes are left marked true
  4. Output the list

Example output:

> (erat-sieve 20)

((2 . #t) (3 . #t) (4 . #f) (5 . #t) (6 . #f) (7 . #t) (8 . #f) (9 . #f) (10 . #f) (11 . #t) (12 . #f) (13 . #t) (14 . #f) (15 . #f) (16 . #f) (17 . #t) (18 . #f) (19 . #t) (20 . #f))

If you could also have comments thoroughly explaining the code, that would be extremely appreciated.

Thank you!

REVISED::: So I've learned a bit of scheme to further explain my question...

This makes the list.

(define (makeList n)
 (if (> n 2)
  (append (makeList (- n 1)) (list (cons n (and))))
  (list (cons 2 (and)))))

This returns a list with each multiple of the divisor marked false.

(define (mark-off-multiples numbers divisor)
 (if (null? numbers)
  '()
  (append 
     (list (cons (car (car numbers)) 
                 (not (zero? (modulo (car (car numbers)) divisor))))) 
     (mark-off-multiples (cdr numbers) divisor))))

Now this is the function I'm having trouble with, it seems like it should work, I've gone through it manually three times, but I can't figure out why its not returning what I need.

(define (call-mark-off-multiples-for-each-true-number numbers)
 (if (null? numbers)
  '()
  (if (cdr (car numbers))
    (append (list (car numbers))
            (call-mark-off-multiples-for-each-true-number 
               (mark-off-multiples (cdr numbers) (car (car numbers)))))
    (append (list (car numbers))
            (call-mark-off-multiples-for-each-true-number 
               (cdr numbers))))))

What I'm trying to make it do is, as the function name suggests, call mark-off-multiples for each number that is still marked true down the list. So you pass in ((3.#t)(4.#t)(5.#t)) and then it calls mark-off-multiples for 2 and returns (3.#t)(4.#f)(5.#t) and you append (2.#t) to it. Then it calls itself again passing in (3.#t)(4.#f)(5.#t) and calls mark-off-multiples with the cdr of the list returning (4.#f)(5.#t) and keeps going down the list...

The output I then get returned is a list with all trues.

This, hopefully with help you better understand my predicament.

Will Ness
  • 62,652
  • 8
  • 86
  • 167
WiseOlMan
  • 866
  • 2
  • 10
  • 24

4 Answers4

2

Here is a solution that works.

(define (divides? m n)
  (if (eq? (modulo n m) 0)
      #t
      #f))

(define (mark-true n)
  (cons n #t))

(define (mark-divisors n ns)
  (cond ((null? ns) '())
        ((and (unmarked? (car ns)) 
              (divides? n (car ns))) 
           (cons (cons (car ns) #f) (mark-divisors n (cdr ns))))
        (else (cons (car ns) (mark-divisors n (cdr ns))))))

(define (unmarked? n)
  (not (pair? n)))

(define (eratosthenes x)
  (cond ((null? x) '())
        ((unmarked? (car x)) 
           (cons (mark-true (car x)) 
                 (eratosthenes (mark-divisors (car x) (cdr x)))))
        (else (cons (car x) (eratosthenes (cdr x))))))

(eratosthenes (list 2 3 4 5 6))

I've used a number of helper functions, but you could add them to the eratosthenes function if you wanted. I think it makes this whole business more readable.

mark-true conses a value onto a #t. mark-divisors takes a number n and a list of numbers and conses all of the numbers that n divides onto a #f. Pretty much everything else is self explanatory. Eratosthenes works as it should, if the first digit is "unmarked" it marks it as "true" or "prime" and then "crosses out" all of its multiples from the remainder of the list and then repeats for each subsequent "unmarked" digit in the list. My eratosthenes function does essentially what you were trying to do with yours. I'm not sure what the problem with yours is, but as a rule, it's helpful to make helpers to make your stuff more readable.

I did this in DrRacket with Neil Van Dyke's SICP package. I don't know what Scheme you're using. Let me know if you have problems getting this to work.

Will Ness
  • 62,652
  • 8
  • 86
  • 167
hraesvelgr
  • 3,961
  • 2
  • 32
  • 58
  • 2
    While this approach works, it isn't a Sieve of Eratosthenes. The point of the sieve algorithm is to avoid doing a costly primality test by successively sieving out the multiples of known primes. All you need is repeated addition. –  Mar 30 '12 at 06:41
  • You're right, the function is misleadingly named. However, the way the OP asks the question totally defeats the purpose of the sieve. He wants to return a list with all of the original elements tagged by t or f. To do this, he'd have to keep two lists I think and then use the one generated by the sieve to notate the first. – hraesvelgr Mar 30 '12 at 06:44
  • Also, the primality test doesn't need to be expensive. While I don't think it's as cheap as the sieve, Miller-Rabin is quite fast. Certainly better than testing all of the divisors up to sqrt(n). Though the Miller-Rabin test isn't deterministic, as Sussman said "It's more likely that cosmic radiation will cause your computer to malfunction and give a wrong answer" than it is that some pseudoprime fools miller-rabin for enough bases. – hraesvelgr Mar 30 '12 at 06:46
  • I amended my answer to do what the OP was asking. – hraesvelgr Mar 30 '12 at 07:19
  • Hi Josh, sorry for the delay. But I took a look at your answer and it is not working for my computer. Regardless, it is much to complicated than my taste. I've revised the question to show you more of what I wanted. Sorry for not being more explicit. – WiseOlMan Apr 02 '12 at 14:16
  • It is a little complicated. I wasn't able to test the code since I don't have a Scheme installed right now. I'll install one and then work it over a little more. – hraesvelgr Apr 02 '12 at 17:44
  • Yep, implementing the sieve of Erastothenes correctly with linked list wont even work very well. The follow the actual method it works best with a fast random access structure like a vector. – WorBlux May 19 '13 at 12:21
1

OK, so the point of SoE is not to test any divisibility, but just count, by p numbers at a time:

(define (make-list n)              ; list of unmarked numbers 2 ... n
  (let loop ((i n) 
             (a '()))
    (if (= i 1)
      a            ; (cons '(2 . #t) (cons (3 . #t) ... (list '(n . #t))...))
      (loop (- i 1) (cons (cons i #t) a)))))

(define (skip2t xs)                ; skip to first unmarked number
  (if (cdar xs) xs (skip2t (cdr xs))))

(define (mark-each! k n i xs)      ; destructive update of list xs - 
  (set-cdr! (car xs) #f)           ;  mark each k-th elem,
  (if (<= (+ i k) n)               ;  head is i, last is n 
    (mark-each! k n (+ i k)
                    (list-tail xs k))))

(define (erat-sieve n)
  (let ((r  (sqrt n))              ; unmarked multiples start at prime's square
        (xs (make-list n)))
    (let loop ((a xs))
      (let ((p (caar a)))          ; next prime
        (cond ((<= p r)
               (mark-each! p n (* p p) (list-tail a (- (* p p) p)))
               (loop (skip2t (cdr a)))))))
    xs))

So that (erat-sieve 20) ==> ((2 . #t) (3 . #t) (4) (5 . #t) (6) (7 . #t) (8) (9) (10) (11 . #t) (12) (13 . #t) (14) (15) (16) (17 . #t) (18) (19 . #t) (20))


An unbounded sieve, following the formula

      P = {3,5,7,9, ...} \ U { {p2, p2+2p, p2+4p, p2+6p, ...} | p in P }

can be defined using SICP styled streams (as can be seen here):

 ;;;; Stream Implementation
 (define (head s) (car s))
 (define (tail s) ((cdr s))) 
 (define-syntax s-cons
   (syntax-rules () ((s-cons h t) (cons h (lambda () t))))) 

 ;;;; Stream Utility Functions
 (define (from-By x s)
   (s-cons x (from-By (+ x s) s)))
 (define (take n s) 
   (cond ((= n 0) '())
         ((= n 1) (list (car s)))
         (else (cons (head s) (take (- n 1) (tail s))))))
 (define (drop n s)
   (cond ((> n 0) (drop (- n 1) (tail s)))
         (else s)))
 (define (s-map f s)
   (s-cons (f (head s)) (s-map f (tail s))))
 (define (s-diff s1 s2)
   (let ((h1 (head s1)) (h2 (head s2)))
    (cond
     ((< h1 h2) (s-cons h1 (s-diff  (tail s1)       s2 )))
     ((< h2 h1)            (s-diff        s1  (tail s2)))
     (else                 (s-diff  (tail s1) (tail s2))))))
 (define (s-union s1 s2)
   (let ((h1 (head s1)) (h2 (head s2)))
    (cond
     ((< h1 h2) (s-cons h1 (s-union (tail s1)       s2 )))
     ((< h2 h1) (s-cons h2 (s-union       s1  (tail s2))))
     (else      (s-cons h1 (s-union (tail s1) (tail s2)))))))

 ;;;; odd multiples of an odd prime
 (define (mults p) (from-By (* p p) (* 2 p)))

 ;;;; The Sieve itself, bounded, ~ O(n^1.4) in n primes produced
 ;;;;   (unbounded version runs at ~ O(n^2.2), and growing worse)
 ;;;;   **only valid up to m**, includes composites above it        !!NB!!
 (define (primes-To m)
   (define (sieve s) 
    (let ((p (head s))) 
     (cond ((> (* p p) m) s) 
      (else (s-cons p 
              (sieve (s-diff (tail s) (mults p))))))))
   (s-cons 2 (sieve (from-By 3 2))))

 ;;;; all the primes' multiples, tree-merged, removed; 
 ;;;;    ~O(n^1.17..1.15) time in producing 100K .. 1M primes
 ;;;;    ~O(1) space (O(pi(sqrt(m))) probably)
 (define (primes-TM)
   (define (no-mults-From from)
       (s-diff (from-By from 2) (s-tree-join (s-map mults odd-primes))))
   (define odd-primes 
       (s-cons 3 (no-mults-From 5)))
   (s-cons 2 (no-mults-From 3)))

 ;;;; join an ordered stream of streams (here, of primes' multiples)
 ;;;; into one ordered stream, via an infinite right-deepening tree
 (define (s-tree-join sts)                               ;; sts -> s
   (define (join-With of-Tail sts)                       ;; sts -> s
     (s-cons (head (head sts))
              (s-union (tail (head sts)) (of-Tail (tail sts)))))
   (define (pairs sts)                                   ;; sts -> sts
     (s-cons (join-With head sts) (pairs (tail (tail sts)))))
   (join-With (lambda (t) (s-tree-join (pairs t))) sts))

 ;;;; Print 10 last primes from the first thousand primes
 (begin 
   (newline)
   (display (take 10 (drop 990 (primes-To 7919)))) (newline)
   (display (take 10 (drop 990 (primes-TM)))) (newline))

Tested in MIT Scheme.

Will Ness
  • 62,652
  • 8
  • 86
  • 167
1
(define (prime-sieve-to n)
  (let* ((sz (quotient n 2)) (sv (make-vector sz 1)) (lm (integer-sqrt n)))
    (for ((i (in-range 1 lm))) 
      (cond ((vector-ref sv i)
        (let ((v (+ 1 (* 2 i))))
          (for ((i (in-range (+ i (* v (/ (- v 1) 2))) sz v)))
            (vector-set! sv i 0))))))
    (cons 2
          (for/list ((i (in-range 1 sz)) 
                     #:when (and (> (vector-ref sv i) 0) (> i 0)))
                    (+ 1 (* 2 i))))))

This is another one in racket dialect of scheme that works but for up to 100,000,000. Above that, I would not vouch for its efficiency.

cobie
  • 5,869
  • 8
  • 34
  • 58
  • in the outer `for-each` loop, `(if (vector-ref sv i)` is always true. In Scheme, 0 stands for true value, no false, as in C. -- `(map ... (2i+1) ... (filter ...` combination is better. :) – Will Ness May 19 '13 at 16:00
  • the inner `for-each` starting point corresponds to `3v`, but it's OK to start from `v*v`. -- what is `range`? Which Scheme are you using? It is necessary to include `(lm-1)/2` in the range, not `lm` (does `range` include its 2nd arg?). With all this, the code will be faster (probably about twice, because of `lm`). :) – Will Ness May 19 '13 at 16:13
  • I am using racket and the range function such as (range 1 5) results in 1 2 3 4 – cobie May 19 '13 at 20:07
  • 0 is still Boolean True in Racket, as in Scheme, in `cond` as in `if`. `(/ (- v 1) 2)` is just `i` ; `(v*v - 1)/2 = (4i^2+4i+1 - 1)/2 = i(2i+2) = i(v+1)`. In `for i in range iv+i to sz`, maybe using `j` is less confusing? the value `lm` still corresponds to index `(lm-1)/2`. for `i` in range 1 to `sz` implies `i>0` always. :) If it works up to 100 mln in an acceptable time, for a Scheme that's impressive. I wonder, how much time does it get for it to get to 15485864? 32452844? – Will Ness May 20 '13 at 03:22
0

code and explanations can be found in SICP 3.5.2Infinite Streams http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-24.html#%_sec_3.5.2

FooBee
  • 630
  • 6
  • 19
  • 4
    Note that contrary to what the book says, that is not a sieve of Eratosthenes. The algorithm is the Turner sieve, which is neat but horribly inefficient. – Daniel Fischer Mar 29 '12 at 15:08
  • I can't find the original 1976 SASL manual anywhere; the usual list comprehension formulation of the Turner sieve is from 1983 revised edition with "ZF expressions". Davie 1992 gives it as `primes = sieve [2..] ; sieve (p:nos) = p : sieve (remove (multsof p) nos)`. [*Curiously*](https://stackoverflow.com/a/8871918/849891) (see "update"), it admits *two* pairs of `remove / multsof` implementations. It can also be tweaked into postponing, though such pseudocode has not turned into a code in any language I know of, yet. :) – Will Ness Jan 26 '20 at 16:02