我正在尝试从Paul Graham学习Common Lisp阅读Ansi Common Lisp并使用EEC325 课程评论和运行测试功能和讲座.我用粘液和SBCL设置了Emacs
问题在于第3章练习8说:
定义一个获取列表并以点表示法打印的函数:
Run Code Online (Sandbox Code Playgroud)> (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))))))
Run Code Online (Sandbox Code Playgroud)
问题是它产生的字符串不会打印字符串,但它可以工作
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 Code Online (Sandbox Code Playgroud)
测试期望打印真的失败了,但显然打印结果时会出现问题
(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.
Run Code Online (Sandbox Code Playgroud)
所以我认为我必须要做的唯一事情是打印这个字符串,创建后但这些东西不起作用,我不明白为什么
1)尝试
(defun show-dots (lst)
(cond
((atom lst) (format t "~A" lst ))
((consp lst) (format t "(~A . ~A)"
(show-dots (car lst))
(show-dots (cdr lst))))))
Run Code Online (Sandbox Code Playgroud)
有了这个结果
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
Run Code Online (Sandbox Code Playgroud)
所以我说好的,这是疯狂的第一个字符串和打印它
(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)))))))
Run Code Online (Sandbox Code Playgroud)
但结果不正确
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
Run Code Online (Sandbox Code Playgroud)
所以我说好了让我们创建一个局部变量放在字符串并打印它,但它不再工作
(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)))
Run Code Online (Sandbox Code Playgroud)
结果不正确
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
Run Code Online (Sandbox Code Playgroud)
所以我真的很想了解这里发生的事情,也许是愚蠢的事情,但我不明白.
谢谢你的时间
你产生字符串的原始函数实际上非常接近.问题是生成字符串的函数不应该打印字符串,如果它将被递归调用,因为你不希望打印中间字符串.您可以进行的一个非常简单的更改是使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))
Run Code Online (Sandbox Code Playgroud)
CL-USER> (show-dots '(a b c))
(A . (B . (C . NIL)))
NIL
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用可选参数来指示是否应该打印或返回字符串,并且它可以默认为打印,但在递归情况下,您将返回它.实际上,由于format使用t和nil作为那些语义的输出参数,你可以使它非常偷偷摸摸:
(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)))))
Run Code Online (Sandbox Code Playgroud)
CL-USER> (show-dots '(a b c))
(A . (B . (C . NIL)))
NIL
Run Code Online (Sandbox Code Playgroud)
也就是说,这两个实现最终都会创建一堆中间字符串,然后将它们连接在一起.这不是对空间的有效利用.在遍历正在打印的对象时写入流可能会更好.也许最直接的方法是处理括号的格式并点自己.这将导致一个或多或少像这样的解决方案(它返回nil,因为这是你给出的第一个例子):
(defun print-dotted (object &optional (stream *standard-output*))
"Print the object as usual, unless it is a cons, in which case
always print it in dotted notation. Return NIL."
(prog1 nil
(cond
;; write non-conses with WRITE
((not (consp object))
(write object :stream stream))
;; write the "(" " . " and ")" by hand,
;; and call print-dotted recursively for
;; the car and the cdr.
(t (write-char #\( stream)
(print-dotted (car object) stream)
(write-string " . " stream)
(print-dotted (cdr object) stream)
(write-char #\) stream)))))
Run Code Online (Sandbox Code Playgroud)
CL-USER> (print-dotted '(a b c))
(A . (B . (C . NIL)))
;=> NIL
Run Code Online (Sandbox Code Playgroud)
现在,format函数实际上能够使用tilde slash指令在格式字符串中命名时调用其他函数.这意味着你可以做这样的事情,我觉得它很优雅(我定义了一个新包,只是为了说明代字号格式可以在其他包中查找符号;如果你在CL-USER中进行事件处理,你可以忽略它):
(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)))
Run Code Online (Sandbox Code Playgroud)
CL-USER> (format t "~/ex:dot-cons/" '(a b c))
(A . (B . (C . NIL)))
;=> NIL
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
903 次 |
| 最近记录: |