如何理解此递归代码?


12

我在手册中找到了此代码,该手册An Introduction to Programming in Emacs Lispcond函数的帮助下基于输入的行数找出了小卵石的数量,即如果rows = 2,则小卵石应为3,如果4行,则应为10小卵石那里。

(defun triangle-using-cond (number)
  (cond ((<= number 0) 0)
        ((= number 1) 1)
        ((> number 1)
         (+ number (triangle-using-cond (1- number))))))

在传递参数4之后求值到10:

(triangle-using-cond 4)

手册并未明确说明此示例代码中每个步骤会发生什么,并且我无法弄清楚递归的工作原理。您能否帮助我逐步了解机械原理,每种情况下会发生什么?


我会让别人用“递归”一词来帮助您(因为我认为它与本文不同),或者更好地解释我将要写的内容:(a)如果数字小于或等于为0,然后为0;(b)如果数字等于1,则为1;(c)如果number大于1,则将数字加到函数返回的值triangle-using-cond上,参数要比数字小1。条件按a,b,c的顺序排列-无论先匹配什么,都是降压的地方。
法律列表

像往常一样,elisp解释器从最里面到最外面进行评估。因此,1-4 = 3。现在递归调用将是(triangle-using-cond 3),但是最终将一次又一次地返回相同的递归调用,直到它达到1个条件为止,对吗?接下来会发生什么?
博士学位

哦,我知道了-该函数在第3步可以重用-好的,很好。
法律列表

我想知道会有什么结果(triangle-using-cond 3)吗?
博士学位

2
nb该函数的1-名称特别容易引起误解,尤其是当您读取呼叫时,好像它是中缀符号。它返回其参数减一。没有一个减去论点。
phils 2014年

Answers:


14

使用“ printf调试”

您可以让Emacs通过修改函数定义来帮助您理解:

(defun triangle-using-cond (number)
  (message (format "called with %d" number))
  (cond ((<= number 0) 0)
        ((= number 1) 1)
        ((> number 1)
         (+ number (triangle-using-cond (1- number))))))

只需在(message ...)某处添加一条线索即可将其打印到*Messages*缓冲区。

使用Edebug

将点放置在函数定义内的任何位置,然后点击C-u C-M-x“ 插入”它。然后评估功能,例如,将点放在之后(triangle-using-cond 3)并点击C-x C-e

您现在处于Edebug模式。点击空格键以逐步完成该功能。每个表达式的中间值显示在回显区域中。要退出Edebug模式,只需点击q。要删除检测,请将​​点放在定义内的任何位置,然后单击C-M-x以重新评估定义。

使用标准的Emacs调试器

M-x debug-on-entry triangle-using-cond,然后在triangle-using-cond被调用时,您将被放置在Emacs调试器(buffer *Backtrace*)中。

使用d(或c跳过任何无趣的评估)逐步进行评估。

要查看中间状态(变量值等),您可以e随时使用。系统将提示您输入sexp进行评估,并打印评估结果。

使用调试器时,请在另一帧中保留源代码的副本,以便您可以跟踪发生的情况。

您还可以在源代码中的任意位置插入显式调用以进入调试器(或多或少的断点)。您插入(debug)(debug nil SOME-SEXP-TO-EVALUATE)。在后一种情况下,将SOME-SEXP-TO-EVALUATE评估进入调试器的时间并打印结果。(请记住,您可以将这样的代码插入源代码中并用于C-M-x评估它,然后撤消-您无需保存已编辑的文件。)

有关Using Debugger更多信息,请参见Elisp手册,节点。

递归循环

无论如何,将递归视为一个循环。定义了两种终止情况:(<= number 0)(= number 1)。在这些情况下,函数将返回一个简单数字。

在递归的情况下,该函数将返回该数字的和与函数的结果number - 1。最终,将使用1小于或等于零的一个或一个数字来调用该函数。

因此,递归案例结果为:

