[英]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
。
現在看一下固定版本,將this
和next
放在一起作為循環變量是被誤導的,這樣做是為了避免另一個內部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.