Professional Documents
Culture Documents
Lecture 17
Stephen Brookes
Today
A case study in modular programming
signatures
structures
functors
information hiding
abstract types
dictionaries
Earlier: sorting with a comparison function
Now: revisit, using modular programming
A signature for dictionaries
collections of entries, sorted by keys
An implementation of dictionaries as
binary search trees, parameterized by
an ordered type of keys
signatures
signature ORDERED =!
sig!
type t!
val compare : t * t -> order!
end!
!
signature DICT =!
sig!
structure Key : ORDERED!
type a dict!
val empty : a dict!
val insert : Key.t * a -> a dict -> a dict!
val lookup : Key.t -> a dict -> a option!
val trav : a dict -> (Key.t * a) list!
end
signatures
can contain
structures
ordered types
structure Integers : ORDERED =!
struct!
type t = int!
val compare = Int.compare!
end
structure Strings : ORDERED =!
struct!
type t = string!
val compare = String.compare!
end
sig!
type t!
val compare : t * t -> order!
end
a dictionary functor
sig!
structure Key : ORDERED!
type a dict!
val empty : a dict!
val insert : Key.t * a -> a dict -> a dict!
val lookup : Key.t -> a dict -> a option!
val trav : a dict -> (Key.t * a) list!
end
structures
.....!
no need to
pass Key.compare
as an argument
end
invariant
T is a binary search tree
Characterized inductively by:
properties
Let K be a structure implementing ORDERED
why?
S.empty is a binary search tree
Whenever T is a binary search tree,
importance?
local reasoning guarantees a global property
The correctness proof only examines code
inside the structure (local reasoning)
results
Standard ML of New Jersey v110.76 []
-
structure S = BSTDict(Integers);!
open S;!
fun build [ ] = empty!
| build (x::L) = insert (x, x*x) (build L) ;!
val D = build [1,2,3,4,5];
ML says
val D = Node (Node (,(5,25),Leaf)) : int dict
results
(5, 25)
(4, 16)
(3, 9)
.
(2, 4)
.
(1, 1)
.
a binary search tree
but badly balanced
so far
We have a functor for building
implementations of DICT
building better
balanced trees
Now well implement red-black trees
Binary search trees with colored nodes
Color used to constrain tree structure
Constraints ensure that the ratio of
longest path : shortest path
is no worse than
2:1
why better?
In a tree with n nodes
red-black trees
datatype color = Red | Black
datatype a tree = Leaf !
| Node of a tree * (color * (Key.t * a)) * a tree!
!
"pow"
"dinger"
"hatee"
"pa"
"A-oo"
"ding"
red-black invariant
Roses are red, Violets are blue,
Nodes are red or black, Leaves are black,
Red nodes have black children,
Every path from the root to a leaf has the same number of black nodes,
And this poem doesnt really rhyme
red-black invariant
bst
well-red
black height is 4
only keys and colors shown
red-black trees
A red-black tree is a tree value
that satisfies the invariant:
empty
val empty = Leaf
(This is a red-black tree!)
bst
well-red
black height is 1
lookup
!
insertion
Two kinds of inserts: updates and true insertions
updates at root
"dinger"
"hatee"
"pa"
ringring
"ring"
"A-oo"
"wa"
"ding" "pow"
"dinger"
"hatee"
"pa"
"A-oo"
"ding"
updates inside
When Key.compare(k, k) is LESS
we need to put (k,v) into L, so use
insert (k, v) (Node(L, (c,(k, v)), R))
= Node(insert (k, v) L, (c,(k, v)), R)
true inserts
at a leaf
Lets define
insert (k, v) Leaf = Node(Leaf, (Red, (k, v)), Leaf)
the new entry replaces a leaf node,
leaves are black,
so choosing Black here would mess up black height
if this leaf is in a larger tree
bst
well-red
black height is 1
true inserts
in a non-empty tree
true insertion
example
insert (1,"1")
(4,"4")!
!
!
!
(3, "3")
a red-black tree
(5, "5")
.
bst
well-red
black height is 2
insert
!
(1,"1")
!
!
(4,"4")!
(3, "3")
(5, "5")
(4,"4")!
!
!
!
=>*
insert (1,"1")
(3, "3")
(5, "5")
(4,"4")!
!
=>*
insert (1,"1")
!
!
(3, "3")
(5, "5")
(4,"4")!
!
=>*
!
!
(3,"3")
(5,"5")
(4,"4")!
=>*
!
!
!
(3,"3")
(1,"1")
(5,"5")
bst
well-red
black height is 2
restoring invariant
The left-child is almost a red-black tree
keys are sorted,
paths have same black height
root node is red and has a red child
Thats the only invariant violation
We can re-balance, by rotating and re-coloring,
to obtain a red-black tree...
balancing
(4,"4")!
!
!
!
!
!
(3,"3")
(1,"1")
(3,"3")!
(5,"5")
bst
well-black, bh is 2
almost well-red
(4,"4")
(1,"1")
(5,"5")
bst
well-black, bh is 2
well-red
more generally
For a new insert into a non-empty tree
there are 4 awkward cases like this example:
right child
has a red root
and red left child
z!
z!
!
!
y
!
!
x!
x!
!
!
!
!
balancing
rotate & re-color
z!
y!
!
!
!
!
and so on
(all four cases)
using patterns
z!
y!
!
!
!
!
Node(Node(a, (Black, x), b), (Red, y), Node(c, (Black, z), d))
balance
balance : a tree -> a tree
insert?
So it seems like we should define
!
Specification
REQUIRES D is a rbt
ENSURES insert (k,v) D is a rbt
insert
invalid spec!
(
(
.
.
(1,"1")
(3, "3")
( insert
= balance
= balance
(1,"1")
(3,"3")
(1,"1")
(3, "3")
.
=
(3,"3")
(1,"1")
.
.
red violation at root
restoring invariant
Its easy to turn a tree with just a
red violation at its root into a red-black tree...
insert
!
specifications
T is rbt means
T is bst, T is well-red, T is well-black
T is almost-rbt means
T is bst, T is almost-well-red, T is well-black
well-red: no red node has a red child
almost-well-red: no red node has a red child,
except possibly the root node
well-black: every path has the same black height
specifications
ins : Key.t * a -> a tree -> a tree
REQUIRES D is rbt
ENSURES ins (k, v) D is almost-rbt
with same black height as D
balance : a tree -> a tree
REQUIRES D is Node(A, x, B), D is bst,
A is almost-rbt, B is rbt,
(or vice versa)
A and B have same black height
ENSURES balance D is almost-rbt
with same black height as D
specifications
blackenroot : a tree -> a tree
REQUIRES D is almost-rbt
ENSURES blackenroot D is rbt
insert : Key.t * a -> a tree -> a tree
REQUIRES D is rbt
ENSURES insert (k, v) D is rbt
balance spec
y!
z!
!
!
!
!
!
y
bst
well-black
left child almost well-red
right child well-red
bh(a) = bh(b) = bh(c) = bc(d)
bst
well-black
left child well-red
right child well-red
bst
well-black
well-red
trav
Traversal ignores color
fun trav Leaf = [ ]
| trav (Node(L, (_, (k,v)), R))
= (trav L) @ (k,v) :: (trav R)
documentation
The specs for the RBTDict code belong
(* ALMOST-INVARIANT:!
A tree is almost-red-black if it is a bst, well-black,!
and no red node has a red child, except possibly the root *)!
!
!
!
and so on
Fill in the rest of the implementation, with
appropriate documentation
(5, "5")
(5, "5")
(4, "4")
(4, "4")
(3, "3") (5, "5")
is Leaf
representation independence