undefined
.
Eventually, the complete
version will be made available.
Red-Black Trees with GADTs
This version of RedBlack trees demonstrates the use of GADTs to statically verify three of the four red-black invariants. The final invariant (the black height constraint) requires a bit more mechanism and will be handled separately.
> {-# LANGUAGE InstanceSigs, GADTs, DataKinds, KindSignatures, ScopedTypeVariables,
> MultiParamTypeClasses, FlexibleInstances, TypeFamilies,
> StandaloneDeriving #-}
Here, again, are the invariants for red-black trees:
The empty nodes at the leaves are black.
The root is black.
From each node, every path to a leaf has the same number of black nodes.
Red nodes have black children.
Type structure
First, we define the data structure for red/black trees.
The type RBT a
is used at the top of a red-black tree.
Implementation
Now we implement the operations of sets using our refined red-black trees.
Membership testing and listing the elements in a tree are straightforward.
> member :: Ord a => a -> RBT a -> Bool
> member x (Root t) = aux x t where
> aux :: Ord a => a -> T a -> Bool
> aux _ E = False
> aux x (N _ a y b)
> | x < y = aux x a
> | x > y = aux x b
> | otherwise = True
> elements :: Ord a => RBT a -> [a]
> elements (Root t) = aux t [] where
> aux :: Ord a => T a -> [a] -> [a]
> aux E acc = acc
> aux (N _ a x b) acc = aux a (x : aux b acc)
Insertion is naturally a bit trickier...
As before, after performing the insertion with the auxiliary function ins
, we blacken the top node of the tree to make sure that invariant (2) is always satisfied.
> ins :: Ord a => a -> T a -> T a
> ins x E = N R E x E
> ins x s@(N c a y b)
> | x < y = balanceL c (ins x a) y b
> | x > y = balanceR c a y (ins x b)
> | otherwise = (N c a y b)
The original balance
function looked like this:
> {-
> balance (N B (N R (N R a x b) y c) z d) = N R (N B a x b) y (N B c z d)
> balance (N B (N R a x (N R b y c)) z d) = N R (N B a x b) y (N B c z d)
> balance (N B a x (N R (N R b y c) z d)) = N R (N B a x b) y (N B c z d)
> balance (N B a x (N R b y (N R c z d))) = N R (N B a x b) y (N B c z d)
> balance t = t
> -}
The first two clauses handled cases where the left subtree was unbalanced as a result of an insertion, while the last two handle cases where a right-insertion has unbalanced the tree.
Here, we split this function in two to recognize that we have information from ins
above. We know exactly where to look for the red/red violation! If we inserted on the left, then we should balance on the left. If we inserted on the right, then we should balance on the right.
> balanceL :: Color -> T a -> a -> T a -> T a
> balanceL B (N R (N R a x b) y c) z d = N R (N B a x b) y (N B c z d)
> balanceL B (N R a x (N R b y c)) z d = N R (N B a x b) y (N B c z d)
> balanceL col a x b = N col a x b
> balanceR :: Color -> T a -> a -> T a -> T a
> balanceR B a x (N R (N R b y c) z d) = N R (N B a x b) y (N B c z d)
> balanceR B a x (N R b y (N R c z d)) = N R (N B a x b) y (N B c z d)
> balanceR col a x b = N col a x b
> within :: Ord a => a -> Interval a -> Bool
> within y (Just x, Just z) = x < y && y < z
> within y (Just x, Nothing) = x < y
> within y (Nothing, Just z) = y < z
> within y (Nothing, Nothing) = True