[英]Printing a string in Common Lisp, after concatening function format with recursion
我正在嘗試從Paul Graham學習Common Lisp閱讀Ansi Common Lisp並使用EEC325 課程評論和運行測試功能和講座。 我用粘液和SBCL設置了Emacs
問題在於第3章練習8說:
定義一個獲取列表並以點表示法打印的函數:
> (showdots ' ( abc)) (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)嘗試
(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))))))
有了這個結果
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
所以我真的很想了解這里發生的事情,也許是愚蠢的事情,但我不明白。
謝謝你的時間
你產生字符串的原始函數實際上非常接近。 問題是生成字符串的函數不應該打印字符串,如果它將被遞歸調用,因為你不希望打印中間字符串。 您可以進行的一個非常簡單的更改是使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))
CL-USER> (show-dots '(a b c))
(A . (B . (C . NIL)))
NIL
另一種方法是使用可選參數來指示是否應該打印或返回字符串,並且它可以默認為打印,但在遞歸情況下,您將返回它。 實際上,由於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)))))
CL-USER> (show-dots '(a b c))
(A . (B . (C . NIL)))
NIL
也就是說,這兩個實現最終都會創建一堆中間字符串,然后將它們連接在一起。 這不是對空間的有效利用。 在遍歷正在打印的對象時寫入流可能會更好。 也許最直接的方法是處理括號的格式並點自己。 這將導致一個或多或少像這樣的解決方案(它返回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)))))
CL-USER> (print-dotted '(a b c))
(A . (B . (C . NIL)))
;=> NIL
現在, 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)))
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
FORMAT
是一個功能。 首先評估參數。 (princ 1)
得到評估。 它打印1
並返回1
。 然后評估(princ 2)
。 它打印2
並返回2
。 使用已計算的參數調用函數FORMAT
: t
, "(~a ~a)"
, 1
和2
。 它打印(1 2)
並返回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)))
注意 :生成字符串的原始遞歸show-dots
不是一個好主意。 為什么? 因為它以遞歸方式匯集字符串並可能產生大量垃圾......
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))"
所有這些對FORMAT
調用都在分配新的字符串......
更好:
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))"
一般認為是流輸出,而不是字符串連接。
如果返回一個字符串實際上是問題規范的一部分,那么使用CONCATENATE 'STRING
。 如果沒有,請不要打擾它,只需堅持使用FORMAT T
並打印到控制台即可。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.