Lisp中的“堆栈溢出(深层)”错误

时间:2018-03-18 14:00:46

标签: lisp common-lisp

我正在尝试创建一个函数prime-factors,它返回数字的素数因子。为此,我创建了is-prime函数和prime-factors-helper,它将对素数因子进行递归检查。

(defun is-prime (n &optional (d (- n 1))) 
  (if (/= n 1) (or (= d 1)
          (and (/= (rem n d) 0)
               (is-prime  n (- d 1)))) ()))

(defun prime-factors-helper (x n)
   (if (is-prime x) (list x) 
        (if (is-prime n) 
            (if (AND (= (mod x n) 0) (< n (/ x 2)))
                (append (list n) (prime-factors-helper (/ x n) (+ 1 n)))
                (prime-factors-helper x (+ 1 n)))       
            (prime-factors-helper x (+ 1 n)))))

(defun prime-factors (x)
    (prime-factors-helper x 2)) 

prime-factors的主要功能似乎适用于某些数字。但是对于大数字,它会返回"Stack overflow (deep)"

CL-USER 44 : 5 > (prime-factors 66)
(2 3 11)

CL-USER 49 : 5 > (prime-factors 512)
"Stack overflow (deep)" 

你能告诉我为什么它会返回这个错误吗?递归调用有什么问题吗?

[UPDATE]

我重新定义了is-prime函数,但显然这不是问题。

(defun is-prime (x &optional (i 2))
    (if (= x 1) nil
        (if (or (= x 2) (= x 3)) t
            (if (<= i (sqrt x))
                (if (= (mod x i ) 0) nil
                    (is-prime x (+ i 1)))
                t))))



(defun prime-factors-helper (x n)
   (if (is-prime x) 
       (list x) 
       (if (is-prime n) 
            (if (AND (= (mod x n) 0) (<= n (/ x 2)))
                (cons n (prime-factors-helper (/ x n) n))
                (prime-factors-helper x (+ 1 n)))       
            (prime-factors-helper x (+ 1 n)))))

优化问题

我还有另一个优化问题。如果我有一个很大的数字,例如123456789,我会收到此错误消息Stack overflow (stack size 261120)。我相信,因为正确的答案是(3 3 3607 3803),我的程序一旦用第一个元素(3 3)构造列表,找到下一个素数因子需要很长时间。如何优化我的代码?

1 个答案:

答案 0 :(得分:5)

关于您的代码的一些一般性评论:

(defun is-prime (x &optional (i 2))
    (if (= x 1) nil
        (if (or (= x 2) (= x 3)) t
            (if (<= i (sqrt x))
                (if (= (mod x i ) 0) nil
                    (is-prime x (+ i 1)))
                t))))

(defun prime-factors-helper (x n)
   (if (is-prime x) (list x) 
        (if (is-prime n) 
            (if (and (= (mod x n) 0) (<= n (/ x 2)))
                (append (list n) (prime-factors-helper (/ x n) (+ 1 n)))
                (prime-factors-helper x (+ 1 n)))       
            (prime-factors-helper x (+ 1 n)))))


(defun prime-factors (x)
    (prime-factors-helper x 2)) 

CL-USER 44 : 5 > (prime-factors 66)
(2 3 11)

您应该改进的一些事项:缩进,代码格式化,正确使用REPL以及选择Lisp函数:

代码缩进和格式

让我们从缩进和格式化开始。通常的缩进是:

(defun is-prime (x &optional (i 2))
  (if (= x 1)
      nil
    (if (or (= x 2) (= x 3))
        t
      (if (<= i (sqrt x))
          (if (= (mod x i ) 0)
              nil
            (is-prime x (+ i 1)))
        t))))

(defun prime-factors-helper (x n)
  (if (is-prime x)
      (list x) 
    (if (is-prime n) 
        (if (and (= (mod x n) 0) (<= n (/ x 2)))
            (append (list n) (prime-factors-helper (/ x n) (+ 1 n)))
          (prime-factors-helper x (+ 1 n)))
      (prime-factors-helper x (+ 1 n)))))


(defun prime-factors (x)
  (prime-factors-helper x 2)) 

改进代码

现在我们可以重写前两个函数:

使用case比较数字,然后您可以使用iforandnot表达式重写为逻辑表达式。

(defun is-prime (x &optional (i 2))
  (case x
    (1     nil)
    ((2 3) t)
    (t     (or (not (<= i (sqrt x)))
               (and (/= (mod x i) 0)
                    (is-prime x (+ i 1)))))))

接下来,我们使用cond来降低缩进级别。

(append (list foo) bar)只是(cons foo bar)

我们也可以摆脱一个if

(defun prime-factors-helper (x n)
  (cond ((is-prime x)
         (list x))
        ((and (is-prime n)
              (= (mod x n) 0)
              (<= n (/ x 2)))
         (cons n (prime-factors-helper (/ x n) (+ 1 n))))
        (t (prime-factors-helper x (+ 1 n)))))

测试功能

现在我们需要测试它:

(defun test (&optional (n 20))
  (loop for i from 2 below n
        for factors = (prime-factors i)
        do (print (list i
                        (= (reduce #'* factors) i)
                        (cons '* factors)))))

有问题:修复它!

正如你所看到的,还有一个问题......当我们计算8的因子时,我不得不从无限循环中破解代码。

CL-USER 51 > (test)

(2 T (* 2)) 
(3 T (* 3)) 
(4 T (* 2 2)) 
(5 T (* 5)) 
(6 T (* 2 3)) 
(7 T (* 7)) 
Break.
  1 (continue) Return from break.
  2 (abort) Return to top loop level 0.

但是那个很容易修复并留作练习。

REPL。

在LispWorks中有这样的提示时

CL-USER 44 : 5 > 

这意味着您在休息时间内处于五个(!)级别。

通过输入:top命令到达顶级代表的时间:

CL-USER 44 : 5 > :top

CL-USER 45 >