【问题标题】:Trying to print a triangle recursively in lisp尝试在 lisp 中递归打印三角形
【发布时间】:2019-10-29 16:47:28
【问题描述】:

尝试在 lisp 中递归地打印三角形。我得到一个溢出,但我不知道从哪里开始。请注意,我是 Lisp 编程的新手。

(defun triangle (n)
    (if (not (oddp n))(progn 
        (print "This is not an odd integer")
        (return-from triangle n)))   
    (if (< n 1) '())
            (setf lst (cons (car(list n)) (triangle (- n 2))))
    (print lst))

(三角形 7)

【问题讨论】:

  • 您确实需要正确缩进和格式化您的代码。如果没有正确的代码格式和缩进,成功的 Lisp 编程机会很小。还要确保不要使用制表符,而是使用空格来缩进。

标签: arrays recursion common-lisp clisp cons


【解决方案1】:

让我们回顾一下您的代码的某些部分。

(if (not (oddp n)) (progn (print ...) (return-from ...)))

当您有一个只有一个分支的if 时,请考虑使用whenunless,尤其是 如果您在子表单中输入progn。例如,您可以通过切换摆脱prognwhen

(when (not (oddp n))
  (print ...)
  (return-from ...))

(when (not ...)) 表达式与(unless ...) 相同:

(unless (oddp n)
  ...)

在这里,您打印一条错误消息,并从函数返回。 Common Lisp 有针对这些用例的例外。 你通常会打电话给(error "Not an odd integer: ~d" n), 但在这里你可以依赖assert 的默认行为,并且 将整个检查替换为:

(assert (oddp n))

如果你用 2 来尝试,你会得到一个类似这样的错误消息:

The assertion (ODDP N) failed with N = 2.

这对于测试来说已经足够了。

那么,你有以下表达式:

(cons (car (list n)) (triangle (- n 2)))

当你写(list e1 e2 .. en)时,就好像你写了:

(cons e1 (cons e2 (... (cons en nil))))

在您的情况下,这意味着 (list n) 与:

(cons n nil)

但是既然你做了以下事情:

(car (cons n nil))

实际上,您只是为了访问n 而分配一个单元并丢弃它。 整个表达式可以替换为n

第三,您还在lst 上使用setf,其中lst 是一个未定义的变量。 在大多数实现上(但实际上这种行为是未指定的),这将设置全局绑定 对于lst,从函数内部设置全局变量是一种不好的做法。 您可以改用let

(let ((lst (cons n (triangle (- n 2)))))
  (print lst))

但变量只使用一次,你也可以内联它:

(print (cons n (triangle (- n 2))))

最后,你有:

(defun triangle (n)
  (assert (oddp n))
  (if (< n 1)
      ()
      (print (cons n (triangle (- n 2))))))

这是风格问题,但请记住,在其一个分支中返回 nilif 通常可以替换 通过whenunless(此处为unless)。有些人不喜欢这样,宁愿不依赖nil 的返回值whenunless。 无论如何,让我们测试一下:

(triangle 7)

这给了:

(1)
(3 1)
(5 3 1)
(7 5 3 1)

注意列表是如何倒退的。 您可以尝试解决问题的一种方法是将(print ...) 替换为(print (reverse ...)),但这不起作用。你知道为什么吗?

相反,让我们向后构建列表,这需要从 1 开始计数,直到达到 n

(defun triangle (n &optional (c 1))
  (assert (oddp n))
  (when (<= c n)
    (print
      (cons c (triangle n (+ c 2))))))

由于第二个参数是可选的,我们可以像以前一样调用它:

(triangle 7)

但是现在,输出是:

(7)
(5 7)
(3 5 7)
(1 3 5 7)

【讨论】:

  • 哇。我真的希望 OP 花时间研究这个答案。
【解决方案2】:

括号错误!根据您的缩进,我相信您想要以下内容:

(if (< n 1) '())
    (setf ...

成为一个 if-then-else,其中setf 在 else 分支中。为此,它应该如下所示:

(if (< n 1) '()
    (setf ...

在当前设置中,始终评估 setf

【讨论】:

    【解决方案3】:

    一个有用的策略是将构建三角形与显示它分开。 这样,您无需更改生成三角形的代码即可更改打印格式。

    让我们选择将三角形表示为列表列表 这样create-triangle 7 =&gt; ((1) (1 3) (1 3 5) (1 3 5 7))。 以下实现生成所需的三角形:

    (defun create-triangle (n)
      (labels ((aux (x acc)
                 (if (> x n)
                     acc
                     (let* ((hd (car acc))
                            (new (append hd (list x))))
                       (aux (+ x 2) (cons new acc))))))
        (when (and (plusp n) (oddp n))
          (reverse (aux 1 nil)))))
    

    注意事项:Lisp 是一种面向表达式的语言 因此,通常不需要 return like 语句。

    辅助函数aux接受一个整数x和 列表acc。如果x 大于n,则返回 acc 否则,我们创建 2 个局部变量:hd 保存第一个列表 在我们的累加器中找到:这将是最近的一行 计算。 new 是通过附加当前数字 x 形成的 到hd。例如,如果acc = ((1 3 5) (1 3) (1))x = 7,然后是 hd = (1 3 5)new =(1 3 5 7). Having made this computation, we call aux with the newx bound tox+2which is 7 in out example, andaccbound to ((1 3 5 7) (1 3 5) (1 3 ) (1)). This way we build the triangle in reverse. The functioncreate-triangle, first checks thatnis both positive and odd, and when that is satisfied, it returns the reverse of the triangle built by aux. Thus we get((1) (1 3) (1 3 5) (1 3 5 7))in our example. Ifn`不是正数和奇数,它只返回空列表。 按照 Coredump 的说明,您可以更改它以进行错误检查。

    现在我们有了三角形,剩下的就是打印三角形了。 我们用这个函数来做到这一点:

    (defun print-triangle (xss)
      (when xss
        (format t "~&~{~a ~}" (car xss))
        (print-triangle (cdr xss))))
    

    print-triangle 的有趣部分是format 表达式。 t 表示输出为stdout,通常为 屏幕,在控制字符串中; ~&amp; 表示:确保我们在一个新行上,并且 ~{~a ~} 打印列表的内容,它们之间有一个空格, (car xss) 是格式参数。

    现在我们可以把两个函数放在一起

    (defun triangle (n)
      (print-triangle (create-triangle n)))
    

    快速测试:

    CL-USER> (triangle 19)
    1
    1 3
    1 3 5
    1 3 5 7
    1 3 5 7 9
    1 3 5 7 9 11
    1 3 5 7 9 11 13
    1 3 5 7 9 11 13 15
    1 3 5 7 9 11 13 15 17
    1 3 5 7 9 11 13 15 17 19
    NIL
    

    【讨论】:

      【解决方案4】:

      将问题分解成更小的部分始终是一种很好的做法,这种方法还可以使您的代码更加模块化,因此您可以更安全、更灵活地更改部分代码。

      首先,您可以定义一个小函数来将三角形的每一行收集为一个列表(由于您要求递归解决方案,因此这也是递归定义的):

      (defun upwards-range (x y &optional (by 2))
        "Returns a list containing numbers from X to Y
      by BY steps."
         (if (>= x y)
            (list y)
            (cons x (upwards-range (+ x by) y by))))
      
      (upwards-range 1 7) ; => (1 3 5 7)
      

      第二次创建一个收集三角形所有行的函数。请注意,在这里我隐含地期望参数N 是一个奇数。在下一个函数show-triangle 中,如果不是这样,您可以进行断言并返回错误。

      (DEFUN TRIANGLE (N)
        "COLLECTS TRIANGULAR LISTS."
        (IF (= -1 N)
            NIL
            (APPEND (TRIANGLE (- N 2))
                (LIST (UPWARDS-RANGE 1 N)))))
      
      (TRIANGLE 7) ; => ((1) (1 3) (1 3 5) (1 3 5 7))
      

      以及打印三角形的功能:

      (defun print-triangle (N)
        "Makes sure that N is an odd number and prints the 
      rows of the triangle if so."
        (assert (oddp N) (N) "~S is an even number. Please supply an odd onve" N)
        (let ((rows (triangle N)))
          (dolist (row rows)
            (print row))))
      
      (print-triangle 7)
      

      返回:

      (1) 
      (1 3) 
      (1 3 5) 
      (1 3 5 7) 
      

      【讨论】:

        猜你喜欢
        • 2021-02-12
        • 1970-01-01
        • 2020-08-14
        • 1970-01-01
        • 1970-01-01
        • 2016-02-05
        • 1970-01-01
        • 1970-01-01
        • 2022-12-20
        相关资源
        最近更新 更多