aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Peter McGoron 2025-01-16 23:16:32 -0500
committerGravatar Peter McGoron 2025-01-16 23:16:32 -0500
commit500be7d0c9d8ef212448eb20b6ebd22f38ee0189 (patch)
tree28c8c83e3f455b1a9f60d77a3c504b3a00256b8f
parentjoin2 (diff)
split and search
-rw-r--r--doc/mcgoron.weight-balanced-trees.internal.scm36
-rw-r--r--mcgoron.weight-balanced-trees.internal.scm29
-rw-r--r--mcgoron.weight-balanced-trees.internal.sld6
-rw-r--r--tests/random-number-vector.sld49
-rw-r--r--tests/run.scm230
-rw-r--r--tests/util.sld26
6 files changed, 263 insertions, 113 deletions
diff --git a/doc/mcgoron.weight-balanced-trees.internal.scm b/doc/mcgoron.weight-balanced-trees.internal.scm
index 5812d72..176d18e 100644
--- a/doc/mcgoron.weight-balanced-trees.internal.scm
+++ b/doc/mcgoron.weight-balanced-trees.internal.scm
@@ -18,10 +18,7 @@
(desc "Returns the number of elements in this tree."))
((name . "balanced?")
(signature lambda ((wb-tree-node? x)) => boolean?)
- (tags internal)
- (desc "Recursively traverses `x` and checks if it is weight balanced.
-This function is not called in normal code, but can be useful for
-debugging."))
+ (desc "Recursively traverses `x` and checks if it is weight balanced."))
((name . "in-order-vector->node")
(signature lambda ((vector? x)) => wb-tree-node?)
(desc "
@@ -45,4 +42,33 @@ elements of `x`."))
`right`.
Returns a balanced tree containing all elements of `left` and `right`
-with `data`."))) \ No newline at end of file
+with `data`."))
+ ((name . "join2")
+ (signature lambda ((wb-tree-node? left) (wb-tree-node? right))
+ => wb-tree-node?)
+ (desc "
+* It is an error if any element in `left` is greater than or equal to any
+ element in `right`.
+
+Returns a new tree with the elements of `left` and `right`."))
+ ((name . "split")
+ (signature lambda ((comparator? cmp) (wb-tree-node? tree) key)
+ => (values wb-tree-node? boolean? wb-tree-node?))
+ "
+* It is an error if any element of `tree` is not comparable by `cmp`.
+* It is an error if `key` is not comparable by `cmp`.
+* It is an error if `cmp` does not have an order.
+
+Returns two trees, one of all values to the left of `key`, and one with
+all values to the right of `key`. The boolean is true if the key was
+found and false otherwise.")
+ ((name . "search")
+ (signature case-lambda ((((wb-tree-node? tree) (comparator? cmp) key) *)
+ (((wb-tree-node? tree) (comparator? cmp) key (procedure? default)) *)))
+ (desc "
+* It is an error if `cmp` does not order `key` and all elements in `tree`.
+* It is an error if `default` is not a thunk.
+
+Searches `tree` for `key` using `cmp`. If a node contains data that compares equal
+to `key`, return that data. Otherwise, tail call `default.` If `default` is not
+supplied, the function returns `#f`."))) \ No newline at end of file
diff --git a/mcgoron.weight-balanced-trees.internal.scm b/mcgoron.weight-balanced-trees.internal.scm
index 8d9dc6b..c657178 100644
--- a/mcgoron.weight-balanced-trees.internal.scm
+++ b/mcgoron.weight-balanced-trees.internal.scm
@@ -236,3 +236,32 @@
(let-values (((new-left new-data)
(split-last left)))
(join new-data new-left right))))
+
+;;; XXX: The comparator library does not export the struct type for
+;;; the comparator.
+(: split (* node-type * --> node-type boolean node-type))
+(define (split cmp tree key)
+ (let split ((tree tree))
+ (if (null? tree)
+ (values '() #f '())
+ (with-node (tree data ("<" left) (">" right))
+ (comparator-if<=> cmp key data
+ (let-values (((new-left bool new-right) (split left)))
+ (values new-left bool (join data new-right right)))
+ (values left #t right)
+ (let-values (((new-left bool new-right) (split right)))
+ (values (join data left new-left) bool new-right)))))))
+
+(define search
+ (case-lambda
+ ((tree cmp key) (search tree cmp key (lambda () #f)))
+ ((tree cmp key default)
+ (let search ((tree tree))
+ (if (null? tree)
+ (default)
+ (with-node (tree data ("<" left) (">" right))
+ (comparator-if<=> cmp key data
+ (search left)
+ data
+ (search right))))))))
+
diff --git a/mcgoron.weight-balanced-trees.internal.sld b/mcgoron.weight-balanced-trees.internal.sld
index 804a8bf..cdb47ee 100644
--- a/mcgoron.weight-balanced-trees.internal.sld
+++ b/mcgoron.weight-balanced-trees.internal.sld
@@ -14,7 +14,8 @@
|#
(define-library (mcgoron weight-balanced-trees internal)
- (import (scheme base))
+ (import (scheme base) (scheme case-lambda)
+ (srfi 128))
(cond-expand
;; Handle type declarations
(chicken (import (chicken type)))
@@ -30,6 +31,7 @@
balanced?
in-order-vector->node
node->in-order-list
- join join2)
+ join join2 split
+ search)
(include "mcgoron.weight-balanced-trees.internal.scm"))
diff --git a/tests/random-number-vector.sld b/tests/random-number-vector.sld
new file mode 100644
index 0000000..2e40cd0
--- /dev/null
+++ b/tests/random-number-vector.sld
@@ -0,0 +1,49 @@
+#| Copyright 2024 Peter McGoron
+ |
+ | Licensed under the Apache License, Version 2.0 (the "License");
+ | you may not use this file except in compliance with the License.
+ | You may obtain a copy of the License at
+ |
+ | http://www.apache.org/licenses/LICENSE-2.0
+ |
+ | Unless required by applicable law or agreed to in writing, software
+ | distributed under the License is distributed on an "AS IS" BASIS,
+ | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ | See the License for the specific language governing permissions and
+ | limitations under the License.
+ |#
+
+(define-library (mcgoron weight-balanced-trees test random-number-vector)
+ (import (scheme base)
+ (srfi 158) (srfi 197) (srfi 252) (srfi 133) (srfi 26) (srfi 132))
+ (export in-order-vector-generator)
+ (begin
+ (define (random-number-vector of)
+ ;; Generate a random vector of exact integers with at most `of` number
+ ;; of elements.
+ (vector-generator-of (exact-integer-generator) of))
+ (define (filter-non-empty-vectors gen)
+ ;; Filter out the empty vector.
+ (gremove (lambda (vec) (zero? (vector-length vec))) gen))
+ (define (remove-duplicates gen)
+ ;; Filter out vectors with duplicate entries.
+ (define (filter vec)
+ (call/cc
+ (lambda (return)
+ (vector-fold (lambda (prev elem)
+ (cond
+ ((null? prev) elem)
+ ((= elem prev) (return #f))
+ (else elem)))
+ '()
+ vec)
+ #t)))
+ (gfilter filter gen))
+ (define (make-in-order gen)
+ ;; Sort all vectors to be in-order.
+ (gmap (cut vector-sort < <>) gen))
+ (define (in-order-vector-generator of)
+ (chain (random-number-vector of)
+ (filter-non-empty-vectors _)
+ (make-in-order _)
+ (remove-duplicates _)))))
diff --git a/tests/run.scm b/tests/run.scm
index 1aea3b0..c2e06ec 100644
--- a/tests/run.scm
+++ b/tests/run.scm
@@ -13,7 +13,14 @@
| limitations under the License.
|#
+(import r7rs)
+
+(load "random-number-vector.sld")
+(load "util.sld")
+
(import (mcgoron weight-balanced-trees internal)
+ (mcgoron weight-balanced-trees test util)
+ (mcgoron weight-balanced-trees test random-number-vector)
(prefix (only (mcgoron srfi 64) factory) mcgoron-test-)
(except (mcgoron srfi 64) factory)
(srfi 1) (srfi 26) (srfi 64) (srfi 128) (srfi 132) (srfi 133)
@@ -32,57 +39,13 @@
(test-runner-factory mcgoron-test-factory)
(test-runner-current (test-runner-create))
-;;; ;;;;;;;;;;;
-;;; Utils
-;;; ;;;;;;;;;;;
-
-(define (vector-copy-exclusive vec start end)
- ;; Copy `[start,end)` from `vec`.
- (if (= start end)
- #()
- (vector-copy vec start (- end 1))))
-
;;; ;;;;;;;;;;;;;;;;;;;
;;; Random in-order vector generator
;;; ;;;;;;;;;;;;;;;;;;;
-(define (random-number-vector of)
- ;; Generate a random vector of exact integers with at most `of` number
- ;; of elements.
- (vector-generator-of (exact-integer-generator) of))
-
-(define (filter-non-empty-vectors gen)
- ;; Filter out the empty vector.
- (gremove (lambda (vec) (zero? (vector-length vec))) gen))
-
-(define (remove-duplicates gen)
- ;; Filter out vectors with duplicate entries.
- (define (filter vec)
- (call/cc
- (lambda (return)
- (vector-fold (lambda (prev elem)
- (cond
- ((null? prev) elem)
- ((= elem prev) (return #f))
- (else elem)))
- '()
- vec)
- #t)))
- (gfilter filter gen))
-
-(define (make-in-order gen)
- ;; Sort all vectors to be in-order.
- (gmap (cut vector-sort < <>) gen))
-
-(define (in-order-vector-generator of)
- (chain (random-number-vector of)
- (filter-non-empty-vectors _)
- (make-in-order _)
- (remove-duplicates _)))
-
-(define (vector->node-generator of)
- (chain (in-order-vector-generator of)
- (gmap in-order-vector->node _)))
+(define (vector->node-generator)
+ (gmap in-order-vector->node (in-order-vector-generator
+ (max-vector-length))))
(define number-comparator
(make-comparator
@@ -108,8 +71,7 @@
(test-group "vector->node balanced?"
(test-assert (balanced? '()))
- (test-property balanced? (list (vector->node-generator
- (max-vector-length)))))
+ (test-property balanced? (list (vector->node-generator))))
;;; ;;;;;;;;;
;;; Make a "split" vector. A "split" vector is two sorted vectors and a
@@ -129,82 +91,138 @@
(vector-ref vec pivot)
(vector-copy-exclusive vec (+ pivot 1) len))))
-(define (split-vector-generator of)
- (chain (random-number-vector of)
- (filter-non-empty-vectors _)
- (remove-duplicates _)
- (make-in-order _)
+(define (split-vector-generator)
+ (chain (in-order-vector-generator (max-vector-length))
(gmap split-in-order-vector _)))
+(define (call/split-vecs procedure)
+ (lambda (vec)
+ (let ((left (vector-ref vec 0))
+ (middle (vector-ref vec 1))
+ (right (vector-ref vec 2)))
+ (procedure left middle right))))
+
+(define (join-vectors left middle right)
+ (join middle (in-order-vector->node left) (in-order-vector->node right)))
+
;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; join
;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define (join-preserves-order-of vec)
- ;; Test if joining two in-order vectors with a node in between will
- ;; preserve order in the tree.
- (let ((left (vector-ref vec 0))
- (middle (vector-ref vec 1))
- (right (vector-ref vec 2)))
- ;; Log the left, middle, and right vectors.
- (when (verbose)
- (dynamic-property-set! 'right right)
- (dynamic-property-set! 'middle middle)
- (dynamic-property-set! 'left left))
- (let ((ret (node->in-order-list
- (join middle
- (in-order-vector->node left)
- (in-order-vector->node right))))
- (orig (append (vector->list left)
- (list middle)
- (vector->list right))))
- (when (verbose)
- (dynamic-property-set! 'ret ret))
- (equal? ret orig))))
-
-(test-group "join-preserves-order-of"
+(define join-preserves-order-of
+ (call/split-vecs
+ (lambda (left middle right)
+ ;; Test if joining two in-order vectors with a node in between will
+ ;; preserve order in the tree.
+ ;; Log the left, middle, and right vectors.
+ (when (verbose)
+ (dynamic-property-set! 'right right)
+ (dynamic-property-set! 'middle middle)
+ (dynamic-property-set! 'left left))
+ (let ((ret (node->in-order-list
+ (join-vectors left middle right)))
+ (orig (append (vector->list left)
+ (list middle)
+ (vector->list right))))
+ (when (verbose)
+ (dynamic-property-set! 'ret ret))
+ (equal? ret orig)))))
+
+(test-group "join of in-order vectors preserves order"
(parameterize ((verbose #f))
- (test-property join-preserves-order-of (list (split-vector-generator
- (max-vector-length))))))
+ (test-property join-preserves-order-of
+ (list (split-vector-generator)))))
-(test-group "join balanced?"
- (define (join-split-vectors vec)
- (let ((left (vector-ref vec 0))
- (middle (vector-ref vec 1))
- (right (vector-ref vec 2)))
- (join middle
- (in-order-vector->node left)
- (in-order-vector->node right))))
- (define (joined-vector-generator of)
- (gmap join-split-vectors (split-vector-generator of)))
- (test-property balanced? (list (joined-vector-generator (max-vector-length)))))
+(test-group "join of in-order vectors is balanced"
+ (define (joined-vector-generator)
+ (gmap (call/split-vecs join-vectors)
+ (split-vector-generator)))
+ (test-property balanced? (list (joined-vector-generator))))
;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; join2
;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;
-(define (join2-preserves-order-of vec)
- (let* ((left (vector-ref vec 0))
- (right (vector-ref vec 2))
- (ret (node->in-order-list
- (join2 (in-order-vector->node left)
- (in-order-vector->node right))))
- (orig (append (vector->list left)
- (vector->list right))))
- (equal? ret orig)))
-
-(test-group "join2 preserves order"
+(define join2-preserves-order-of
+ (call/split-vecs
+ (lambda (left _ right)
+ (let ((ret (node->in-order-list
+ (join2 (in-order-vector->node left)
+ (in-order-vector->node right))))
+ (orig (append (vector->list left)
+ (vector->list right))))
+ (equal? ret orig)))))
+
+(test-group "join2 of in-order vectors preserves order"
(test-property join2-preserves-order-of
- (list (split-vector-generator
- (max-vector-length)))))
+ (list (split-vector-generator))))
-(test-group "join2 is balanced"
+(test-group "join2 of in-order vectors is balanced"
(define (join2-split-vector-generator)
(define (join2-of-split vec)
(join2 (in-order-vector->node (vector-ref vec 0))
(in-order-vector->node (vector-ref vec 2))))
- (gmap join2-of-split (split-vector-generator
- (max-vector-length))))
+ (gmap join2-of-split (split-vector-generator)))
(test-property balanced?
(list (join2-split-vector-generator))))
+;;; ;;;;;;
+;;; search
+;;; ;;;;;;
+
+(define (call/inserted procedure)
+ (call/split-vecs
+ (lambda (left middle right)
+ (let ((node (join-vectors left middle right)))
+ (procedure left middle right node)))))
+
+(define (call-w/o-inserted procedure)
+ (call/split-vecs
+ (lambda (left middle right)
+ (let ((node (join2 (in-order-vector->node left)
+ (in-order-vector->node right))))
+ (procedure left middle right node)))))
+
+(test-group "search with inserted node"
+ (define search-finds
+ (call/inserted
+ (lambda (left middle right node)
+ (when (verbose)
+ (dynamic-property-set! 'expected middle)
+ (dynamic-property-set! 'right right)
+ (dynamic-property-set! 'left left)
+ (dynamic-property-set! 'node (node->in-order-list node)))
+ (search node number-comparator middle))))
+ (parameterize ((verbose #f))
+ (test-property search-finds (list (split-vector-generator)))))
+
+(test-group "search without inserted node"
+ (define search-does-not-find
+ (call-w/o-inserted
+ (lambda (left middle right node)
+ (not (search node number-comparator middle)))))
+ (test-property search-does-not-find (list (split-vector-generator))))
+
+;;; ;;;;;;;;;;;;;;;;;;;;;;;;
+;;; split
+;;; ;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define split-finds
+ (call/inserted
+ (lambda (left middle right node)
+ (let-values (((new-left found? new-right)
+ (split number-comparator node middle)))
+ found?))))
+
+(test-group "split finds"
+ (test-property split-finds (list (split-vector-generator))))
+
+(define split-does-not-find
+ (call-w/o-inserted
+ (lambda (left middle right node)
+ (let-values (((new-left found? new-right)
+ (split number-comparator node middle)))
+ (not found?)))))
+
+(test-group "split does not find"
+ (test-property split-does-not-find (list (split-vector-generator))))
diff --git a/tests/util.sld b/tests/util.sld
new file mode 100644
index 0000000..daaa9db
--- /dev/null
+++ b/tests/util.sld
@@ -0,0 +1,26 @@
+#| Copyright 2024 Peter McGoron
+ |
+ | Licensed under the Apache License, Version 2.0 (the "License");
+ | you may not use this file except in compliance with the License.
+ | You may obtain a copy of the License at
+ |
+ | http://www.apache.org/licenses/LICENSE-2.0
+ |
+ | Unless required by applicable law or agreed to in writing, software
+ | distributed under the License is distributed on an "AS IS" BASIS,
+ | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ | See the License for the specific language governing permissions and
+ | limitations under the License.
+ |#
+
+(define-library (mcgoron weight-balanced-trees test util)
+ (import (scheme base) (srfi 158) (srfi 26))
+ (export vector-copy-exclusive)
+ (begin
+ (define (vector-copy-exclusive vec start end)
+ ;; Copy `start,end` (start inclusive end exclusive) from `vec`.
+ (if (= start end)
+ #()
+ (vector-copy vec start (- end 1))))
+ (define (boolean-equivalent? x y)
+ (boolean=? (not (not x)) (not (not y)))))) \ No newline at end of file