diff options
| author | 2025-01-16 18:56:53 -0500 | |
|---|---|---|
| committer | 2025-01-16 18:56:53 -0500 | |
| commit | 59b07ed6184c1c19b36a5fad42d09151c17dc89a (patch) | |
| tree | fd4385fb3e0b20e8a1acce4d5251ac89d1527288 /mcgoron.weight-balanced-trees.scm | |
succesfully test join
Diffstat (limited to 'mcgoron.weight-balanced-trees.scm')
| -rw-r--r-- | mcgoron.weight-balanced-trees.scm | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/mcgoron.weight-balanced-trees.scm b/mcgoron.weight-balanced-trees.scm new file mode 100644 index 0000000..a3c9059 --- /dev/null +++ b/mcgoron.weight-balanced-trees.scm @@ -0,0 +1,221 @@ +;;; ;;;;;;;;;;;;;;;;;;; +;;; Definition of nodes and functions to calculate values for nodes. +;;; ;;;;;;;;;;;;;;;;;;; + +(define-type node-type (or (struct <wb-tree>) null)) + +(: %wb-tree-node (* fixnum node-type node-type --> (struct <wb-tree>))) +(: get-data ((struct <wb-tree>) --> *)) +(: %get-weight ((struct <wb-tree>) --> fixnum)) +(: get-left ((struct <wb-tree>) --> node-type)) +(: get-right ((struct <wb-tree>) --> node-type)) + +(define-record-type <wb-tree> + (%wb-tree-node data weight left right) + non-null-wb-tree-node? + (data get-data) + (weight %get-weight) + (left get-left) + (right get-right)) + +(define (wb-tree-node? x) + (or (null? x) (non-null-wb-tree-node? x))) + +(: get-weight (node-type --> fixnum)) +(define (get-weight node) + ;; Get the stored size of a node. + (cond + ((null? node) 1) + (else (%get-weight node)))) + +(: get-size (node-type --> fixnum)) +(define (get-size node) + (- (get-weight node) 1)) + +(: fixnum-calculate-weight (fixnum fixnum --> fixnum)) +(define (fixnum-calculate-weight left-weight right-weight) + ;; Calculate the weight of a node given the weight of its children. + (+ left-weight right-weight)) + +(: calculate-weight (node-type node-type --> fixnum)) +(define (calculate-weight left right) + ;; Calculate the weight of a node that has children `left` and `right`. + (fixnum-calculate-weight (get-weight left) (get-weight right))) + +(: wb-tree-node (* node-type node-type --> (struct <wb-tree>))) +(define (wb-tree-node data left right) + ;; Construct a node with `data`, `left`, and `right`, with the correct + ;; weight. + (%wb-tree-node data (calculate-weight left right) left right)) + +(: balanced-as-child? (fixnum fixnum --> boolean)) +(define (balanced-as-child? child-weight node-weight) + ;; Determine if child would be weight-balanced if its parent was a node + ;; with weight `node-weight`. + (let ((alpha #e0.29)) + (>= child-weight (* alpha node-weight)))) + +(: fixnum-would-be-balanced? (fixnum fixnum --> boolean)) +(define (fixnum-would-be-balanced? left-weight right-weight) + ;; Determine if the two weights would be balanced if placed into a node. + (let ((size (+ left-weight right-weight))) + (and (balanced-as-child? left-weight size) + (balanced-as-child? right-weight size)))) + +(: would-be-balanced? (node-type node-type --> boolean)) +(define (would-be-balanced? left right) + ;; Determine if the two nodes would be balanced if placed into a node. + (fixnum-would-be-balanced? (get-weight left) (get-weight right))) + +(: heavy<=> (node-type node-type --> fixnum)) +(define (heavy<=> left right) + ;; Return 1 if right > left, -1 if left < right, and 0 if left = right + ;; weightwise. + (let ((left (get-weight left)) + (right (get-weight right))) + (cond + ((< left right) -1) + ((> left right) 1) + (else 0)))) + +(: balanced? (node-type --> boolean)) +(define (balanced? node) + ;; Recursively check if node is weight balanced. + (cond + ((null? node) #t) + (else (let ((left (get-left node)) + (right (get-right node)) + (weight (get-weight node))) + (and (balanced? left) (balanced? right) + (balanced-as-child? (get-weight left) weight) + (balanced-as-child? (get-weight right) weight)))))) + +;;; ;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Convert in-order vectors to ordered trees. +;;; ;;;;;;;;;;;;;;;;;;;;;;;;; + +(: in-order-vector->node (vector --> node-type)) +(define (in-order-vector->node vec) + (define (divide left right) + (if (< right left) + '() + (let ((midpoint (floor (/ (+ left right) 2)))) + (wb-tree-node (vector-ref vec midpoint) + (divide left (- midpoint 1)) + (divide (+ midpoint 1) right))))) + (divide 0 (- (vector-length vec) 1))) + +(: node->in-order-list (node-type --> list)) +(define (node->in-order-list node) + (if (null? node) + '() + (append (node->in-order-list (get-left node)) + (list (get-data node)) + (node->in-order-list (get-right node))))) + +;;; ;;;;;;;;;;;;;;;;;;;; +;;; Fundamental tree operations +;;; +;;; These macros are used to automatically generate symmetric cases for +;;; tree operations. They work with a trick with the syntax-rules +;;; matcher where it will match literal strings. +;;; +;;; Since syntax-rules is not eager it cannot calculate the inverse +;;; direction, so both must be supplied. This is fine for most cases, +;;; and helps make the algorithm more explicit. +;;; ;;;;;;;;;;;;;;;;;;;; + +(define-syntax s-make-node + ;; Create a node with these directions. + (syntax-rules () + ((_ data ("<" left) (">" right)) + (wb-tree-node data left right)) + ((_ data (">" right) ("<" left)) + (s-make-node data ("<" left) (">" right))))) + +(define-syntax with-node + (syntax-rules () + ((with-node (%node data ("<" left) (">" right)) body ...) + (let* ((node %node) + (left (get-left node)) + (right (get-right node)) + (data (get-data node))) + body ...)) + ((with-node (%node data (">" right) ("<" left)) body ...) + (with-node (%node data ("<" left) (">" right)) body ...)))) + +(define-syntax s-get + (syntax-rules () + ((s-get "<" node) (get-left node)) + ((s-get ">" node) (get-right node)))) + +(define-syntax s-rotate + ;; Generate rotation based on direction. Rotations are: + ;; + ;; A C + ;; / \ / \ + ;; B C -> A E + ;; / \ / \ + ;; D E B D + ;; + ;; A C + ;; / \ / \ + ;; C B -> E A + ;; / \ / \ + ;; E D D B + (syntax-rules () + ((_ dir invdir A) + (with-node (A A-data (dir B) (invdir C)) + (with-node (C C-data (dir D) (invdir E)) + (s-make-node C-data + (dir (s-make-node A-data (dir B) (invdir D))) + (invdir E))))))) + +(define-syntax s-join + ;; Generate a macro that traverses `invdir` to make a balanced tree with + ;; `dir` in it and `data` in the middle. + (syntax-rules () + ((s-join %data (dir init-in-dir) (invdir init-in-invdir)) + (let ((in-dir init-in-dir) + (%data data)) + (let join ((in-invdir init-in-invdir)) + (if (would-be-balanced? in-invdir in-dir) + (s-make-node data + (invdir in-invdir) + (dir in-dir)) + (with-node (in-invdir invdir-data + (dir dir-in-invdir) + (invdir invdir-in-invdir)) + (let ((new-dir (join dir-in-invdir))) + (if (would-be-balanced? invdir-in-invdir new-dir) + (s-make-node invdir-data + (invdir invdir-in-invdir) + (dir new-dir)) + (with-node (new-dir _ + (dir dir-in-new-dir) + (invdir invdir-in-new-dir)) + (if (and (would-be-balanced? invdir-in-invdir + invdir-in-new-dir) + (fixnum-would-be-balanced? + (+ (get-weight invdir-in-invdir) + (get-weight invdir-in-new-dir)) + (get-weight dir-in-new-dir))) + (s-rotate invdir + dir + (s-make-node invdir-data + (invdir invdir-in-invdir) + (dir new-dir))) + (s-rotate invdir + dir + (s-make-node invdir-data + (invdir invdir-in-invdir) + (dir (s-rotate dir invdir new-dir))))))))))))))) + +(: join (* node-type node-type --> (struct <wb-tree>))) +(define (join data left right) + (let ((dir (heavy<=> left right))) + (cond + ((positive? dir) (s-join data (">" right) ("<" left))) + ((negative? dir) (s-join data ("<" left) (">" right))) + (else (wb-tree-node data left right))))) + |
