module Test.RedBlackTreeSpec where import Control.Applicative import Control.Monad import Data.List import Data.RedBlackTree import System.Random import Test.Hspec import Test.Hspec.Core.QuickCheck (modifyMaxSuccess) import Test.QuickCheck redBlackTreesWithRightChild :: (Bounded a, Ord a, Random a) => Gen (RedBlackTree a) redBlackTreesWithRightChild = (mkRedBlackTree <$> (listOf arbitraryBoundedRandom)) `suchThat` ((mkEmpty /=) . rightChild) redBlackTreesWithLeftChild :: (Bounded a, Ord a, Random a) => Gen (RedBlackTree a) redBlackTreesWithLeftChild = (mkRedBlackTree <$> (listOf arbitraryBoundedRandom)) `suchThat` ((mkEmpty /=) . leftChild) redBlackTreesWithDistinctKeys :: (Bounded a, Ord a, Random a) => Gen (RedBlackTree a) redBlackTreesWithDistinctKeys = do xs <- (listOf arbitraryBoundedRandom) `suchThat` (\ys -> (length $ nub ys) == length ys) return $ mkRedBlackTree xs redBlackTreeInsertions :: (Bounded a, Ord a, Random a) => Gen ((RedBlackTree a), a) redBlackTreeInsertions = do xs <- listOf arbitraryBoundedRandom x <- arbitraryBoundedRandom return $ ((Data.RedBlackTree.insert (mkRedBlackTree xs) x), x) spec :: Spec spec = modifyMaxSuccess (const 10000) $ describe "Binary Search Trees" $ do context "when balanced externally" $ do context "when constructing trees" $ do it "preserves the BST ordering property" $ property $ \xs -> inorderTraversal (mkRedBlackTree (xs :: [Int])) `shouldBe` (sort xs) context "when traversing" $ do it "can be traversed in order" $ do let bst = mkRedBlackTree [2, 1, 3] inorderTraversal bst `shouldBe` [1, 2, 3] context "when finding the smallest element" $ do it "returns Nothing given the empty tree" $ do Data.RedBlackTree.minimum (mkEmpty :: RedBlackTree Int) `shouldBe` Nothing it "can find the smallest element of non-empty trees" $ do let bst = mkRedBlackTree [2, 1, 3] let expectedMinimum = search bst 1 Data.RedBlackTree.minimum bst `shouldBe` expectedMinimum context "when finding the successors of nodes" $ do it "can yield the successor of a node in the empty tree" $ do successor (mkEmpty :: RedBlackTree Int) 42 `shouldBe` Nothing it "can yield the successor of a node in a non-empty tree" $ do let bst = mkRedBlackTree [2, 1, 3] let expectedSuccessor = search bst 3 successor bst 2 `shouldBe` expectedSuccessor it "can yield the successor of a node in a non-empty tree when the provided node has no right subtree" $ do let bst = mkRedBlackTree [2, 1, 3] let expectedSuccessor = search bst 2 successor bst 1 `shouldBe` expectedSuccessor it "always yields the correct successor" $ forAll (redBlackTreeInsertions :: Gen ((RedBlackTree Int), Int)) $ \(rbt, newKey) -> (keyOf =<< successor rbt newKey) `shouldBe` (find (> newKey) $ inorderTraversal rbt) context "when searching" $ do it "returns nothing if the tree is empty" $ do let emptyBST = mkEmpty :: RedBlackTree Int search emptyBST 1 `shouldBe` Nothing it "retrieves keys that are present in the singleton tree" $ do let bst = mkRoot 1 search bst 1 `shouldBe` (Just bst) it "returns nothing if the key is not present in the tree" $ do let bst = mkRoot 1 search bst 99 `shouldBe` Nothing it "can recurse down the left hand path of the tree" $ do let bst = mkRedBlackTree [2, 1] :: RedBlackTree Int let searchResult = search bst 1 (keyOf =<< searchResult) `shouldBe` (Just 1) it "can recurse down the right hand path of the tree" $ do let bst = mkRedBlackTree [2, 3] let searchResult = search bst 3 (keyOf =<< searchResult) `shouldBe` (Just 3) context "when inserting new elements" $ do it "turns the empty tree into a singleton tree" $ do Data.RedBlackTree.insert mkEmpty 1 `shouldBe` mkRoot 1 it "makes keys less than the key of a singleton tree the left child" $ do let bst = mkRoot 1 :: RedBlackTree Int let bst' = Data.RedBlackTree.insert bst 0 let newNode = search bst' 0 (keyOf . leftChild $ bst') `shouldBe` (Just 0) rightChild bst' `shouldBe` mkEmpty it "makes keys greater than the key of a singleton tree the right child" $ do let bst = mkRoot 1 let bst' = Data.RedBlackTree.insert bst 2 let newNode = search bst' 2 leftChild bst' `shouldBe` mkEmpty (keyOf . rightChild $ bst') `shouldBe` (Just 2) it "inserts keys less than the key of a node into the left subtree" $ do let bst = mkRedBlackTree [2, 1, 3] let bst' = Data.RedBlackTree.insert bst 0 let left = leftChild bst' let leftLeft = leftChild . leftChild $ bst' let right = rightChild $ bst' (keyOf left) `shouldBe` (Just 1) (keyOf leftLeft) `shouldBe` (Just 0) (keyOf right) `shouldBe` (Just 3) it "inserts keys greater than the key of a node into the right subtree" $ do let bst = mkRedBlackTree [2, 1, 3] let bst' = Data.RedBlackTree.insert bst 4 let left = leftChild bst' let right = rightChild $ bst' let rightRight = rightChild . rightChild $ bst' (keyOf left) `shouldBe` (Just 1) (keyOf right) `shouldBe` (Just 3) (keyOf rightRight) `shouldBe` (Just 4) context "when balancing internally" $ do context "when rotating left" $ do it "preserves the BST ordering property" $ forAll (redBlackTreesWithRightChild :: Gen (RedBlackTree Int)) $ \rbt -> inorderTraversal (rotateLeft rbt) `shouldBe` (inorderTraversal rbt) it "is the inverse of rotateRight" $ forAll (redBlackTreesWithRightChild :: Gen (RedBlackTree Int)) $ \rbt -> do (inorderTraversal (rotateRight . rotateLeft $ rbt)) `shouldBe` (inorderTraversal rbt) (preorderTraversal (rotateRight . rotateLeft $ rbt)) `shouldBe` (preorderTraversal rbt) it "makes the right child of a tree the new root" $ do let rbt = mkRedBlackTree [100, 25, 175, 0, 150, 50, 200] let rbt' = rotateLeft rbt (keyOf rbt') `shouldBe` (Just 175) it "makes the old root the new root's left subtree" $ do let rbt = mkRedBlackTree [100, 25, 175, 0, 150, 50, 200] let rbt' = rotateLeft rbt (keyOf $ leftChild rbt') `shouldBe` (Just 100) it "makes the right child's left subtree the old root's right subtree" $ do let rbt = mkRedBlackTree [100, 25, 175, 0, 150, 50, 200] let rbt' = rotateLeft rbt (keyOf . rightChild . leftChild $ rbt') `shouldBe` (Just 150) it "makes the new root's right subtree the right child's right subtree" $ do let rbt = mkRedBlackTree [100, 25, 175, 0, 150, 50, 200] let rbt' = rotateLeft rbt (keyOf . rightChild $ rbt') `shouldBe` (Just 200) context "when rotating right" $ do it "preserves the BST ordering property" $ forAll (redBlackTreesWithLeftChild :: Gen (RedBlackTree Int)) $ \rbt -> inorderTraversal (rotateRight rbt) `shouldBe` (inorderTraversal rbt) it "is the inverse of rotateLeft" $ forAll (redBlackTreesWithLeftChild :: Gen (RedBlackTree Int)) $ \rbt -> do (inorderTraversal (rotateLeft . rotateRight $ rbt)) `shouldBe` (inorderTraversal rbt) (preorderTraversal (rotateLeft . rotateRight $ rbt)) `shouldBe` (preorderTraversal rbt) it "makes the left child of a tree the new root" $ do let rbt = mkRedBlackTree [100, 25, 175, 0, 150, 50, 200] let rbt' = rotateRight rbt (keyOf rbt') `shouldBe` (Just 25) it "makes the old root the new root's right subtree" $ do let rbt = mkRedBlackTree [100, 25, 175, 0, 150, 50, 200] let rbt' = rotateRight rbt (keyOf $ rightChild rbt') `shouldBe` (Just 100) it "makes the left child's right subtree the old root's left subtree" $ do let rbt = mkRedBlackTree [100, 25, 175, 0, 150, 50, 200] let rbt' = rotateRight rbt (keyOf . leftChild . rightChild $ rbt') `shouldBe` (Just 50) it "makes the new root's left subtree the left child's left subtree" $ do let rbt = mkRedBlackTree [100, 25, 175, 0, 150, 50, 200] let rbt' = rotateRight rbt (keyOf . leftChild $ rbt') `shouldBe` (Just 0) context "when inserting new elements" $ do it "keeps the root node black" $ property $ \xs -> colourOf (mkRedBlackTree (xs :: [Int])) `shouldBe` Black it "ensures all (sentinel) leaves are black" $ do let leavesAreBlack rbt = case mkEmpty == rbt of True -> colourOf rbt == Black _ -> leavesAreBlack (leftChild rbt) && leavesAreBlack (rightChild rbt) property $ \xs -> leavesAreBlack (mkRedBlackTree (xs :: [Int])) it "ensures that both children of red nodes are black" $ do let bothChildrenAreBlack rbt = colourOf (leftChild rbt) == Black && colourOf (rightChild rbt) == Black let bothChildrenOfRedNodesAreBlack rbt = case mkEmpty == rbt of True -> True _ -> ((colourOf rbt == Black) || (colourOf rbt == Red && bothChildrenAreBlack rbt)) && bothChildrenOfRedNodesAreBlack (leftChild rbt) && bothChildrenOfRedNodesAreBlack (rightChild rbt) property $ \xs -> bothChildrenOfRedNodesAreBlack $ mkRedBlackTree (xs :: [Int]) it "ensures that all paths to leaves have the same number of black nodes" $ do let blackHeightLeft rbt n = case mkEmpty == rbt of True -> n _ -> case (colourOf $ leftChild rbt) of Black -> blackHeightLeft (leftChild rbt) (n + 1) Red -> blackHeightLeft (leftChild rbt) n let blackHeightRight rbt n = case mkEmpty == rbt of True -> n _ -> case (colourOf $ rightChild rbt) of Black -> blackHeightRight (rightChild rbt) (n + 1) Red -> blackHeightRight (rightChild rbt) n let allPathsHaveSameNumberOfBlackNodes rbt = case mkEmpty == rbt of True -> True _ -> blackHeightLeft rbt 0 == blackHeightRight rbt 0 forAll (redBlackTreesWithDistinctKeys :: Gen (RedBlackTree Int)) $ allPathsHaveSameNumberOfBlackNodes it "colours the new node red" $ do let rbt = mkRedBlackTree [1] let rbt' = Data.RedBlackTree.insert rbt 0 let newNode = search rbt' 0 colourOf <$> newNode `shouldBe` (Just Red) it "handles the case in which the new node was inserted as a right child of a left child, and its uncle is red" $ do let rbt = mkRedBlackTree [50, 0, 100] let rbt' = Data.RedBlackTree.insert rbt 25 let newNode = search rbt' 25 let newNodeParent = search rbt' 0 let newNodeGrandparent = search rbt' 50 let newNodeUncle = search rbt' 100 colourOf <$> newNode `shouldBe` (Just Red) colourOf <$> newNodeParent `shouldBe` (Just Black) colourOf <$> newNodeGrandparent `shouldBe` (Just Black) colourOf <$> newNodeUncle `shouldBe` (Just Black) it "handles the case in which the new node was inserted as a left child of a left child, and its uncle is red" $ do let rbt = mkRedBlackTree [100, 50, 200] let rbt' = Data.RedBlackTree.insert rbt 25 let newNode = search rbt' 25 let newNodeParent = search rbt' 50 let newNodeGrandparent = search rbt' 100 let newNodeUncle = search rbt' 200 colourOf <$> newNode `shouldBe` (Just Red) colourOf <$> newNodeParent `shouldBe` (Just Black) colourOf <$> newNodeGrandparent `shouldBe` (Just Black) colourOf <$> newNodeUncle `shouldBe` (Just Black) it "handles the case in which the new node was inserted as a right child of a right child, and its uncle is red" $ do let rbt = mkRedBlackTree [50, 0, 100] let rbt' = Data.RedBlackTree.insert rbt 200 let newNode = search rbt' 200 let newNodeParent = search rbt' 100 let newNodeGrandparent = search rbt' 50 let newNodeUncle = search rbt' 0 colourOf <$> newNode `shouldBe` (Just Red) colourOf <$> newNodeParent `shouldBe` (Just Black) colourOf <$> newNodeGrandparent `shouldBe` (Just Black) colourOf <$> newNodeUncle `shouldBe` (Just Black) it "handles the case in which the new node was inserted as a left child of a right child, and its uncle is red" $ do let rbt = mkRedBlackTree [100, 50, 200] let rbt' = Data.RedBlackTree.insert rbt 150 let newNode = search rbt' 150 let newNodeParent = search rbt' 200 let newNodeGrandparent = search rbt' 100 let newNodeUncle = search rbt' 50 colourOf <$> newNode `shouldBe` (Just Red) colourOf <$> newNodeParent `shouldBe` (Just Black) colourOf <$> newNodeGrandparent `shouldBe` (Just Black) colourOf <$> newNodeUncle `shouldBe` (Just Black) it "handles the case in which the new node was inserted as a left child of a left child, and its uncle is black" $ do let rbt = mkRedBlackTree [200, 50] let rbt' = Data.RedBlackTree.insert rbt 0 let newNode = search rbt' 0 let newNodeParent = search rbt' 50 let newNodeSibling = search rbt' 200 colourOf <$> newNode `shouldBe` (Just Red) colourOf <$> newNodeParent `shouldBe` (Just Black) colourOf <$> newNodeSibling `shouldBe` (Just Red) it "handles the case in which the new node was inserted as a right child of a left child, and its uncle is black" $ do let rbt = mkRedBlackTree [50, 0] let rbt' = Data.RedBlackTree.insert rbt 25 let newNode = search rbt' 25 -- Becomes the root after rebalancing let newNodeLeftChild = search rbt' 0 let newNodeRightChild = search rbt' 50 colourOf <$> newNode `shouldBe` (Just Black) colourOf <$> newNodeLeftChild `shouldBe` (Just Red) colourOf <$> newNodeRightChild `shouldBe` (Just Red) it "handles the case in which the new node was inserted as a left child of a right child, and its uncle is black" $ do let rbt = mkRedBlackTree [200, 500] let rbt' = Data.RedBlackTree.insert rbt 300 let newNode = search rbt' 300 -- Becomes the root after rebalancing let newNodeLeftChild = search rbt' 200 let newNodeRightChild = search rbt' 500 colourOf <$> newNode `shouldBe` (Just Black) colourOf <$> newNodeLeftChild `shouldBe` (Just Red) colourOf <$> newNodeRightChild `shouldBe` (Just Red) it "handles the case in which the new node was inserted as a right child of a right child, and its uncle is black" $ do let rbt = mkRedBlackTree [0, 50] let rbt' = Data.RedBlackTree.insert rbt 100 let newNode = search rbt' 100 let newNodeParent = search rbt' 50 let newNodeSibling = search rbt' 0 colourOf <$> newNode `shouldBe` (Just Red) colourOf <$> newNodeParent `shouldBe` (Just Black) colourOf <$> newNodeSibling `shouldBe` (Just Red)