ANSI Common Lisp 第二章參考答案
習題連結:
https://acl.readthedocs.io/en/latest/zhCN/ch2-cn.html
下面是我寫的答案,僅供參考
1
描述下列表達式求值之後的結果:
(a) (+ (- 5 1) (+ 3 7))
(b) (list 1 (+ 2 3))
(c) (if (listp 1) (+ 1 2) (+ 3 4))
(d) (list (and (listp 3) t) (+ 1 2))
- (a) 14
- (b) (1 5)
- © 7
- (d) (nil 3)
2
給出 3 種不同表示 (a b c) 的 cons 表示式 。
(cons 'a '(b c))
(cons 'a (cons 'b '(c)))
(cons 'a (cons 'b (cons 'c')))
3
使用 car 與 cdr 來定義一個函式,返回一個列表的第四個元素。
(defun my-fourth (x)
(car (cdr (cdr (cdr x))))
)
4
定義一個函式,接受兩個實參,返回兩者當中較大的那個。
(defun my-greater (x y)
(if (> x y) x y)
)
5
這些函式做了什麼?
(a) (defun enigma (x) (and (not (null x)) (or (null (car x)) (enigma (cdr x))))) (b) (defun mystery (x y) (if (null y) nil (if (eql (car y) x) 0 (let ((z (mystery x (cdr y)))) (and z (+ z 1))))))
a
該函式的作用:
判斷一個列表中是否有 nil 或 (),有就返回T,沒有就返回NIL
[46]> (enigma '(1 2 3 4 5))
NIL
[47]> (enigma '(1 2 nil 4 5))
T
[48]> (enigma '(1 2 3 () 5))
T
原始碼解析
(defun enigma (x) ;if x不為空 then (and (not (null x)) ;這個or的第一個括號裡,判斷第一個元素是不是null ;如果是整個函式就返回了,不會再遞迴下去 ;空表和nil都會被null函式視為true (or (null (car x)) (enigma (cdr x)) ) ) )
對 null 函式的試驗
[41]> (null ())
T
[42]> (null 1)
NIL
[43]> (null '(1 2))
NIL
[44]> (null nil)
T
b
函式作用:
x 是一個數字
y 是一個列表
計算 y 列表中,第一個等於 x 的元素前面總共有幾個元素
[51]> (mystery 1 '(1 2 3 4 5 6 7))
0
[56]> (mystery 1 '(2 3 4 5 6 7 1 1 1))
6
[57]> (mystery 1 '(2 3 4 5 1 6 7 1 1))
4
[58]> (mystery 1 '(2 3 4 5 6 7))
NIL
這個函式看的我頭都大了,最後結合試驗結果才看明白。還是考驗對遞迴函式的理解能力。
(defun mystery (x y)
(if (null y)
nil
;如果第一個元素等於x,則不再往下遞迴,返回0
(if (eql (car y) x)
0
;前面快取起來的遞迴呼叫棧(不等於x的元素),這裡會一一累加
(let ((z (mystery x (cdr y)) ))
(and z (+ z 1))
)
)
)
)
6
Q: 下列表達式, x 該是什麼,才會得到相同的結果?
(a) > (car (x (cdr '(a (b c) d))))
B
(b) > (x 13 (/ 1 0))
13
(c) > (x #'list 1 nil)
(1)
A:
a
car
[59]> (car (car (cdr '(a (b c) d))))
B
b
or
c
apply
這裡不能填funcall
, 因為funcall
會把最後的nil也做為引數傳給list函式,而apply只接受兩個引數: 函式和引數列表。
apply 接受一個函式和實參列表
7
Q: 只使用本章所介紹的操作符,定義一個函式,它接受一個列表作為實參,如果有一個元素是列表時,就返回真。
(defun answer-7 (x)
(if (null x)
nil
(or (listp (car x))
(answer-7 (cdr x))
)
)
)
這裡有個小細節,我第一版寫的是下面這樣的:
(defun answer-7 (x)
(or (listp (car x))
(answer-7 (cdr x))
)
)
但是這樣寫永遠都返回true, 我覺得很奇怪,正常的話,最後一個遞迴呼叫是 listp nil,
應該不會返回true啊,結果後面試驗了下,發現listp函式在傳入nil時居然返回T!!
[84]> (listp nil)
T
好吧,那隻能加上 if 判斷一下了。
8: 給出函式的迭代與遞迴版本
a. 接受一個正整數,並打印出數字數量的點。
;迭代版本
(defun answer-8a-iter (x)
(do ((i 1 (+ i 1)))
((> i x) 'done)
(format t ".")
)
)
;遞迴版本
(defun answer-8a-recursion (x)
(if (<= x 0)
'done
(progn
(format t ".")
(answer-8a-recursion (- x 1))
)
)
)
;說來慚愧,這兩個寫了好久,最後還是參考了第二章裡講迭代時的那個例子才寫出來,其實完全一樣,就稍微變形了一點。
b. 接受一個列表,並返回 a 在列表裡所出現的次數。
;迭代版本
(defun answer-8b-iter (lst)
(let ((times 0))
(dolist (obj lst)
(if (eql obj 'a)
(setf times (+ times 1))
)
)
times
)
)
;遞迴版本
(defun answer-8b-recursion (lst)
(if (null lst)
0
(+ (if (eql 'a (car lst)) 1 0)
(answer-8b-recursion (cdr lst))
)
)
)
9
一位朋友想寫一個函式,返回列表裡所有非 nil 元素的和。他寫了此函式的兩個版本,但兩個都不能工作。請解釋每一個的錯誤在哪裡,並給出正確的版本。
(a) (defun summit (lst)
(remove nil lst)
(apply #'+ lst))
(b) (defun summit (lst)
(let ((x (car lst)))
(if (null x)
(summit (cdr lst))
(+ x (summit (cdr lst))))))
解答:
a
因為remove不會改為lst的內容,只會把刪除nil的新列表以返回值的形式返回。
正確的做法應該是直接在apply裡接受remove的返回值,這裡注意不能用setf去改lst的值,因為這產生了副作用,不符合Lisp的“精神”。
(defun summit (lst)
(apply #'+ (remove nil lst))
)
b
很顯然b是用遞迴實現的,首先,我想不看題中錯誤程式碼自己實現一個。免得在錯誤的方向越陷越深。
(defun summit (lst)
(if (null lst)
0
(+ (if (numberp (car lst)) (car lst) 0)
(summit (cdr lst))
)
)
)
;上面這個實現能正常工作,但是重複呼叫了兩次 car lst, 不能忍,下面是看了題目的錯誤程式碼後受到啟發而修改的版本
(defun summit (lst)
(if (null lst)
0
(let ((x (car lst)))
(+ (if (numberp x) x 0)
(summit (cdr lst))
)
)
)
)
再去看題中的實現,錯誤百出:
- 沒有判斷lst是不是null,遞迴沒有退出條件。
- 判斷 x 時用的是 null,而不是numberp,這樣如果元素是個symbol,就會報錯,當然這裡可能出題人故意忽略這個問題的,但這確實是個bug.
- if 下面的 then 和 else 子句有重複程式碼,這個不影響功能
所以這個函式只要加上退出條件,就可以正常工作了:
; Fixed version
(defun summit (lst)
(if (null lst)
0;這裡不能返回nil,否則下面+的時候會報錯
(let ((x (car lst)))
(if (null x)
(summit (cdr lst))
(+ x (summit (cdr lst)))
)
)
)
)
以上就是所有第二章的習題了,做完後有點入門的感覺了,起碼獲得了一點Lisp的程式設計體驗。
THE END