ANSI Common Lisp Chapter 2
Chapter 2 總結 (Summary)
- Lisp 是一種交互式語言。如果你在頂層輸入一個表達式, Lisp 會顯示它的值。
- Lisp 程序由表達式組成。表達式可以是原子,或一個由操作符跟著零個或多個實參的列表。前序表示法代表操作符可以有任意數量的實參。
- Common Lisp 函數調用的求值規則: 依序對實參從左至右求值,接著把它們的值傳入由操作符表示的函數。 quote 操作符有自己的求值規則,它完封不動地返回實參。
- 除了一般的數據類型, Lisp 還有符號跟列表。由於 Lisp 程序是用列表來表示的,很輕松就能寫出能編程的程序。
- 三個基本的列表函數是 cons ,它創建一個列表; car ,它返回列表的第一個元素;以及 cdr ,它返回第一個元素之後的所有東西。
- 在 Common Lisp 裏, t 表示邏輯 真 ,而 nil 表示邏輯 假 。在邏輯的上下文裏,任何非 nil 的東西都視為 真 。基本的條件式是 if 。 and 與 or 是相似的條件式。
- Lisp 主要由函數所組成。可以用 defun 來定義新的函數。
- 自己調用自己的函數是遞歸的。一個遞歸函數應該要被想成是過程,而不是機器。
- 括號不是問題,因為程序員通過縮排來閱讀與編寫 Lisp 程序。
- 基本的 I/O 函數是 read ,它包含了一個完整的 Lisp 語法分析器,以及 format ,它通過字符串模板來產生輸出。
- 你可以用 let 來創造新的局部變量,用 defparameter 來創造全局變量。
- 賦值操作符是 setf 。它的第一個實參可以是一個表達式。
- 函數式編程代表避免產生副作用,也是 Lisp 的主導思維。
- 基本的叠代操作符是 do 。
- 函數是 Lisp 的對象。可以被當成實參傳入,並且可以用 lambda 表達式來表示。
- 在 Lisp 裏,是數值才有類型,而不是變量。
Chapter 2 習題 (Exercises)
1. 描述下列表達式求值之後的結果:
(a) (+ (- 5 1) (+ 3 7))
答案:14
(b) (list 1 (+ 2 3))
答案:(1 5)
(c) (if (listp 1) (+ 1 2) (+ 3 4))
答案:7
(d) (list (and (listp 3) t) (+ 1 2))
答案:(NIL 3)
2. 給出 3 種不同表示 (a b c) 的 cons 表達式 。
答案:
(cons ‘a ‘(b c))
(cons ‘a (cons ‘b ‘(c)))
(cons ‘a (cons ‘b (cons ‘c nil)))
3. 使用 car 與 cdr 來定義一個函數,返回一個列表的第四個元素。
答案:
(defun get-forth(lst)
(car (cdr (cdr (cdr lst)))))
4. 定義一個函數,接受兩個實參,返回兩者當中較大的那個。
答案:
(defun get-max(x y)
(if (< x y)
y
x))
5. 這些函數做了什麽?
(a)
(defun enigma (x)
(and (not (null x))
(or (null (car x))
(enigma (cdr x)))))
答案:判斷 x 列表中是否有 nil 元素
(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))))))
答案:查找 x 在列表 y 中的下標,如果沒有則為 nil
6. 下列表達式, x 該是什麽,才會得到相同的結果?
(a) > (car (x (cdr ‘(a (b c) d))))
B
答案:car
(b) > (x 13 (/ 1 0))
13
答案:or
(c) > (x #’list 1 nil)
(1)
答案:or ‘(1) 或 apply
7. 只使用本章所介紹的操作符,定義一個函數,它接受一個列表作為實參,如果有一個元素是列表時,就返回真。
答案:
非遞歸版本
(defun has-child-list (lst)
(let ((x nil))
(dolist (obj lst)
(setf x (or x (listp obj))))
x))
遞歸版本
(defun has-child-list-re (lst)
(if (null lst)
nil
(if (listp (car lst))
t
(has-child-list-re (cdr lst)))))
8. 給出函數的叠代與遞歸版本:
a. 接受一個正整數,並打印出數字數量的點。
答案:
非遞歸版本
(defun print-dots (n)
(do ((i 0 (+ i 1)))
((= i n ) ‘done)
(format t ".")))
遞歸版本
(defun print-dots-re (n)
(if (= n 0)
‘done
(progn
(format t ".")
(print-dots-re (- n 1)))))
b. 接受一個列表,並返回 a 在列表裏所出現的次數。
答案:
非遞歸版本:
(defun print-a-times (lst)
(let ((flag ‘a)(x 0))
(dolist (obj lst)
(setf x (+ x (if (eql obj flag) 1 0))))
x))
遞歸版本:
(defun print-a-times-re (lst)
(if (null lst)
0
(let ((flag ‘a))
(+ (if (eql flag (car lst)) 1 0)
(print-a-times-re (cdr lst))))))
9. 一位朋友想寫一個函數,返回列表裏所有非 nil 元素的和。他寫了此函數的兩個版本,但兩個都不能工作。請解釋每一個的錯誤在哪裏,並給出正確的版本。
(a)
(defun summit (lst)
(remove nil lst)
(apply #‘+ lst))
答案:因為 remove 並不會改變 lst 本身。正確的程序:
(defun summit (lst)
(let ((newlst (remove nil lst)))
(apply #‘+ newlst)))
(b)
(defun summit (lst)
(let ((x (car lst)))
(if (null x)
(summit (cdr lst))
(+ x (summit (cdr lst))))))
答案:因為遞歸沒有邊界退出分支。正確的程序:
(defun summit (lst)
(if (null lst)
0
(let ((x (car lst)))
(if (null x)
(summit (cdr lst))
(+ x (summit (cdr lst)))))))
ANSI Common Lisp Chapter 2