M.Hiroi's Home Page

Common Lisp Programming

お気楽 Common Lisp プログラミング入門 : 自作ライブラリ編

[ Common Lisp | library ]

trie

トライ (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

関数 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)))

Copyright (C) 2023 Makoto Hiroi
All rights reserved.

[ Common Lisp | library ]