
A few invariants involving subtree-width and black-height balance could break if singleton nodes were reused; bugs were due to stale data in the nodes. The token tree implements the implicit interface in the original splay-based token tree module in syntax-color/token-tree. However, it does not appear to perform significantly differently in the case of indentation yet. It needs some additional profiling and analysis to see if we can take advantage of the richer structure in the rb tree.
805 lines
24 KiB
Racket
805 lines
24 KiB
Racket
#lang scribble/doc
|
|
@(require scribble/manual
|
|
scribble/eval
|
|
(for-label syntax-color/private/red-black
|
|
racket/base
|
|
racket/string))
|
|
|
|
@(define my-eval (make-base-eval))
|
|
@(my-eval '(require syntax-color/private/red-black racket/string))
|
|
|
|
@title{Ordered Red-Black Trees}
|
|
@author+email["Danny Yoo" "dyoo@hashcollision.org"]
|
|
|
|
|
|
@defmodule[syntax-color/private/red-black]
|
|
|
|
This is an implementation of an augmented red-black tree with extra information
|
|
to support position-based queries.
|
|
|
|
The intended usage case of this structure is to maintain an ordered sequence of
|
|
items, where each item has an internal length. Given such a sequence, we want
|
|
to support quick lookup by position and in-place insertions and deletions.
|
|
We also want to support the catenation and splitting of sequences.
|
|
|
|
For example:
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(for ([w (in-list '("This" " " "is" " " "a" " " "test"))])
|
|
(insert-last/data! a-tree w (string-length w)))
|
|
(node-data (search a-tree 0))
|
|
(node-data (search a-tree 10))
|
|
(define at-test-node (search a-tree 10))
|
|
(insert-before/data! a-tree at-test-node "small" 5)
|
|
(tree-items a-tree)
|
|
@code:comment{Split at the node holding "small":}
|
|
(define at-small-node (search a-tree 10))
|
|
(define-values (left-side right-side) (split! a-tree at-small-node))
|
|
(tree-items left-side)
|
|
(tree-items right-side)
|
|
(define joined-tree (join! left-side right-side))
|
|
(tree-items joined-tree)
|
|
]
|
|
|
|
|
|
This implementation follows the basic outline for order-statistic red-black
|
|
trees described in @cite{clrs2009} and incorporates a few extensions suggsted
|
|
in @cite{wein2005}. As a red-black tree, the structure ensures that the tree's
|
|
height is never greater than @math{2*lg(#-of-nodes + 1)}, guaranteeing good
|
|
worst-case behavior for its operations.
|
|
|
|
The main types of values used in the library are @emph{trees} and @emph{nodes}.
|
|
A tree has a @emph{root} node, and each node has holds arbitrary @emph{data}
|
|
and a natural @emph{self-width}, along with a reference to the elements smaller
|
|
(@racket[node-left]) and larger (@racket[node-right]). Each node also
|
|
remembers the entire width of its subtree, which can be accessed with
|
|
@racket[node-subtree-width]. The tree holds first and last pointers into the
|
|
structure to allow for fast access to the beginning and end of the sequence. A
|
|
distinguished @racket[nil] node lies at the leaves of the tree.
|
|
|
|
|
|
|
|
@section{API}
|
|
@declare-exporting[syntax-color/private/red-black]
|
|
|
|
|
|
@subsection{Data types}
|
|
|
|
@defproc[(new-tree) tree?]{
|
|
Constructs a new tree. The tree's root is initially @racket[nil].
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
a-tree
|
|
(nil-node? (tree-root a-tree))
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(tree? [x any/c]) boolean?]{
|
|
Returns @racket[#t] if @racket[x] is a tree.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(tree? a-tree)
|
|
(tree? "not a tree")
|
|
(tree? (new-node '(not a tree either) 0))
|
|
]}
|
|
|
|
|
|
|
|
@defproc[(tree-root [t tree?]) node?]{
|
|
Returns the root node of the tree @racket[t].
|
|
If the tree is empty, returns the distinguished @racket[nil] node.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(nil-node? (tree-root (new-tree)))
|
|
(define a-node (new-node "first node!" 11))
|
|
(insert-first! a-tree a-node)
|
|
(eq? a-node (tree-root a-tree))]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(tree-first [t tree?]) node?]{
|
|
Returns the first node in the tree.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(nil-node? (tree-first (new-tree)))
|
|
(define a-node (new-node "first node!" 11))
|
|
(define another-node (new-node "last node!" 11))
|
|
(insert-first! a-tree a-node)
|
|
(insert-last! a-tree another-node)
|
|
(eq? a-node (tree-first a-tree))]
|
|
}
|
|
|
|
|
|
@defproc[(tree-last [t tree?]) node?]{
|
|
Returns the last node in the tree.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(nil-node? (tree-first (new-tree)))
|
|
(define a-node (new-node "first node!" 11))
|
|
(define another-node (new-node "last node!" 11))
|
|
(insert-first! a-tree a-node)
|
|
(insert-last! a-tree another-node)
|
|
(eq? another-node (tree-last a-tree))]
|
|
}
|
|
|
|
|
|
@defproc[(new-node [data any/c] [width natural-number/c]) singleton-node?]{
|
|
Constructs a new singleton node. This node can be inserted into a tree with
|
|
@racket[insert-first!], @racket[insert-last!], @racket[insert-before!], or
|
|
@racket[insert-after!].
|
|
|
|
@interaction[#:eval my-eval
|
|
(new-node #("a" "node") 7)]
|
|
}
|
|
|
|
|
|
@defproc[(node? [x any/c]) boolean?]{
|
|
Returns @racket[#t] if @racket[x] is a node.
|
|
@interaction[#:eval my-eval
|
|
(node? (new-node #("a" "node") 7))
|
|
@code:comment{Trees are not nodes: they _have_ nodes.}
|
|
(node? (new-tree))
|
|
(node? (tree-root (new-tree)))
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(singleton-node? [x any/c]) boolean?]{
|
|
Returns @racket[#t] if @racket[x] is a @emph{singleton node}. A singleton node
|
|
is unattached to any tree, and is not the @racket[nil] node.
|
|
@interaction[#:eval my-eval
|
|
(singleton-node? (new-node #("a" "node") 7))
|
|
(singleton-node? nil)
|
|
|
|
@code:comment{Create a fresh node:}
|
|
(define a-node (new-node "about to attach" 0))
|
|
(singleton-node? a-node)
|
|
@code:comment{After attachment, it is no longer singleton:}
|
|
(define a-tree (new-tree))
|
|
(insert-first! a-tree a-node)
|
|
(singleton-node? a-node)
|
|
@code:comment{Operations such as delete! or split! will break}
|
|
@code:comment{off nodes as singletons again:}
|
|
(delete! a-tree a-node)
|
|
(singleton-node? a-node)
|
|
]
|
|
}
|
|
|
|
|
|
@defthing[nil node?]{
|
|
|
|
The distinguished @racket[nil] node. By definition, @racket[nil] is colored
|
|
black, and its @racket[node-parent], @racket[node-left], and
|
|
@racket[node-right] are pointed to itself.}
|
|
|
|
|
|
@defproc[(non-nil-node? [x any/c]) boolean?]{
|
|
Returns @racket[#t] if @racket[x] is a non-nil node.
|
|
@interaction[#:eval my-eval
|
|
(non-nil-node? nil)
|
|
(non-nil-node? (new-node "I am not a number" 1))
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(nil-node? [x any/c]) boolean?]{
|
|
Returns @racket[#t] if @racket[x] is the nil node.
|
|
@interaction[#:eval my-eval
|
|
(nil-node? nil)
|
|
(nil-node? (new-node "I am not a number" 1))
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(node-data [n node?]) any/c]{
|
|
Returns the data associated to node @racket[n]. Note that the
|
|
@racket[node-data] and @racket[node-self-width] are entirely independent.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-node (new-node "utah" 4))
|
|
(node-data a-node)
|
|
]
|
|
}
|
|
|
|
@defproc[(set-node-data! [n node?] [v any/c]) void?]{
|
|
Assigns the data associated to node @racket[n]. Note that the
|
|
@racket[node-data] and @racket[node-self-width] are entirely independent.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-node (new-node "utah" 4))
|
|
(set-node-data! a-node "rhode island")
|
|
(node-data a-node)
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(node-self-width [n node?]) any/c]{
|
|
Returns the self-width associated to node @racket[n]. Note that the
|
|
@racket[node-data] and @racket[node-self-width] are entirely independent.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-node (new-node "utah" 4))
|
|
(node-self-width a-node)
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(update-node-self-width! [n node?] [w natural-number/c]) any/c]{
|
|
Updates the self-width associated to node @racket[n]. When attached to a tree,
|
|
also propagates the width's change to the widths of subtrees, upward through
|
|
its parents to the root. Note that the @racket[node-data] and
|
|
@racket[node-self-width] are entirely independent.
|
|
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(insert-last/data! a-tree "hello" 5)
|
|
(insert-last/data! a-tree "world" 1)
|
|
@code:comment{The tree as a whole has width 6:}
|
|
(node-subtree-width (tree-root a-tree))
|
|
@code:comment{Updates will propagate to the root:}
|
|
(update-node-self-width! (tree-last a-tree) 5)
|
|
(node-self-width (tree-last a-tree))
|
|
(node-subtree-width (tree-root a-tree))
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(node-subtree-width [n node?]) any/c]{
|
|
Returns the width of the entire subtree at node @racket[n]. This sums the
|
|
width of the left and right child subtrees, as well as its self-width.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(insert-last/data! a-tree "berkeley" 1)
|
|
(insert-last/data! a-tree "stanford" 1)
|
|
(insert-last/data! a-tree "wpi" 1)
|
|
(insert-last/data! a-tree "brown" 1)
|
|
(insert-last/data! a-tree "utah" 1)
|
|
@code:comment{The entire tree should sum to five, since each element contributes one.}
|
|
(node-subtree-width (tree-root a-tree))
|
|
(node-subtree-width (node-left (tree-root a-tree)))
|
|
(node-subtree-width (node-right (tree-root a-tree)))
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(node-parent [n node?]) node?]{
|
|
Returns the parent of the node @racket[n].
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(insert-last/data! a-tree "bill and ted's excellent adventure" 1)
|
|
(insert-last/data! a-tree "the matrix" 1)
|
|
(insert-last/data! a-tree "speed" 1)
|
|
(define p (node-parent (tree-last a-tree)))
|
|
(node-data p)]
|
|
}
|
|
|
|
|
|
@defproc[(node-left [n node?]) node?]{
|
|
Returns the left child of the node @racket[n].
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(insert-last/data! a-tree "bill and ted's excellent adventure" 1)
|
|
(insert-last/data! a-tree "the matrix" 1)
|
|
(insert-last/data! a-tree "speed" 1)
|
|
(define p (node-left (tree-root a-tree)))
|
|
(node-data p)]
|
|
}
|
|
|
|
|
|
@defproc[(node-right [n node?]) node?]{
|
|
Returns the right child of the node @racket[n].
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(insert-last/data! a-tree "bill and ted's excellent adventure" 1)
|
|
(insert-last/data! a-tree "the matrix" 1)
|
|
(insert-last/data! a-tree "speed" 1)
|
|
(define p (node-right (tree-root a-tree)))
|
|
(node-data p)]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(node-color [n node?]) (or/c 'red 'black)]{
|
|
Returns the color of the node @racket[n]. The red-black tree structure uses
|
|
this value to maintain balance.
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(insert-last/data! a-tree "the color purple" 1)
|
|
(insert-last/data! a-tree "pretty in pink" 1)
|
|
(insert-last/data! a-tree "the thin red line" 1)
|
|
(insert-last/data! a-tree "clockwork orange" 1)
|
|
(insert-last/data! a-tree "fried green tomatoes" 1)
|
|
(node-color (tree-root a-tree))
|
|
(tree-fold-inorder a-tree
|
|
(lambda (n acc)
|
|
(cons (list (node-data n) (node-color n))
|
|
acc))
|
|
'())]
|
|
}
|
|
|
|
|
|
@defproc[(red? [n node?]) boolean?]{
|
|
Returns @racket[#t] if node @racket[n] is red.
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(insert-last/data! a-tree "the hobbit" 1)
|
|
(insert-last/data! a-tree "the fellowship of the ring" 1)
|
|
(red? (tree-root a-tree))
|
|
(red? (node-right (tree-root a-tree)))
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(black? [n node?]) boolean?]{
|
|
Returns @racket[#t] if node @racket[n] is black.
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(insert-last/data! a-tree "the fellowship of the ring" 1)
|
|
(insert-last/data! a-tree "the two towers" 1)
|
|
(insert-last/data! a-tree "return of the king" 1)
|
|
@code:comment{The root is always black.}
|
|
(black? (tree-root a-tree))
|
|
@code:comment{The tree should have towers as the root, with}
|
|
@code:comment{the fellowship and king as left and right respectively.}
|
|
(map node-data
|
|
(list (tree-root a-tree)
|
|
(node-left (tree-root a-tree))
|
|
(node-right (tree-root a-tree))))
|
|
(black? (tree-root a-tree))
|
|
(black? (node-left (tree-root a-tree)))
|
|
(black? (node-right (tree-root a-tree)))
|
|
]
|
|
}
|
|
|
|
|
|
@subsection{Operations}
|
|
|
|
@defproc[(insert-first! [t tree?] [n singleton-node?]) void?]{
|
|
Adds node @racket[n] as the first element in tree @racket[t].
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(define a-node (new-node "pear" 1))
|
|
(insert-first! a-tree a-node)
|
|
(eq? (tree-root a-tree) a-node)
|
|
]
|
|
|
|
Note that attempting to add an attached, non-singleton node to a tree will
|
|
raise a contract error.
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(define a-node (new-node "persimmon" 1))
|
|
(insert-first! a-tree a-node)
|
|
(insert-first! a-tree a-node)
|
|
]
|
|
}
|
|
|
|
@defproc[(insert-last! [t tree?] [n singleton-node?]) void?]{
|
|
Adds node @racket[n] as the last element in tree @racket[t].
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(define a-node (new-node "apple" 1))
|
|
(insert-last! a-tree a-node)
|
|
(eq? (tree-root a-tree) a-node)
|
|
]
|
|
|
|
Note that attempting to add an attached, non-singleton node to a tree will
|
|
raise a contract error.
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(define a-node (new-node "orange" 1))
|
|
(insert-last! a-tree a-node)
|
|
(insert-last! a-tree a-node)
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(insert-before! [t tree?] [n1 node?] [n2 node?]) void?]{
|
|
Adds node @racket[n2] before node @racket[n1] in tree @racket[t]. This effectively
|
|
makes @racket[n2] the @racket[predecessor] of @racket[n1].
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(define a-node (new-node "banana" 1))
|
|
(define b-node (new-node "mango" 1))
|
|
(insert-first! a-tree a-node)
|
|
(insert-before! a-tree a-node b-node)
|
|
(eq? (predecessor a-node) b-node)
|
|
(eq? (successor b-node) a-node)
|
|
]
|
|
|
|
Note that attempting to add an attached, non-singleton node to a tree will
|
|
raise a contract error.
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(define a-node (new-node "peach" 1))
|
|
(insert-first! a-tree a-node)
|
|
(insert-before! a-tree a-node a-node)
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(insert-after! [t tree?] [n1 node?] [n2 node?]) void?]{
|
|
Adds node @racket[n2] after node @racket[n1] in tree @racket[t]. This effectively
|
|
makes @racket[n2] the @racket[successor] of @racket[n1].
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(define a-node (new-node "cherry" 1))
|
|
(define b-node (new-node "pawpaw" 1))
|
|
(insert-first! a-tree a-node)
|
|
(insert-after! a-tree a-node b-node)
|
|
(eq? (successor a-node) b-node)
|
|
(eq? (predecessor b-node) a-node)
|
|
]
|
|
|
|
Note that attempting to add an attached, non-singleton node to a tree will
|
|
raise a contract error.
|
|
@interaction[#:eval my-eval
|
|
(define a-tree (new-tree))
|
|
(define a-node (new-node "grapefruit" 1))
|
|
(insert-first! a-tree a-node)
|
|
(insert-after! a-tree a-node a-node)
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@deftogether[
|
|
(
|
|
@defproc[(insert-first/data! [t tree?] [data any/c] [width natural-number/c]) void?]{}
|
|
@defproc[(insert-last/data! [t tree?] [data any/c] [width natural-number/c]) void?]{}
|
|
@defproc[(insert-before/data! [t tree?] [n node?] [data any/c] [width natural-number/c]) void?]{}
|
|
@defproc[(insert-after/data! [t tree?] [n node?] [data any/c] [width natural-number/c]) void?]{})
|
|
]{
|
|
|
|
For user convenience, the functions @racket[insert-first/data!],
|
|
@racket[insert-last/data!], @racket[insert-before/data!], and
|
|
@racket[insert-after/data!] have been provided. These create nodes and insert
|
|
into the tree structure the same way as @racket[insert-first!],
|
|
@racket[insert-last!], @racket[insert-before!], and @racket[insert-after!].
|
|
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(insert-first/data! t "message in a bottle" 1)
|
|
(insert-last/data! t "don't stand so close to me" 1)
|
|
(insert-before/data! t (tree-first t) "everything she does is magic" 1)
|
|
(insert-after/data! t (tree-last t) "king of pain" 1)
|
|
(tree-items t)
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(delete! [t tree?] [n non-nil-node?]) void?]{
|
|
Deletes node @racket[n] from the tree @racket[t]. After deletion, @racket[n]
|
|
will become a singleton node.
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(define n1 (new-node "George, George, George of the Jungle," 1))
|
|
(define n2 (new-node "strong as he can be..." 1))
|
|
(define n3 (new-node "aaaaaaaaaaah!" 1))
|
|
(define n4 (new-node "watch out for that..." 1))
|
|
(define n5 (new-node "<thump!>" 1))
|
|
(define n6 (new-node "treeeeeeeeee!, " 1))
|
|
(for ([n (in-list (list n1 n2 n3 n4 n5 n6))])
|
|
(insert-last! t n))
|
|
(delete! t n5)
|
|
(tree-items t)
|
|
]
|
|
|
|
Note that @racket[n] must be attached to tree @racket[t] or else will raise
|
|
a contract error:
|
|
@interaction[#:eval my-eval
|
|
(define t1 (new-tree))
|
|
(insert-first/data! t1 "tricky" 1)
|
|
(define n (new-node "tricky" 1))
|
|
@code:comment{This should raise an error:}
|
|
(delete! t1 n)
|
|
]}
|
|
|
|
|
|
@defproc[(join! [t1 tree?] [t2 tree?]) tree?]{
|
|
Destructively joins trees @racket[t1] and @racket[t2], returning a tree that
|
|
has the contents of both. Every element in @racket[t1] is treated less than
|
|
the elements in @racket[t2].
|
|
|
|
@interaction[#:eval my-eval
|
|
(define t1 (new-tree))
|
|
(for ([name (in-list '(goku gohan krillin piccolo vegeta))])
|
|
(insert-last/data! t1 name 1))
|
|
@code:comment{Tier two characters:}
|
|
(define t2 (new-tree))
|
|
(for ([name (in-list '(yamcha tien chiaotzu bulma chi-chi
|
|
roshi))])
|
|
(insert-last/data! t2 name 1))
|
|
(define tree-of-mighty-z-warriors (join! t1 t2))
|
|
(map car (tree-items tree-of-mighty-z-warriors))
|
|
]
|
|
|
|
Note that @racket[t1] should not be @racket[eq?] to @racket[t2] or else will raise
|
|
a contract error.
|
|
@interaction[#:eval my-eval
|
|
(define t1 (new-tree))
|
|
(join! t1 t1)
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(concat! [t1 tree?] [n singleton-node?] [t2 tree?]) tree?]{
|
|
Destructively joins tree @racket[t1], singleton node @racket[n], and tree
|
|
@racket[t2], returning a tree that has the contents of both. Every element in
|
|
@racket[t1] is treated less than @racket[x], and @racket[x] is treated smaller than all
|
|
the elements in @racket[t2].
|
|
|
|
@interaction[#:eval my-eval
|
|
(define t1 (new-tree))
|
|
(define t2 (new-tree))
|
|
(insert-last/data! t1 "inigo" 50)
|
|
(define x (new-node "vizzini" 1))
|
|
(insert-last/data! t2 "fezzik" 100)
|
|
(define poor-lost-circus-performers (concat! t1 x t2))
|
|
(tree-items poor-lost-circus-performers)
|
|
]
|
|
|
|
Note that @racket[t1] should not be @racket[eq?] to @racket[t2] or else will raise
|
|
a contract error.
|
|
@interaction[#:eval my-eval
|
|
(define t1 (new-tree))
|
|
(define n (new-node "a-node" 1))
|
|
(concat! t1 n t1)
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(split! [t tree?] [n non-nil-node?]) (values tree? tree?)]{
|
|
Destructively splits tree @racket[t] into two trees, the first containing the
|
|
elements smaller than node @racket[n], and the second containing those larger.
|
|
Afterwards, @racket[n] becomes a singleton node.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(for ([name '(melchior caspar bob balthazar)])
|
|
(insert-last/data! t name 1))
|
|
(define bob-node (search t 2))
|
|
(singleton-node? bob-node)
|
|
(define-values (l r) (split! t bob-node))
|
|
@code:comment{We tree kings of orient are:}
|
|
(append (tree-items l) (tree-items r))
|
|
(singleton-node? bob-node)
|
|
]
|
|
|
|
Note that @racket[n] must be attached to tree @racket[t] or else raise
|
|
a contract error.
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(for ([name '(melchior caspar bob balthazar)])
|
|
(insert-last/data! t name 1))
|
|
@code:comment{This should raise an error:}
|
|
(define t2 (new-tree))
|
|
(insert-last! t2 (new-node "bob" 1))
|
|
(split! t (tree-root t2))
|
|
]}
|
|
|
|
|
|
@defproc[(reset! [t tree?]) void?]{
|
|
Resets the contents of the tree to the empty state.
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(insert-last/data! t "house" 5)
|
|
(insert-last/data! t "cleaning" 8)
|
|
(tree-items t)
|
|
(reset! t)
|
|
(tree-items t)]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(search [t tree?] [p natural-number/c]) node?]{
|
|
Searches for the node at or within the given position @racket[p] of the tree.
|
|
If the position is out of bounds, returns @racket[nil].
|
|
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(for ([word '("alpha" "beta" "gamma" "delta" "epsilon" "zeta")])
|
|
(insert-last/data! t word (string-length word)))
|
|
(node-data (search t 0))
|
|
(node-data (search t 5))
|
|
(node-data (search t 6))
|
|
(node-data (search t 7))
|
|
(node-data (search t 8))
|
|
(node-data (search t 9))
|
|
(nil-node? (search t 100))
|
|
]
|
|
|
|
Note: nodes with a self-width of zero are effectively invisible to
|
|
@racket[search], and will be skipped over.
|
|
}
|
|
|
|
|
|
@defproc[(search/residual [t tree?] [p natural-number/c]) (values node? natural-number/c)]{
|
|
Searches for the node at or within the given position @racket[p] of the tree.
|
|
This is an extension of @racket[search] that returns a second value: the offset
|
|
into the element where the search has terminated. If the position is out of
|
|
bounds of any element, the first component of the returned value is
|
|
@racket[nil].
|
|
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(for ([word '("alpha" "beta" "gamma" "delta" "epsilon" "zeta")])
|
|
(insert-last/data! t word (string-length word)))
|
|
(search/residual t 5)
|
|
(search/residual t 6)
|
|
(search/residual t 7)
|
|
(define-values (a-node residual)
|
|
(search/residual t 100))
|
|
(nil-node? a-node)
|
|
residual
|
|
(+ residual (node-subtree-width (tree-root t)))
|
|
]
|
|
}
|
|
|
|
|
|
@defproc[(minimum [n node?]) node?]{
|
|
Given a node @racket[n], returns the minimum element of the subtree rooted at
|
|
@racket[n].
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(for ([x (in-list '("ftl" "xcom" "civ"))])
|
|
(insert-first/data! t x (string-length x)))
|
|
(node-data (minimum (tree-root t)))
|
|
]
|
|
Note: to get the minimum of the whole tree, it's faster to use
|
|
@racket[tree-first].
|
|
}
|
|
|
|
|
|
@defproc[(maximum [n node?]) node?]{
|
|
Given a node @racket[n], returns the maximum element of the subtree rooted at
|
|
@racket[n].
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(for ([x (in-list '("ftl" "xcom" "civ"))])
|
|
(insert-first/data! t x (string-length x)))
|
|
(node-data (maximum (tree-root t)))
|
|
]
|
|
Note: to get the maximum of the whole tree, it's faster to use
|
|
@racket[tree-last].
|
|
}
|
|
|
|
|
|
@defproc[(successor [n node?]) node?]{
|
|
Given a node @racket[n] contained in some tree, returns the immediate
|
|
successor of @racket[n] in an inorder traversal of that tree.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define partial-alien-tree (new-tree))
|
|
(for ([name '("sectoid" "floater" "thin man" "chryssalid"
|
|
"muton" "cyberdisk")])
|
|
(insert-last/data! partial-alien-tree name 1))
|
|
(define first-alien (tree-first partial-alien-tree))
|
|
(node-data (successor first-alien))
|
|
(node-data (successor (successor first-alien)))
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(predecessor [n node?]) node?]{
|
|
Given a node @racket[n] contained in some tree, returns the immediate
|
|
predecessor of @racket[n] in an inorder traversal of that tree.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define partial-alien-tree (new-tree))
|
|
(for ([name '("sectoid" "floater" "thin man" "chryssalid"
|
|
"muton" "cyberdisk")])
|
|
(insert-last/data! partial-alien-tree name 1))
|
|
(define last-alien (tree-last partial-alien-tree))
|
|
(node-data (predecessor last-alien))
|
|
(node-data (predecessor (predecessor last-alien)))
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(position [n node?]) natural-number/c]{
|
|
Given a node @racket[n] contained in some tree, returns the immediate
|
|
position of @racket[n] in that tree.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define story-tree (new-tree))
|
|
(for ([word (string-split "if you give a mouse a cookie")])
|
|
(insert-last/data! story-tree word (string-length word)))
|
|
(define a-pos (position (tree-last story-tree)))
|
|
a-pos
|
|
(node-data (search story-tree a-pos))
|
|
]
|
|
}
|
|
|
|
|
|
|
|
@defproc[(tree-items [t tree?]) (listof/c (list/c any/c natural-number/c))]{
|
|
Given a tree, returns a list of its data and width pairs.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(insert-last/data! t "rock" 4)
|
|
(insert-last/data! t "paper" 5)
|
|
(insert-last/data! t "scissors" 8)
|
|
(tree-items t)
|
|
]
|
|
}
|
|
|
|
|
|
@deftogether[
|
|
(@defproc[(tree-fold-inorder [t tree?] [f (node? any/c . -> . any)] [acc any/c]) any]{}
|
|
@defproc[(tree-fold-preorder [t tree?] [f (node? any/c . -> . any)] [acc any/c]) any]{}
|
|
@defproc[(tree-fold-postorder [t tree?] [f (node? any/c . -> . any)] [acc any/c]) any]{})]{
|
|
|
|
Iterates a function @racket[f] across the nodes of the tree, in inorder, preorder,
|
|
and postorder respectively.
|
|
|
|
@interaction[#:eval my-eval
|
|
(define t (new-tree))
|
|
(insert-last/data! t "three" 1)
|
|
(insert-last/data! t "blind" 1)
|
|
(insert-last/data! t "mice" 1)
|
|
@code:comment{"blind" should be the root, with}
|
|
@code:comment{"three" and "mice" as left and right.}
|
|
(define (f n acc) (cons (node-data n) acc))
|
|
(reverse (tree-fold-inorder t f '()))
|
|
(reverse (tree-fold-preorder t f '()))
|
|
(reverse (tree-fold-postorder t f '()))
|
|
]
|
|
|
|
}
|
|
|
|
|
|
@section{Uncontracted library}
|
|
|
|
This library uses contracts extensively to prevent the user from messing up;
|
|
however, the contract checking may be prohibitively
|
|
expensive for certain applications.
|
|
|
|
The uncontracted bindings of this library can be accessed through:
|
|
|
|
@racketblock[(require (submod syntax-color/private/red-black uncontracted))]
|
|
|
|
This provides the same bindings as the regular API, but with no contract
|
|
checks. Use this with extreme care: Improper use of the uncontracted form of
|
|
this library may lead to breaking the red-black invariants, or (even worse)
|
|
introducing cycles in the structure. If you don't know whether you should be
|
|
using the uncontracted forms or not, you probably should not.
|
|
|
|
|
|
@section{Bibliography}
|
|
|
|
@bibliography[
|
|
@bib-entry[#:key "clrs2009"
|
|
#:title "Introduction to Algorithms, Third Edition"
|
|
#:is-book? #t
|
|
#:author "Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein"
|
|
#:date "2009"
|
|
#:url "http://mitpress.mit.edu/books/introduction-algorithms"]
|
|
|
|
@bib-entry[#:key "wein2005"
|
|
#:title "Efficient implementation of red-black trees with split and catenate operations"
|
|
#:author "Ron Wein"
|
|
#:date "2005"
|
|
#:url "http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.109.4875"]
|
|
]
|
|
|
|
|
|
|
|
|
|
@close-eval[my-eval] |