(+ number (+ (1- number) (+ (1- (1- number)) ... 1)

举个例子(triangle-using-cond 4)。让我们累积最终的表达式:

  • 在第一个迭代中number4,因此(> number 1)跟随分支。我们开始建立一个表达式(+ 4 ...,并调用与函数(1- 4),即(triangle-using-cond 3)

  • 现在number3,结果是(+ 3 (triangle-using-cond 2))。总结果表达式为(+ 4 (+ 3 (triangle-using-cond 2)))

  • number2现在,所以表达式是(+ 4 (+ 3 (+ 2 (triangle-using-cond 1))))

  • number就是1现在,我们采取(= number 1)的分支,导致在一个无聊的1。整个表达式是(+ 4 (+ 3 (+ 2 1)))。评估是由内而外的,你会得到:(+ 4 (+ 3 3))(+ 4 6)或只10


3
Edebug会更好。=)
马拉巴巴

如何使用进行打印message (...),击中C-x C-e仅显示最终结果(10),没有其他显示?我想念什么吗?
博士学位

@Malabarba,如何付诸Edebug行动?
博士学位

1
@doctorate命中C-u C-M-x函数内部的点以对其进行调试。然后只需正常运行该功能即可。
马拉巴巴

@将(message ...)打印的内容打印到*Message*缓冲区。
rekado 2014年

6

SICP的过程应用程序替代模型可以解释算法,从而理解代码。

我也编写了一些代码来促进这一点。 lispy-flattenlispy包执行此操作。下面是应用的结果lispy-flatten(triangle-using-cond 4)

(cond ((<= 4 0)
       0)
      ((= 4 1)
       1)
      ((> 4 1)
       (+ 4 (triangle-using-cond (1- 4)))))

您可以将上面的表达式简化为:

(+ 4 (triangle-using-cond 3))

然后再次展平:

(+ 4 (cond ((<= 3 0)
            0)
           ((= 3 1)
            1)
           ((> 3 1)
            (+ 3 (triangle-using-cond (1- 3))))))

最终结果:

(+ 4 (+ 3 (+ 2 1)))

3

这不是Emacs / Elisp特有的,但是如果您具有数学背景,则递归就像是数学归纳法。(或者如果不这样做,那么当您学习归纳法时,就像递归一样!)

让我们从定义开始:

(defun triangle-using-cond (number)
  (cond ((<= number 0) 0)
        ((= number 1) 1)
        ((> number 1)
         (+ number (triangle-using-cond (1- number))))))

number为is时4,前两个条件都不成立,因此将根据第三个条件
(triangle-using-cond 4)对其进行评估:被评估为
(+ number (triangle-using-cond (1- number))),即为
(+ 4 (triangle-using-cond 3))

同样,
(triangle-using-cond 3)被评估为
(+ 3 (triangle-using-cond 2))

同样, (triangle-using-cond 2)被评估为
(+ 2 (triangle-using-cond 1))

但是对于(triangle-using-cond 1),第二个条件成立,并且被评估为1

给任何学习递归的人的建议:尽量避免

尝试思考递归调用而不是相信递归调用起作用的初学者常见的错误(有时称为信念的递归飞跃)。

如果您试图说服自己是否(triangle-using-cond 4)会返回正确的答案,只需假设(triangle-using-cond 3)会返回正确的答案,然后验证在这种情况下它是否正确。当然,您也必须验证基本情况。


2

您的示例的计算步骤如下所示:

(4 +               ;; step 1
   (3 +            ;; step 2
      (2 +         ;; step 3
         (1))))    ;; step 4
=> 10

实际上,从不满足0条件,因为作为输入的1已经结束了递归。


(1)不是有效的表达式。
rekado 2014年

1
用评估就很好M-x calc。:-)认真地说,我的意思是显示计算结果,而不是Lisp评估。
辣椒粉

哦,我什至没有注意到它(4 +不是(+ 4您的答案... :)
rekado 2014年

0

我认为这很简单,在此之下您不需要emacs lisp,这只是小学数学。

f(0)= 0

f(1)= 1

当n> 1时f(n)= f(n-1)+ n

所以f(5)= 5 + f(4)= 5 + 4 + f(3)= 5 + 4 + 3 + 2 + 1 + 0

现在很明显。


但是,对于此函数,永远不会调用f(0)。
rekado 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.