简体   繁体   English

在其过滤过程中使用局部 state 突变的方案中的 Eratosthenes 筛选

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

While answering a recent question I came up with the following code, implementing a variant of the sieve of Eratosthenes, repeatedly culling the initial 2...n sequence, stopping as early as possible:回答最近的一个问题时,我想出了以下代码,实现了 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)))

Running (sieve 50) in Racket results in '(2 3 3 5 5 7 7 11 11 13 17 19 23 29 31 37 41 43 47) though.在 Racket 中运行(sieve 50)会导致'(2 3 3 5 5 7 7 11 11 13 17 19 23 29 31 37 41 43 47)

It has some error in it, as is obvious in the results, and I don't immediately see where it is.它有一些错误,结果很明显,我没有立即看到它在哪里。 It can either be some stupid mistake that I made or an expression of some fundamental misalignment of the algorithmic pieces in use, and I can't say which is which.这可能是我犯的一些愚蠢的错误,也可能是所使用的算法部分的一些基本错位的表达,我不能说哪个是哪个。

What is that error and how can it be fixed, please?请问这个错误是什么,如何解决?

To be clear, I'm not asking for algorithmic improvements to the code, I want the computational structure expressed in it preserved .需要明确的是,我并不是要求对代码进行算法改进,我希望保留其中表达的计算结构 Moreover, the challenge that I saw in the linked question was to devise the missing functions -- and alter the sieve itself -- while keeping the sievehelper function as given , up to some minor alterations as evident in this question's code.此外,我在链接问题中看到的挑战是 devise 缺少的功能 - 并改变sieve本身 - 同时保持sievehelper function为给定,直到在这个问题的代码中明显的一些小改动。 This is also a requirement I'd like to make in this question.这也是我想在这个问题中提出的要求。

I'm also not happy with the two calls to sievehelper2 in sieve2 .我对sievehelper2中对sieve2两次调用也不满意。 Perhaps fixing the code structure somehow will also make the error go away?也许以某种方式修复代码结构也会使错误 go 消失?

The problem is here:问题在这里:

(loop next (sievehelper2 ls n))

The list ls is passed for a second time to sievehelper2 in this call;在此调用中,列表ls第二次传递给sievehelper2 but sievehelper2 needs to process next :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)))))))

With this change, the sieve seems to work as expected:随着这一变化,筛子似乎按预期工作:

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

It may help code clarity to get rid of the outer let in sieve2 , and make only one call to sievehelper2 :摆脱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))))))

This also works as expected:这也可以按预期工作:

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

Some Improvements一些改进

I am not happy with sieve3 above.我对上面的sieve3 While I think that showing only one call to sievehelper2 aids clarity, the code could still be made more clear.虽然我认为只显示一次对sievehelper2的调用有助于清晰,但代码仍然可以更清晰。

Initially sieve3 had a result variable which has since been changed to filtered .最初sieve3有一个result变量,该变量后来被更改为filtered result was not descriptive enough to be helpful, and I think that the change is an improvement; result的描述性不足以提供帮助,我认为更改是一种改进; after all, filtered does contain the results of filtering the candidates list.毕竟, filtered确实包含过滤candidates列表的结果。 Although, the initial value of filtered is meaningless in that sense because candidates has not yet been filtered.虽然,从这个意义上说, filtered的初始值是没有意义的,因为candidates者还没有被过滤掉。

What bothers me more is the construction:更困扰我的是建筑:

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

It is not very clear that (car candidates) is a prime that is being collected and that (cdr candidates) is the partially filtered list of candidates, or that the goal is to cons primes which have been found onto a fully filtered list of candidates. (car candidates)是正在收集的素数并且(cdr candidates)是部分过滤的候选列表,或者目标是将已找到的素数组合到完全过滤的候选列表中,这不是很清楚.

Here is an improved version of sieve that uses an explicit accumulator primes to save prime numbers as they are encountered.这是sieve的改进版本,它使用显式累加器primes来保存遇到的素数。 When sievehelper2 returns an empty list, we know that the filtered list has been fully filtered of non-prime numbers.sievehelper2返回一个空列表时,我们知道filtered后的列表已经完全过滤掉了非素数。 Finally, the found primes and the fully filtered list of candidates can be appended together and returned (but not before reversing the list of found primes, since the most recently found primes are consed onto the front of primes ).最后,可以将找到的素数和完全过滤的候选列表附加在一起并返回(但不能在反转找到的素数列表之前,因为最近找到的素数被放在primes的前面)。 This sieve procedure also has the virtue of being tail-recursive:这个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)

update: After some mulling over I've come to dislike the new structure and re-appreciate the one I came up with originally.更新:经过一番深思熟虑后,我开始不喜欢新结构并重新欣赏我最初提出的结构。

It comes from the perspective of working with a pair -- the current sequence, and its next iteration (with the error fixed, thanks to the answer by ad absurdum ):它来自使用一的角度 - 当前序列及其下一次迭代(由于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)))))))

the original (deprecated) answer follows, just for the record.原始(已弃用)答案如下,仅供记录。


Thanks to the answer from ad-absurdum , the error is now fixed.感谢ad-absurdum的回答,该错误现已修复。

As to the code error issue, what I should have done really was to use proper names for the variables in the first place, like so:至于代码错误问题,我真正应该做的是首先为变量使用正确的名称,如下所示:

(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)))))))

The only thing changed here compared to sieve2 in the question are the names (and one more newline).与问题中的sieve2相比,这里唯一改变的是名称(以及另外一个换行符)。 Now the error is clear, and chances are with these names it could be much easier for me to notice it myself.现在错误很明显了,而且这些名字很可能让我自己更容易注意到它。

As to the code structure and clarity issue, I'm not too fond of the build-in-reverse paradigm used in the sieve re-write from that answer.至于代码结构和清晰度问题,我不太喜欢从那个答案重写sieve中使用的内置反向范例。 Yes it is a staple of functional programming, but actually, it doesn't have to be anymore -- not if we run our code in Racket, where the heap is used for the stack, stack overflow is as impossible as total memory exhaustion, and the straight tail-recursive-modulo-cons code just might have better performance, having avoided the superfluous reverse .是的,它是函数式编程的主要内容,但实际上,它不再需要了——如果我们在 Racket 中运行我们的代码,堆用于堆栈,堆栈溢出就像总的 memory 耗尽一样不可能,并且直接的尾递归模数代码可能具有更好的性能,避免了多余的reverse

Now looking at the fixed version, putting the this and the next together as the loop variables was misguided, done in an attempt to avoid another, inner let for some reason.现在看一下固定版本,将thisnext放在一起作为循环变量是被误导的,这样做是为了避免另一个内部let出于某种原因。 But the computation is much clearer expressed with it, as但是它来表示计算要清楚得多,因为

(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.

Now there's only one call to helper as it should be, the code is clear, and the original error becomes impossible to make just as I had hoped / suspected.现在只有一个对助手的调用,代码很清晰,原始错误变得不可能像我希望/怀疑的那样发生。

Simplicity and straightforwardness wins over unnecessary overcomplification .简单和直截了当胜过不必要的过度复杂化 D'oh.哦。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM