トライ (trie)、パトリシア (patricia)、Ternary Search Tree (TST) は木構造の一種で、根 (root) から葉 (leaf) までの経路が一つの文字列に対応します。これらのデータ構造は文字列を高速に探索することができますが、それだけではなく、共通の接頭辞 (common prefix) を持つ文字列、たとえば 'abc' で始まる文字列を簡単に見つけることができます。詳しい説明は以下に示す拙作のページをお読みくださいませ。
アーカイブファイル minlib.tar.gz をインストールする、または プログラムリスト にある以下の 4 つのファイルを、~/common-lisp/ 以下の適当なサブディレクトリ (たとえば trie など) に配置してください。
* (asdf:test-system :trie)
; compiling file ... 略...
----- test start -----
(TRIE-P (SETQ TS (MAKE-TRIE)))
=> T OK
(TRIE-EMPTYP TS)
=> T OK
(TRIE-COUNT TS)
=> 0 OK
(PROGN
(SETQ KS '(foo bar baz oops)
VS '(10 20 30 40))
(MAPC (LAMBDA (K V) (TRIE-SET TS K V)) KS VS)
(TRIE-COUNT TS))
=> 4 OK
(TRIE-EMPTYP TS)
=> NIL OK
(MAPCAR (LAMBDA (K) (TRIE-EXISTS TS K)) '(foo Foo))
=> (T NIL) OK
(MAPCAR (LAMBDA (K) (TRIE-EXISTS TS K)) KS)
=> (T T T T) OK
(MAPCAR (LAMBDA (K) (TRIE-GET TS K)) '(foo Foo))
=> (10 NIL) OK
(MAPCAR (LAMBDA (K) (TRIE-GET TS K)) KS)
=> (10 20 30 40) OK
(LET ((A NIL))
(TRIE-MAPC TS (LAMBDA (K V) (PUSH (CONS (TO-STR K) V) A)))
(REVERSE A))
=> ((oops . 40) (baz . 30) (bar . 20) (foo . 10)) OK
(TRIE-FOLD TS NIL (LAMBDA (A K V) (CONS (CONS (TO-STR K) V) A)))
=> ((foo . 10) (bar . 20) (baz . 30) (oops . 40)) OK
(TRIE-DEL TS foo)
=> T OK
(TRIE-EXISTS TS foo)
=> NIL OK
(TRIE-COUNT TS)
=> 3 OK
(TRIE-DEL TS bar)
=> T OK
(TRIE-EXISTS TS bar)
=> NIL OK
(TRIE-COUNT TS)
=> 2 OK
(TRIE-DEL TS baz)
=> T OK
(TRIE-EXISTS TS baz)
=> NIL OK
(TRIE-COUNT TS)
=> 1 OK
(TRIE-DEL TS oops)
=> T OK
(TRIE-EXISTS TS oops)
=> NIL OK
(TRIE-COUNT TS)
=> 0 OK
(TRIE-EMPTYP TS)
=> T OK
(PROGN (MAPC (LAMBDA (K V) (TRIE-PUSH TS K V)) KS VS) (TRIE-COUNT TS))
=> 4 OK
(TRIE-PUSH TS Foo 0)
=> T OK
(TRIE-COUNT TS)
=> 5 OK
(TRIE-GET TS Foo)
=> (0) OK
(TRIE-PUSH TS Foo 1)
=> NIL OK
(TRIE-COUNT TS)
=> 5 OK
(TRIE-GET TS Foo)
=> (1 0) OK
(TRIE-POP TS Foo)
=> 1 OK
(TRIE-POP TS Foo)
=> 0 OK
(TRIE-POP TS Foo)
=> NIL OK
(TRIE-COUNT TS)
=> 5 OK
(TRIE-DEL TS Foo)
=> T OK
(TRIE-COUNT TS)
=> 4 OK
(TRIE-CLEAR TS)
=> 0 OK
(TRIE-COUNT TS)
=> 0 OK
(TRIE-EMPTYP TS)
=> T OK
(PROGN
(MAPC (LAMBDA (K) (TRIE-SET TS K T)) '(abcde abcd abc ab a))
(TRIE-COUNT TS))
=> 5 OK
(LET ((A NIL))
(TRIE-COMMON-PREFIX TS abc (LAMBDA (K V) (PUSH (CONS (TO-STR K) V) A)))
(REVERSE A))
=> ((abc . T) (abcd . T) (abcde . T)) OK
(TRIE-COUNT (SETQ CS (TRIE-COPY TS)))
=> 5 OK
(MAPCAR (LAMBDA (K) (TRIE-GET CS K)) '(abcde abcd abc ab a))
=> (T T T T T) OK
(TRIE-DEL CS abcde)
=> T OK
(TRIE-EXISTS CS abcde)
=> NIL OK
(TRIE-EXISTS TS abcde)
=> T OK
(PATRICIA-P (SETQ TS (MAKE-PATRICIA)))
=> T OK
; 結果は省略
(TERNARY-P (SETQ TS (MAKE-TERNARY)))
=> T OK
; 結果は省略
----- test end -----
TEST: 141
OK: 141
NG: 0
ERR: 0
T
;;;
;;; sample_tree.lisp : treemap と trie のサンプルプログラム
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(require :treemap)
(use-package :treemap)
(require :trie)
(use-package :trie)
;;; リストの比較 (xs と ys は同じ長さ)
(defun list< (xs ys)
(cond
((null xs) nil)
((< (car xs) (car ys)) t)
((> (car xs) (car ys)) nil)
(t
(list< (cdr xs) (cdr ys)))))
;;; 点 (x y z) を作る
(defun make-point ()
(list (random 100) (random 100) (random 100)))
;;;
;;; 異なる点を n 個作る
;;;
;;; 線形探索
(defun make-data (n)
(do ((a nil))
((zerop n) a)
(let ((xs (make-point)))
(unless (member xs a :test #'equal)
(decf n)
(push xs a)))))
;;; ハッシュ
(defun make-data-hash (n)
(do ((a (make-hash-table :test #'equal)))
((zerop n) a)
(let ((xs (make-point)))
(unless (gethash xs a)
(decf n)
(setf (gethash xs a) t)))))
;;; 赤黒木
(defun make-data-rbtree (n)
(do ((a (make-treemap #'equal #'list<)))
((zerop n) a)
(let ((xs (make-point)))
(unless (tree-exists a xs)
(decf n)
(tree-set a xs t)))))
;;; スプレー木
(defun make-data-splay (n)
(do ((a (make-splaytree #'equal #'list<)))
((zerop n) a)
(let ((xs (make-point)))
(unless (tree-exists a xs)
(decf n)
(tree-set a xs t)))))
;;; トライ木
(defun make-data-trie (n)
(do ((a (make-trie)))
((zerop n) a)
(let ((xs (make-point)))
(unless (trie-exists a xs)
(decf n)
(trie-set a xs t)))))
;;; パトリシア木
(defun make-data-patricia (n)
(do ((a (make-patricia)))
((zerop n) a)
(let ((xs (make-point)))
(unless (trie-exists a xs)
(decf n)
(trie-set a xs t)))))
;;; 三分木
(defun make-data-ternary (n)
(do ((a (make-ternary :elt= #'= :elt< #'<)))
((zerop n) a)
(let ((xs (make-point)))
(unless (trie-exists a xs)
(decf n)
(trie-set a xs t)))))
表 : 実行結果 (単位 : 秒)
個数 : 10000 : 20000 : 40000
-----------+-------+-------+--------
線形探索 : 0.629 : 2.500 : 10.240
ハッシュ : 0.003 : 0.007 : 0.011
赤黒木 : 0.030 : 0.050 : 0.120
スプレー木 : 0.030 : 0.060 : 0.169
トライ : 0.040 : 0.060 : 0.139
パトリシア : 0.030 : 0.070 : 0.160
三分木 : 0.010 : 0.020 : 0.050
実行環境 : SBCL 2.1.11, Ubunts 22.04 (WSL2), Intel Core i5-6200U 2.30GHz
;;;
;;; eight.lisp : 幅優先探索による 8 パズルの解法
;;;
;;; Copyright (c) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(require :queue)
(use-package :queue)
(require :trie)
(use-package :trie)
;;; 盤面
;;; 0 1 2
;;; 3 4 5
;;; 6 7 8
;;; 隣接リスト
(defconstant adjacent
#((1 3) ; 0
(0 2 4) ; 1
(1 5) ; 2
(0 4 6) ; 3
(1 3 5 7) ; 4
(2 4 8) ; 5
(3 7) ; 6
(4 6 8) ; 7
(5 7))) ; 8
;;; 駒の移動
(defun move-piece (board x space)
(let ((newbd (copy-seq board)))
(setf (aref newbd space) (aref newbd x)
(aref newbd x) 0)
newbd))
;;; 解の表示
;;; 局面 st はリスト (board space prev)
(defun print-answer (st)
(when (third st)
(print-answer (third st)))
(format t "~A~%" (first st)))
;;; 幅優先探索
(defun eight-puzzle (start goal)
;; 初期化
(let ((que (make-queue))
(chk (make-ternary :elt= #'= :elt< #'<)))
(enqueue que (list start (position 0 start) nil))
(trie-set chk start t)
(do ()
((queue-emptyp que))
(let* ((st (dequeue que))
(bd (first st))
(sp (second st)))
(when (equalp bd goal)
(print-answer st)
(return))
(dolist (x (aref adjacent sp))
(let ((newbd (move-piece bd x sp)))
(unless (trie-exists chk newbd)
(enqueue que (list newbd x st))
(trie-set chk newbd t))))))))
* (load "eight.lisp") T * (time (eight-puzzle #(8 6 7 2 5 4 3 0 1) #(1 2 3 4 5 6 7 8 0))) #(8 6 7 2 5 4 3 0 1) #(8 6 7 2 0 4 3 5 1) #(8 0 7 2 6 4 3 5 1) #(0 8 7 2 6 4 3 5 1) #(2 8 7 0 6 4 3 5 1) #(2 8 7 3 6 4 0 5 1) #(2 8 7 3 6 4 5 0 1) #(2 8 7 3 6 4 5 1 0) #(2 8 7 3 6 0 5 1 4) #(2 8 0 3 6 7 5 1 4) #(2 0 8 3 6 7 5 1 4) #(2 6 8 3 0 7 5 1 4) #(2 6 8 0 3 7 5 1 4) #(2 6 8 5 3 7 0 1 4) #(2 6 8 5 3 7 1 0 4) #(2 6 8 5 3 7 1 4 0) #(2 6 8 5 3 0 1 4 7) #(2 6 0 5 3 8 1 4 7) #(2 0 6 5 3 8 1 4 7) #(2 3 6 5 0 8 1 4 7) #(2 3 6 0 5 8 1 4 7) #(2 3 6 1 5 8 0 4 7) #(2 3 6 1 5 8 4 0 7) #(2 3 6 1 5 8 4 7 0) #(2 3 6 1 5 0 4 7 8) #(2 3 0 1 5 6 4 7 8) #(2 0 3 1 5 6 4 7 8) #(0 2 3 1 5 6 4 7 8) #(1 2 3 0 5 6 4 7 8) #(1 2 3 4 5 6 0 7 8) #(1 2 3 4 5 6 7 0 8) #(1 2 3 4 5 6 7 8 0) Evaluation took: 0.640 seconds of real time 0.648431 seconds of total run time (0.638835 user, 0.009596 system) [ Run times consist of 0.057 seconds GC time, and 0.592 seconds non-GC time. ] 101.25% CPU 92 lambdas converted 1,556,284,779 processor cycles 91,599,088 bytes consed NIL
実行時間 : 単位 (秒) トライ : 0.72 パトリシア : 0.70 三分木 : 0.64 ハッシュ表 : 0.22
* (require :trie)
("TRIE")
* (use-package :trie)
T
* (defun make-suffix (ts seq) (dotimes (x (length seq) ts) (trie-set ts (subseq seq x) t)))
MAKE-SUFFIX
* (defvar a (make-suffix (make-trie) "banana"))
A
* (trie-mapc a (lambda (k v) (format t "~s, ~s~%" k v)))
(#\n #\a), T
(#\n #\a #\n #\a), T
(#\a), T
(#\a #\n #\a), T
(#\a #\n #\a #\n #\a), T
(#\b #\a #\n #\a #\n #\a), T
NIL
* (defvar b (make-suffix (make-ternary) "banana"))
B
* (trie-mapc b (lambda (k v) (format t "~s, ~s~%" k v)))
(#\a), T
(#\a #\n #\a), T
(#\a #\n #\a #\n #\a), T
(#\b #\a #\n #\a #\n #\a), T
(#\n #\a), T
(#\n #\a #\n #\a), T
NIL
* (defvar c (make-suffix (make-patricia) "banana"))
C
* (trie-mapc c (lambda (k v) (format t "~s, ~s~%" k v)))
("na"), T
("na" "na"), T
("a"), T
("a" "na"), T
("a" "na" "na"), T
("banana"), T
NIL
* (trie-common-prefix a "an" (lambda (k v) (format t "~s, ~s~%" k v)))
(#\a #\n #\a), T
(#\a #\n #\a #\n #\a), T
NIL
* (trie-common-prefix b "an" (lambda (k v) (format t "~s, ~s~%" k v)))
(#\a #\n #\a), T
(#\a #\n #\a #\n #\a), T
NIL
* (trie-common-prefix c "an" (lambda (k v) (format t "~s, ~s~%" k v)))
("a" "an"), T
("an" "a" "na"), T
NIL
関数 make-suffix は引数 seq の suffix trie (または suffix tree) を生成します。(subseq seq x) で seq の接尾辞を作り、メソッド trie-set でトライに追加します。とても簡単な方法ですが、データが多くなると時間がかかるのが欠点です。データ数を N とすると、実行時間は N2 に比例します。とても遅いので実用的ではありません。ご注意くださいませ。
;;;
;;; trie.lisp : トライ、パトリシア、三分木
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :trie)
(defpackage :trie (:use :cl))
(in-package :trie)
(export '(trie make-trie trie-p
patricia make-patricia patricia-p
ternary make-ternary ternary-p
trie-emptyp trie-count trie-clear
trie-exists trie-get trie-set trie-del
trie-fold trie-mapc trie-push trie-pop
trie-copy trie-common-prefix
))
;;; メソッドの宣言
(defgeneric trie-emptyp (ts))
(defgeneric trie-count (ts))
(defgeneric trie-clear (ts))
(defgeneric trie-exists (ts seq))
(defgeneric trie-get (ts seq))
(defgeneric trie-set (ts seq val))
(defgeneric trie-del (ts seq))
(defgeneric trie-push (ts seq val))
(defgeneric trie-pop (ts seq))
(defgeneric trie-mapc (ts fn))
(defgeneric trie-fold (ts a fn))
(defgeneric trie-copy (ts))
(defgeneric trie-common-prefix (ts seq fn))
;;;
;;; トライ
;;;
;;; 節
(defstruct node item child (value 'none))
;;; トライ
(defstruct trie
(root (make-node)) (size 0) (elt= #'eql))
;;;
;;; 作業用操作関数
;;;
;;; 葉
(defun leaf-p (nd)
(and (node-p nd)
(not (eq (node-value nd) 'none))))
;;; 挿入
(defun node-insert (nd seq i elt=)
(if (= (length seq) i)
nd
(let ((child (find (elt seq i) (node-child nd) :key #'node-item :test elt=)))
(unless child
(setf child (make-node :item (elt seq i)))
(push child (node-child nd)))
(node-insert child seq (1+ i) elt=))))
;;; 探索
(defun node-search (nd seq i elt=)
(if (= (length seq) i)
nd
(let ((child (find (elt seq i) (node-child nd) :key #'node-item :test elt=)))
(when child
(node-search child seq (1+ i) elt=)))))
;;; 巡回
(defun node-traverse (nd func a)
(let ((a1 (cons (node-item nd) a)))
(when (leaf-p nd)
(funcall func (reverse a1) (node-value nd)))
(dolist (x (node-child nd))
(node-traverse x func a1))))
;;; 畳み込み
(defun node-fold (nd func seq a)
(let ((seq1 (cons (node-item nd) seq)))
(when (leaf-p nd)
(setf a (funcall func a (reverse seq1) (node-value nd))))
(dolist (x (node-child nd) a)
(setf a (node-fold x func seq1 a)))))
;;;
;;; メソッドの定義
;;;
;;; トライは空か
(defmethod trie-emptyp ((ts trie)) (zerop (trie-size ts)))
;;; 要素数を求める
(defmethod trie-count ((ts trie)) (trie-size ts))
;;; クリア
(defmethod trie-clear ((ts trie))
(setf (node-child (trie-root ts)) nil
(trie-size ts) 0))
;;; 挿入
(defmethod trie-set ((ts trie) seq val)
(let ((nd (node-insert (trie-root ts) seq 0 (trie-elt= ts))))
(cond
((leaf-p nd)
(setf (node-value nd) val)
nil)
(t
(setf (node-value nd) val)
(incf (trie-size ts))
t))))
(defmethod trie-push ((ts trie) seq val)
(let ((nd (node-insert (trie-root ts) seq 0 (trie-elt= ts))))
(cond
((leaf-p nd)
(setf (node-value nd) (cons val (node-value nd)))
nil)
(t
(setf (node-value nd) (list val))
(incf (trie-size ts))
t))))
;;; 探索
(defmethod trie-exists ((ts trie) seq)
(leaf-p (node-search (trie-root ts) seq 0 (trie-elt= ts))))
(defmethod trie-get ((ts trie) seq)
(let ((nd (node-search (trie-root ts) seq 0 (trie-elt= ts))))
(when (leaf-p nd)
(node-value nd))))
;;; 削除
(defmethod trie-del ((ts trie) seq)
(let ((nd (node-search (trie-root ts) seq 0 (trie-elt= ts))))
(when (leaf-p nd)
(setf (node-value nd) 'none)
(decf (trie-size ts))
(when (zerop (trie-size ts))
(setf (node-child (trie-root ts)) nil))
t)))
(defmethod trie-pop ((ts trie) seq)
(let ((nd (node-search (trie-root ts) seq 0 (trie-elt= ts))))
(when (and (leaf-p nd)
(consp (node-value nd)))
(pop (node-value nd)))))
;;; 巡回
(defmethod trie-mapc ((ts trie) func)
(dolist (x (node-child (trie-root ts)))
(node-traverse x func nil)))
;;; 畳み込み
(defmethod trie-fold ((ts trie) a func)
(dolist (x (node-child (trie-root ts)) a)
(setf a (node-fold x func nil a))))
;;; 共通接頭辞の取得
(defmethod trie-common-prefix ((ts trie) seq func)
(let ((k (coerce seq 'list)))
(let ((nd (node-search (trie-root ts) seq 0 (trie-elt= ts))))
(when nd
(when (leaf-p nd)
(funcall func k (node-value nd)))
(dolist (x (node-child nd))
(node-traverse x func (reverse k)))))))
;;; トライのコピー
(defmethod trie-copy ((ts trie))
(let ((new-ts (make-trie :elt= (trie-elt= ts))))
(trie-mapc ts (lambda (k v) (trie-set new-ts k v)))
new-ts))
;;;
;;; patricia tree
;;;
;;; 型の定義
(defstruct patricia
(root (make-node)) (size 0) (elt= #'eql))
;;; 子の探索
(defun child-search-pat (nd seq si elt=)
(dolist (x (node-child nd))
(when (funcall elt= (elt seq si) (elt (node-item x) 0))
(return x))))
;;; node から最長一致する節を求める
(defun node-longest-match (nd seq elt=)
(do ((k (length seq))
(i 0))
((>= i k) (values nd i 0))
(let ((child (child-search-pat nd seq i elt=)))
(unless child
(return (values nd i 0)))
(do* ((j 1)
(s1 (node-item child))
(k1 (length s1)))
((>= j k1) (incf i k1))
(when (or (>= (+ i j) k)
(not (funcall elt= (elt seq (+ i j)) (elt s1 j))))
;; child の途中まで一致
(return-from node-longest-match (values child (+ i j) j)))
(incf j))
(setf nd child))))
;;; 新しい節と葉を追加する
;;; nd <- new-node + val
(defun insert-new-node-leaf (nd seq val)
(let ((new-node (make-node :item seq :value val)))
(push new-node (node-child nd))
t))
;;; 節を分割する
;;; nd -> (nd - child)
(defun divide-node (nd sub-match)
(let ((child (make-node :item (subseq (node-item nd) sub-match))))
(setf (node-child child) (node-child nd)
(node-value child) (node-value nd)
(node-child nd) (list child)
(node-value nd) 'none
(node-item nd) (subseq (node-item nd) 0 sub-match))
child))
;;;
;;; メソッドの定義
;;;
;;; 空か?
(defmethod trie-emptyp ((ts patricia))
(zerop (patricia-size ts)))
;;; 要素数
(defmethod trie-count ((ts patricia))
(patricia-size ts))
;;; 空にする
(defmethod trie-clear ((ts patricia))
(setf (node-child (patricia-root ts)) nil
(patricia-size ts) 0))
;;; データの挿入
(defmethod trie-set ((ts patricia) seq val)
(multiple-value-bind
(nd match sub-match)
(node-longest-match (patricia-root ts) seq (patricia-elt= ts))
(cond
((zerop sub-match)
(if (= (length seq) match)
;; 終端のチェック
(cond
((leaf-p nd)
(setf (node-value nd) val)
nil)
(t
(setf (node-value nd) val)
(incf (patricia-size ts))
t))
;; nd に新しい節と葉を追加する
(progn
(insert-new-node-leaf nd (subseq seq match) val)
(incf (patricia-size ts))
t)))
(t
;; nd を sub-match で分割
;; nd - child
(divide-node nd sub-match)
(if (= (length seq) match)
(setf (node-value nd) val)
(insert-new-node-leaf nd (subseq seq match) val))
(incf (patricia-size ts))
t))))
(defmethod trie-push ((ts patricia) seq val)
(multiple-value-bind
(nd match sub-match)
(node-longest-match (patricia-root ts) seq (patricia-elt= ts))
(cond
((zerop sub-match)
(if (= (length seq) match)
(cond
((leaf-p nd)
(setf (node-value nd) (cons val (node-value nd)))
nil)
(t
(setf (node-value nd) (list val))
(incf (patricia-size ts))
t))
(progn
(insert-new-node-leaf nd (subseq seq match) (list val))
(incf (patricia-size ts))
t)))
(t
(divide-node nd sub-match)
(if (= (length seq) match)
(setf (node-value nd) (list val))
(insert-new-node-leaf nd (subseq seq match) (list val)))
(incf (patricia-size ts))
t))))
;;; データの探索
(defmethod trie-exists ((ts patricia) seq)
(multiple-value-bind
(nd match sub-match)
(node-longest-match (patricia-root ts) seq (patricia-elt= ts))
(and (zerop sub-match)
(= (length seq) match)
(leaf-p nd))))
(defmethod trie-get ((ts patricia) seq)
(multiple-value-bind
(nd match sub-match)
(node-longest-match (patricia-root ts) seq (patricia-elt= ts))
(when (and (zerop sub-match)
(= (length seq) match)
(leaf-p nd))
(node-value nd))))
(defmethod trie-pop ((ts patricia) seq)
(multiple-value-bind
(nd match sub-match)
(node-longest-match (patricia-root ts) seq (patricia-elt= ts))
(when (and (zerop sub-match)
(= (length seq) match)
(leaf-p nd)
(consp (node-value nd)))
(pop (node-value nd)))))
;;; 削除
(defmethod trie-del ((ts patricia) seq)
(multiple-value-bind
(nd match sub-match)
(node-longest-match (patricia-root ts) seq (patricia-elt= ts))
(when (and (zerop sub-match)
(= (length seq) match)
(leaf-p nd))
(setf (node-value nd) 'none)
(decf (patricia-size ts))
(when (zerop (patricia-size ts))
(setf (node-child (patricia-root ts)) nil))
t)))
;;; 巡回
(defmethod trie-mapc ((ts patricia) func)
(dolist (x (node-child (patricia-root ts)))
(node-traverse x func nil)))
;;; 畳み込み
(defmethod trie-fold ((ts patricia) a func)
(dolist (x (node-child (patricia-root ts)) a)
(setf a (node-fold x func nil a))))
;;; コピー
(defmethod trie-copy ((ts patricia))
(let ((new-ts (make-patricia :elt= (patricia-elt= ts))))
(trie-mapc ts (lambda (k v)
(let ((seq-type (if (stringp (car k)) 'string (car (type-of (car k))))))
(trie-set new-ts (apply #'concatenate seq-type k) v))))
new-ts))
;;; 共通接頭辞を持つデータを求める
(defmethod trie-common-prefix ((ts patricia) seq func)
(multiple-value-bind
(nd match sub-match)
(node-longest-match (patricia-root ts) seq (patricia-elt= ts))
(when (= (length seq) match)
(let ((seq1 (if (zerop sub-match)
(list seq)
(list (subseq (node-item nd) sub-match) seq))))
(when (leaf-p nd)
(funcall func seq1 (node-value nd)))
(dolist (x (node-child nd))
(node-traverse x func seq1))))))
;;;
;;; ternary search tree (三分木)
;;;
;;; 節
(defstruct node3
item left mid right (value 'none))
;;; データ型
(defstruct ternary
(root (make-node3)) (size 0) (elt= #'char=) (elt< #'char<))
;;; 葉
(defun leaf3-p (nd)
(and (node3-p nd)
(not (eq (node3-value nd) 'none))))
;;; 子の探索
(defun child-search-tst (nd x elt= elt<)
(cond
((null nd) nil)
((funcall elt= x (node3-item nd)) nd)
((funcall elt< x (node3-item nd))
(child-search-tst (node3-left nd) x elt= elt<))
(t
(child-search-tst (node3-right nd) x elt= elt<))))
;;; 子の挿入 (x は node3)
(defun child-insert-tst (nd x elt= elt<)
(cond
((null nd) x)
((funcall elt= (node3-item x) (node3-item nd)) nd)
((funcall elt< (node3-item x) (node3-item nd))
(setf (node3-left nd)
(child-insert-tst (node3-left nd) x elt= elt<))
nd)
(t
(setf (node3-right nd)
(child-insert-tst (node3-right nd) x elt= elt<))
nd)))
;;; 挿入
(defun node-insert-tst (nd seq i elt= elt<)
(if (= (length seq) i)
nd
(let ((child (child-search-tst (node3-mid nd) (elt seq i) elt= elt<)))
(unless child
(setf child (make-node3 :item (elt seq i))
(node3-mid nd) (child-insert-tst (node3-mid nd) child elt= elt<)))
(node-insert-tst child seq (1+ i) elt= elt<))))
;;; 探索
(defun node-search-tst (nd seq i elt= elt<)
(if (= (length seq) i)
nd
(let ((child (child-search-tst (node3-mid nd) (elt seq i) elt= elt<)))
(when child
(node-search-tst child seq (1+ i) elt= elt<)))))
;;; 巡回
(defun node-traverse-tst (nd func a)
(when nd
(node-traverse-tst (node3-left nd) func a)
(let ((a1 (cons (node3-item nd) a)))
(when (leaf3-p nd)
(funcall func (reverse a1) (node3-value nd)))
(node-traverse-tst (node3-mid nd) func a1))
(node-traverse-tst (node3-right nd) func a)))
;;; 畳み込み
(defun node-fold-tst (nd func seq a)
(when nd
(setf a (node-fold-tst (node3-left nd) func seq a))
(let ((seq1 (cons (node3-item nd) seq)))
(when (leaf3-p nd)
(setf a (funcall func a (reverse seq1) (node3-value nd))))
(setf a (node-fold-tst (node3-mid nd) func seq1 a)))
(setf a (node-fold-tst (node3-right nd) func seq a)))
a)
;;;
;;; メソッドの定義
;;;
;;; トライは空か
(defmethod trie-emptyp ((ts ternary)) (zerop (ternary-size ts)))
;;; 要素数を求める
(defmethod trie-count ((ts ternary)) (ternary-size ts))
;;; クリア
(defmethod trie-clear ((ts ternary))
(setf (node3-mid (ternary-root ts)) nil
(ternary-size ts) 0))
;;; 挿入
(defmethod trie-set ((ts ternary) seq val)
(let ((nd (node-insert-tst (ternary-root ts) seq 0 (ternary-elt= ts) (ternary-elt< ts))))
(cond
((leaf3-p nd)
(setf (node3-value nd) val)
nil)
(t
(setf (node3-value nd) val)
(incf (ternary-size ts))
t))))
(defmethod trie-push ((ts ternary) seq val)
(let ((nd (node-insert-tst (ternary-root ts) seq 0 (ternary-elt= ts) (ternary-elt< ts))))
(cond
((leaf3-p nd)
(setf (node3-value nd) (cons val (node3-value nd)))
nil)
(t
(setf (node3-value nd) (list val))
(incf (ternary-size ts))
t))))
;;; 探索
(defmethod trie-exists ((ts ternary) seq)
(leaf3-p (node-search-tst (ternary-root ts) seq 0 (ternary-elt= ts) (ternary-elt< ts))))
(defmethod trie-get ((ts ternary) seq)
(let ((nd (node-search-tst (ternary-root ts) seq 0 (ternary-elt= ts) (ternary-elt< ts))))
(when (leaf3-p nd)
(node3-value nd))))
;;; 削除
(defmethod trie-del ((ts ternary) seq)
(let ((nd (node-search-tst (ternary-root ts) seq 0 (ternary-elt= ts) (ternary-elt< ts))))
(when (leaf3-p nd)
(setf (node3-value nd) 'none)
(decf (ternary-size ts))
(when (zerop (ternary-size ts))
(setf (node3-mid (ternary-root ts)) nil))
t)))
(defmethod trie-pop ((ts ternary) seq)
(let ((nd (node-search-tst (ternary-root ts) seq 0 (ternary-elt= ts) (ternary-elt< ts))))
(when (and (leaf3-p nd)
(consp (node3-value nd)))
(pop (node3-value nd)))))
;;; 巡回
(defmethod trie-mapc ((ts ternary) func)
(node-traverse-tst (node3-mid (ternary-root ts)) func nil))
;;; 畳み込み
(defmethod trie-fold ((ts ternary) a func)
(node-fold-tst (node3-mid (ternary-root ts)) func nil a))
;;; コピー
(defmethod trie-copy ((ts ternary))
(let ((new-ts (make-ternary :elt= (ternary-elt= ts) :elt< (ternary-elt< ts))))
(trie-mapc ts (lambda (k v) (trie-set new-ts k v)))
new-ts))
;;; 共通接頭辞の取得
(defmethod trie-common-prefix ((ts ternary) seq func)
(let ((k (coerce seq 'list))
(nd (node-search-tst (ternary-root ts) seq 0 (ternary-elt= ts) (ternary-elt< ts))))
(when nd
(when (leaf3-p nd)
(funcall func k (node3-value nd)))
(node-traverse-tst (node3-mid nd) func (reverse k)))))
;;;
;;; trie_tst.lisp : trie のテスト
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :trie_tst)
(defpackage :trie_tst (:use :cl :mintst :trie))
(in-package :trie_tst)
(export '(test))
(defvar ts nil)
(defvar ks nil)
(defvar vs nil)
(defvar cs nil)
(defun to-str (xs)
(if (stringp (car xs))
(apply #'concatenate 'string xs)
(coerce xs 'string)))
(defun test-sub ()
(run (trie-emptyp ts) t)
(run (trie-count ts) 0)
(run (progn
(setq ks '("foo" "bar" "baz" "oops")
vs '(10 20 30 40))
(mapc (lambda (k v) (trie-set ts k v)) ks vs)
(trie-count ts))
4)
(run (trie-emptyp ts) nil)
(run (mapcar (lambda (k) (trie-exists ts k)) '("foo" "Foo")) '(t nil))
(run (mapcar (lambda (k) (trie-exists ts k)) ks) '(t t t t))
(run (mapcar (lambda (k) (trie-get ts k)) '("foo" "Foo")) '(10 nil))
(run (mapcar (lambda (k) (trie-get ts k)) ks) '(10 20 30 40))
(cond
((ternary-p ts)
(run (let ((a nil))
(trie-mapc ts (lambda (k v) (push (cons (to-str k) v) a)))
(reverse a))
'(("bar" . 20) ("baz" . 30) ("foo" . 10) ("oops" . 40)))
(run (trie-fold ts nil (lambda (a k v) (cons (cons (to-str k) v) a)))
'(("oops" . 40) ("foo" . 10) ("baz" . 30) ("bar" . 20))))
(t
(run (let ((a nil))
(trie-mapc ts (lambda (k v) (push (cons (to-str k) v) a)))
(reverse a))
'(("oops" . 40) ("baz" . 30) ("bar" . 20) ("foo" . 10)))
(run (trie-fold ts nil (lambda (a k v) (cons (cons (to-str k) v) a)))
'(("foo" . 10) ("bar" . 20) ("baz" . 30) ("oops" . 40)))))
(run (trie-del ts "foo") t)
(run (trie-exists ts "foo") nil)
(run (trie-count ts) 3)
(run (trie-del ts "bar") t)
(run (trie-exists ts "bar") nil)
(run (trie-count ts) 2)
(run (trie-del ts "baz") t)
(run (trie-exists ts "baz") nil)
(run (trie-count ts) 1)
(run (trie-del ts "oops") t)
(run (trie-exists ts "oops") nil)
(run (trie-count ts) 0)
(run (trie-emptyp ts) t)
(run (progn
(mapc (lambda (k v) (trie-push ts k v)) ks vs)
(trie-count ts))
4)
(run (trie-push ts "Foo" 0) t)
(run (trie-count ts) 5)
(run (trie-get ts "Foo") '(0))
(run (trie-push ts "Foo" 1) nil)
(run (trie-count ts) 5)
(run (trie-get ts "Foo") '(1 0))
(run (trie-pop ts "Foo") 1)
(run (trie-pop ts "Foo") 0)
(run (trie-pop ts "Foo") nil)
(run (trie-count ts) 5)
(run (trie-del ts "Foo") t)
(run (trie-count ts) 4)
(run (trie-clear ts) 0)
(run (trie-count ts) 0)
(run (trie-emptyp ts) t)
;;
(run (progn
(mapc (lambda (k) (trie-set ts k t)) '("abcde" "abcd" "abc" "ab" "a"))
(trie-count ts))
5)
(run (let ((a nil))
(trie-common-prefix ts "abc" (lambda (k v) (push (cons (to-str k) v) a)))
(reverse a))
'(("abc" . t) ("abcd" . t) ("abcde" . t)))
(run (trie-count (setq cs (trie-copy ts))) 5)
(run (mapcar (lambda (k) (trie-get cs k)) '("abcde" "abcd" "abc" "ab" "a"))
'(t t t t t))
(run (trie-del cs "abcde") t)
(run (trie-exists cs "abcde") nil)
(run (trie-exists ts "abcde") t))
(defun test ()
(initial)
(run (trie-p (setq ts (make-trie))) t)
(test-sub)
(run (patricia-p (setq ts (make-patricia))) t)
(test-sub)
(run (ternary-p (setq ts (make-ternary))) t)
(test-sub)
(final))
リスト : trie_tst.asd (defsystem :trie_tst :description "test for trie" :version "0.1.0" :author "Makoto Hiroi" :license "MIT" :depends-on (:mintst :trie) :components ((:file "trie_tst")) :perform (test-op (o s) (symbol-call :trie_tst :test)))