【问题标题】:Lisp recursive function missing base case on first callLisp递归函数在第一次调用时缺少基本情况
【发布时间】:2016-04-13 14:19:46
【问题描述】:

我昨天开始用 lisp 编程,所以如果我犯了一些真正的新手错误,请原谅。我正在尝试创建一个函数,该函数使用钟形三角形计算钟形数,而我的递归三角形函数无法正常工作。我还确定如果我的递归三角形函数正常工作,那么我的递归钟形函数也会以某种方式被破坏。

当我测试我的三角形函数时,我得到了输出:

(defun bell(l n)
    (if(< n 1)(list 1))
    (if (= n 1)(last l))
    (bell (triangle (reverse l) (last l) (list-length l)) (- n 1))
)
(defun triangle(pL nL i)
    (if(<= i 0)
        (write "equals zero!")
        (reverse nL)
    )
    (triangle pL (append (list (+ (nth i pL) (nth i nL))) nL) (- i 1))  
)
(write (triangle '(1) '(1) 0))


=>


"equals zero!""equals zero!"
*** - NTH: -1 is not a non-negative integer

由于某种原因,它会打印我的调试代码两次,即使该函数在第一次调用时应该满足我的基本情况。

【问题讨论】:

  • 我回滚了对标题的编辑,因为当 OP is 试图创建一个三角形时,OP 也发现了真正的问题:函数 isn't 终止于预期的基本情况,这是因为 if 的分组。虽然三角形的解决方案可能对 OP 有所帮助,但问题实际上集中在为什么 (defun foo (n) (if (= n 0) 35) 42)总是返回 42,即使 n 为 0。这是因为代码忽略了如果.
  • @JoshuaTaylor 我明白你的意思。 OP 不是在问如何解决三角形,而是问他为什么会出现这个错误。无论如何,我认为这篇文章可能会更有帮助,并且更容易找到更合适的标题,因为当前的帖子甚至不代表错误。

标签: recursion lisp common-lisp


【解决方案1】:

由于某种原因,它会打印我的调试代码两次,即使该函数在第一次调用时应该满足我的基本情况。

它被打印了两次,因为if 没有按照您的想法执行。第一个 if 测试为真,因此 等于零! 被打印出来。之后,调用三角形函数的递归调用。测试再次为真 (-1 等于零! 再次打印。最后,你会得到一个错误,因为nthcdr 函数是用-1 调用的。我强烈推荐你一个好的 lisp 调试器。 Lispworks 的那个不错。

老实说,我不明白你试图用你的代码实现什么的逻辑。所以我写了我的:

(defun generate-level (l &optional (result))
  "given a list l that represents a triangle level, it generates the next level"
  (if (null l) result
    (if (null result)
        (generate-level l (list (car (last l))))
      (generate-level (cdr l) (append result 
                                      (list (+ (car l) 
                                               (car (last result)))))))))


(defun bell (levels &optional (l))
  "generate a bell triangle with the number of labels given by the first parameter"
  (unless (zerop levels)
        (let ((to-print (if (null l) (list 1) (generate-level l))))
          (print to-print)
          (bell (1- levels) to-print))))

了解实现的事项:

  1. &amp;optional (parameter):这个参数是可选的,默认为nil。
  2. append 连接两个列表。我用它来插入列表的后面。
  3. let ((to-print x)) 创建一个名为to-print 的新变量绑定(局部变量)并初始化为x
  4. 我差点忘了提到if 在普通 lisp 中的工作原理: (if (= x 1) y z) 表示如果 x 等于 1 则返回 y,否则返回 z

现在,如果您调用该函数来创建一个 7 层的贝尔三角形:

CL-USER 9 > (bell 7)

(1) 
(1 2) 
(2 3 5) 
(5 7 10 15) 
(15 20 27 37 52) 
(52 67 87 114 151 203) 
(203 255 322 409 523 674 877) 
NIL

使用适当的填充打印会更好,如下所示:

                    1
                 1     2
              2     3     5
           5     7    10    15
       15    20    27    37    52
    52    67    87   114   151   203
203   255   322   409   523   674   877

但我把它作为练习留给读者。

【讨论】:

  • 代码上的一些小问题:BELL 中似乎有一个不必要的AND。您还应该使用UNLESS 而不是(WHEN (NOT ...)),并且(IF (NULL L) ...) 可以移动到LET 绑定中,因此您不需要重复代码。也许也可以使用COND,而不是在GENERATE-LEVEL 中嵌套IFs。
  • @jkiiski 谢谢!我已经有一段时间没有用 Lisp 编码了,我已经生疏了。
  • @FrankS101 感谢您的深入解释和解决方案。我最终弄清楚了 if 条件的工作方式。这是我从命令式编程到函数式编程的第一次跳跃,所以我有点挣扎。我只需要第 n 个铃号而不是整个三角形,这是实现这一目标最简单的方法,所以我选择了这种方法。
【解决方案2】:

您的 if 没有任何效果。它们被评估并产生结果,但随后您将它们丢弃。就像

(defun abc ()
  'a
  'b
  'c)

将评估 'a'b 以生成符号 ab,然后评估'c 生成符号 c,然后将其返回。在

的情况下
(if(<= i 0)
        (write "equals zero!")  ; then 
        (reverse nL)            ; else
    )

您正在比较 i 是否小于或等于零,如果是,则打印为零,如果不是,则(非破坏性地)反转 nL 并丢弃结果。然后通过调用triangle 来完成该函数。当 i 小于或等于零时,您可能想要 return 反转的 nL。请改用 cond,因为您可以有多种正文形式,例如:

(cond
  ((<= i 0) (write ...) (reverse nL))
  (t (triangle ...)))

您还可以使用 ifprogn 对表单进行分组:

(if (<= i 0) 
  (progn 
    (write ...)
    (reverse nL))
  (triangle ...))

您的其他功能也有同样的问题。如果您想在第一种情况下返回值,则需要使用实际返回它们的表单。例如:

(if (< n 1)
    (list 1)
    (if (= n 1)
        (last l)
        (bell #| ... |#)))

更惯用的是 cond,并使用 list 而不是 l,这看起来很像 1

(cond
  ((< n 1) (list 1))
  ((= n 1) (last list))
  (t (bell #| ... |#)))

【讨论】:

    【解决方案3】:

    谢谢大家的解释。我最终到达了下面的代码。我意识到 if 块的工作原理类似于..

    (if(条件)(执行语句)(否则执行该语句))

    (defun bell(l n)
        (if (< n 2)(last l)
            (bell (triangle l (last l) 0) (- n 1))
        )
    )
    (defun triangle(pL nL i)
        (if(= i (list-length pL)) nL
            (triangle pL (append  nL (list (+ (nth  i pL) (nth i nL)))) (+ i 1))
        )  
    )
    (write (bell (list 1) 10))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-22
      • 2013-10-11
      • 2015-03-26
      • 1970-01-01
      • 1970-01-01
      • 2021-10-03
      相关资源
      最近更新 更多