赤黒木 (red-black tree) とスプレー木 (splay tree) を使って連想配列を実装したライブラリです。アルゴリズムの詳しい説明は以下に示す拙作のページをお読みくださいませ。
アーカイブファイル minlib.tar.gz をインストールする、または プログラムリスト にある以下の 4 つのファイルを、~/common-lisp/ 以下の適当なサブディレクトリ (たとえば treemap など) に配置してください。
* (asdf:test-system :treemap)
; compiling file ... 略 ...
----- test start -----
(TREEMAP-P (SETQ TS (MAKE-TREEMAP #'STRING= #'STRING<)))
=> T OK
(TREE-EMPTYP TS)
=> T OK
(TREE-COUNT TS)
=> 0 OK
(PROGN
(SETQ KS '(foo bar baz oops)
VS '(10 20 30 40))
(MAPC (LAMBDA (K V) (TREE-SET TS K V)) KS VS)
(TREE-COUNT TS))
=> 4 OK
(TREE-EMPTYP TS)
=> NIL OK
(MAPCAR (LAMBDA (K) (TREE-EXISTS TS K)) '(foo Foo))
=> (T NIL) OK
(MAPCAR (LAMBDA (K) (TREE-EXISTS TS K)) KS)
=> (T T T T) OK
(MAPCAR (LAMBDA (K) (TREE-GET TS K)) '(foo Foo))
=> (10 NIL) OK
(MAPCAR (LAMBDA (K) (TREE-GET TS K)) KS)
=> (10 20 30 40) OK
(TREE-MIN TS)
=> (bar . 20) OK
(TREE-MAX TS)
=> (oops . 40) OK
(LET ((A NIL))
(TREE-MAPC TS (LAMBDA (K V) (PUSH (CONS K V) A)))
(REVERSE A))
=> ((bar . 20) (baz . 30) (foo . 10) (oops . 40)) OK
(TREE-FOLD-RIGHT TS NIL (LAMBDA (A K V) (CONS (CONS K V) A)))
=> ((bar . 20) (baz . 30) (foo . 10) (oops . 40)) OK
(TREE-FOLD-LEFT TS NIL (LAMBDA (A K V) (CONS (CONS K V) A)))
=> ((oops . 40) (foo . 10) (baz . 30) (bar . 20)) OK
(TREE-MAX-DEL TS)
=> T OK
(TREE-COUNT TS)
=> 3 OK
(TREE-MAX TS)
=> (foo . 10) OK
(TREE-MIN-DEL TS)
=> T OK
(TREE-COUNT TS)
=> 2 OK
(TREE-MIN TS)
=> (baz . 30) OK
(TREE-DEL TS foo)
=> T OK
(TREE-COUNT TS)
=> 1 OK
(TREE-MIN TS)
=> (baz . 30) OK
(TREE-MAX TS)
=> (baz . 30) OK
(TREE-DEL TS baz)
=> T OK
(TREE-COUNT TS)
=> 0 OK
(TREE-EMPTYP TS)
=> T OK
(TREE-MAX TS)
=> NIL OK
(TREE-MIN TS)
=> NIL OK
(PROGN (MAPC (LAMBDA (K V) (TREE-PUSH TS K V)) KS VS) (TREE-COUNT TS))
=> 4 OK
(TREE-PUSH TS Foo 0)
=> T OK
(TREE-COUNT TS)
=> 5 OK
(TREE-GET TS Foo)
=> (0) OK
(TREE-PUSH TS Foo 1)
=> NIL OK
(TREE-COUNT TS)
=> 5 OK
(TREE-GET TS Foo)
=> (1 0) OK
(TREE-POP TS Foo)
=> 1 OK
(TREE-POP TS Foo)
=> 0 OK
(TREE-POP TS Foo)
=> NIL OK
(TREE-COUNT TS)
=> 5 OK
(TREE-DEL TS Foo)
=> T OK
(TREE-COUNT TS)
=> 4 OK
(TREE-CLEAR TS)
=> 0 OK
(TREE-COUNT TS)
=> 0 OK
(TREE-EMPTYP TS)
=> T OK
(SPLAYTREE-P (SETQ TS (MAKE-SPLAYTREE #'STRING= #'STRING<)))
=> T OK
;;; テスト結果は treemap と同じなので省略
----- test end -----
TEST: 90
OK: 90
NG: 0
ERR: 0
T
;;;
;;; testrbtree.lisp : 赤黒木のテスト
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(require :utils)
(use-package :utils)
(require :treemap)
(use-package :treemap)
;;; 赤黒木のチェック
(defun check-rbtree (node)
(cond
((treemap::node-emptyp node) 0)
(t
(when (treemap::node-redp node)
(when (or (treemap::node-redp (treemap::node-left node))
(treemap::node-redp (treemap::node-right node)))
(error "treemap error1")))
(let ((a (check-rbtree (treemap::node-left node)))
(b (check-rbtree (treemap::node-right node))))
(when (/= a b)
(error "treemap error2"))
(when (treemap::node-blackp node)
(incf a))
a))))
(defun test-sub (xs)
(let ((a (make-treemap #'= #'<)))
(print "insert")
(dolist (x xs)
(tree-set a x t)
(check-rbtree (treemap::tree-root a)))
(print (tree-emptyp a))
(print (tree-count a))
(print "search")
(dolist (x xs)
(if (not (tree-get a x))
(error "test search error")))
(print "delete")
(dolist (x xs)
(tree-del a x)
(check-rbtree (treemap::tree-root a)))
(print (tree-emptyp a))
(print (tree-count a))))
(defun test (n)
(print "昇順")
(test-sub (iota n))
(print "逆順")
(test-sub (nreverse (iota n)))
(print "ランダム")
(test-sub (tabulate (lambda (x) (random 1d0)) n)))
;;;
;;; 表示
;;;
(defun print-rb-tree (n node)
(unless (treemap::node-emptyp node)
(print-rb-tree (1+ n) (treemap::node-left node))
(dotimes (x n) (princ " "))
(format t "(~A, ~A)~%" (treemap::node-key node) (treemap::node-color node))
(print-rb-tree (1+ n) (treemap::node-right node))))
(defun print-tree (tm)
(format t "--------~%")
(print-rb-tree 0 (treemap::tree-root tm)))
(defun test1 (xs)
(let ((a (make-treemap #'= #'<)))
(dolist (x xs)
(tree-set a x t)
(print-tree a))
(dolist (x xs)
(format t "~A~%" (tree-exists a x)))
(dolist (x xs)
(tree-del a x)
(print-tree a))))
(load "testrbtree.lisp")
T
*(test 10000)
"昇順"
"insert"
NIL
10000
"search"
"delete"
T
0
"逆順"
"insert"
NIL
10000
"search"
"delete"
T
0
"ランダム"
"insert"
NIL
10000
"search"
"delete"
T
0
0
* (test1 (iota 8))
--------
(0, BLACK)
--------
(0, BLACK)
(1, RED)
--------
(0, RED)
(1, BLACK)
(2, RED)
--------
(0, BLACK)
(1, BLACK)
(2, BLACK)
(3, RED)
--------
(0, BLACK)
(1, BLACK)
(2, RED)
(3, BLACK)
(4, RED)
--------
(0, BLACK)
(1, BLACK)
(2, BLACK)
(3, RED)
(4, BLACK)
(5, RED)
--------
(0, BLACK)
(1, BLACK)
(2, BLACK)
(3, RED)
(4, RED)
(5, BLACK)
(6, RED)
--------
(0, BLACK)
(1, RED)
(2, BLACK)
(3, BLACK)
(4, BLACK)
(5, RED)
(6, BLACK)
(7, RED)
T
T
T
T
T
T
T
T
--------
(1, BLACK)
(2, RED)
(3, BLACK)
(4, BLACK)
(5, RED)
(6, BLACK)
(7, RED)
--------
(2, BLACK)
(3, BLACK)
(4, BLACK)
(5, RED)
(6, BLACK)
(7, RED)
--------
(3, BLACK)
(4, RED)
(5, BLACK)
(6, BLACK)
(7, RED)
--------
(4, BLACK)
(5, BLACK)
(6, BLACK)
(7, RED)
--------
(5, BLACK)
(6, BLACK)
(7, BLACK)
--------
(6, BLACK)
(7, RED)
--------
(7, BLACK)
--------
NIL
;;;
;;; testsplay.lisp : スプレー木のテスト
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(require :utils)
(use-package :utils)
(require :treemap)
(use-package :treemap)
(defun test-sub (xs)
(let ((a (make-splaytree #'= #'<)))
(print "insert")
(dolist (x xs)
(tree-set a x t))
(print (tree-emptyp a))
(print (tree-count a))
(print "search")
(dolist (x xs)
(if (not (tree-get a x))
(error "test search error")))
(print "delete")
(dolist (x xs)
(tree-del a x))
(print (tree-emptyp a))
(print (tree-count a))))
(defun test (n)
(print "昇順")
(test-sub (iota n))
(print "逆順")
(test-sub (nreverse (iota n)))
(print "ランダム")
(test-sub (tabulate (lambda (x) (random 1d0)) n)))
;;;
;;; 表示
;;;
(defun print-splay-tree (n node)
(unless (null node)
(print-splay-tree (1+ n) (treemap::node-left node))
(dotimes (x n) (princ " "))
(format t "(~A, ~A)~%" (treemap::node-key node) (treemap::node-value node))
(print-splay-tree (1+ n) (treemap::node-right node))))
(defun print-tree (tm)
(format t "--------~%")
(print-splay-tree 0 (treemap::tree-root tm)))
(defun test1 (xs)
(let ((a (make-splaytree #'= #'<)))
(dolist (x xs)
(tree-set a x t)
(print-tree a))
(dolist (x xs)
(format t "~A~%" (tree-exists a x))
(print-tree a))
(dolist (x xs)
(tree-del a x)
(print-tree a))))
(load "testsplay.lisp")
T
* (test 10000)
"昇順"
"insert"
NIL
10000
"search"
"delete"
T
0
"逆順"
"insert"
NIL
10000
"search"
"delete"
T
0
"ランダム"
"insert"
NIL
10000
"search"
"delete"
T
0
0
* (test1 (iota 8))
--------
(0, T)
--------
(0, T)
(1, T)
--------
(0, T)
(1, T)
(2, T)
--------
(0, T)
(1, T)
(2, T)
(3, T)
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
T
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
T
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
T
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
T
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
T
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
T
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
T
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
T
--------
(0, T)
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
--------
(1, T)
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
--------
(2, T)
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
--------
(3, T)
(4, T)
(5, T)
(6, T)
(7, T)
--------
(4, T)
(5, T)
(6, T)
(7, T)
--------
(5, T)
(6, T)
(7, T)
--------
(6, T)
(7, T)
--------
(7, T)
--------
NIL
;;;
;;; sample_tree.lisp : treemap のサンプルプログラム
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(require :treemap)
(use-package :treemap)
;;; リストの比較 (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)))))
表 : 実行結果 (単位 : 秒)
個数 : 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
実行環境 : SBCL 2.1.11, Ubunts 22.04 (WSL2), Intel Core i5-6200U 2.30GHz
;;;
;;; treemap.lisp : 連想配列 (赤黒木, スプレー木)
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :treemap)
(defpackage :treemap (:use :cl))
(in-package :treemap)
(export '(make-treemap
make-splaytree
treemap-p
splaytree-p
tree-emptyp
tree-count
tree-clear
tree-exists
tree-get
tree-max
tree-min
tree-set
tree-push
tree-pop
tree-del
tree-max-del
tree-min-del
tree-mapc
tree-fold-left
tree-fold-right))
;;; メソッドの宣言
(defgeneric tree-emptyp (tm))
(defgeneric tree-count (tm))
(defgeneric tree-clear (tm))
(defgeneric tree-exists (tm k))
(defgeneric tree-get (tm k))
(defgeneric tree-max (tm))
(defgeneric tree-min (tm))
(defgeneric tree-set (tm k v))
(defgeneric tree-push (tm k v))
(defgeneric tree-pop (tm k))
(defgeneric tree-del (tm k))
(defgeneric tree-max-del (tm))
(defgeneric tree-min-del (tm))
(defgeneric tree-mapc (tm fn))
(defgeneric tree-fold-left (tm a fn))
(defgeneric tree-fold-right (tm a fn))
;;; 作業用メソッド
(defgeneric node-emptyp (node))
(defgeneric rotate-left (node))
(defgeneric rotate-right (node))
(defgeneric node-search-max (node))
(defgeneric node-search-min (node))
;;;
;;; スプレー木
;;;
;;; 節の定義 (終端は nil)
(defclass snode ()
((key :accessor node-key :initform nil :initarg :key)
(value :accessor node-value :initform nil :initarg :value)
(left :accessor node-left :initform nil :initarg :left)
(right :accessor node-right :initform nil :initarg :right)))
;;; 終端か?
;;; (高階関数で使用, 操作関数は null でチェック)
(defmethod node-emptyp ((node snode)) (null node))
(defmethod node-emptyp ((node null)) t)
;;;
;;; splay 操作関数
;;;
;;; 右回転
(defmethod rotate-right ((node snode))
(let ((lnode (node-left node)))
(setf (node-left node) (node-right lnode)
(node-right lnode) node)
lnode))
;;; 左回転
(defmethod rotate-left ((node snode))
(let ((rnode (node-right node)))
(setf (node-right node) (node-left rnode)
(node-left rnode) node)
rnode))
;;; Top-Down Splay
(defun node-splay (node key elt= elt<)
(let* ((wnode (make-instance 'snode)) ; Splay 作業用セル
(rnode wnode) ; rnode は右部分木になる節を追加する
(lnode wnode)) ; lnode は左部分木になる節を追加する
(loop
(cond
((funcall elt= key (node-key node)) (return))
((funcall elt< key (node-key node))
;; node は右部分木になる
(cond
((null (node-left node)) (return))
((funcall elt< key (node-key (node-left node)))
;; 右回転
(setf node (rotate-right node))
(when (null (node-left node)) (return))))
(setf (node-left rnode) node
rnode node
node (node-left node)))
(t
;; node は左部分木になる
(cond
((null (node-right node)) (return))
((funcall elt< (node-key (node-right node)) key)
;; 左回転
(setf node (rotate-left node))
(when (null (node-right node)) (return))))
(setf (node-right lnode) node
lnode node
node (node-right node)))))
(setf (node-left rnode) (node-right node)
(node-right lnode) (node-left node)
(node-left node) (node-right wnode)
(node-right node) (node-left wnode))
node))
;;;
;;; 探索
;;;
;;; 最大値の探索
(defmethod node-search-max ((node snode))
(let* ((wnode (make-instance 'snode)) ; Splay 作業用セル
(lnode wnode)) ; lnode は左部分木になる節を追加する
(loop
(when (null (node-right node)) (return))
;; node は左部分木になる
;; 左回転
(setf node (rotate-left node))
(when (null (node-right node)) (return))
(setf (node-right lnode) node
lnode node
node (node-right node)))
(setf (node-right lnode) (node-left node)
(node-left node) (node-right wnode))
node))
;;; 最小値の探索
(defmethod node-search-min ((node snode))
(let* ((wnode (make-instance 'snode)) ; Splay 作業用セル
(rnode wnode)) ; rnode は右部分木になる節を追加する
(loop
(when (null (node-left node)) (return))
;; node は右部分木になる
;; 右回転
(setf node (rotate-right node))
(when (null (node-left node)) (return))
(setf (node-left rnode) node
rnode node
node (node-left node)))
(setf (node-left rnode) (node-right node)
(node-right node) (node-left wnode))
node))
;;;
;;; 高階関数 (共通で使用できる)
;;;
;;; 巡回
(defun node-traverse (func node)
(cond
((not (node-emptyp node))
(node-traverse func (node-left node))
(funcall func (node-key node) (node-value node))
(node-traverse func (node-right node)))))
;;; 畳み込み (左部分木から)
(defun node-fold-left (func a node)
(if (node-emptyp node)
a
(let ((b (node-fold-left func a (node-left node))))
(node-fold-left func (funcall func b (node-key node) (node-value node)) (node-right node)))))
;;; 畳み込み (右部分木から)
(defun node-fold-right (func a node)
(if (node-emptyp node)
a
(let ((b (node-fold-right func a (node-right node))))
(node-fold-right func (funcall func b (node-key node) (node-value node)) (node-left node)))))
;;;
;;; 連想配列 (スプレイ木)
;;;
(defclass splaytree ()
((root :accessor tree-root :initform nil :initarg :root)
(size :accessor tree-size :initform 0 :initarg :size)
(elt= :accessor tree-elt= :initform nil :initarg :elt=)
(elt< :accessor tree-elt< :initform nil :initarg :elt<)))
;;; コンストラクタ
(defun make-splaytree (elt= elt<)
(make-instance 'splaytree :elt= elt= :elt< elt<))
;;; 型述語
(defun splaytree-p (x) (typep x 'splaytree))
;;; 木は空か?
(defmethod tree-emptyp ((tm splaytree)) (null (tree-root tm)))
;;; 木の要素数を返す
(defmethod tree-count ((tm splaytree)) (tree-size tm))
;;; 木を空にする
(defmethod tree-clear ((tm splaytree))
(setf (tree-root tm) nil
(tree-size tm) 0))
;;; 木に key があるか?
(defmethod tree-exists ((tm splaytree) key)
(with-slots
(root elt= elt<) tm
(unless (null root)
(setf root (node-splay root key elt= elt<))
(funcall elt= key (node-key root)))))
;;; キーの値を求める
(defmethod tree-get ((tm splaytree) key)
(with-slots
(root elt= elt<) tm
(unless (null root)
(setf root (node-splay root key elt= elt<))
(when (funcall elt= key (node-key root))
(node-value root)))))
;;; 最大のキーとその値を求める
(defmethod tree-max ((tm splaytree))
(with-slots
(root) tm
(unless (null root)
(setf root (node-search-max root))
(cons (node-key root) (node-value root)))))
;;; 最小のキーとその値を求める
(defmethod tree-min ((tm splaytree))
(with-slots
(root) tm
(unless (null root)
(setf root (node-search-min root))
(cons (node-key root) (node-value root)))))
;;; root に新しい node を追加する
(defun insert-new-node (root node elt<)
(if (funcall elt< (node-key node) (node-key root))
(setf (node-left node) (node-left root)
(node-right node) root
(node-left root) nil)
(setf (node-left node) root
(node-right node) (node-right root)
(node-right root) nil))
node)
;;; キーと値を挿入する
(defmethod tree-set ((tm splaytree) key value)
(with-slots
(root size elt= elt<) tm
(cond
((null root)
;; 最初のデータ
(setf root (make-instance 'snode :key key :value value))
(incf size)
t)
(t
(setf root (node-splay root key elt= elt<))
(cond
((funcall elt= key (node-key root))
;; 同じキーを発見
(setf (node-value root) value)
nil)
(t
;; 新規追加
(let ((node (make-instance 'snode :key key :value value)))
(setf root (insert-new-node root node elt<)))
(incf size)
t))))))
;;; キーの値に value を push する
(defmethod tree-push ((tm splaytree) key value)
(with-slots
(root size elt= elt<) tm
(cond
((null root)
;; 最初のデータ
(setf root (make-instance 'snode :key key :value (list value)))
(incf size)
t)
(t
(setf root (node-splay root key elt= elt<))
(cond
((funcall elt= key (node-key root))
;; 同じキーを発見
(setf (node-value root) (cons value (node-value root)))
nil)
(t
;; 新規追加
(let ((node (make-instance 'snode :key key :value (list value))))
(setf root (insert-new-node root node elt<)))
(incf size)
t))))))
;;; キーの値からデータを pop する
(defmethod tree-pop ((tm splaytree) key)
(with-slots
(root elt= elt<) tm
(unless (null root)
(setf root (node-splay root key elt= elt<))
(when (and (funcall elt= key (node-key root))
(consp (node-value root)))
(pop (node-value root))))))
;;; 木から key とその値を削除する
(defmethod tree-del ((tm splaytree) key)
(with-slots
(root size elt= elt<) tm
(unless (null root)
(setf root (node-splay root key elt= elt<))
(when (funcall elt= key (node-key root))
(cond
((null (node-left root))
(setf root (node-right root)))
((null (node-right root))
(setf root (node-left root)))
(t
(let ((node (node-splay (node-left root) key elt= elt<)))
(setf (node-right node) (node-right root)
root node))))
(decf size)
t))))
;;; 最大のキーと値を削除
(defmethod tree-max-del ((tm splaytree))
(unless (tree-emptyp tm)
(let ((node (node-search-max (tree-root tm))))
(setf (tree-root tm) (node-left node))
(decf (tree-size tm))
t)))
;;; 最小のキーと値を削除
(defmethod tree-min-del ((tm splaytree))
(unless (tree-emptyp tm)
(let ((node (node-search-min (tree-root tm))))
(setf (tree-root tm) (node-right node))
(decf (tree-size tm))
t)))
;;; 巡回
(defmethod tree-mapc ((tm splaytree) fn)
(node-traverse fn (tree-root tm)))
;;; 左部分木から畳み込む
(defmethod tree-fold-left ((tm splaytree) a fn)
(node-fold-left fn a (tree-root tm)))
;;; 右部分木から畳み込む
(defmethod tree-fold-right ((tm splaytree) a fn)
(node-fold-right fn a (tree-root tm)))
;;;
;;; 赤黒木
;;;
;;; 終端
(defvar empty nil)
;;; 節の定義
(defclass rbnode (snode)
((color :accessor node-color :initform 'red :initarg :color)))
;;; 終端の生成
(defun make-empty ()
(when (null empty)
(setf empty
(make-instance 'rbnode :color 'black)))
empty)
;;; 終端のチェック
(defmethod node-emptyp ((node rbnode)) (eq node empty))
;;; 色のチェック
(defun node-blackp (node) (eq (node-color node) 'black))
(defun node-redp (node) (eq (node-color node) 'red))
;;;
;;; バランスの修正処理
;;;
;;; 右回転
(defmethod rotate-right ((node rbnode))
(let ((lnode (node-left node)))
(setf (node-left node) (node-right lnode)
(node-right lnode) node
(node-color lnode) (node-color node)
(node-color node) 'red)
lnode))
;;; 左回転
(defmethod rotate-left ((node rbnode))
(let ((rnode (node-right node)))
(setf (node-right node) (node-left rnode)
(node-left rnode) node
(node-color rnode) (node-color node)
(node-color node) 'red)
rnode))
;;; 4node の分割
(defun node-split (node)
(setf (node-color node) 'red
(node-color (node-left node)) 'black
(node-color (node-right node)) 'black))
;;; 左部分木の修正
(defun balance-insert-left (node flag)
(unless flag
(when (node-blackp node)
(setf flag t)
;; 左(赤)の子に赤があるか
(when (node-redp (node-right (node-left node)))
(setf (node-left node)
(rotate-left (node-left node))))
(when (node-redp (node-left (node-left node)))
;; 赤が 2 つ続く
(if (node-redp (node-right node))
(progn
(node-split node)
(setf flag nil))
(setf node (rotate-right node))))))
(values node flag))
(defun balance-delete-left (node flag)
(unless flag
(cond
((and (node-blackp (node-left (node-right node)))
(node-blackp (node-right (node-right node))))
;; right is 2 node
(cond
((node-blackp (node-right node))
;; node is 2 node
(setf (node-color (node-right node)) 'red)
(when (node-blackp node)
(return-from balance-delete-left (values node nil)))
(setf (node-color node) 'black))
(t
;; node is 3 node
(setf node (rotate-left node)
(node-left node) (balance-delete-left (node-left node) nil)))))
(t
;; right is 3, 4 node
(when (node-redp (node-left (node-right node)))
(setf (node-right node) (rotate-right (node-right node))))
(setf node (rotate-left node)
(node-color (node-left node)) 'black
(node-color (node-right node)) 'black))))
(values node t))
;;; 右部分木の修正
(defun balance-insert-right (node flag)
(unless flag
(when (node-blackp node)
(setf flag t)
;; 右(赤)の子に赤があるか
(when (node-redp (node-left (node-right node)))
(setf (node-right node)
(rotate-right (node-right node))))
(when (node-redp (node-right (node-right node)))
;; 赤が 2 つ続く
(if (node-redp (node-left node))
(progn
(node-split node)
(setf flag nil))
(setf node (rotate-left node))))))
(values node flag))
(defun balance-delete-right (node flag)
(unless flag
(cond
((and (node-blackp (node-left (node-left node)))
(node-blackp (node-right (node-left node))))
(cond
((node-blackp (node-left node))
;; left is 2 node
(setf (node-color (node-left node)) 'red)
(when (node-blackp node)
(return-from balance-delete-right (values node nil)))
(setf (node-color node) 'black))
(t
(setf node (rotate-right node)
(node-right node) (balance-delete-right (node-right node) nil)))))
(t
;; node is 3, 4 node
(when (node-redp (node-right (node-left node)))
(setf (node-left node)
(rotate-left (node-left node))))
(setf node (rotate-right node)
(node-color (node-right node)) 'black
(node-color (node-left node)) 'black))))
(values node t))
;;;
;;; 探索
;;;
(defun node-search (node key elt= elt<)
(cond
((node-emptyp node) empty)
((funcall elt= key (node-key node)) node)
((funcall elt< key (node-key node))
(node-search (node-left node) key elt= elt<))
(t
(node-search (node-right node) key elt= elt<))))
;;; 最小のキーを持つ node を返す
(defmethod node-search-min ((node rbnode))
(if (node-emptyp (node-left node))
node
(node-search-min (node-left node))))
;;; 最大のキーを持つ node を返す
(defmethod node-search-max ((node rbnode))
(if (node-emptyp (node-right node))
node
(node-search-max (node-right node))))
;;;
;;; 挿入
;;;
(defun node-insert (node key x elt= elt<)
(cond
((node-emptyp node)
;; 新しい節を挿入する
(values (make-instance 'rbnode :key key :value x :left empty :right empty) nil))
((funcall elt= key (node-key node))
;; 同じキーを見つけたので値を書き換える
(setf (node-value node) x)
(throw 'key-found nil))
((funcall elt< key (node-key node))
;; 左部分木をたどる
(multiple-value-bind
(node1 flag)
(node-insert (node-left node) key x elt= elt<)
(setf (node-left node) node1)
(multiple-value-setq (node1 flag) (balance-insert-left node flag))
(values node1 flag)))
(t
;; 右部分木をたどる
(multiple-value-bind
(node1 flag)
(node-insert (node-right node) key x elt= elt<)
(setf (node-right node) node1)
(multiple-value-setq (node1 flag) (balance-insert-right node flag))
(values node1 flag)))))
(defun node-push (node key x elt= elt<)
(cond
((node-emptyp node)
;; 新しい節を挿入する
(values (make-instance 'rbnode :key key :value (list x) :left empty :right empty) nil))
((funcall elt= key (node-key node))
;; 同じキーを見つけたので値を書き換える
(setf (node-value node) (cons x (node-value node)))
(throw 'key-found nil))
((funcall elt< key (node-key node))
;; 左部分木をたどる
(multiple-value-bind
(node1 flag)
(node-push (node-left node) key x elt= elt<)
(setf (node-left node) node1)
(multiple-value-setq (node1 flag) (balance-insert-left node flag))
(values node1 flag)))
(t
;; 右部分木をたどる
(multiple-value-bind
(node1 flag)
(node-push (node-right node) key x elt= elt<)
(setf (node-right node) node1)
(multiple-value-setq (node1 flag) (balance-insert-right node flag))
(values node1 flag)))))
;;;
;;; 削除
;;;
;;; 最大のキーとその値を削除
(defun node-delete-max (node)
(cond
((node-emptyp (node-right node))
(cond
((node-emptyp (node-left node))
(values empty (node-redp node)))
(t
(setf (node-color (node-left node)) 'black)
(values (node-left node) t))))
(t
(multiple-value-bind
(node1 flag)
(node-delete-max (node-right node))
(setf (node-right node) node1)
(multiple-value-setq (node1 flag) (balance-delete-right node flag))
(values node1 flag)))))
;;; 最小のキーとその値を削除
(defun node-delete-min (node)
(cond
((node-emptyp (node-left node))
(cond
((node-emptyp (node-right node))
(values empty (node-redp node)))
(t
(setf (node-color (node-right node)) 'black)
(values (node-right node) t))))
(t
(multiple-value-bind
(node1 flag)
(node-delete-min (node-left node))
(setf (node-left node) node1)
(multiple-value-setq (node1 flag) (balance-delete-left node flag))
(values node1 flag)))))
;;; キー key と等しい node を削除
(defun node-delete (node key elt= elt<)
(cond
((node-emptyp node)
(throw 'key-not-found nil))
((funcall elt= key (node-key node))
(cond
((and (node-emptyp (node-left node))
(node-emptyp (node-right node)))
(values empty (node-redp node)))
((node-emptyp (node-right node))
(setf (node-color (node-left node)) 'black)
(values (node-left node) t))
((node-emptyp (node-left node))
(setf (node-color (node-right node)) 'black)
(values (node-right node) t))
(t
(let ((del-node (node-search-min (node-right node))))
(setf (node-key node) (node-key del-node)
(node-value node) (node-value del-node))
(multiple-value-bind
(node1 flag)
(node-delete-min (node-right node))
(setf (node-right node) node1)
(multiple-value-setq (node1 flag) (balance-delete-right node flag))
(values node1 flag))))))
((funcall elt< key (node-key node))
(multiple-value-bind
(node1 flag)
(node-delete (node-left node) key elt= elt<)
(setf (node-left node) node1)
(multiple-value-setq (node1 flag) (balance-delete-left node flag))
(values node1 flag)))
(t
(multiple-value-bind
(node1 flag)
(node-delete (node-right node) key elt= elt<)
(setf (node-right node) node1)
(multiple-value-setq (node1 flag) (balance-delete-right node flag))
(values node1 flag)))))
;;;
;;; 連想配列 (赤黒木) の定義
;;;
(defclass treemap ()
((root :accessor tree-root :initform nil :initarg :root)
(size :accessor tree-size :initform 0 :initarg :size)
(elt= :accessor tree-elt= :initform nil :initarg :elt=)
(elt< :accessor tree-elt< :initform nil :initarg :elt<)))
;;; コンストラクタ
(defun make-treemap (elt= elt<)
(make-instance 'treemap :root (make-empty) :elt= elt= :elt< elt<))
;;; 型述語
(defun treemap-p (x) (typep x 'treemap))
;;; treemap は空か?
(defmethod tree-emptyp ((tm treemap)) (node-emptyp (tree-root tm)))
;;; treemap のデータ数を返す
(defmethod tree-count ((tm treemap)) (tree-size tm))
;;; treemap を空にする
(defmethod tree-clear ((tm treemap))
(setf (tree-root tm) empty
(tree-size tm) 0))
;;; キーが存在すれば t を返す
(defmethod tree-exists ((tm treemap) key)
(not (node-emptyp (node-search (tree-root tm) key (tree-elt= tm) (tree-elt< tm)))))
;;; キーの値を返す (見つからない場合は nil を返す)
(defmethod tree-get ((tm treemap) key)
(node-value (node-search (tree-root tm) key (tree-elt= tm) (tree-elt< tm))))
;;; 最大の (key . val) を返す
(defmethod tree-max ((tm treemap))
(if (tree-emptyp tm)
nil
(let ((node (node-search-max (tree-root tm))))
(cons (node-key node) (node-value node)))))
;;; 最小の (key . val) を返す
(defmethod tree-min ((tm treemap))
(if (tree-emptyp tm)
nil
(let ((node (node-search-min (tree-root tm))))
(cons (node-key node) (node-value node)))))
;;; キーと値をセットする
;;; 新規追加の場合は t を返す。同じキーを見つけたら、値を書き換えて nil を返す
(defmethod tree-set ((tm treemap) key value)
(catch 'key-found
(setf (tree-root tm) (node-insert (tree-root tm) key value (tree-elt= tm) (tree-elt< tm))
(node-color (tree-root tm)) 'black)
(incf (tree-size tm))
t))
;;; キーが新規の場合、キーの値に (list value) を追加して t を返す
;;; キーが存在する場合、その先頭に value を追加して nil を返す
(defmethod tree-push ((tm treemap) key value)
(catch 'key-found
(setf (tree-root tm) (node-push (tree-root tm) key value (tree-elt= tm) (tree-elt< tm))
(node-color (tree-root tm)) 'black)
(incf (tree-size tm))
t))
;;; キーが存在して値がリストの場合、リストからデータを pop する
;;; それ以外は nil を返す
(defmethod tree-pop ((tm treemap) key)
(let ((node (node-search (tree-root tm) key (tree-elt= tm) (tree-elt< tm))))
(when (consp (node-value node))
(pop (node-value node)))))
;;; キーとその値を削除して t を返す
;;; キーが見つからない場合は nil を返す
(defmethod tree-del ((tm treemap) key)
(catch 'key-not-found
(setf (tree-root tm) (node-delete (tree-root tm) key (tree-elt= tm) (tree-elt< tm))
(node-color (tree-root tm)) 'black)
(decf (tree-size tm))
t))
;;; 最大の key, value を削除して t を返す
(defmethod tree-max-del ((tm treemap))
(unless (tree-emptyp tm)
(setf (tree-root tm) (node-delete-max (tree-root tm)))
(decf (tree-size tm))
t))
;;; 最小の key, value を削除して t を返す
(defmethod tree-min-del ((tm treemap))
(unless (tree-emptyp tm)
(setf (tree-root tm) (node-delete-min (tree-root tm)))
(decf (tree-size tm))
t))
;;; キーと値に関数 fn を適用する
(defmethod tree-mapc ((tm treemap) fn)
(node-traverse fn (tree-root tm)))
;;; 左部分木からの畳み込み
(defmethod tree-fold-left ((tm treemap) a fn)
(node-fold-left fn a (tree-root tm)))
;;; 右部分木からの畳み込み
(defmethod tree-fold-right ((tm treemap) a fn)
(node-fold-right fn a (tree-root tm)))
リスト : treemap.asd (defsystem "treemap" :description "tree mapping (red-black tree, splay tree)" :version "0.1.0" :author "Makoto Hiroi" :license "MIT" :depends-on () :in-order-to ((test-op (test-op :treemap_tst))) :components ((:file "treemap")))
;;;
;;; treemap_tst.lisp : treemap のテスト
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :treemap_tst)
(defpackage :treemap_tst (:use :cl :mintst :treemap))
(in-package :treemap_tst)
(export '(test))
(defvar ts nil)
(defvar ks nil)
(defvar vs nil)
(defun test-sub ()
(run (tree-emptyp ts) t)
(run (tree-count ts) 0)
(run (progn
(setq ks '("foo" "bar" "baz" "oops")
vs '(10 20 30 40))
(mapc (lambda (k v) (tree-set ts k v)) ks vs)
(tree-count ts))
4)
(run (tree-emptyp ts) nil)
(run (mapcar (lambda (k) (tree-exists ts k)) '("foo" "Foo")) '(t nil))
(run (mapcar (lambda (k) (tree-exists ts k)) ks) '(t t t t))
(run (mapcar (lambda (k) (tree-get ts k)) '("foo" "Foo")) '(10 nil))
(run (mapcar (lambda (k) (tree-get ts k)) ks) '(10 20 30 40))
(run (tree-min ts) '("bar" . 20))
(run (tree-max ts) '("oops" . 40))
(run (let ((a nil))
(tree-mapc ts (lambda (k v) (push (cons k v) a)))
(reverse a))
'(("bar" . 20) ("baz" . 30) ("foo" . 10) ("oops" . 40)))
(run (tree-fold-right ts nil (lambda (a k v) (cons (cons k v) a)))
'(("bar" . 20) ("baz" . 30) ("foo" . 10) ("oops" . 40)))
(run (tree-fold-left ts nil (lambda (a k v) (cons (cons k v) a)))
(reverse '(("bar" . 20) ("baz" . 30) ("foo" . 10) ("oops" . 40))))
(run (tree-max-del ts) t)
(run (tree-count ts) 3)
(run (tree-max ts) '("foo" . 10))
(run (tree-min-del ts) t)
(run (tree-count ts) 2)
(run (tree-min ts) '("baz" . 30))
(run (tree-del ts "foo") t)
(run (tree-count ts) 1)
(run (tree-min ts) '("baz" . 30))
(run (tree-max ts) '("baz" . 30))
(run (tree-del ts "baz") t)
(run (tree-count ts) 0)
(run (tree-emptyp ts) t)
(run (tree-max ts) nil)
(run (tree-min ts) nil)
(run (progn
(mapc (lambda (k v) (tree-push ts k v)) ks vs)
(tree-count ts))
4)
(run (tree-push ts "Foo" 0) t)
(run (tree-count ts) 5)
(run (tree-get ts "Foo") '(0))
(run (tree-push ts "Foo" 1) nil)
(run (tree-count ts) 5)
(run (tree-get ts "Foo") '(1 0))
(run (tree-pop ts "Foo") 1)
(run (tree-pop ts "Foo") 0)
(run (tree-pop ts "Foo") nil)
(run (tree-count ts) 5)
(run (tree-del ts "Foo") t)
(run (tree-count ts) 4)
(run (tree-clear ts) 0)
(run (tree-count ts) 0)
(run (tree-emptyp ts) t))
(defun test ()
(initial)
(run (treemap-p (setq ts (make-treemap #'string= #'string<))) t)
(test-sub)
(run (splaytree-p (setq ts (make-splaytree #'string= #'string<))) t)
(test-sub)
(final))
リスト : treemap_tst.asd (defsystem :treemap_tst :description "test for treemap" :version "0.1.0" :author "Makoto Hiroi" :license "MIT" :depends-on (:mintst :treemap) :components ((:file "treemap_tst")) :perform (test-op (o s) (symbol-call :treemap_tst :test)))