「双方向リスト (doubly-linked list)」と、それを使って実装した「両端キュー (deque)」を操作するライブラリです。双方向リストの説明は拙作のページ CLOS 入門: 双方向リスト をお読みください。
アーカイブファイル minlib.tar.gz をインストールする、または プログラムリスト にある以下の 4 つのファイルを、~/common-lisp/ 以下の適当なサブディレクトリ (たとえば deque など) に配置してください。
* (asdf:test-system :deque) ; compiling file ... 略 ... ----- test start ----- (DLIST-P (SETQ D (MAKE-DLIST))) => T OK (DLIST-EMPTYP D) => T OK (DLIST-COUNT D) => 0 OK (DLIST-INSERT-AT D 0 1) => 1 OK (DLIST-EMPTYP D) => NIL OK (DLIST-COUNT D) => 1 OK (DLIST-NTH D 0) => 1 OK (DLIST-NTH D 0 FROM-END T) => 1 OK (DOLIST (X '(2 3 4) (DLIST-COUNT D)) (DLIST-INSERT-AT D 0 X FROM-END T)) => 4 OK (DOLIST (X '(5 6 7 8) (DLIST-COUNT D)) (DLIST-INSERT-AT D 0 X)) => 8 OK (LET ((A NIL)) (DOTIMES (X (DLIST-COUNT D) A) (PUSH (DLIST-NTH D X) A))) => (4 3 2 1 5 6 7 8) OK (LET ((A NIL)) (DOTIMES (X (DLIST-COUNT D) A) (PUSH (DLIST-NTH D X FROM-END T) A))) => (8 7 6 5 1 2 3 4) OK (LET ((A NIL)) (DLIST-MAPC D (LAMBDA (X) (PUSH X A))) A) => (4 3 2 1 5 6 7 8) OK (LET ((A NIL)) (DLIST-MAPC D (LAMBDA (X) (PUSH X A)) FROM-END T) A) => (8 7 6 5 1 2 3 4) OK (DLIST-DELETE-AT D 0) => 8 OK (DLIST-DELETE-AT D 0 FROM-END T) => 4 OK (DLIST-DELETE-AT D 2) => 5 OK (DLIST-DELETE-AT D 2 FROM-END T) => 1 OK (DLIST->LIST D) => (7 6 2 3) OK (DLIST-COUNT D) => 4 OK (DLIST-FOLD D (LAMBDA (A X) (CONS X A)) NIL) => (3 2 6 7) OK (DLIST-FOLD D #'CONS NIL FROM-END T) => (7 6 2 3) OK (DOTIMES (X (DLIST-COUNT D) (DLIST->LIST D)) (DLIST-SET D X X)) => (0 1 2 3) OK (DOTIMES (X (DLIST-COUNT D) (DLIST->LIST D)) (DLIST-SET D X X FROM-END T)) => (3 2 1 0) OK (DLIST-CLEAR D) => 0 OK (DLIST-EMPTYP D) => T OK (DLIST-COUNT D) => 0 OK (DLIST-COUNT (SETQ D (LIST->DLIST '((1 2) (3 4) (5 6))))) => 3 OK (DLIST-EXISTS D 3) => NIL OK (DLIST-EXISTS D (LIST 3 4)) => NIL OK (DLIST-EXISTS D (LIST 3 4) TEST #'EQUAL) => T OK (DLIST-FIND D 4 KEY #'CAR) => NIL OK (DLIST-FIND D 4 KEY #'SECOND) => (3 4) OK (DLIST-FIND-IF D #'EVENP KEY #'CAR) => NIL OK (DLIST-FIND-IF D #'EVENP KEY #'SECOND) => (1 2) OK (DLIST-FIND-IF D #'EVENP KEY #'SECOND FROM-END T) => (5 6) OK (DLIST-FIND-IF-NOT D #'EVENP KEY #'CAR) => (1 2) OK (DLIST-FIND-IF-NOT D #'EVENP KEY #'SECOND) => NIL OK (DLIST-FIND-IF-NOT D #'EVENP KEY #'CAR FROM-END T) => (5 6) OK (DLIST-COUNT (SETQ D (LIST->DLIST '(1 2 3 1 2 3 1 2 3)))) => 9 OK (DLIST->LIST (DLIST-DELETE D 1)) => (2 3 2 3 2 3) OK (DLIST-COUNT D) => 6 OK (DLIST->LIST (DLIST-DELETE D 2 COUNT 1)) => (3 2 3 2 3) OK (DLIST-COUNT D) => 5 OK (DLIST->LIST (DLIST-DELETE D 2 COUNT 1 FROM-END T)) => (3 2 3 3) OK (DLIST-COUNT D) => 4 OK (DLIST-COUNT (SETQ D (LIST->DLIST '(1 2 3 4 5 6)))) => 6 OK (DLIST->LIST (DLIST-DELETE-IF D #'EVENP)) => (1 3 5) OK (DLIST-COUNT D) => 3 OK (DLIST-COUNT (SETQ D (LIST->DLIST '(1 2 3 4 5 6)))) => 6 OK (DLIST->LIST (DLIST-DELETE-IF-NOT D #'EVENP)) => (2 4 6) OK (DLIST-COUNT D) => 3 OK (DOLIST (X '(2 4 6) (DLIST-EMPTYP D)) (DLIST-DELETE D X)) => T OK (DEQUE-P (SETQ Q (MAKE-DEQUE))) => T OK (DEQUE-EMPTYP Q) => T OK (DEQUE-COUNT Q) => 0 OK (PUSH-FRONT Q 1) => 1 OK (DEQUE-EMPTYP Q) => NIL OK (DEQUE-COUNT Q) => 1 OK (PUSH-BACK Q 10) => 10 OK (DEQUE-EMPTYP Q) => NIL OK (DEQUE-COUNT Q) => 2 OK (PEEK-FRONT Q) => 1 OK (PEEK-BACK Q) => 10 OK (DOLIST (X '(2 3 4) (DEQUE-COUNT Q)) (PUSH-FRONT Q X)) => 5 OK (DOLIST (X '(20 30 40) (DEQUE-COUNT Q)) (PUSH-BACK Q X)) => 8 OK (PEEK-FRONT Q) => 4 OK (PEEK-BACK Q) => 40 OK (LET ((A NIL)) (DOTIMES (X 4 A) (PUSH (POP-FRONT Q) A))) => (1 2 3 4) OK (LET ((A NIL)) (DOTIMES (X 4 A) (PUSH (POP-BACK Q) A))) => (10 20 30 40) OK (DEQUE-EMPTYP Q) => T OK (DEQUE-COUNT Q) => 0 OK (PUSH-FRONT Q 10) => 10 OK (DEQUE-CLEAR Q) => 0 OK (DEQUE-COUNT Q) => 0 OK (DEQUE-EMPTYP Q) => T OK ----- test end ----- TEST: 76 OK: 76 NG: 0 ERR: 0 T
;;;
;;; sample_deque.lisp : deque のサンプルプログラム
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(require :deque)
(use-package :deque)
;;;
;;; 経路の探索
;;; リストのほうが簡単だが、あえて dlist を使ってみた
;;;
;;; 隣接リスト
(defvar *adjacent*
'((A B C)
(B A C D)
(C A B E)
(D B E F)
(E C D G)
(F D)
(G E)))
;;; パスの表示
(defun print-path (path)
(format t "~S~%" (dlist->list path)))
;;; 深さ優先探索
(defun depth-first-search (goal path)
(let ((p (dlist-nth path 0 :from-end t)))
(if (eq p goal)
(print-path path)
(dolist (x (cdr (assoc p *adjacent*)))
(unless (dlist-exists path x)
(dlist-insert-at path 0 x :from-end t)
(depth-first-search goal path)
(dlist-delete-at path 0 :from-end t))))))
;;; 幅優先探索
(defun breadth-first-search (start goal)
(let ((q (make-deque)))
(push-back q (list start))
(loop
(if (deque-emptyp q) (return))
(let ((path (pop-front q)))
(if (eq (car path) goal)
(print (reverse path))
(dolist (x (cdr (assoc (car path) *adjacent*)))
(unless (member x path)
(push-back q (cons x path)))))))))
;;; 反復深化用深さ優先探索
(defun dfs (limit goal path)
(let ((p (dlist-nth path 0 :from-end t)))
(if (= (dlist-count path) limit)
(when (eq p goal)
(print-path path))
(dolist (x (cdr (assoc p *adjacent*)))
(unless (dlist-exists path x)
(dlist-insert-at path 0 x :from-end t)
(dfs limit goal path)
(dlist-delete-at path 0 :from-end t))))))
;;; 反復深化
(defun id-search (start goal)
(do ((i 2 (1+ i)))
((> i 7))
(format t "----- ~d -----~%" i)
(dfs i goal (list->dlist (list start)))))
* (load "sample_deque.lisp") T * (depth-first-search 'g (list->dlist '(a))) (A B C E G) (A B D E G) (A C B D E G) (A C E G) NIL * (breadth-first-search 'a 'g) (A C E G) (A B C E G) (A B D E G) (A C B D E G) NIL * (id-search 'a 'g) ----- 2 ----- ----- 3 ----- ----- 4 ----- (A C E G) ----- 5 ----- (A B C E G) (A B D E G) ----- 6 ----- (A C B D E G) ----- 7 ----- NIL
;;;
;;; deque.lisp : 両端キューと双方向リスト
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :deque)
(defpackage :deque (:use :cl))
(in-package :deque)
(export '(dlist dlist-p make-dlist dlist-emptyp dlist-count dlist-clear
dlist-nth dlist-set dlist-insert-at dlist-delete-at
dlist-exists dlist-find dlist-find-if dlist-find-if-not
dlist-delete dlist-delete-if dlist-delete-if-not
dlist-fold list->dlist dlist->list
dlist-mapc dlist-iterator
deque deque-p make-deque
push-front push-back
pop-front pop-back
peek-front peek-back
deque-count deque-clear deque-emptyp))
;;; セルの定義
(defstruct cell (item nil) (next nil) (prev nil))
;;; 空リストの生成
(defun make-empty ()
(let ((cp (make-cell)))
(setf (cell-next cp) cp
(cell-prev cp) cp)
cp))
;;; 双方向リストクラスの定義
(defstruct dlist (top (make-empty)) (size 0))
;;; n 番目のセルを求める (操作用関数)
(defun cell-nth (d n iter)
(do ((i -1 (1+ i))
(cp (dlist-top d) (funcall iter cp)))
((= i n) cp)
(if (and (<= 0 i) (eq (dlist-top d) cp))
(error "cell-nth --- oops!"))))
;;; 参照
(defun dlist-nth (d n &key (from-end nil))
(cell-item (cell-nth d n (if from-end #'cell-prev #'cell-next))))
;;; 書き換え
(defun dlist-set (d n value &key (from-end nil))
(setf (cell-item (cell-nth d n (if from-end #'cell-prev #'cell-next)))
value))
;;;
;;; 探索
;;;
(defun dlist-exists (d x &key (key #'identity) (test #'eql))
(do ((cp (cell-next (dlist-top d)) (cell-next cp)))
((eq (dlist-top d) cp))
(when (funcall test (funcall key (cell-item cp)) x)
(return t))))
(defun dlist-find (d x &key (key #'identity) (test #'eql) (from-end nil))
(let ((iter (if from-end #'cell-prev #'cell-next)))
(do ((cp (funcall iter (dlist-top d)) (funcall iter cp)))
((eq (dlist-top d) cp))
(when (funcall test (funcall key (cell-item cp)) x)
(return (cell-item cp))))))
(defun dlist-find-if (d pred &key (key #'identity) (from-end nil))
(let ((iter (if from-end #'cell-prev #'cell-next)))
(do ((cp (funcall iter (dlist-top d)) (funcall iter cp)))
((eq (dlist-top d) cp))
(when (funcall pred (funcall key (cell-item cp)))
(return (cell-item cp))))))
(defun dlist-find-if-not (d pred &key (key #'identity) (from-end nil))
(let ((iter (if from-end #'cell-prev #'cell-next)))
(do ((cp (funcall iter (dlist-top d)) (funcall iter cp)))
((eq (dlist-top d) cp))
(unless (funcall pred (funcall key (cell-item cp)))
(return (cell-item cp))))))
;;; セルの挿入
;;; p - next -> cp - next -> q
(defun cell-insert (p cp q)
(setf (cell-next cp) q
(cell-prev cp) p
(cell-prev q) cp
(cell-next p) cp))
;;; 挿入
(defun dlist-insert-at (d n value &key (from-end nil))
(let* ((iter (if from-end #'cell-prev #'cell-next))
(p (cell-nth d (1- n) iter))
(q (funcall iter p))
(cp (make-cell :item value)))
(if from-end
(cell-insert q cp p)
(cell-insert p cp q))
(incf (dlist-size d))
value))
;;; セルの削除
;;; p - next -> [cp] - next -> q
(defun cell-delete (p q)
(setf (cell-next p) q
(cell-prev q) p))
;;; 削除
(defun dlist-delete-at (d n &key (from-end nil))
(let* ((iter (if from-end #'cell-prev #'cell-next))
(p (cell-nth d (1- n) iter))
(cp (funcall iter p))
(q (funcall iter cp)))
(if from-end (cell-delete q p) (cell-delete p q))
(decf (dlist-size d))
(cell-item cp)))
(defun dlist-delete (d x &key (key #'identity) (test #'eql) (from-end nil) (count 0))
(let ((iter (if from-end #'cell-prev #'cell-next)))
(do ((cp (funcall iter (dlist-top d)) (funcall iter cp)))
((eq (dlist-top d) cp) d)
(when (funcall test (funcall key (cell-item cp)) x)
(cell-delete (cell-prev cp) (cell-next cp))
(decf (dlist-size d))
(decf count)
(when (zerop count)
(return d))))))
(defun dlist-delete-if (d pred &key (key #'identity) (from-end nil) (count 0))
(let ((iter (if from-end #'cell-prev #'cell-next)))
(do ((cp (funcall iter (dlist-top d)) (funcall iter cp)))
((eq (dlist-top d) cp) d)
(when (funcall pred (funcall key (cell-item cp)))
(cell-delete (cell-prev cp) (cell-next cp))
(decf (dlist-size d))
(decf count)
(when (zerop count)
(return d))))))
(defun dlist-delete-if-not (d pred &key (key #'identity) (from-end nil) (count 0))
(let ((iter (if from-end #'cell-prev #'cell-next)))
(do ((cp (funcall iter (dlist-top d)) (funcall iter cp)))
((eq (dlist-top d) cp) d)
(unless (funcall pred (funcall key (cell-item cp)))
(cell-delete (cell-prev cp) (cell-next cp))
(decf (dlist-size d))
(decf count)
(when (zerop count)
(return d))))))
;;; 畳み込み
(defun dlist-fold (d func init &key from-end)
(let ((iter (if from-end #'cell-prev #'cell-next)))
(do ((cp (funcall iter (dlist-top d)) (funcall iter cp))
(a init))
((eq cp (dlist-top d)) a)
(setq a (if from-end
(funcall func (cell-item cp) a)
(funcall func a (cell-item cp)))))))
;;; サイズ
(defun dlist-count (d) (dlist-size d))
;;; クリア
(defun dlist-clear (d)
(let ((cp (dlist-top d)))
(setf (cell-next cp) cp
(cell-prev cp) cp
(dlist-size d) 0)))
;;; 空リストか?
(defun dlist-emptyp (d)
(let ((cp (dlist-top d)))
(eq cp (cell-next cp))))
;;; リストを双方向リストに変換
(defun list->dlist (xs)
(let ((d (make-dlist)))
(dolist (x xs d)
(dlist-insert-at d 0 x :from-end t))))
;;; 双方向リストをリストに変換
(defun dlist->list (d)
(dlist-fold d (lambda (x y) (cons x y)) nil :from-end t))
;;; 巡回
(defun dlist-mapc (d func &key (from-end nil))
(let ((iter (if from-end #'cell-prev #'cell-next)))
(do ((cp (funcall iter (dlist-top d)) (funcall iter cp)))
((eq (dlist-top d) cp))
(funcall func (cell-item cp)))))
;;; イテレータの生成
(defun dlist-iterator (d &key (from-end nil))
(let* ((iter (if from-end #'cell-prev #'cell-next))
(cp (funcall iter (dlist-top d))))
(lambda ()
(if (eq (dlist-top d) cp)
(values nil nil)
(multiple-value-prog1
(values (cell-item cp) t)
(setq cp (funcall iter cp)))))))
;;; 表示
(defmethod print-object ((x dlist) stream)
(format stream "#<dlist: ~S>" (dlist->list x)))
;;;
;;; 両端キュー
;;;
;;; 定義
(defstruct deque (top (make-dlist)))
;;; データの追加
(defun push-front (d value)
(dlist-insert-at (deque-top d) 0 value))
(defun push-back (d value)
(dlist-insert-at (deque-top d) 0 value :from-end t))
;;; データの取り出し
(defun pop-front (d)
(dlist-delete-at (deque-top d) 0))
(defun pop-back (d)
(dlist-delete-at (deque-top d) 0 :from-end t))
;;; データの参照
(defun peek-front (d)
(dlist-nth (deque-top d) 0))
(defun peek-back (d)
(dlist-nth (deque-top d) 0 :from-end t))
;;; 要素数を求める
(defun deque-count (q) (dlist-count (deque-top q)))
;;; ディーキューを空にする
(defun deque-clear (q) (dlist-clear (deque-top q)))
;;; ディーキューは空か?
(defun deque-emptyp (q) (dlist-emptyp (deque-top q)))
リスト : deque.asd (defsystem :deque :description "両端キューと双方向リスト" :version "0.1.0" :author "Makoto Hiroi>" :license "MIT" :depends-on () :in-order-to ((test-op (test-op :deque_tst))) :components ((:file "deque")))
;;;
;;; deque_tst.lisp : deque のテスト
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :deque_tst)
(defpackage :deque_tst (:use :cl :deque :mintst))
(in-package :deque_tst)
(export '(test))
(defvar d nil)
(defvar q nil)
(defun test ()
(initial)
(run (dlist-p (setq d (make-dlist))) t)
(run (dlist-emptyp d) t)
(run (dlist-count d) 0)
(run (dlist-insert-at d 0 1) 1)
(run (dlist-emptyp d) nil)
(run (dlist-count d) 1)
(run (dlist-nth d 0) 1)
(run (dlist-nth d 0 :from-end t) 1)
(run (dolist (x '(2 3 4) (dlist-count d)) (dlist-insert-at d 0 x :from-end t)) 4)
(run (dolist (x '(5 6 7 8) (dlist-count d)) (dlist-insert-at d 0 x)) 8)
(run (let ((a nil))
(dotimes (x (dlist-count d) a) (push (dlist-nth d x) a)))
'(4 3 2 1 5 6 7 8))
(run (let ((a nil))
(dotimes (x (dlist-count d) a) (push (dlist-nth d x :from-end t) a)))
'(8 7 6 5 1 2 3 4))
(run (let ((a nil))
(dlist-mapc d (lambda (x) (push x a)))
a)
'(4 3 2 1 5 6 7 8))
(run (let ((a nil))
(dlist-mapc d (lambda (x) (push x a)) :from-end t)
a)
'(8 7 6 5 1 2 3 4))
(run (dlist-delete-at d 0) 8)
;; 7 6 5 1 2 3 4
(run (dlist-delete-at d 0 :from-end t) 4)
;; 7 6 5 1 2 3
(run (dlist-delete-at d 2) 5)
;; 7 6 1 2 3
(run (dlist-delete-at d 2 :from-end t) 1)
(run (dlist->list d) '(7 6 2 3))
(run (dlist-count d) 4)
(run (dlist-fold d (lambda (a x) (cons x a)) nil) '(3 2 6 7))
(run (dlist-fold d #'cons nil :from-end t) '(7 6 2 3))
(run (dotimes (x (dlist-count d) (dlist->list d)) (dlist-set d x x))
'(0 1 2 3))
(run (dotimes (x (dlist-count d) (dlist->list d)) (dlist-set d x x :from-end t))
'(3 2 1 0))
(run (dlist-clear d) 0)
(run (dlist-emptyp d) t)
(run (dlist-count d) 0)
;; 探索
(run (dlist-count (setq d (list->dlist '((1 2) (3 4) (5 6))))) 3)
(run (dlist-exists d 3) nil)
(run (dlist-exists d (list 3 4)) nil)
(run (dlist-exists d (list 3 4) :test #'equal) t)
(run (dlist-find d 4 :key #'car) nil)
(run (dlist-find d 4 :key #'second) '(3 4))
(run (dlist-find-if d #'evenp :key #'car) nil)
(run (dlist-find-if d #'evenp :key #'second) '(1 2))
(run (dlist-find-if d #'evenp :key #'second :from-end t) '(5 6))
(run (dlist-find-if-not d #'evenp :key #'car) '(1 2))
(run (dlist-find-if-not d #'evenp :key #'second) nil)
(run (dlist-find-if-not d #'evenp :key #'car :from-end t) '(5 6))
;; 削除
(run (dlist-count (setq d (list->dlist '(1 2 3 1 2 3 1 2 3)))) 9)
(run (dlist->list (dlist-delete d 1)) '(2 3 2 3 2 3))
(run (dlist-count d) 6)
(run (dlist->list (dlist-delete d 2 :count 1)) '(3 2 3 2 3))
(run (dlist-count d) 5)
(run (dlist->list (dlist-delete d 2 :count 1 :from-end t)) '(3 2 3 3))
(run (dlist-count d) 4)
(run (dlist-count (setq d (list->dlist '(1 2 3 4 5 6)))) 6)
(run (dlist->list (dlist-delete-if d #'evenp)) '(1 3 5))
(run (dlist-count d) 3)
(run (dlist-count (setq d (list->dlist '(1 2 3 4 5 6)))) 6)
(run (dlist->list (dlist-delete-if-not d #'evenp)) '(2 4 6))
(run (dlist-count d) 3)
(run (dolist (x '(2 4 6) (dlist-emptyp d)) (dlist-delete d x)) t)
;; 両端キュー
(run (deque-p (setq q (make-deque))) t)
(run (deque-emptyp q) t)
(run (deque-count q) 0)
(run (push-front q 1) 1)
(run (deque-emptyp q) nil)
(run (deque-count q) 1)
(run (push-back q 10) 10)
(run (deque-emptyp q) nil)
(run (deque-count q) 2)
(run (peek-front q) 1)
(run (peek-back q) 10)
(run (dolist (x '(2 3 4) (deque-count q)) (push-front q x)) 5)
(run (dolist (x '(20 30 40) (deque-count q)) (push-back q x)) 8)
(run (peek-front q) 4)
(run (peek-back q) 40)
(run (let ((a nil)) (dotimes (x 4 a) (push (pop-front q) a)))
'(1 2 3 4))
(run (let ((a nil)) (dotimes (x 4 a) (push (pop-back q) a)))
'(10 20 30 40))
(run (deque-emptyp q) t)
(run (deque-count q) 0)
(run (push-front q 10) 10)
(run (deque-clear q) 0)
(run (deque-count q) 0)
(run (deque-emptyp q) t)
(final))
リスト : deque_tst.asd (defsystem :deque_tst :description "test for deque" :version "0.1.0" :author "Makoto Hiroi" :license "MIT" :depends-on (:mintst :deque) :components ((:file "deque_tst")) :perform (test-op (o s) (symbol-call :deque_tst :test)))