【问题标题】:Can we improve upon this primes sieve code from SICPCan we improve upon this primes sieve code from SICP
【发布时间】:2022-12-27 21:27:56
【问题描述】:

A recent Q&A entry showcased the following primes generating code from SICP, using lazy streams:

(define (sieve stream)
  (cons-stream
   (stream-car stream)
   (sieve (stream-filter
            (lambda (x)
              (not (divisible? x (stream-car stream))))
            (stream-cdr stream)))))

(define primes (sieve (integers-starting-from 2)))

An answer there showed primes to be equivalent, among other possibilities, to the following:

  (cons-stream 2
   (cons-stream 3
    (cons-stream 5
     (cons-stream 7
       (sieve 
         (stream-filter (lambda (x) (not (divisible? x 7)))
          (stream-filter (lambda (x) (not (divisible? x 5)))
           (stream-filter (lambda (x) (not (divisible? x 3)))
            (stream-filter (lambda (x) (not (divisible? x 2)))
             (integers-starting-from 9))))))))))

It seems there are too many filter streams here -- for instance 7 was produced by filtering the input numbers by 2, 3 and 5, whereas it only really had to be tested by 2 alone -- only the numbers above 9 need really be test divided by 3, let alone by 5 etc.

This problem becomes more and more pronounced as we go along producing this stream of primes. Overall, producing first n primes takes O(n^2) with this code.

Can we do better?

【问题讨论】:

    标签: scheme primes sicp lazy-sequences sieve


    【解决方案1】:

    Indeed, we need to only start filtering out multiples of a prime after its square is encountered in the input. For that, we shall use the primes and their squares. And we'll use the same code to produce those primes we need to produce our primes:

    (define (pprimes) 
      (cons-stream 2
        (psieve (stream-map (lambda (x) (cons x (* x x)))
                            (pprimes))    ;; here 
                (integers-starting-from 3))))
    
    (define (psieve pr-sqrs numbers)      ;; pairs of primes+squares
      (if (< (stream-car numbers) (cdr (stream-car pr-sqrs)))
        (cons-stream 
           (stream-car numbers)
           (psieve pr-sqrs                 ;; same prime-square pair
                   (stream-cdr numbers)))  ;;  for the next number
        (psieve 
           (stream-cdr pr-sqrs)            ;; advance prime-square's stream
           (stream-filter                  ;; and start filtering 
              (let ((p  (car (stream-car pr-sqrs)))) ;; by this prime now
                   (lambda (x)
                       (not (divisible? x p))))
              (stream-cdr numbers)))))
    

    Nowthisleads to

      (pprimes)
    =
      ....
    =
      (cons-stream 2
       (cons-stream 3
        (cons-stream 5
         (cons-stream 7
          (cons-stream 11
           (cons-stream 13
            (cons-stream 17
             (cons-stream 19
               (psieve (cons-stream 5 ... )
                       (cons-stream 25 ... )
                  (stream-filter (lambda (x) (not (divisible? x 3)))
                   (stream-filter (lambda (x) (not (divisible? x 2)))
                      (integers-starting-from 20))))))))))))
    =
      ....
    

    which, undoubtedly, is much better. No number below 25 will be tested by 5, etc.

    This is stilltrial division, and runs in about n^1.5. Truesieveof Eratosthenes should run at n log n log log n which is empirically usually close to n^1.1..1.2 or thereabouts. But this n^1.5 is a great improvement over the quadratic algorithm too, and in practice will run much faster than it in absolute terms as well.

    【讨论】:

    • Have you read The Genuine Sieve of Eratosthenes? (In Haskell, but the ideas should be "portable".)
    • Yes. And indeed they are. Except the "sieve of Bird" is a much better basis for development. Btw there's lots of related stuff on Rosettacode's sieve of Eratosthenes page's Scheme (and Haskell) section. As well as haskellwiki's prime numbers page. @molbdnilo I'm focusing on SICP styled approach here.
    • I find that article very confusing. It misses this most immediate imorovement of "postponement" of filtering which is applicable there as well, and even claims that "using squares" is no improvement at all (whichistrue, but for random access arrays, not lists). Two valuable parts in the article, for me, were the complexity derivations and Richard Bird's code in the cmets. @molbdnilo
    • @molbdnilo IOW, the "true reason" that Turner's sieve is so slow (and the original SICP one as well) is not that it uses lists instead of priority queues, but that it does not postpone the filtering properly.
    • Excellent points. To be honest, I haven't read that paper in a good while and forgot both the details and that there was something odd about it. Memory like a sieve...
    猜你喜欢
    • 2022-12-27
    • 2022-12-28
    • 2022-12-27
    • 2022-12-02
    • 2022-12-01
    • 2022-12-02
    • 2022-12-01
    • 1970-01-01
    • 2022-12-26
    相关资源
    最近更新 更多