这可能是一个基本问题,但我在使用Scheme编写的程序时遇到问题。该过程应返回小于或等于N的所有素数(N来自输入)。
(define (isPrimeHelper x k)
(if (= x k) #t
(if (= (remainder x k) 0) #f
(isPrimeHelper x (+ k 1)))))
(define ( isPrime x )
(cond
(( = x 1 ) #t)
(( = x 2 ) #t)
( else (isPrimeHelper x 2 ) )))
(define (printPrimesUpTo n)
(define result '())
(define (helper x)
(if (= x (+ 1 n)) result
(if (isPrime x) (cons x result) ))
( helper (+ x 1)))
( helper 1 ))
我对素数的检查工作,但函数printPrimesUpTo
似乎永远循环。基本上,我们的想法是检查数字是否为素数并将其放入结果列表中。
谢谢:)
答案 0 :(得分:3)
你有几件事情错了,你的代码非常不恰当。首先,数字1不是素数;事实上,它既不是素数也不是复合素。其次,result
变量没有按照您的想法进行。第三,你if
的使用在它出现的每个地方都是不正确的; if
是一个表达式,而不是其他一些编程语言中的语句。并且,作为一种风格问题,结束括号被堆叠在线的末尾,并且不占据他们自己的线。您需要与您的教授或助教交谈,以澄清对Scheme的一些基本误解。
找到小于 n 的素数的最佳算法是Eratosthenes的筛子,大约二十二世纪前由一位发明闰日和经纬系统的希腊数学家发明的,准确地测量了地球的周长和从地球到太阳的距离,并且是亚历山大的托勒密图书馆的首席图书管理员。这是他的算法的简单版本:
(define (primes n)
(let ((bits (make-vector (+ n 1) #t)))
(let loop ((p 2) (ps '()))
(cond ((< n p) (reverse ps))
((vector-ref bits p)
(do ((i (+ p p) (+ i p))) ((< n i))
(vector-set! bits i #f))
(loop (+ p 1) (cons p ps)))
(else (loop (+ p 1) ps))))))
称为(primes 50)
,返回列表(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47)
。正如你试图做的那样,它比通过试验部门测试数字的素数要快得多。如果必须,这是一个正确的素性检查器:
(define (prime? n)
(let loop ((d 2))
(cond ((< n (* d d)) #t)
((zero? (modulo n d)) #f)
(else (loop (+ d 1))))))
两种算法均可实现改进。如果您有兴趣,我会在我的博客上谦虚地推荐这个essay。
答案 1 :(得分:2)
(if)
函数中的(helper)
表达式不是函数的尾部表达式,因此不会返回,但控件将始终继续(helper (+ x 1))
并递归。
答案 2 :(得分:2)
首先,通过缩进来表达嵌套结构是一种很好的风格,因此它是在视觉上明显; 并且还要放置每个if
的子句,结果< / em>和替代,在其自己的行上:
(define (isPrimeHelper x k)
(if (= x k)
#t ; consequent
(if (= (remainder x k) 0) ; alternative
;; ^^ indentation
#f ; consequent
(isPrimeHelper x (+ k 1))))) ; alternative
(define (printPrimesUpTo n)
(define result '())
(define (helper x)
(if (= x (+ 1 n))
result ; consequent
(if (isPrime x) ; alternative
(cons x result) )) ; no alternative!
;; ^^ indentation
( helper (+ x 1)))
( helper 1 ))
现在可以清楚地看到,helper
函数执行的最后一件事就是始终使用递增的x
值来调用自身。没有停止条件,即这是一个无限循环。
另一件事是,调用(cons x result)
不会以任何方式改变result
的值。为此,您需要设置它,如下所示:(set! result (cons x result))
。您还需要将此表达式放在begin
组中,因为它的评估不是因为它的值,而是评估它的side-effect:
(define (helper x)
(if (= x (+ 1 n))
result
(begin
(if (isPrime x)
(set! result (cons x result)) ) ; no alternative!
(helper (+ x 1)) )))
通常,明确使用set!
被认为是不好的风格。表达循环的一种标准方法是使用命名为let 的tail-recursive代码,通常使用规范名称“loop
”(但它可以是任何名称):
(define (primesUpTo n)
(let loop ((x n)
(result '()))
(cond
((<= x 1) result) ; return the result
((isPrime x)
(loop (- x 1) (cons x result))) ; alter the result being built
(else (loop (- x 1) result))))) ; go on with the same result
,在tail-call optimization存在的情况下,实际上等同于之前的版本。
答案 3 :(得分:1)
你可以做得更好。我重新格式化了代码:
(define (prime? x)
(define (prime-helper x k)
(cond ((= x k) #t)
((= (remainder x k) 0) #f)
(else
(prime-helper x (+ k 1)))))
(cond ((= x 1) #f)
((= x 2) #t)
(else
(prime-helper x 2))))
(define (primes-up-to n)
(define (helper x)
(cond ((= x 0) '())
((prime? x)
(cons x (helper (- x 1))))
(else
(helper (- x 1)))))
(reverse
(helper n)))
scheme@(guile-user)> (primes-up-to 20)
$1 = (2 3 5 7 11 13 17 19)
请不要编写像C或Java这样的Scheme - 为了便于阅读,请查看这些style rules语言的lisp-family:不要使用驼峰式,不要在括号上加上括号自己的行,用?
标记谓词,注意正确的缩进,不要在括号内添加额外的空格。
答案 4 :(得分:1)
效率更高prime?
(来自Sedgewick&#34;算法&#34;):
(define (prime? n)
(define (F n i) "helper"
(cond ((< n (* i i)) #t)
((zero? (remainder n i)) #f)
(else
(F n (+ i 1)))))
"primality test"
(cond ((< n 2) #f)
(else
(F n 2))))