有限オートマトン
よく考えたら遷移関数はただのグラフだった。グラフの隣接リスト表現で十分だ。マクロをちょっと書き換え、名前を付けられるようにし、ほとんどこれ以上短くできないようにした。
(defmacro def-dfa (name (states alphabet transition init-state final-states)) (let ((string (gensym "string")) (state (gensym "state")) (symbol (gensym "symbol"))) `(defun ,name (,string) (let ((,state ,init-state)) #'(lambda () (cond ((null ,string) (if (member ,state ',final-states) (values nil ,state 'accept) (values nil ,state 'not-accept))) (t (let ((,symbol (pop ,string))) (assert (member ,symbol ',alphabet)) (setf ,state (ecase ,state ,@(loop for (s . adj) in transition collect `(,s (ecase ,symbol ,@adj))))) (assert (member ,state ',states)) (values ,symbol ,state))))))))) (defun run-dfa (dfa) (multiple-value-bind (a state acceptp) (funcall dfa) (format t ";; a: ~a state: ~a~%" a state) (if a (run-dfa dfa) acceptp))) ;; accept x01y | x, y {0,1}* (def-dfa a ((:q0 :q1 :q2) (0 1) ((:q0 (0 :q2) (1 :q0)) (:q1 (0 :q1) (1 :q1)) (:q2 (0 :q2) (1 :q1))) :q0 (:q1))) ;; 0 and 1 oocurs even time (def-dfa even-0-1-automaton ((:q0 :q1 :q2 :q3) (0 1) ((:q0 (0 :q2) (1 :q1)) (:q1 (0 :q3) (1 :q0)) (:q2 (0 :q0) (1 :q3)) (:q3 (0 :q1) (1 :q2))) :q0 (:q0)))
マクロ def-dfa は名前と 有限オートマトンの定義通りに5個組の情報を受け取り、関数を定義する。その関数は1引数で"アルファベット"からなる"文字列"をリストとして受け取る。関数に文字列を与えて実行すると、引数なしのクロージャを返す。このクロージャは実行するたびに文字列から文字を一つ取り出し、次の状態に遷移する。文字列が無くなったとき、受理状態かどうかを判定する。遷移関数はグラフの隣接リスト表現で与えられる(が、実際上は唯の case 文のようにしかみえないな、、)。
SECD> (run-dfa (even-0-1-automaton '(1 1 1 0 0 0 0 1))) ;; a: 1 state: Q1 ;; a: 1 state: Q0 ;; a: 1 state: Q1 ;; a: 0 state: Q3 ;; a: 0 state: Q1 ;; a: 0 state: Q3 ;; a: 0 state: Q1 ;; a: 1 state: Q0 ;; a: NIL state: Q0 ACCEPT SECD> (run-dfa (even-0-1-automaton '(1 1 1 0 0 0 1))) ;; a: 1 state: Q1 ;; a: 1 state: Q0 ;; a: 1 state: Q1 ;; a: 0 state: Q3 ;; a: 0 state: Q1 ;; a: 0 state: Q3 ;; a: 1 state: Q2 ;; a: NIL state: Q2 NOT-ACCEPT SECD>
「計算理論の基礎 1オートマトンと言語」という本がとても良さそうなので、しばらくこれにそって急いでオートマトンを動かそう。