【问题标题】:Scheme prime numbers计划素数
【发布时间】:2012-12-09 19:44:06
【问题描述】:

这可能是一个基本的问题,但我在使用 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 似乎永远循环。基本上这个想法是检查一个数字是否是素数并将其放入结果列表中。

谢谢:)

【问题讨论】:

    标签: scheme primes


    【解决方案1】:

    您有几处错误,而且您的代码非常不习惯。首先,数字 1 不是素数;事实上,它既不是素数也不是合数。其次,result 变量并没有像您想象的那样做。第三,您对if 的使用在任何地方都是不正确的; if 是一个表达式,而不是其他一些编程语言中的语句。而且,作为一种风格,右括号堆叠在行尾,并且不占用自己的一行。你需要和你的教授或助教谈谈,以澄清对 Scheme 的一些基本误解。

    找出小于n的素数的最佳算法是埃拉托色尼筛法,它是大约二十二世纪前由一位发明闰日和经纬度系统的希腊数学家发明的,准确地测量了地球的周长和地球到太阳的距离,并且是亚历山大托勒密图书馆的首席图书馆员。这是他算法的一个简单版本:

    (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

    【讨论】:

      【解决方案2】:

      首先,通过缩进来表达嵌套结构是一种很好的风格,因此视觉上很明显;并且还可以将if的每个子句放在consequent替代,单独一行:

      (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! 被认为是不好的风格。表达循环的一种标准方法是使用 named lettail-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】:

        (helper) 函数中的 (if) 表达式不是函数的尾部表达式,因此不会返回,但控制将始终继续到 (helper (+ x 1)) 并递归。

        【讨论】:

        • 啊,我明白了。因此,如果当我这样做时,检查 = x (+ 1 n) 如果它为真,那么我应该退出该函数。类似于 return 声明的东西。方案中有没有这样的说法
        • 不过,您不需要返回声明。如果(= x (+ 1 n)) 为假,只需执行(helper (+ x 1));也就是说,在您的情况下,作为第二个 (if) 表达式的一部分。
        【解决方案4】:

        更高效的prime?(来自 Sedgewick 的“算法”):

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

        【讨论】:

          【解决方案5】:

          你可以做得更好。我重新编写了您的代码:

          (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 家族的语言:不要使用驼峰式大小写,不要在自己的行上加上括号, 用? 标记谓词,注意正确的缩进,不要在括号内添加额外的空格。

          【讨论】:

            猜你喜欢
            • 2023-03-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多