1. 程式人生 > >ANSI Common Lisp 第二章參考答案

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