簡體   English   中英

在其過濾過程中使用局部 state 突變的方案中的 Eratosthenes 篩選

[英]Sieve of Eratosthenes in Scheme using mutation of local state in its filtering procedure

回答最近的一個問題時,我想出了以下代碼,實現了 Eratosthenes 篩的變體,反復剔除最初的2...n序列,盡早停止:

(define (sieve2 n)
  (let ((ls (makelist n)))
    (let loop ((ls ls) 
               (next (sievehelper2 ls n)))
      (if (null? next)
          ls
          (cons (car ls) 
                (loop next (sievehelper2 ls n)))))))

(define (sievehelper2 list n)
  (if (> (* (car list) (car list)) n)
      '()
      (filterfunc (not-divisible-by (car list)) 
                  list)))

(define filterfunc filter)

(define (not-divisible-by n)
  (let ((m n))   ; the current multiple of n
    (lambda (x)
      (let ((ret (not (= x m))))
        (if (>= x m) (set! m (+ m n)) #f)
        ret))))

(define (makelist n)
  (range 2 (+ 1 n)))

在 Racket 中運行(sieve 50)會導致'(2 3 3 5 5 7 7 11 11 13 17 19 23 29 31 37 41 43 47)

它有一些錯誤,結果很明顯,我沒有立即看到它在哪里。 這可能是我犯的一些愚蠢的錯誤,也可能是所使用的算法部分的一些基本錯位的表達,我不能說哪個是哪個。

請問這個錯誤是什么,如何解決?

需要明確的是,我並不是要求對代碼進行算法改進,我希望保留其中表達的計算結構 此外,我在鏈接問題中看到的挑戰是 devise 缺少的功能 - 並改變sieve本身 - 同時保持sievehelper function為給定,直到在這個問題的代碼中明顯的一些小改動。 這也是我想在這個問題中提出的要求。

我對sievehelper2中對sieve2兩次調用也不滿意。 也許以某種方式修復代碼結構也會使錯誤 go 消失?

問題在這里:

(loop next (sievehelper2 ls n))

在此調用中,列表ls第二次傳遞給sievehelper2 sievehelper2需要next處理:

(define (sieve2 n)
  (let ((ls (makelist n)))
    (let loop ((ls ls) 
               (next (sievehelper2 ls n)))
      (if (null? next)
          ls
          (cons (car ls) 
                (loop next (sievehelper2 next n)))))))

隨着這一變化,篩子似乎按預期工作:

sieve2.rkt> (sieve2 50)
'(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47)

擺脫sieve2中的外部let可能有助於代碼清晰,並且只調用sievehelper2

(define (sieve3 n)
  (let loop ((filtered '())
             (candidates (makelist n)))
    (if (null? candidates)
        filtered
        (cons (car candidates) 
              (loop (cdr candidates) (sievehelper2 candidates n))))))

這也可以按預期工作:

sieve2.rkt> (sieve3 50)
'(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47)

一些改進

我對上面的sieve3 雖然我認為只顯示一次對sievehelper2的調用有助於清晰,但代碼仍然可以更清晰。

最初sieve3有一個result變量,該變量后來被更改為filtered result的描述性不足以提供幫助,我認為更改是一種改進; 畢竟, filtered確實包含過濾candidates列表的結果。 雖然,從這個意義上說, filtered的初始值是沒有意義的,因為candidates者還沒有被過濾掉。

更困擾我的是建築:

(cons (car candidates) 
      (loop (cdr candidates) (sievehelper2 candidates n)))

(car candidates)是正在收集的素數並且(cdr candidates)是部分過濾的候選列表,或者目標是將已找到的素數組合到完全過濾的候選列表中,這不是很清楚.

這是sieve的改進版本,它使用顯式累加器primes來保存遇到的素數。 sievehelper2返回一個空列表時,我們知道filtered后的列表已經完全過濾掉了非素數。 最后,可以將找到的素數和完全過濾的候選列表附加在一起並返回(但不能在反轉找到的素數列表之前,因為最近找到的素數被放在primes的前面)。 這個sieve過程還具有尾遞歸的優點:

(define (sieve n)
  (let loop ((primes '())
             (filtered (makelist n)))
    (let ((next (sievehelper2 filtered n)))
      (if (null? next)
          (append (reverse primes) filtered)
          (loop (cons (car filtered) primes)
                next)))))
sieve2.rkt> (sieve 50)
'(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47)

更新:經過一番深思熟慮后,我開始不喜歡新結構並重新欣賞我最初提出的結構。

它來自使用一的角度 - 當前序列及其下一次迭代(由於ad absurdum的回答,錯誤已修復):

(define (sieve2_fixed n)
  (let ((start (makelist n)))
    (let loop ((this               start   ) 
               (next (sievehelper2 start n)))
      (if (null? next)
          this
          (cons (car this) 
                (loop               next
                      (sievehelper2 next n)))))))

原始(已棄用)答案如下,僅供記錄。


感謝ad-absurdum的回答,該錯誤現已修復。

至於代碼錯誤問題,我真正應該做的是首先為變量使用正確的名稱,如下所示:

(define (sieve2a n)
  (let ((start (makelist n)))
    (let loop ((this start) 
               (next (sievehelper2 start n)))
      (if (null? next)
          this
          (cons (car this) 
                (loop next         ;____ error: `this`: WRONG
                      (sievehelper2 this n)))))))

與問題中的sieve2相比,這里唯一改變的是名稱(以及另外一個換行符)。 現在錯誤很明顯了,而且這些名字很可能讓我自己更容易注意到它。

至於代碼結構和清晰度問題,我不太喜歡從那個答案重寫sieve中使用的內置反向范例。 是的,它是函數式編程的主要內容,但實際上,它不再需要了——如果我們在 Racket 中運行我們的代碼,堆用於堆棧,堆棧溢出就像總的 memory 耗盡一樣不可能,並且直接的尾遞歸模數代碼可能具有更好的性能,避免了多余的reverse

現在看一下固定版本,將thisnext放在一起作為循環變量是被誤導的,這樣做是為了避免另一個內部let出於某種原因。 但是它來表示計算要清楚得多,因為

(define (sieve2b n)
  (let loop ((this (makelist n))) 
     (let ((next (sievehelper2 this n)))
        (if (null? next)
          this
          (cons (car this)
                (loop next))))))

;; Start with the range of numbers. On each iteration,
;; try running the helper to get the next version of the list. 
;; If the attempt produces '(), stop and use what we've got
;;   as the remaining prime numbers, which they all are.
;; Otherwise, keep the first number as the next prime, and go on 
;; using that "next" version in the next iteration of the loop.

現在只有一個對助手的調用,代碼很清晰,原始錯誤變得不可能像我希望/懷疑的那樣發生。

簡單和直截了當勝過不必要的過度復雜化 哦。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM