Common lisp 使用递归连接函数格式后,在Common Lisp中打印字符串

Common lisp 使用递归连接函数格式后,在Common Lisp中打印字符串,common-lisp,sbcl,Common Lisp,Sbcl,我正在尝试学习公共Lisp,阅读Paul Graham提供的Ansi公共Lisp,并使用EEC325评论和运行测试函数以及讲座。我用slime和SBCL设置Emacs 问题出在第三章练习8上,练习说: 定义一个函数,该函数接受列表并以点表示法打印: > (showdots ' ( a b c)) (A . (B . (C . NIL))) NIL 我制作了以下函数,结果是一个字符串,它在案例中运行良好,但不打印,这是本练习的主要目标 (defun show-dots (lst) (c

我正在尝试学习公共Lisp,阅读Paul Graham提供的Ansi公共Lisp,并使用EEC325评论和运行测试函数以及讲座。我用slime和SBCL设置Emacs

问题出在第三章练习8上,练习说:

定义一个函数,该函数接受列表并以点表示法打印:

> (showdots ' ( a b c))
(A . (B . (C . NIL)))
NIL
我制作了以下函数,结果是一个字符串,它在案例中运行良好,但不打印,这是本练习的主要目标

(defun show-dots (lst)
  (cond
    ((atom lst) (format nil  "~A" lst ))
    ((consp lst) (format nil "(~A . ~A)"
             (show-dots (car lst))
             (show-dots (cdr lst))))))
问题是它生成字符串而不是打印字符串,但它可以工作

CS325-USER> (SHOW-DOTS '(A B C))
"(A . (B . (C . NIL)))"
CS325-USER>  (SHOW-DOTS '(A (B C)))
"(A . ((B . (C . NIL)) . NIL))"
CS325-USER>  (SHOW-DOTS '(A . B))
"(A . B)"
CS325-USER>  (SHOW-DOTS NIL)
"NIL"
CS325-USER>  (SHOW-DOTS '(NIL))
"(NIL . NIL)"
测试期望打印结果,所以实际上它失败了,但很明显,打印结果时会出现问题

 (run-tests show-dots)
SHOW-DOTS: (SHOW-DOTS '(A B C)) failed: 
Should have printed "(A . (B . (C . NIL)))" but saw ""
SHOW-DOTS: (SHOW-DOTS '(A (B C))) failed: 
Should have printed "(A . ((B . (C . NIL)) . NIL))" but saw ""
SHOW-DOTS: (SHOW-DOTS '(A . B)) failed: 
Should have printed "(A . B)" but saw ""
SHOW-DOTS: (SHOW-DOTS NIL) failed: 
Should have printed "NIL" but saw ""
SHOW-DOTS: (SHOW-DOTS '(NIL)) failed: 
Should have printed "(NIL . NIL)" but saw ""
SHOW-DOTS: 0 assertions passed, 5 failed. 
所以我想我必须做的唯一一件事就是在创建后打印这个字符串,但是这个东西不起作用,我不明白为什么

1) 尝试

有了这个结果

CS325-USER> (SHOW-DOTS '(A B C))
ABCNIL(NIL . NIL)(NIL . NIL)(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS '(A (B C)))
ABCNIL(NIL . NIL)(NIL . NIL)NIL(NIL . NIL)(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS '(A . B))
AB(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS NIL)
NIL
NIL
CS325-USER>  (SHOW-DOTS '(NIL))
NILNIL(NIL . NIL)
NIL
所以我说,好吧,这是疯狂的第一个字符串和打印它

(defun show-dots (lst)
 (format t (cond
    ((atom lst) (format nil  "~A" lst ))
    ((consp lst) (format nil "(~A . ~A)"
             (show-dots (car lst))
             (show-dots (cdr lst)))))))
但结果并不正确

CS325-USER> (SHOW-DOTS '(A B C))
ABCNIL(NIL . NIL)(NIL . NIL)(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS '(A (B C)))
ABCNIL(NIL . NIL)(NIL . NIL)NIL(NIL . NIL)(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS '(A . B))
AB(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS NIL)
NIL
NIL
CS325-USER>  (SHOW-DOTS '(NIL))
NILNIL(NIL . NIL)
NIL
所以我说,好吧,让我们创建一个局部变量,把字符串放在那里,然后打印出来,但是它不再工作了

(defun show-dots (lst)
  (let ((str (cond
         ((atom lst) (format nil  "~A" lst ))
         ((consp lst) (format nil "(~A . ~A)"
                  (show-dots (car lst))
                  (show-dots (cdr lst)))))))
    (format t str)))
结果是不正确的

CS325-USER> (SHOW-DOTS '(A B C))
ABCNIL(NIL . NIL)(NIL . NIL)(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS '(A (B C)))
ABCNIL(NIL . NIL)(NIL . NIL)NIL(NIL . NIL)(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS '(A . B))
AB(NIL . NIL)
NIL
CS325-USER>  (SHOW-DOTS NIL)
NIL
NIL
CS325-USER>  (SHOW-DOTS '(NIL))
NILNIL(NIL . NIL)
NIL
所以我真的很想了解这里发生了什么,也许是一些愚蠢的事情,但我不明白重点


感谢您的宝贵时间

如果返回字符串确实是问题规范的一部分,那么请使用
CONCATENATE'string
。如果没有,甚至不用麻烦了,只需坚持使用
格式t
并打印到控制台。

生成字符串的原始函数实际上非常接近。问题是,如果要递归调用字符串,则生成该字符串的函数不应同时打印该字符串,因为您不希望同时打印中间字符串。您可以做的一个非常简单的更改是将show dots函数的主体设置为创建字符串的内部辅助函数,然后在main函数中打印辅助函数的结果:

(defun show-dots (lst)
  (labels ((%show-dots (lst)
             (cond
               ((atom lst) (format nil  "~A" lst ))
               ((consp lst) (format nil "(~A . ~A)"
                                    (%show-dots (car lst))
                                    (%show-dots (cdr lst)))))))
    (write-string (%show-dots lst))
    nil))

另一种方法是使用可选参数来指示是否应打印或返回字符串,它可以默认为打印,但在递归情况下,您将返回它。事实上,由于format将tnil作为具有这些语义的输出参数,因此您可以使其非常隐蔽:

(defun show-dots (lst &optional (output t))
  ;; If OUTPUT is T (the default) then stuff will actually be printed,
  ;; and FORMAT returns NIL.  If OUTPUT is NIL (as it is in the
  ;; recursive calls), then FORMAT creates the string and returns it,
  (cond
    ((atom lst) (format output "~A" lst))
    ((consp lst) (format output "(~A . ~A)"
                         (show-dots (car lst) nil)
                         (show-dots (cdr lst) nil)))))

也就是说,这两种实现最终都会创建一组中间字符串,然后将它们连接在一起。这不是对空间的有效利用。最好在遍历正在打印的对象时写入流。也许最直接的方法是自己处理括号和圆点的格式。这将导致或多或少类似这样的解决方案(它返回nil,因为您给出的第一个示例就是这样做的):

现在,format函数实际上能够使用指令调用以格式字符串命名的其他函数。这意味着您可以这样做,我认为这有点优雅(我定义了一个新的包,只是为了说明tilde格式可以在其他包中查找符号;如果您在CL-USER中进行事件处理,您可以忽略它):


试着理解它的作用:

CL-USER 9 > (format t "(~a ~a)" (princ 1) (princ 2))
12(1 2)
NIL
格式
是一个函数。首先计算参数<代码>(原则1)得到评估。它打印
1
并返回
1
。然后计算
(原则2)
。它打印
2
并返回
2
。调用函数
FORMAT
时,将使用经过计算的参数:
t
“(~a~a)”
1
2
。它打印
(12)
,并返回
NIL

现在看看这个:

CL-USER 8 > (progn (princ "(")
                   (princ 1)
                   (princ " . ")
                   (princ 2)
                   (princ ")"))
(1 . 2)
")"
打印cons单元格时,只需重复使用上述内容:

CL-USER 10 > (defun princme (c)
               (if (consp c)
                   (progn
                     (princ "(")
                     (princme (car c))
                     (princ " . ")
                     (princme (cdr c))
                     (princ ")"))
                 (princ c)))
PRINCME

CL-USER 11 > (princme '(1 2 3))
(1 . (2 . (3 . NIL)))
注意:原始递归
显示点
生成字符串不是一个好主意。为什么?因为它递归地消耗字符串并可能产生大量垃圾

CL-USER 14 > (trace show-dots)
(SHOW-DOTS)

CL-USER 15 > (show-dots '((1 2) (3 (4 (5 6) ))))
0 SHOW-DOTS > ...
  >> LST : ((1 2) (3 (4 (5 6))))
  1 SHOW-DOTS > ...
    >> LST : (1 2)
    2 SHOW-DOTS > ...
      >> LST : 1
    2 SHOW-DOTS < ...
      << VALUE-0 : "1"
    2 SHOW-DOTS > ...
      >> LST : (2)
      3 SHOW-DOTS > ...
        >> LST : 2
      3 SHOW-DOTS < ...
        << VALUE-0 : "2"
      3 SHOW-DOTS > ...
        >> LST : NIL
      3 SHOW-DOTS < ...
        << VALUE-0 : "NIL"
    2 SHOW-DOTS < ...
      << VALUE-0 : "(2 . NIL)"
  1 SHOW-DOTS < ...
    << VALUE-0 : "(1 . (2 . NIL))"
  1 SHOW-DOTS > ...
    >> LST : ((3 (4 (5 6))))
    2 SHOW-DOTS > ...
      >> LST : (3 (4 (5 6)))
      3 SHOW-DOTS > ...
        >> LST : 3
      3 SHOW-DOTS < ...
        << VALUE-0 : "3"
      3 SHOW-DOTS > ...
        >> LST : ((4 (5 6)))
        4 SHOW-DOTS > ...
          >> LST : (4 (5 6))
          5 SHOW-DOTS > ...
            >> LST : 4
          5 SHOW-DOTS < ...
            << VALUE-0 : "4"
          5 SHOW-DOTS > ...
            >> LST : ((5 6))
            6 SHOW-DOTS > ...
              >> LST : (5 6)
              7 SHOW-DOTS > ...
                >> LST : 5
              7 SHOW-DOTS < ...
                << VALUE-0 : "5"
              7 SHOW-DOTS > ...
                >> LST : (6)
                8 SHOW-DOTS > ...
                  >> LST : 6
                8 SHOW-DOTS < ...
                  << VALUE-0 : "6"
                8 SHOW-DOTS > ...
                  >> LST : NIL
                8 SHOW-DOTS < ...
                  << VALUE-0 : "NIL"
              7 SHOW-DOTS < ...
                << VALUE-0 : "(6 . NIL)"
            6 SHOW-DOTS < ...
              << VALUE-0 : "(5 . (6 . NIL))"
            6 SHOW-DOTS > ...
              >> LST : NIL
            6 SHOW-DOTS < ...
              << VALUE-0 : "NIL"
          5 SHOW-DOTS < ...
            << VALUE-0 : "((5 . (6 . NIL)) . NIL)"
        4 SHOW-DOTS < ...
          << VALUE-0 : "(4 . ((5 . (6 . NIL)) . NIL))"
        4 SHOW-DOTS > ...
          >> LST : NIL
        4 SHOW-DOTS < ...
          << VALUE-0 : "NIL"
      3 SHOW-DOTS < ...
        << VALUE-0 : "((4 . ((5 . (6 . NIL)) . NIL)) . NIL)"
    2 SHOW-DOTS < ...
      << VALUE-0 : "(3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL))"
    2 SHOW-DOTS > ...
      >> LST : NIL
    2 SHOW-DOTS < ...
      << VALUE-0 : "NIL"
  1 SHOW-DOTS < ...
    << VALUE-0 : "((3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL)) . NIL)"
0 SHOW-DOTS < ...
  << VALUE-0 : "((1 . (2 . NIL)) . ((3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL)) . NIL))"
"((1 . (2 . NIL)) . ((3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL)) . NIL))"

通常从流输出的角度考虑,而不是字符串连接。

问题只是打印,但格式不起作用,或者我不知道如何正确执行。这一解释有很多帮助:-)这也确实有助于meI进行更新,更多地介绍您的原始代码,这实际上非常接近解决方案。我发布的第二个代码与您的几乎完全相同。谢谢您的解释。这真的让我了解了格式、流输出和连接的要点
CL-USER> (print-dotted '(a b c))
(A . (B . (C . NIL)))
;=> NIL
(defpackage ex
  (:use "COMMON-LISP"))

(in-package #:ex)

(defun dot-cons (stream object &rest args)
  (declare (ignore args))
  (if (consp object)
      (format stream "(~/ex:dot-cons/ . ~/ex:dot-cons/)" (car object) (cdr object))
      (write object :stream stream)))
CL-USER> (format t "~/ex:dot-cons/" '(a b c))
(A . (B . (C . NIL)))
;=> NIL
CL-USER 9 > (format t "(~a ~a)" (princ 1) (princ 2))
12(1 2)
NIL
CL-USER 8 > (progn (princ "(")
                   (princ 1)
                   (princ " . ")
                   (princ 2)
                   (princ ")"))
(1 . 2)
")"
CL-USER 10 > (defun princme (c)
               (if (consp c)
                   (progn
                     (princ "(")
                     (princme (car c))
                     (princ " . ")
                     (princme (cdr c))
                     (princ ")"))
                 (princ c)))
PRINCME

CL-USER 11 > (princme '(1 2 3))
(1 . (2 . (3 . NIL)))
CL-USER 14 > (trace show-dots)
(SHOW-DOTS)

CL-USER 15 > (show-dots '((1 2) (3 (4 (5 6) ))))
0 SHOW-DOTS > ...
  >> LST : ((1 2) (3 (4 (5 6))))
  1 SHOW-DOTS > ...
    >> LST : (1 2)
    2 SHOW-DOTS > ...
      >> LST : 1
    2 SHOW-DOTS < ...
      << VALUE-0 : "1"
    2 SHOW-DOTS > ...
      >> LST : (2)
      3 SHOW-DOTS > ...
        >> LST : 2
      3 SHOW-DOTS < ...
        << VALUE-0 : "2"
      3 SHOW-DOTS > ...
        >> LST : NIL
      3 SHOW-DOTS < ...
        << VALUE-0 : "NIL"
    2 SHOW-DOTS < ...
      << VALUE-0 : "(2 . NIL)"
  1 SHOW-DOTS < ...
    << VALUE-0 : "(1 . (2 . NIL))"
  1 SHOW-DOTS > ...
    >> LST : ((3 (4 (5 6))))
    2 SHOW-DOTS > ...
      >> LST : (3 (4 (5 6)))
      3 SHOW-DOTS > ...
        >> LST : 3
      3 SHOW-DOTS < ...
        << VALUE-0 : "3"
      3 SHOW-DOTS > ...
        >> LST : ((4 (5 6)))
        4 SHOW-DOTS > ...
          >> LST : (4 (5 6))
          5 SHOW-DOTS > ...
            >> LST : 4
          5 SHOW-DOTS < ...
            << VALUE-0 : "4"
          5 SHOW-DOTS > ...
            >> LST : ((5 6))
            6 SHOW-DOTS > ...
              >> LST : (5 6)
              7 SHOW-DOTS > ...
                >> LST : 5
              7 SHOW-DOTS < ...
                << VALUE-0 : "5"
              7 SHOW-DOTS > ...
                >> LST : (6)
                8 SHOW-DOTS > ...
                  >> LST : 6
                8 SHOW-DOTS < ...
                  << VALUE-0 : "6"
                8 SHOW-DOTS > ...
                  >> LST : NIL
                8 SHOW-DOTS < ...
                  << VALUE-0 : "NIL"
              7 SHOW-DOTS < ...
                << VALUE-0 : "(6 . NIL)"
            6 SHOW-DOTS < ...
              << VALUE-0 : "(5 . (6 . NIL))"
            6 SHOW-DOTS > ...
              >> LST : NIL
            6 SHOW-DOTS < ...
              << VALUE-0 : "NIL"
          5 SHOW-DOTS < ...
            << VALUE-0 : "((5 . (6 . NIL)) . NIL)"
        4 SHOW-DOTS < ...
          << VALUE-0 : "(4 . ((5 . (6 . NIL)) . NIL))"
        4 SHOW-DOTS > ...
          >> LST : NIL
        4 SHOW-DOTS < ...
          << VALUE-0 : "NIL"
      3 SHOW-DOTS < ...
        << VALUE-0 : "((4 . ((5 . (6 . NIL)) . NIL)) . NIL)"
    2 SHOW-DOTS < ...
      << VALUE-0 : "(3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL))"
    2 SHOW-DOTS > ...
      >> LST : NIL
    2 SHOW-DOTS < ...
      << VALUE-0 : "NIL"
  1 SHOW-DOTS < ...
    << VALUE-0 : "((3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL)) . NIL)"
0 SHOW-DOTS < ...
  << VALUE-0 : "((1 . (2 . NIL)) . ((3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL)) . NIL))"
"((1 . (2 . NIL)) . ((3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL)) . NIL))"
CL-USER 16 > (with-output-to-string (*standard-output*)
               (princme '((1 2) (3 (4 (5 6) )))))
"((1 . (2 . NIL)) . ((3 . ((4 . ((5 . (6 . NIL)) . NIL)) . NIL)) . NIL))"