HOME Common Lisp code 書き込む

P.Graham 著 ANSI Common LISP 練習問題解答


0. はじめに

P.Graham 著 ANSI Common LISP (日本語訳) は優れた LISP の入門書です。この本には練習問題が載っていますが、解答は付いていないので、 解答案を書いてみました。その問題までに出てきた本文の内容を使って解いていますので (特に最初の章では)ぎこちない点があります。あまりにぎこちない場合は別解をつけました。

自信の無いところもあります。 間違いなどが見つかりましたら takafumi@shido.info までご連絡ください。

1. 目次

  1. 第2章
  2. 第3章
  3. 第4章
  4. 第5章
  5. 第6章
  6. 第7章
  7. 第8章
  8. 第9章
  9. 第10章
  10. 第11章
  11. 第12章
  12. 第13章

2. 第2章


  1. "X => Y" は "X が評価されて Y になる”を表す。
      1. 5 => 5, 1 => 1, 3 => 3, and 7 => 7.
      2. (- 5 1) => 4 and (+ 3 7) => 10.
      3. (+ 4 10) => 14.
      1. 2 => 2 and 3 => 3.
      2. 1 => 1 and (+ 2 3)=> 5.
      3. (list 1 5) => (1 5).
      1. 1 => 1.
      2. (listp 1) => nil.
      3. 3 => 3, 4=> 4.
      4. (+ 3 4) => 7.
      1. 3 => 3, 1 => 1, 2 => 2.
      2. (listp 3) => nil, (+ 1 2) => 3.
      3. (and (listp 3) t) => nil.
      4. (list nil 3) => (nil 3).

    • (cons 'a '(b c))
    • (cons 'a (cons 'b '(c)))
    • (cons 'a (cons 'b (cons 'c nil)))

  2. (defun our-fourth (ls)
      (car (cdr (cdr (cdr ls)))))
    

  3. (defun our-max (a b)
      (if (> a b) a b))
    

    1. もし nil が x の要素ならば t を返す。
    2. y の要素 x の位置を返す。もしなければ nil を返す。

    1. car
    2. or
    3. apply

  4. (defun nest-p (ls)
      (if ls
          (or (listp (car ls))
              (nest-p (cdr ls)))))
    


    1. ;;反復
      (defun ndots-rep (n)
        (do ((i 0 (+ i 1))) ((= i n))
          (format t ".")))                      
      
      ;;再帰
      (defun ndots-rec (n)    
        (if (plusp n)
            (progn
               (format t ".")
               (ndots-rec (- n 1)))))
      

    2. ;;反復
      (defun a-rep (ls)
        (do ((ls1 ls (cdr ls1))
             (n 0 (+ n (if (eq (car ls1) 'a) 1 0))))
            ((not ls1) n)))
      
      ;;再帰
      (defun a-rec (ls) 
        (if ls
            (+ (if (eq (car ls) 'a) 1 0) (a-rec (cdr ls)))
          0))
      	

    1. (remove nil lst) の返り値を (apply #'+ lst) に渡していない。
      修正:
      (defun summit (lst)
         (apply #'+ (remove nil lst)))
            
    2. 終了条件が抜けている。
      修正:
      (defun summit (lst)
         (if lst
            (+ (or (car lst) 0) (summit (cdr lst)))
           0))
            

3. 第3章







  1. (defun new-union (a b)
      (let ((ra (reverse a)))
        (dolist (x b)
          (if (not (member x ra))
              (push x ra)))
        (reverse ra)))
      

  2. (defun occurrences (ls)
      (let ((acc nil))
        (dolist (obj ls)
          (let ((p (assoc obj acc)))
            (if p
                (incf (cdr p)) 
              (push (cons obj 1) acc))))
        (sort acc  #'> :key #'cdr)))
    

  3. '(a) と '((a) (b)) の要素を比較するためには以下の様にキー :test #'equal を指定する必要がある。

    (menber '(a) '((a) (b)) :test #'equal)



    1. (defun pos+ (ls)
        (pos+rec ls 0))
      (defun pos+rec (ls i)
        (if ls
            (cons (+ i (car ls)) (pos+rec (cdr ls) (+ i 1)))))
      
      ;;別解
      (defun pos+ (ls &optional (i 0))         ; we need an optional parameter 
        (if ls
            (cons (+ i (car ls)) (pos+ (cdr ls) (+ i 1)))))
      

    2. (defun pos+ (ls)
        (do ((ls1 ls (cdr ls1))
             (i 0 (+ i 1))
             (acc nil (cons (+ i (car ls1)) acc)))
            ((not ls1) (reverse acc))))
      

    3. (defun pos+ (ls)
        (let ((i -1))
          (mapcar #'(lambda (x)  (+ x (incf i))) ls)))
      


    1. (defun cons (x y)
        (let ((ls '(nil . nil)))
          (setf (cdr ls) x
      	  (car ls) y)
          ls))
      

    2. (defun list (&rest items)
        (list-0 items))
      (defun list-0 (ls)
        (if ls
            (cons (car ls) (list-0 (cdr ls)))))
      
      ;; 別解
      (defun list (&rest items)
        items)
      

    3. (defun length (ls)
        (if ls
            (+ 1 (length (car ls)))
          0))
      

    4. (defun menber (obj ls)
        (if ls
            (if (eql obj (cdr ls))
      	  ls
      	(menbar obj (car ls)))))
      
    ノート:&rest パラメータがありなら、list は別解のように簡単に定義できる。

  4. n-elts を以下の様に変える。
    (defun n-elts (elt n)
      (if (> n 1)
          (cons n elt)   ;instead of (list n elt)
          elt))
    

  5. (defun showdots (ls)
      (showdots-rec ls 0))
    
    (defun showdots-rec (ls i)
      (if ls
          (progn
    	(if (atom (car ls))
    	    (format t "(~A . " (car ls))
    	  (progn
    	    (format t "(")
    	    (showdots-rec (car ls) 0)
    	    (format t " . ")))
    	(showdots-rec (cdr ls) (+ 1 i)))
        (progn
          (format t "NIL")
          (dotimes (j i)
    	(format t ")")))))
    
    
    ;;別解
    (defun showdots (ls)
      (format t "~A" (showdots-rec ls)))
    (defun showdots-rec (ls)
      (if ls
          (if (atom ls)
    	  ls
    	(format nil "(~A . ~A)" (showdots-rec (car ls)) (showdots-rec (cdr ls))))))
    
    ノート:
    ネストしたリストにも対応。"format nil" を使ってよければ別解のようにすっきりかける。
    "format t" を使った方は副作用を使うといかにコードが醜くなるかの見本。

  6. (defparameter *net* '((a b c) (b a c) (c a b d) (d c)))
    
    (defun new-paths (path node net)
      (let (acc)
        (dolist (x (cdr (assoc node net)))
          (or (member x path)
              (push (cons x path) acc)))
        acc))
    
    (defun bfs-l (end queue net sol)
      (if  queue
          (let ((path (car queue)))
            (let ((node (car path)))
              (bfs-l  end
                      (append (cdr queue)(new-paths path node net))
                      net
                      (if (eql node end)  path sol))))
        (reverse sol)))
    
    (defun longest-path (start end net)
      (bfs-l end (list (list start)) net nil))
    
    実行例:
    >(longest-path 'a 'd *net*)
    (a b c d)
    

第4章


  1. (defun quarter-turn (a)
      (let ((d2 (array-dimensions a)))
        (let ((d (car d2))
              (b (make-array d2)))
          (let ((c (/ (- d 1) 2)))
            (do ((i 0 (+ i 1))) ((= i d))
              (do ((j 0 (+ j 1))) ((= j d))
                (setf (aref b (+ (* -1 (- j c)) c) i ) (aref a i j)))))
          b)))
    


    1. (defun copy-list (li)
        (reduce #'cons  li :from-end t :initial-value nil))
      

    2. (defun reverse (li)
        (reduce #'(lambda (x y) (cons y x)) li :initial-value nil))
      

  2. (defstruct tst item left middle right)
    


    1. (defun copy-tst (tst0)
        (if tst0
            (make-tst :item (tst-item tst0)
                      :left (copy-tst (tst-left tst0))
                      :middle (copy-tst (tst-middle tst0))
                      :right (copy-tst (tst-right tst0)))))
      

    2. (defun find-tst (obj tst0)
        (if tst0
            (or
             (eql obj (tst-item tst0))
             (find-tst obj (tst-left tst0))
             (find-tst obj (tst-middle tst0))
             (find-tst obj (tst-right tst0)))))
      

  3. ;; bst を format t で降順に表示する
    (defun show-bst(bst0)
      (when bst0
        (show-bst (node-r bst0))
        (format t "~A " (node-elt bst0))
        (show-bst (node-l bst0))))
    
    ;; 別解、降順のリストを返す。
    (defun bst->list (bst0)
      (labels ((rec (bst1 acc)
                 (if bst1
                     (rec (node-r bst1) (cons (node-elt bst1) (rec (node-l bst1) acc)))
                   acc)))
        (rec bst0 nil)))
    
  4. bst-adjoin は bst-insert に同じ。(see errata)


    1. (defun alist->hash (al &key (test #'eql))
        (let ((h (make-hash-table :test test)))
          (dolist (p al)
            (setf (gethash (car p) h) (cdr p)))
          h))
      

    2. (defun hash->alist (h)
        (let ((acc nil))
          (maphash #'(lambda (k v) (push (cons k v) acc)) h)
          acc))
      

第5章



    1. ((lambda (x) (cons x x)) (car y))
      

    2. ((lambda (w)
         ((lambda (y) (cons w y)) (+ w z))) (car x))
      

  1. (defun mystery (x y)
      (cond
       ((null y) nil)
       ((eql (car y) x) 0)
       (t (let ((z (mystery x (cdr y))))
    	(and z (+ z 1))))))
    

  2. (defun sq (x)
      (if (and (< 0 x 6) (integerp x))
          x
        (* x x)))
    

  3. (defun month-num (m y)
      (+ (case m
           (1 0)
           (2 31)
           (3 59)
           (4 90)
           (5 120)
           (6 151)
           (7 181)
           (8 212)
           (9 243)
           (10 273)
           (11 304)
           (12 334)
           (13 365))
         (if (and (> m 2) (leap? y)) 1 0)))
    

  4. (defun presedes (x v)
      (let (acc (v1 (concatenate 'vector v)))
        (dotimes (i (length v))
          (if (and (eql x (svref v i)) (< 0 i))
    	  (push (svref v (- i 1)) acc)))
        (remove-duplicates acc)))
    

  5. ;; 反復
    (defun intersperse (obj ls)
      (do ((ls1 (reverse (cdr ls)) (cdr ls1))
           (ls2 nil (cons obj (cons (car ls1) ls2))))
          ((not ls1) (cons (car ls) ls2))))
    
    ;; 再帰
    (defun intersperse (obj ls)
      (cons (car ls) (intersperse-rec obj (reverse (cdr ls)) nil)))
    
    (defun intersperse-rec (obj ls acc)
      (if ls
          (intersperse-rec obj (cdr ls) (cons obj (cons (car ls) acc)))
         acc))
    

    1. 反復
      (defun suc (ls)
        (let ((o (car ls)))
          (dolist (x (cdr ls) t)
            (if (= 1 (abs (- o x)))
                (setf o x)
              (return-from suc nil)))))
      
    2. do
      (defun suc (ls)
        (do ((ls1 (cdr ls) (cdr ls1))
             (o (car ls) (car ls1)))
            ((not ls1) t)
          (if (/= 1 (abs (- o (car ls1))))
         (return nil))))
      
    3. mapc and return
      (defun suc (ls)
        (block nil
          (let ((o (car ls)))
            (if (mapc #'(lambda (x)
                          (if (= 1 (abs (- o x)))
                              (setf o x)
                            (return nil)))
                      (cdr ls))
                t))))
      

  6. (defun extreme (v)
      (extreme-rec v 1 (length v) (svref v 0) (svref v 0)))
    
    (defun extreme-rec (v i n mn mx)
      (if (= i n) (values mn mx)
        (let ((x (svref v i)))
          (extreme-rec v (+ i 1) n (if (< x mn) x mn) (if (< mx x) x mx)))))
          

  7. ;;; use catch and throw
    (defun shortest-path (start end net)
      (if (eql start end)
          (list start)
        (catch 'found                          
          (bfs end (list (list start)) net))))
    
    (defun bfs (end queue net)
      (if (null queue)
          nil
        (let* ((path (car queue)) (node (car path)))
          (bfs end
               (append (cdr queue)
                       (new-paths path node net end))
               net))))
    
    (defun new-paths (path node net end)
      (mapcar #'(lambda (n)
                  (let ((path1 (cons n path)))
                    (if (eql n end)
                        (throw 'found (reverse path1))  
                      path1)))
              (cdr (assoc node net))))
    

第6章


  1. (defun tokens-6-1 (str &key (test #'constituent) (start 0))   ;変更部分  
      (let ((p1 (position-if test str :start start)))
        (if p1
            (let ((p2 (position-if-not test str :start p1)))
              (cons (subseq str p1 p2)
                    (if p2
                        (tokens-6-1 str :test test :start p2)))))))
    

  2. (defun bin-search-6-2 (obj vec &key (key #'identity) (test #'eql) (start 0) end)  ;変更部分
      (let ((len (or end (length vec))))                     
        (and (not (zerop len))
             (finder-6-2 obj vec start (- len 1) key test))))
    
    (defun finder (obj vec start end key test)
      (let ((range (- end start)))
        (if (zerop range)
            (if (funcall test obj (funcall key (aref vec start)))    ;変更部分
                (aref vec start))                                    ;変更部分
          (let ((mid (+ start (round (/ range 2)))))
             (let ((obj2 (funcall key (aref vec mid))))              ;変更部分
                (if (< obj obj2)
                    (finder obj vec start (- mid 1) key test)  
                   (if (> obj obj2)
                       (finder obj vec (+ mid 1) end key test)   
                       (aref vec mid))))))))                       ;;変更部分
    

  3. (defun n-av (&rest av)
      (length av))
    

  4. (defun most2 (f ls)
      (cond
       ((not ls) (values nil nil))
       ((not (cdr ls)) (values (car ls) nil))
       (t
        (let* ((n1 (first ls))
               (n2 (second ls))
               (v1 (funcall f n1))
               (v2 (funcall f n2)))
          (when (< v1 v2)
            (rotatef n1 n2)
            (rotatef v1 v2))
          (dolist (o (nthcdr 2 ls))
            (let ((vo (funcall f o)))
              (cond
               ((< v1 vo) (setf v2 v1 v1 vo n2 n1 n1 o))
               ((< v2 vo) (setf v2 vo n2 o))
               (t nil))))
          (values n1 n2)))))
    

  5. (defun remove-if-6-5 (f ls)
      (filter #'(lambda (x) (and (not (funcall f x)) x)) ls))
    

  6. (let (mx)
      (defun max-so-far (n)
        (if (or (not mx) (< mx n))
            (setf mx n)
           mx)))
    

  7. (let (prev)
      (defun greater-p (n)
        (prog1
           (and prev (< prev n))
           (setf prev n))))
    

  8. (let ((store (make-array 101)))
      (defun frugel (n)
        (or (svref store n)
            (setf (svref store n) (expensive n)))))
    

  9. (defun apply8 (&rest av)
      (let ((*print-base* 8))
        (apply #'apply  av)))
    

第7章


  1. (defun lines->list (file)
      (with-open-file (str file :direction :input)
        (do ((line (read-line str nil nil) (read-line str nil nil))
             (acc nil (cons line acc)))
            ((not line) (nreverse acc)))))
    

  2. (defun s->list (file)
      (with-open-file (str file :direction :input)
        (do ((s (read str nil nil) (read str nil nil))
             (acc nil (cons s acc)))
            ((not s) (nreverse acc)))))
    

  3. (defun remove-comments (fin fout &optional (cchar #\%))
      (with-open-file (s-in fin :direction :input)
        (with-open-file (s-out fout :direction :output)
          (do ((line (read-line s-in nil nil) (read-line s-in nil nil)))
              ((not line))
           (let ((cp (position cchar line)))
             (format s-out "~A~%"
                           (if cp (subseq line 0 cp) line)))))))
    

  4. (defun show-matrix (ar)
      (let ((size (array-dimensions ar)))
        (dotimes (i (first size))
          (dotimes (j (second size))
             (format t "   ~10,2F" (aref ar i j)))
          (terpri))))         
    

  5. (defun stream-subst (old new in out)
      (let* ((pos 0)
             (len (length old))
             (buf (new-buf len))
             (from-buf nil))
        (do ((c (read-char in nil :eof)
                (or (setf from-buf (buf-next buf))
                    (read-char in nil :eof))))
            ((eql c :eof))
          (cond ((or (char= #\+ (char old pos)) (char= c (char old pos))) ;変更部分
                 (incf pos)
                 (cond ((= pos len)            ; 3
                        (princ new out)
                        (setf pos 0)
                        (buf-clear buf))
                       ((not from-buf)         ; 2
                        (buf-insert c buf))))
                ((zerop pos)                   ; 1
                 (princ c out)
                 (when from-buf
                   (buf-pop buf)
                   (buf-reset buf)))
                (t                             ; 4
                 (unless from-buf
                   (buf-insert c buf))
                 (princ (buf-pop buf) out)
                 (buf-reset buf)
                 (setf pos 0))))
        (buf-flush buf out)))
    

  6. ;;; three wild cards;  %w, %d, and %a are used.
    ;;; %a, all character
    ;;; %w, a-zA-Z and 0-9
    ;;; %d, 0-9
    ;;; %%, % itself
    
    (defun parse-pattern (pat)
      (labels ((rec (i n ctrl acc)
                 (if (< i n)
                     (let* ((c (char pat i))
                            (ctrl-next (and (not ctrl) (char= c #\%))))
                       (rec (1+ i)
                            n
                            ctrl-next
                            (if ctrl-next
                                acc
                              (cons
                               (if ctrl
                                   (case c
                                     (#\a 'all)
                                     (#\w 'word)
                                     (#\d 'digit)
                                     (#\% #\%))
                                 c)
                               acc))))
                       (concatenate 'vector (nreverse acc)))))
               (rec 0 (length pat) nil nil)))
    
    
    (defun stream-subst (pat new in out)
      (let* ((pos 0)
        (old (parse-pattern pat))                  ;変更部分、 "old" はベクトル
             (len (length old))
             (buf (new-buf len))
             (from-buf nil))
        (do ((c (read-char in nil :eof)
                (or (setf from-buf (buf-next buf))
                    (read-char in nil :eof))))
            ((eql c :eof))
          (let ((c0 (svref old pos)))
       (cond ((or                                  ;変更部分
          (eq c0 'all)                             ;変更部分
          (and (eq c0 'word) (or (alpha-char-p c) (digit-char-p c)))      ;変更部分 
          (and (eq c0 'number) (digit-char-p c))   ;変更部分
          (char= c0 c))                            ;変更部分
              (incf pos)
              (cond ((= pos len)            ; 3
                (princ new out)
                (setf pos 0)
                (buf-clear buf))
               ((not from-buf)         ; 2
                (buf-insert c buf))))
             ((zerop pos)                   ; 1
              (princ c out)
              (when from-buf
           (buf-pop buf)
           (buf-reset buf)))
             (t                             ; 4
              (unless from-buf
           (buf-insert c buf))
              (princ (buf-pop buf) out)
              (buf-reset buf)
              (setf pos 0)))))
          (buf-flush buf out)))
    
    ノート:
    関数 parse-pattern を追加し、stream-subst に変更を加える。。 パターンにワイルドカード "%a", "%w", "%d", 及び "%%" (それぞれ、全ての文字、[0-9a-zA-Z]、[0-9]、及び #\% にマッチする) が使えるようにする。コードを見て分かるとおり、本体部分には変更を加える必要は無い。

第8章

  1. ありうる。パッケージが違う場合。

  2. "FOO" 3 byte
    'FOO  名前       3 byte
          パッケージ 4 byte
          変数       4 byte
          関数       4 byte
          属性リスト 4 byte
        ----------------------
               計    19 byte
    
  3. ほとんどの処理系でシンボルをサポートしているが ANSI 標準では文字列を引数として与えるように 定義されているから。 (see Common LISP Hyper spec
    こういう答えでいいのだろうか?”なぜ”という問いはもっと本質的な答えを要求しているのだろうか?

  4. (defun terminal (sy)
      (or (eq sy '|.|) (eq sy '|!|) (eq sy '|?|) (eq sy '|:|)))
    
    ;; Henley かどうか? 引数はファイル名
    (defun henleyp (fi)
      (let ((buffer (make-string maxword))
    	(pos 0) (nwls nil) (nw 0))
        (with-open-file (s fi :direction :input)
          (do ((c (read-char s nil :eof)
    	      (read-char s nil :eof)))
    	  ((eql c :eof))
    	(if (or (alpha-char-p c) (char= c #\'))
    	    (progn
    	      (setf (aref buffer pos) c)
    	      (incf pos))
    	  (progn
    	    (unless (zerop pos)
    	      (incf nw)
    	      (setf pos 0))
    	    (let ((p (punc c)))
    	      (when p
    		  (if (terminal p)
    		      (progn
    			(push nw nwls)
    			(setf nw 0))
    		    (incf nw))))))))
        (anal-cwlist nwls)))
    
    (defun hispos (x r mn n)
      (let ((p (truncate (- x mn) r)))
        (if (= p n) (- p 1) p)))
         
    (defun nstar (n)
      (make-string n :initial-element #\*))
    
      
    (defun anal-cwlist (cwls)
      (let ((mx (apply #'max cwls))
            (mn (apply #'min cwls))
            (a (make-array 5 :initial-element 0)))
        (if (< 60 mx)
            (progn
              (format t "more than 60 words in one sentence.~%")
              t)
          (let ((r (/ (- mx mn) 5)))
            (dolist (x cwls)
              (incf (aref a (hispos x r mn 5))))
            (let* ((j mn)
                   (hmax (max (aref a 0) (aref a 1) (aref a 2) (aref a 3) (aref a 4)))
                   (n* (/ hmax 20.0)))
              (format t "* = ~A sentences~%" (if (< n* 1.0) 1.0 n*) )
              (dotimes (i 5)
                (format t "~2D-~2D:~A~%"
                        (truncate j)
                        (+ (truncate (incf j r)) (if (= i 4) 1 0))
                        (nstar (if (< n* 1.0) (aref a i) (truncate (/  (aref a i) n*)))))))
            (if (< (aref a 3) (aref a 4))
                t
              nil)))))
    
    
    ノート:
    文の長さを統計的に解析する。
    英語の文は、 のように、 文の最初の部分と最後の部分に同じ単語が出てくることが多々あるので、 Henley の作った文章は、 という特徴がある。 そこで、henley-p では、文の長さのヒストグラム(5つの区間に分ける)を作り、 次の条件のうちどちらかが満たされたら Henley と推定。
    1. 60 語以上からなる文が存在するとき。
    2. ヒストグラムの最後の区間の文の数が、4番目の区間の文の数より多い場合。
    実行例:
    > (henley "original.txt" "henley1000.txt" 1000) ;original.txt をもとにして 1000 語からなる文章を作る。
    NIL
    > (henley "original.txt" "henley100.txt" 100) ;original.txt をもとにして 100 語からなる文章を作る。
    NIL
    > (henleyp "original.txt")    ;original.txt を Henley が書いたか?
    * = 5.4500003 sentences
     1-11:******************
    11-21:*******************
    21-32:********
    32-42:***
    42-54:*
    NIL                           ;No と判断
    > (henleyp "henley100.txt")   ;henley100.txt を Henley が書いたか?
    * = 1.0 sentences
     5-12:**
    12-19:*
    19-27:
    27-34:
    34-43:*                       ;やたら長い文が1つある。
    T                             ;Yes と判断
    > (henleyp "henley1000.txt")
    more than 60 words in one sentence.
    T                             ;Yes と判断
    

  5. (defvar *imin* 4) ;shortest sentence contains 9 words
    (defparameter *f-h* (make-hash-table :test #'eq))  ; a h-table for forward words
    (defparameter *b-h* (make-hash-table :test #'eq))  ; a h-table for backward words
    
    (defconstant mw 100)
    
    ;;; reading a sample text
    (defun read-text6 (f)
      (let ((p 0) (b (make-string mw)))
        (with-open-file (s f :direction :input)
          (do ((c (read-char s nil nil) (read-char s nil nil))) ((not c))
       (if (or (alpha-char-p c) (char= c #\'))
           (setf (char b p) c
            p (1+ p))
         (progn
           (when (plusp p)
             (see6 (intern (string-downcase (substring b 0 p))))
             (setf p 0))
           (let ((p (punc c)))
             (if p (see6 p)))))))))
    
    
    ;;; registration on hash teables
    (let ((prev '|.|))
      (defun see6 (wsym)
        (pushnew wsym (gethash prev *f-h*)) 
        (pushnew prev (gethash wsym *b-h*)) 
        (setf prev wsym)))
    
    
    ; making a sentence
    (defun make-sen (w)
      (labels ((-show (&rest lss)
            (let ((i 0))
              (dolist (ls lss)
           (dolist (x ls)
             (format t "~A " x)
             (if (zerop (mod (incf i) 8)) (terpri)))))
            (throw 'done nil))
          (rec (b f i n)
            (if (< i n)
                (let ((b-next (gethash (car b) *b-h*))
                      (f-next (gethash (car f) *f-h*)))
                  (if (and (< *imin* i)
                           (member-if #'terminal b-next)
                           (member-if #'terminal f-next))
                      (-show b (cdr (reverse f)))
                    (dolist (b1 (remove-if #'terminal b-next))
                      (dolist (f1 (remove-if #'terminal f-next))
                        (rec (cons b1 b) (cons f1 f) (1+ i) n))))))))
        (catch 'done
          (let ((ls (list (intern (string-downcase (symbol-name w))))))
            (do ((n 10 (1+ n)))
                ((= n 30))
              (rec ls ls 0 n))))))
    
    (let (txt0)
       (defun funny-sen (txt word)
         (unless (equal txt0 txt)
            (clrhash *f-h*)
            (clrhash *b-h*)
            (read-text6 txt)
            (setf txt0 txt))
         (make-sen word)))
    
    ノート:
    関数 read-txt6 は引数で与えられたファイルを読んで、 ある単語の前後にあった単語をそれぞれハッシュテーブル *f-h*, *b-h* に保持する。 頻度は問わないので、pushnew によって新出単語だけ追加していく。
    関数 make-sen は引数で与えられた単語を中央に持つ文を生成する。 反復深化法で、 前後に単語を足していく。前後で同時にピリオドなどの文を終端させる語が出てきたら生成した文を 返す。あまり短いと文にならないので繰り返しの最小値 *imin* を 4 に設定。
    関数 funny-sen はファイルと単語を引数に取り、引数で与えられたファイルに基づいてハッシュテーブルをつくり、 与えられた単語を中央に持つ文を生成する。 実行例:
    > (funny-sen "original.txt" 'friend)
    programmers care about my friend robert and rest
    parameters
    NIL
      

第9章


  1. (defun not-descending (ls)
      (not (apply #'>= ls)))
    

  2. (defun coins(a)
      (labels ((rec (am coins ncoins)
            (if coins
                (multiple-value-bind (n r) (floor am (car coins))
                                           (rec r (cdr coins) (cons n ncoins)))
              (nreverse (cons am ncoins)))))
        (rec a '(25 10 5) nil)))
    

  3. (defun best10-10years ()
      (labels ((rec (i n)
                 (if (= i 10)
                      n
                   (rec (1+ i) (+ n (random 2))))))
        (dotimes (i 10)
          (format t "~A " (rec 0 0)))))
    
    ノート:
    関数 best10-10years はある一方の生物からベスト10 に選ばれる人数を 10 回分シミュレートする。 すべてが 4--6 の間に収まることはまず無い。 従って、審査員は本当に best10 を選んでいるとはいえない。
    >(best10-10years)
    2 4 3 3 6 7 6 5 9 4
    NIL
    >(best10-10years)
    4 5 8 4 4 5 4 2 4 8
    NIL
    >(best10-10years)
    3 5 5 5 8 5 2 7 8 4
    NIL
    >(best10-10years)
    4 5 7 5 4 4 8 3 7 5
    NIL

  4. (defun isec (x1 y1 x2 y2 x3 y3 x4 y4)
      (let ((dx1 (- x2 x1))
            (dy1 (- y2 y1))
            (dx2 (- x4 x3))
            (dy2 (- y4 y3))
            (dx3 (- x3 x1))
            (dy3 (- y3 y1)))
        (let ((d (- (* dx1 dy2) (* dx2 dy1))))
          (unless (= d 0)
            (let ((k1 (/ (- (* dx3 dy2) (* dx2 dy3)) d))
                  (k2 (/ (- (* dx3 dy1) (* dx1 dy3)) d)))
              (if (and (<= 0 k1 1) (<= 0 k2 1))
                  (cons (+ x1 (* dx1 k1)) (+ y1 (* dy1 k1)))))))))
           
    ノート:
    平面上の4つの点 A, B, C, D の位置ベクトルをそれぞれ a, b, c, d とすると線分 AB, CD 上の点 P と Q の位置ベクトル p, q はそれぞれ次の様に表される:
     p = a + k1(b - a)    (0 &le k1 &le 1.0)
     q = c + k2(d - c)    (0 &le k2 &le 1.0)
     
    線分 AB と CD が交わるためには p = q となることが必要。

  5. (defun  bisec (f min max epsilon)
      (let ((m (* 0.5 (+ min max))))
        (if (< (- max min) epsilon)
             m
          (let ((fmin (funcall f min))
                (fmax (funcall f max))
                (fm   (funcall f m)))
             (cond
              ((< 0 (* fmin fmax)) (error "wrong range"))
              ((= 0 fm) m)
              ((< 0 (* fmin fm)) (bisec f m max epsilon))
              ((< 0 (* fmax fm)) (bisec f min m epsilon))
              (t nil))))))
           
    ノート:
    アルゴリズムは以下の通り。
    1. min, max の中点を m とする。
    2. もし (max - min) が epsilon より小さければ m を返す。そうでなければ次に進む。
    3. f(min), f(m), f(max) の値を求める。
    4. もし、f(min) と f(m) の符号が逆転していれば m を次のサイクルの max として i. に戻る。 そうでなければ m を次の min として i. に戻る。

  6. (defun horner (x &rest parms)
      (labels ((rec (parms acc)
                 (if parms
                     (rec (cdr parms) (+ (* acc x) (car parms)))
                   acc)))
        (rec parms 0)))
           
  7. clisp の場合は 24 bit。
    > (log (1+ most-positive-fixnum) 2)
    24
    
  8. most-positive-long-float, most-positive-double-float, most-positive-single-float, most-positive-short-float の値を調べる。 clisp の場合は、4種類がある。
    > most-positive-long-float
    8.8080652584198167656L646456992
    > most-positive-double-float
    1.7976931348623157d308
    > most-positive-single-float
    3.4028235E38
    > most-positive-short-float
    1.7014s38
    

第10章

    1. `(,z ,x z)
    2. `(x ,y ,@z)
    3. `(( ,@z ,x) z)

  1. (defmacro cif (pred then else)
      `(cond
        (,pred ,then)
        (t     ,else)))
    

  2. (defmacro nth-expr (n &body body)
      (if (integerp n)
          (nth n body)
        `(case ,n
           ,@(let ((i -1))
               (mapcar #'(lambda(x) `(,(incf i) ,x)) body)))))
    
    ノート:
    n が整数ならば、body のうち n 番目の要素をS式にする。n がコンパイル時に未定ならば case 構文に変換する。ちなみに以下の定義は実行時の処理がまったく減らないのでよろしくない。 (全ての式を評価しているし、そのリストまで作っている。)
     (defmacro nth-expr (n &body body) ; いまいち、実行時の処理がまったく減らない。
       `(nth ,n (list ,@body)))
    
     (defun nth-expr (n &rest av) ; ”いまいち”の定義は関数で書いたのと変わらない。
       (nth n av))
    

  3. (defmacro ntimes (n &body body)
      (with-gensyms (gn grec)
        `(let ((,gn ,n))
           (labels ((,grec (i)
            (when (< i ,gn)
              ,@body
              (,grec (1+ i)))))
        (,grec 0)))))
    

  4. (defmacro n-of (n expr)
      (with-gensyms (gn gi gacc)
        `(do ((,gn ,n) (,gi 0 (1+ ,gi)) (,gacc nil (cons ,expr ,gacc)))
             ((= ,gi ,gn) (nreverse ,gacc))
            ())))
    ;; 別解
    (defmacro n-of (n expr)
      (let((grec (gensym)))
        `(labels ((,grec (i j acc)
                    (if (= i j)
                        (nreverse acc)
                      (,grec (1+ i) j (cons ,expr acc)))))
           (,grec 0 ,n nil))))
    

  5. (defmacro retain (parms &body body)
      `((lambda ,parms ,@body) ,@parms))
    
    ノート:
    lambda の仮引数と実引数は同じ名前でも別物。body をクロージャーで隔離することによって 外側の変数は変化しない。
    実行例:
    >(let ((a 0) (b 1) (c 2) (d 3))
    >  (format t "values before retain: a=~A, b=~A, c=~A, d=~A~%" a b c d)
    >  (retain (a b c)      ;a b c を保持、 d は保持しない。
    >          (setf a (* a 10)
    >                b (* b 10)
    >                c (* c 10)
    >                d (* d 10))
    >          (format t "values in retain: a=~A, b=~A, c=~A, d=~A~%" a b c d))
    >  (format t "values after retain: a=~A, b=~A, c=~A, d=~A~%" a b c d))
    values before retain: a=0, b=1, c=2, d=3
    values in retain: a=0, b=10, c=20, d=30
    values after retain: a=0, b=1, c=2, d=30   ;a b c はもとの値に戻る。
    NIL
    
  6. lst が呼び出されるたびに変わるようだと問題が起こる。 以下の内容のファイルを load してテストしてみる。
    (defmacro push- (obj lst)
      `(setf ,lst (cons ,obj ,lst)))
    
    ;; 本当の push の動作をテストする。
    (defun test-push ()
      (let ((a (make-array 3))
            (i 0))
        (setf (aref a 0) (list 0)
              (aref a 1) (list 1)
              (aref a 2) (list 2))
        (push 4 (aref a (incf i)))
        (format t "~A ~A ~A~%" (aref a 0) (aref a 1) (aref a 2))))
    
    ;; 間違った push の動作をテストする。
    (defun test-push- ()
      (let ((a (make-array 3))
            (i 0))
        (setf (aref a 0) (list 0)
              (aref a 1) (list 1)
              (aref a 2) (list 2))
        (push- 4 (aref a (incf i)))
        (format t "~A ~A ~A~%" (aref a 0) (aref a 1) (aref a 2))))
    
    実行結果:
    > (test-push)
    (0) (4 1) (2)
    NIL
    > (test-push-)
    (0) (4 2) (2)
    NIL
    

第11章


  1. (defclass rectangle ()
      ((height :accessor rectangle-height
               :initarg :height
               :initform 0)
       (width  :accessor rectangle-width
               :initarg :width
               :initform 0)))
    
    (defclass circle ()
      ((radius :accessor circle-radius
               :initarg :radius
               :initform 0)))
    
    (defmethod area ((x rectangle))
      (* (rectangle-width x) (rectangle-height x)))
    
    (defmethod area ((x circle))
      (let ((*WARN-ON-FLOATING-POINT-CONTAGION* nil))
        (* pi (expt (circle-radius x) 2))))
    

  2. (defclass point ()
       ((x :accessor x
           :initarg :x
           :initform 0)
        (y :accessor y
           :initarg :y
           :initform 0)
        (z :accessor z
           :initarg :z
           :initform 0)))
    
    (defclass surface ()
       ((color  :accessor surface-color
                :initarg :color)))
    
    (defclass sphere (surface)
       ((radius :accessor  sphere-radius
               :initarg :radius
               :initform 0)
        (center :accessor sphere-center
                :initarg :center
                :initform (make-instance 'point :x 0 :y 0 :z 0))))
    
    (defun defsphere (x y z r c)
      (let ((s (make-instance  'sphere
                               :radius r
                               :center (make-instance 'point :x x :y y :z z)
                               :color c)))
        (push s *world*)
        s))
    
    (defmethod intersect ((s sphere) (pt point) xr yr zr)
      (let* ((c (sphere-center s))
             (n (minroot (+ (sq xr) (sq yr) (sq zr))
                         (* 2 (+ (* (- (x pt) (x c)) xr)
                                 (* (- (y pt) (y c)) yr)
                                 (* (- (z pt) (z c)) zr)))
                         (+ (sq (- (x pt) (x c)))
                            (sq (- (y pt) (y c)))
                            (sq (- (z pt) (z c)))
                            (- (sq (sphere-radius s)))))))
        (if n
            (make-instance 'point
                            :x  (+ (x pt) (* n xr))
                            :y  (+ (y pt) (* n yr))
                            :z  (+ (z pt) (* n zr))))))
    
    (defmethod normal ((s sphere) (pt point))
      (let ((c (sphere-center s)))
        (unit-vector (- (x c) (x pt))
                     (- (y c) (y pt))
                     (- (z c) (z pt)))))
    



    1. 特定性が大きい順:a, c, d, e, f, g, h



    2. 特定性が大きい順:b, d, e, f, g, h, c

  3.  (defun most-spec-app-meth (gfun av)
       (let ((classlist (mapcar #'precedence av)))
         (dolist (meth (method gfun))
           (if (do ((i 0 (1+ i)) (spec (specialization meth) (cdr spec)))
                   ((not spec) t)
                 (or (member (car spec) (nth i classlist) :test #'equal)
                     (return)))
               (return-from most-spec-app-meth meth)))))
    
  4. 以下のコードを問題1の解答、または本文 図11.1 に追加
    (defvar *area-counter* 0)
    (defmethod area :before (obj)
      (declare (ignore obj))
      (incf *area-counter*))
    
  5. 複数のインスタンス間の動作を定義するとき困難になる。 例えば本文 p.163 の combine はメッセージ伝達モデルでは定義できない。

第12章

    1. ネストしたリストを共有する。
      (let ((ele '(a)))
        (list ele ele ele))
      



    2. ネストしたリストを共有しない。
       (list (list 'a) (list 'a) (list 'a))
      



    3. ネストしたリストを一部共有する。(これは3種類ある。)
      (let ((ele '(a)))
        (list (copy-list ele) ele ele))
      


    1. (setf q (make-queue))


    2. (enqueue 'a q)


    3. (enqueue 'b q)


    4. (dequeue q )



  1. (defun copy-queue (q0)
      (let ((q1 (make-queue)))
        (setf (car q1) (copy-list (car q0))
              (cdr q1) (last (car q1)))
        q1))
    

  2. (defun pushqueue (obj q)
      (setf (car q) (cons (obj) (car q))))
    

  3. (defun move-front (obj q)
      (let ((ls (car q)))
        (setf (car q) (if (member obj ls)
                          (cons obj (remove obj ls))
                        ls)
              (cdr q) (last (car q))))
      (car q))
    

  4.  
    (defun in-circule (obj ls)
      (labels ((rec (ls1)
                 (if ls1
                    (cond
                       ((eql obj (car ls1)) t)
                       ((eq ls (cdr ls1)) nil)
                       (t (rec (cdr ls1)))))))
        (rec ls)))
    

  5. (defun cdr-circular-p (ls)
      (labels ((rec (ls1)
                 (if ls1
                     (or (eq (cdr ls1) ls)
                         (rec (cdr ls1))))))
        (rec ls)))
    

  6. (defun car-circular-p (ls)
      (eq ls (car ls)))
            
    

    第13章


    1. コンパイルされた関数を disassemble で調べる。
      例えば、次の様なコードを書いてコンパイルする。
      (declaim (inline my-add))
      (defun my-add (n)
        (+ n 1))
      
      (defun call-my-add (n)
        (my-add n))
      
      (disassemble 'call-my-add)
      として、 call-my-add が my-add を呼び出しているかチェックする。 呼び出している場合 clisp では以下の様に表示される。
      1     (CALL1 0)                           ; MY-ADD
      
      また、呼び出していない場合、my-add の名前は表示されない。 ちなみに、my-add のような小さい関数は default で inline されてしまうので、 inline されないようにするには、
      (declaim (notinline my-add))
      
      と宣言する必要がある。

    2. (defun foo-tail (x)
        (labels ((rec (x sum)
                     (if (zerop x)
                           sum
                        (rec (1- x) (1+ sum)))))
          (rec x 0)))
      
      2倍ほど改善される。ちなみに、もともとの定義ではすぐに stack overflow を起こす。
    3. clisp の場合、宣言の有無で速度は変化しない。 コード参照
      ray-tracer の場合、数値の構造体が混じって表れるので、ACL2 に載っている with-type の定義は使えない。 '?' で始まるシンボルには "(the [type] ...)" をつけないように変更。
      (defmacro with-type (type expr)
        (or
         (leave-it expr)
         `(the ,type ,(if (atom expr) 
                           expr
                         (expand-call type (binarize expr))))))
      
      (eval-when (:compile-toplevel :load-toplevel)
        (defun leave-it (expr)
          (if (atom expr)
              (if (symbolp expr)
                  (if (char= #\? (char (symbol-name expr) 0)) expr)
                expr)))
      
        (defun expand-call (type expr)
          `(,(car expr) ,@(mapcar #'(lambda (a)
                                      `(with-type ,type ,a))
                                  (cdr expr))))
      
        (defun binarize (expr)
          (if (and (nthcdr 3 expr)
                   (member (car expr) '(+ - * /)))
              (destructuring-bind (op a1 a2 . rest) expr
                                  (binarize `(,op (,op ,a1 ,a2) ,@rest)))
            expr)))
      
    4. queue を配列を使って表す。
      (defconstant qsize 100)
      (defvar *net* '((a b c) (b c) (c d)))
      
      
      (let ((qs 0) (qe 1) (qv (make-array qsize)))
        
        (defun shortest-path (start end net)
          (setf (svref qv 0) (list start))
          (bfs end net))
      
        
        (defun bfs (end net)
          (if (= qs qe)
              nil
            (let ((path (svref qv (mod qs qsize))))
               (let ((node (car path)))
                  (if (eql node end)
                      (reverse path)
                     (progn
                       (incf qs)
                       (new-paths path node net)
                       (bfs end net)))))))
               
      
        (defun new-paths (path node net)
          (dolist (n (cdr (assoc node net)))
            (setf (svref qv (mod qe qsize)) (cons n path))
            (incf qe))))
      
    5. コード参照

    14. ソースコードについて

    解答のソースコードはここにあります。 なお、名前の重複を避けるため、一部関数の名前を変えてあります。 また、13章の解答はパッケージ SPEED に入れました。
    動作は clisp & winXP で確認しました。そのままロードしてもコンパイルしても動作します。