Software Engineering Lecture 5/16

Software Engineering Lecture 5/16



Pure LISP

Pure LISPとは何か?

これらを使わないようにして見る。(これらは、これから勉強する ことになる...) このようなLISPはPure LISPと呼ばれる。Pure LISP は数学的に非常にきれいな性質を持っている。 しかし、現実には? などの理由でPure LISPには限界があると考えている人が多い。し かし、これらはプログラミング技術、コンパイラ技術等で乗り越え られると考えている人もいる。実際、最近の関数型言語は副作用な しでも非常に高度なプログラミングが可能だし、実行も高速である。 ある手の言語だと評価順序が自由なことを利用した遅延評価という 技術により手続き型言語よりも劇的に速く解ける問題を持つものも ある。 ただし、 などは、Pure LISPの範囲を越えていることが分かっている。 一つのプログラムの方針は、 ということだろう。

Pure LISPのプログラミング技術

当り前な再帰をマスタしよう。

(defun integer-list (i)
   (if (> i 0)
       (cons i (integer-list (- i 1)))
       nil))
停止条件から考える。

(defun sum-up (list)
   (if (null list) 0
       (+ (car list) (sum-up (cdr list)))))


末尾再帰

通常の再帰はスタックを使って実装される。 しかし、後始末の計算がないのならば、変数を取っておく必要がないので、 前回の変数を再利用することができる。
(defun integer-list-q (i q)
   (if (>= i 0) (integer-list-q (- i 1) (cons i q))
       q))
iとqの余分な変数がループの状態を表す。

(defun sum-up-q (list q)
   (if (endp list) q
       (sum-up-q (cdr list) (+ q (car list)))))
大抵の再帰は末尾再帰(tail recursion)に直すことができる。



リストの変換

リストの値をすべて2倍にすることを考えよう。

(defun 2-times (x)
   (if (endp x) nil
       (cons (* 2 (car x)) 
              (2-times (cdr x)))))
この関数の(* 2 ...)という動作をカスタマイズしたい。このような 関数は高階関数と呼ばれる。
(defun 2-time (x) (* 2 x))
(mapcar #'2-time '(1 2 3))
問題: funcall と function についてinfoを調べて、mapcar と同じ働きをする 関数 my-mapcar を再帰を用いて作れ。また、その末尾再帰版 my-mapcar-q を作れ。末尾版はリストが逆順になったらreverseすれば良いということに しよう。



連想リスト

(defun find-assoc (key list)
  (if (null list) nil
      (if (eq key (car (car list))) (cdr (car list))
          (find-assoc key (cdr list)))))

(defun add-assoc (key value list)
  (cons (cons key value) list))

(defun remove-assoc (key list)
  (if (null list) nil
      (if (eq key (car (car list))) 
          (remove-assoc key (cdr list))
          (cons (car list) (remove-assoc key (cdr list))))))
問題: addするときに重複を避ける add-assoc-uniq を作れ。



バイナリツリー

((key . value) left right)
(defun tree-find (key tree)
  (if (null tree) nil
      (let ((here  (car tree))
            (left  (car (cdr tree)))
            (right (car (cdr (cdr tree)))))
          (cond ((string< key (car here))
                    (tree-find key left))
                 ((string> key (car here))
                    (tree-find key right))
                 ((string= key (car (car tree)))
                     (cdr here))))))
let は一時変数を生成する。

(defun tree-insert (key value tree)
  (if (null tree)
      (list (cons key value) nil nil)
      (let ((here  (car tree))
            (left  (cadr tree))
            (right (caddr tree)))
          (cond ((string< key (car here))
                    (list here (tree-insert key value left) right))
                 ((string> key (car here))
                    (list here left (tree-insert key value right)))
                 ((string= key (car here))
                    (list (cons key value) left right))))))
cadr, caddr は、car/cdr の合成である。

(defun tree-walk (tree)
  (if (null tree) nil
     (append
         (tree-walk  (cadr tree))          ; left
         (list (car tree))                 ; here
         (tree-walk  (caddr tree))))) ; right

(defun tree-walk-2 (tree q)
  (if (null tree) q
         (tree-walk-2 (cadr tree)                     ; left
           (cons (car tree)                           ; here
               (tree-walk-2  (caddr tree) q))))) ; right

問題:
(setf x (tree-insert 'kono 100 nil))
(setf x (tree-insert 'yonesu 140 x))
(setf x (tree-insert 'takara 120 x))
(setf x (tree-insert 'kyan 70 x))
を実行した後のxの内容を表す木をS式で表せ。また、それを分かりやすい 図で表せ。

問題: このツリーはnilがたくさん出てきてメモリ効率が良くない。 right の後ろのnilを除くことを考えよう。そのデータ形式を 使うように上のプログラムを書き換えよ。

問題: tree-insert と tree-walk-2 を使って tree-sort プログラムを 作ることができる。tree-sort を作成し、その効率を議論せよ。



Common LISPらしいプログラムとは?

以上の例は、Pure LISP らしい例題である。 では、Common LISP らしいプログラムとは?

今日出てきたLISPの要素



prev next Kono's home page