定义 ISPRIME 函数时遇到问题

Trouble with defining an ISPRIME function

我是 LISP 的新手,正在解决一些初学者问题。我尝试定义一个 ISPRIME 函数,但它似乎无法正常工作。这是我的代码:

 (defun ISPRIME (n &optional (d (- n 1))) 
      (if (= d 0)
           ( return-from ISPRIME t )) 
      (if (= (mod n d) 0)
           ( return-from ISPRIME nil )) 
      (ISPRIME n (- d 1)))

但是在 运行 我的代码中,我使用值 5 作为示例:

 (ISPRIME 5)
 Nil

5应该是素数。我怀疑一切都落入了:(if (= (mod n d) 0) 语句,而它不应该是。d 应该继续递减直到达到 0 并返回 true,但它没有。我似乎看不出我的逻辑错误在哪里。

非常感谢任何帮助!

你的代码中有一个错误:函数应该停在 1,而不是 0,因为任何数字都可以被 1 整除。只需这样更改函数即可:

(defun ISPRIME (n &optional (d (- n 1))) 
      (if (= d 1)                         ; <- change from 0 to 1
           ( return-from ISPRIME t )) 
      (if (= (mod n d) 0)
           ( return-from ISPRIME nil )) 
      (ISPRIME n (- d 1)))

但是请注意,您的函数不是很有效,因为 return-from returns 来自函数的最后一次调用,而不是来自“递归塔”,因此您的函数可以重写为以这种方式消除它,通过使用更紧凑的“条件”cond,而不是 if,并将 return-from 替换为结果:

(defun isprime (n &optional (d (- n 1)))
  (cond ((= d 1) t)
        ((= (mod n d) 0) nil)
        (t (isprime n (- d 1)))))

这仍然是递归的,但更符合 Common Lisp 的习惯。当然,可以将此函数转换为更高效的迭代版本,并应用更多 efficient algorithm. Note, however, that a smart compiler will transform automatically this function in iteration, due to the fact that the function is tail recursive.

已添加

您还可以添加初始检查参数 n 是否大于 1,例如:

(defun isprime (n &optional (d (- n 1)))
  (cond ((<= n 1) nil)
        ((= d 1) t)
        ((= (mod n d) 0) nil)
        (t (isprime n (- d 1)))))

由于您正在计算布尔值,因此 (if test T NIL) 的实例可以替换为 test 并且更一般地说,结果可能可以表示为单个布尔表达式。我倾向于发现它们更具可读性。以下是对已接受答案的一点修改作为示例:

(defun is-prime (n &optional (d (1- n)))
  (or (= d 1)
      (and (plusp d)
           (plusp (mod n d))
           (is-prime n (1- d)))))

为了完整起见,根据 Will Ness 的回答,这里是一个 LOOP 版本:

(defun is-prime (n)
  (loop
     for d = 2 then (+ d add)
     for add = 1 then 2
     thereis (> (* d d) n)
     until (= (mod n d) 0))) 

和等效的 DO 版本:

(defun is-prime (n)
  (do ((d 2 (+ d add))
       (add 1 2))
      ((> (* d d) n) t)
    (when (zerop (mod n d))
      (return nil))))

你不应该使用(尾)递归,因为 Common Lisp 没有 tail call optimization 保证。使用 doloop,甚至 proggo

并且在算法上,总是测试你的潜在除数在增加 顺序,从2开始,超过(sqrt n)[=43=就停止]:

(defun ISPRIME (n) 
  (prog ((d 2))                          ; defines implicit block named NIL
    LOOP
      (if (> (* d d) n)
           ( return-from ISPRIME t ))    ; (return T) also works
      (if (= (mod n d) 0)
           ( return-from ISPRIME nil ))  ; or just (return) 
      (if (= d 2)
        (incf d 1)
        (incf d 2))
      (go LOOP)))

(return)等同于(return nil)(return val)等同于(return-from NIL val)。由于 prog 定义了一个名为 NIL 的隐式块,更短、更通用,因此可以在那里使用对 return 的调用。

一个有趣的方法是使用一个可扩展的素数列表,通过使用这个isprime函数过滤递增的数字来创建,用作除数提供另一个 isprime-p 函数,它只会通过这些素数而不是所有可能性来测试其参数,从而实现另一个算法性能增益。素数列表应根据需要进行扩展。素数只会上升到被测试数字的平方根,而素数本身只需要被数字测试到最高素数的平方根(因此,被测试数字的四次方)。