这个功能可以简化(更“快”)吗?

时间:2013-12-05 07:06:38

标签: scheme lisp computation-theory

我想知道这是否是此功能的最快版本。

(defun foo (x y)
  (cond 
   ;if x = 0, return y+1
   ((zp x) (+ 1 y)) 
   ;if y = 0, return foo on decrement x and 1
   ((zp y) (foo (- x 1) 1)) 
   ;else run foo on decrement x and y = (foo x (- y 1))
   (t (foo (- x 1) (foo x (- y 1)))))) 

当我运行它时,我经常会出现堆栈溢出错误,所以我试图找出一种计算类似(foo 3 1000000)的方法,而不使用计算机。

从分析函数我认为它是在导致溢出的递归情况下嵌入foo(foo 3 1000000)。但是,既然你在递减y,那么步数就等于y?

编辑:从评论中删除谎言

3 个答案:

答案 0 :(得分:3)

12年前我写了这个:

(defun ackermann (m n)
  (declare (fixnum m n) (optimize (speed 3) (safety 0)))
  (let ((memo (make-hash-table :test #'equal))
        (ncal 0) (nhit 0))
    (labels ((ack (aa bb)
               (incf ncal)
               (cond ((zerop aa) (1+ bb))
                     ((= 1 aa) (+ 2 bb))
                     ((= 2 aa) (+ 3 (* 2 bb)))
                     ((= 3 aa) (- (ash 1 (+ 3 bb)) 3))
                     ((let* ((key (cons aa bb))
                             (val (gethash key memo)))
                        (cond (val (incf nhit) val)
                              (t (setq val (if (zerop bb)
                                               (ack (1- aa) 1)
                                               (ack (1- aa) (ack aa (1- bb)))))
                                 (setf (gethash key memo) val)
                                 val)))))))
      (let ((ret (ack m n)))
        (format t "A(~d,~d)=~:d (~:d calls, ~:d cache hits)~%"
                m n ret ncal nhit)
        (values ret memo)))))

正如您所看到的,我使用了一个明确的小a公式和更大a的记忆。

但请注意,此函数增长得如此之快,以至于尝试计算实际值没有多大意义;你会更快地耗尽宇宙中的原子 - 记忆与否。

答案 1 :(得分:2)

从概念上讲,堆栈溢出与速度无关,但它们与 space 的使用有关。例如,请考虑length的以下实现。第一个将进入长列表的堆栈溢出。第二个也是,除非你的Lisp实现尾调用优化。第三个不会。但是,它们具有相同的时间复杂度(速度);它们在列表的长度上是线性的。

(defun length1 (list)
  (if (endp list)
      0
      (+ 1 (length1 (rest list)))))

(defun length2 (list)
  (labels ((l2 (list len)
             (if (endp list)
                 len
                 (l2 (rest list) (1+ len)))))
    (l2 list 0)))

(defun length3 (list)
  (do ((list list (rest list))
       (len 0 (1+ len)))
      ((endp list) len)))

你可以为你的代码做类似的事情,尽管你仍然会有一个递归调用,这将有助于堆栈空间。由于这似乎是Ackermann function,我将使用zerop代替zpack而不是foo。因此,您可以这样做:

(defun foo2 (x y)
  (do () ((zp x) (+ 1 y))
    (if (zp y)
        (setf x (1- x)
              y 1)
        (psetf x (1- x)
               y (foo x (1- y))))))

由于x在每次迭代时减少1,并且唯一的条件更改在y,因此您可以将其简化为:

(defun ack2 (x y)
  (do () ((zerop x) (1+ y))
    (if (zerop y)
        (setf x (1- x)
              y 1)
        (psetf x (1- x)
               y (ack2 x (1- y))))))

由于y是迭代期间唯一有条件改变的东西,因此您可以进一步将其简化为:

(defun ack3 (x y)
  (do ((x x (1- x))
       (y y (if (zerop y) 1 (ack3 x (1- y)))))
      ((zerop x) (1+ y))))

这是一项昂贵的计算功能,这会让你更进一步,但你仍然无法获得,例如(ackN 3 1000000)。所有这些定义都可以从http://pastebin.com/mNA9TNTm轻松复制和粘贴。

答案 2 :(得分:2)

通常,memoization是您在此类计算中的朋友。可能不适用,因为它取决于递归中的特定参数;但这是一种有用的探索方法。