3 (let ((*evaluator-mode
* :compile
)) (load "../src/code/redblack.lisp"))
5 (add-package-local-nickname "REDBLACK" "SB-RBTREE.MAP")
7 (import 'sb-rbtree
::(red black
))
8 (import 'redblack
::(redp blackp
10 node-key node-value data
12 (load "bbtree-test-util.lisp")
14 (defmacro define-verifier
(name package
15 key-decoder sentinel-min sentinel-max
)
16 `(defun ,name
(root print
)
19 ;; Pick new names or else: "Lock on package SB-RBTREE.MAP violated when
20 ;; binding NODE-KEY as a local function while in package TEST.." etc
21 (flet ((key (x) (,key-decoder
(,(intern "NODE-KEY" package
) x
)))
22 (*left
(x) (,(intern "LEFT" package
) x
))
23 (*right
(x) (,(intern "RIGHT" package
) x
))
24 (*redp
(x) (,(intern "REDP" package
) x
))
25 (*blackp
(x) (,(intern "BLACKP" package
) x
))
26 (*color-of
(x) (,(intern "COLOR-OF" package
) x
)))
27 (declare (inline node-key left right redp blackp color-of
))
28 ;; Binary search tree invariant
29 (named-let recurse
((node root
) (min ,sentinel-min
) (max ,sentinel-max
))
30 (assert (< min
(key node
) max
))
31 ;; every descendant to the left has a key strictly less than this key
32 (awhen (*left node
) (recurse it min
(key node
)))
33 ;; every descendant to the right has a key strictly more than this key
34 (awhen (*right node
) (recurse it
(key node
) max
)))
35 ;; Red/black tree invariants
36 (assert (eq (*color-of root
) 'black
))
37 (let ((h (make-hash-table :test
#'eq
))
41 (setf (gethash nil h
) 0)
42 (labels ((black-height (node)
44 (setf (gethash node h
)
45 (let ((l (black-height (*left node
)))
46 (r (black-height (*right node
))))
48 (if (eq (*color-of node
) 'black
) (1+ l
) l
))))))
49 ;; Child colors and black height
50 (named-let recurse
((node root
))
51 (ecase (*color-of node
)
52 (red (incf count-red
))
53 (black (incf count-black
)))
54 ;; If a node is red then both its children are black
56 (awhen (*left node
) (assert (*blackp it
)))
57 (awhen (*right node
) (assert (*blackp it
))))
58 (acond ((*left node
) (recurse it
)) (t (incf count-nil
)))
59 (acond ((*right node
) (recurse it
)) (t (incf count-nil
))))
61 (format t
"red=~3d black=~3d nil=~3d black-height = ~3d~%"
62 count-red count-black count-nil
63 (black-height root
))))))
65 (define-verifier verify-invariants
"SB-RBTREE.MAP"
66 identity most-negative-fixnum most-positive-fixnum
)
67 (define-verifier verify-invariants-word
"SB-RBTREE.WORD"
68 sb-kernel
:get-lisp-obj-address
0 sb-ext
:most-positive-word
)
69 (compile 'verify-invariants
)
70 (compile 'verify-invariants-word
)
72 (defun random-integer-seq (n &aux seq
)
73 (let ((a (make-array n
:fill-pointer n
)))
74 (dotimes (i n
) (setf (aref a i
) i
))
76 (let* ((random-index (random (length a
)))
77 (item (aref a random-index
)))
79 (setf (aref a random-index
) (aref a
(1- (length a
))))
80 (decf (fill-pointer a
))))))
82 (defun buildtree (n &optional verify
&aux tree inserted-so-far
)
83 (dolist (item (random-integer-seq n
))
84 (setq tree
(redblack:insert tree item
(- item
)))
85 (push item inserted-so-far
)
87 (verify-invariants tree t
)
88 (dolist (k inserted-so-far
)
89 (assert (eql (node-value (find= k tree
)) (- k
))))))
92 (defun inorder-traverse (tree)
94 (named-let recurse
((node tree
))
95 (awhen (left node
) (recurse it
))
97 (awhen (right node
) (recurse it
)))
100 (with-test (:name
:redblack-basic-insertion
)
102 (tree (buildtree n
)))
103 (let ((list (inorder-traverse tree
)))
105 (let ((item (pop list
)))
106 (assert (eql (car item
) i
))
107 (assert (eql (cdr item
) (- i
))))))
108 (let ((original-tree tree
) strings
)
111 (let ((v (random n
)))
113 (setq tree
(redblack:insert tree v
(format nil
"str~d" v
)))))
114 ;; Find everything again
116 (let ((v (node-value (find= i tree
))))
117 (if (member i strings
)
118 (assert (string= v
(format nil
"str~d" i
)))
119 (assert (eql v
(- i
))))))
120 ;; Nodes are immutable, so the original tree is unchanged
122 (assert (= (node-value (find= i original-tree
)) (- i
)))))))
124 (defun tree-count (tree)
125 (labels ((recurse (tree)
127 (acond ((left tree
) (tree-count it
)) (t 0))
128 (acond ((right tree
) (tree-count it
)) (t 0)))))
129 (if tree
(recurse tree
) 0)))
131 (defun test-deletion (tree n-trials
&optional print
)
132 (let* ((keys (mapcar 'car
(inorder-traverse tree
)))
134 (dotimes (i n-trials
)
135 (let ((deletion-sequence (shuffle keys
))
140 (format t
"deletion sequence:~%~s~%" deletion-sequence
))
142 (format t
"~aTrial ~d" #\return
(1+ i
))
145 (when (null deletion-sequence
) (return))
146 (let ((item (pop deletion-sequence
)))
147 (setq tree
(redblack:delete item tree
))
149 (assert (= (tree-count tree
) n
))
150 (verify-invariants tree nil
)
151 (dolist (item (cdr deletion-sequence
))
152 ;; ensure that everything not deleted is found
153 (assert (= (node-value (find= item tree
))
157 (with-test (:name
:redblack-basic-deletion
)
159 (tree (buildtree n
)))
160 (test-deletion tree
5)))
162 (defun code-as-fixnum (c)
163 (sb-kernel:make-lisp-obj
164 (logandc2 (sb-kernel:get-lisp-obj-address c
) sb-vm
:lowtag-mask
)))
165 (compile 'code-as-fixnum
)
167 (with-test (:name
:redblack-codeblob-tree
)
169 (format t
"~&Insertion ...") (force-output)
170 ;; 32-bit tends to exhaust the heap if performing insertion inside
171 ;; MAP-CODE-OBJECTS because of disabled GC. So collect first, then insert.
172 ;; It's the invariant checker that's a memory hog-
173 ;; this would only cons 8MiB without the checker.
174 (sb-vm:map-code-objects
176 (when (= (sb-kernel:generation-of x
) sb-vm
:+pseudo-static-generation
+)
177 (push (code-as-fixnum x
) list
))))
180 (setq tree
(sb-rbtree.word
:insert tree x
))
181 ;; speed up insertion by checking invariants only once in while
182 (when (zerop (random 6)) (verify-invariants-word tree nil
))))
183 (format t
"~&Deletion ...") (force-output)
184 (let* ((rs (make-random-state t
))
185 (tests (let ((*random-state
* rs
)) (shuffle list
)))
188 (let ((subseq (subseq tests
0 (min 10 n
))))
189 (decf n
(length subseq
))
190 ;; assert that all items in subseq are present
191 (dolist (x subseq
) (assert (sb-rbtree.word
:find
= x tree
)))
193 (dolist (x subseq
) (setq tree
(sb-rbtree.word
:delete x tree
)))
194 (verify-invariants-word tree nil
)
195 ;; now they're not found
196 (dolist (x subseq
) (assert (not (sb-rbtree.word
:find
= x tree
))))
197 (when (null (setq tests
(nthcdr (length subseq
) tests
)))
200 (defun test-randomly (&optional
(n 10) print
&aux
(trial 0))
203 (format t
"trial ~d~%" (incf trial
)))
208 (if (or (null tree
) (< (random 100) 65))
210 (let ((r (random (ash 1 16))))
211 (unless (find= r tree
)
212 (setq tree
(redblack:insert tree r
(- r
)))
213 (verify-invariants tree nil
)
215 (when (= (incf size
) 2000) (return))
216 ;;(format t " +~d" r)
217 (when print
(format t
"+") (force-output))
219 (let ((key-to-remove (nth (random (length list
)) list
)))
221 (setq tree
(redblack:delete key-to-remove tree
))
222 (setq list
(cl:delete key-to-remove list
))
223 ;;(format t " -~d" key-to-remove)
224 (when print
(format t
"-") (force-output))
225 (verify-invariants tree nil
)
227 (assert (= (node-value (find= item tree
)) (- item
))))
232 do
(setq tree
(redblack:delete
(node-key tree
) tree
))
233 (verify-invariants tree nil
)))))
235 (with-test (:name
:redblack-random-test
)
238 (with-test (:name
:redblack-find-less-eql
)
240 ;; insert keys 0, 10, 20, ..., 1000
242 (setq tree
(redblack:insert tree
(* i
10) i
)))
243 (loop for key from -
10 to
1100
245 (let ((found (find<= key tree
)))
249 (assert (= (node-key found
) 1000)))
251 (assert (= (node-key found
)
252 (* (floor key
10) 10)))))))))
254 (defun big-tree (&optional
(n 100000))
257 (setq tree
(redblack:insert tree i
(- i
))))))
259 ;;; 2.2 seconds to insert 10,000 items
260 ;;; 9.1 seconds to insert 20,000 items
261 (defun try-avl (n-items &aux tree
)
263 (setq tree
(sb-thread::avl-insert tree i i
)))
265 ;; .016 seconds to insert 10,000 items
266 ;; .024 seconds to insert 20,000 items
267 (defun try-rb (n-items &aux tree
)
269 (setq tree
(redblack:insert tree i i
)))
272 (defun test-rb-find-inexact (n-nodes n-iterations
)
273 (bbtree-test:test-find-inexact-macro redblack
:insert
274 find
<= redblack
:find
>= node-key
))
276 (test-util:with-test
(:name
:rb-find-inexact
)
277 (bbtree-test:exercise-find-inexact
'test-rb-find-inexact
))