diff --git a/QuadTree.Tests/QuadTree.Tests.fsproj b/QuadTree.Tests/QuadTree.Tests.fsproj index 4de3d64..bd0f0b4 100644 --- a/QuadTree.Tests/QuadTree.Tests.fsproj +++ b/QuadTree.Tests/QuadTree.Tests.fsproj @@ -14,6 +14,7 @@ + diff --git a/QuadTree.Tests/Tests.RedBlackSet.fs b/QuadTree.Tests/Tests.RedBlackSet.fs new file mode 100644 index 0000000..ad6d4de --- /dev/null +++ b/QuadTree.Tests/Tests.RedBlackSet.fs @@ -0,0 +1,196 @@ +module RedBlackSet.Tests + +open System +open RedBlackSet +open Xunit + +let rec blHeightInv tree = + match tree with + | Empty -> 0 + | Node(color, l, _, r) -> + let lH = blHeightInv l + let rH = blHeightInv r + + if lH = -1 || rH = -1 || lH <> rH then -1 + else if color = Red then lH + else lH + 1 + +let rec heightInv tree = + match tree with + | Empty -> 0 + | Node(_, l, _, r) -> + let lH = heightInv l + let rH = heightInv r + + if lH > rH then + if lH = -1 || rH = -1 || (float (lH + 1) / float (rH + 1) > 2) then + -1 + else + lH + else if lH = -1 || rH = -1 || (float (rH + 1) / float (lH + 1) > 2) then + -1 + else + rH + +let rec blackSonsOfRed tree = + match tree with + | Empty -> true + | Node(Red, Node(Red, _, _, _), _, _) -> false + | Node(Red, _, _, Node(Red, _, _, _)) -> false + | Node(_, l, _, r) -> blackSonsOfRed l && blackSonsOfRed r + +let rec numOfElements tree num = + match tree with + | Empty -> 0 + | Node(_, l, _, r) -> + let lN = numOfElements l num + let rN = numOfElements r num + lN + rN + 1 + +[] +let oneElement () = + let t1 = emptySet + let t2 = insert t1 4 + let t3 = insert t2 4 + Assert.True(contains t3 4) + Assert.Equal(1, blHeightInv t3) + Assert.NotEqual(-1, heightInv t3) + Assert.True(blackSonsOfRed t3) + Assert.Equal(1, numOfElements t3 0) + +[] +let insertSomeElem () = + let t1 = emptySet + let t2 = insert t1 5 + let t3 = insert t2 9 + let t4 = insert t3 -7 + let t5 = insert t4 89 + let t6 = insert t5 -27 + let t7 = insert t6 13 + Assert.True(contains t7 -7) + Assert.Equal(2, blHeightInv t7) + Assert.NotEqual(-1, heightInv t7) + Assert.True(blackSonsOfRed t7) + Assert.Equal(6, numOfElements t7 0) + +[] +let deleteSomeElem () = + let t1 = emptySet + let t2 = insert t1 5 + let t3 = insert t2 9 + let t4 = insert t3 -7 + let t5 = insert t4 89 + let t6 = insert t5 -27 + let t7 = insert t6 13 + let t8 = delete t7 99 + let t9 = delete t8 13 + Assert.False(contains t9 13) + Assert.Equal(2, blHeightInv t9) + Assert.NotEqual(-1, heightInv t9) + Assert.True(blackSonsOfRed t9) + Assert.Equal(5, numOfElements t9 0) + +[] +let unionSets () = + let t1 = emptySet + let t2 = insert t1 5 + let t3 = insert t2 9 + let t4 = insert t3 -7 + let t5 = insert t4 89 + let t6 = insert t5 -27 + let t7 = insert t6 13 + + let t1' = emptySet + let t2' = insert t1' 2 + let t3' = insert t2' 7 + let t4' = insert t3' 21 + let t5' = insert t4' 9 + let t6' = insert t5' 5 + + let tU = union t7 t6' + Assert.NotEqual(-1, heightInv tU) + Assert.True(blackSonsOfRed tU) + Assert.Equal(9, numOfElements tU 0) + +[] +let intersectionSets () = + let t1 = emptySet + let t2 = insert t1 5 + let t3 = insert t2 9 + let t4 = insert t3 -7 + let t5 = insert t4 89 + let t6 = insert t5 -27 + let t7 = insert t6 13 + + let t1' = emptySet + let t2' = insert t1' 2 + let t3' = insert t2' 7 + let t4' = insert t3' 21 + let t5' = insert t4' 9 + let t6' = insert t5' 5 + + let tI = intersection t7 t6' + Assert.NotEqual(-1, heightInv tI) + Assert.True(blackSonsOfRed tI) + Assert.Equal(2, numOfElements tI 0) + +[] +let differenceSets () = + let t1 = emptySet + let t2 = insert t1 5 + let t3 = insert t2 9 + let t4 = insert t3 -7 + let t5 = insert t4 89 + let t6 = insert t5 -27 + let t7 = insert t6 13 + + let t1' = emptySet + let t2' = insert t1' 2 + let t3' = insert t2' 7 + let t4' = insert t3' 21 + let t5' = insert t4' 9 + let t6' = insert t5' 5 + + let tD = difference t7 t6' + Assert.NotEqual(-1, heightInv tD) + Assert.True(blackSonsOfRed tD) + Assert.Equal(4, numOfElements tD 0) + +[] +let emptySetProperties () = + let t = emptySet + Assert.False(contains t 0) + Assert.Equal(0, numOfElements t 0) + Assert.Equal(0, blHeightInv t) + Assert.True(blackSonsOfRed t) + +[] +let largeSetInsertion () = + let randomValues = [ for i in 1..1000 -> Random().Next(-10000, 10000) ] + let tree = Seq.fold (fun acc x -> insert acc x) emptySet randomValues + + Assert.NotEqual(-1, blHeightInv tree) + Assert.True(blackSonsOfRed tree) + + for x in randomValues do + Assert.True(contains tree x) + +[] +let deleteRoot () = + let t1 = insert emptySet 5 + let t2 = insert t1 3 + let t3 = insert t2 7 + let t4 = delete t3 5 + + Assert.False(contains t4 5) + Assert.True(contains t4 3) + Assert.True(contains t4 7) + Assert.NotEqual(-1, blHeightInv t4) + +[] +let complexRedBlackViolations () = + let values = [ 1..20 ] + let tree = Seq.fold (fun acc x -> insert acc x) emptySet values + + Assert.NotEqual(-1, blHeightInv tree) + Assert.True(blackSonsOfRed tree) diff --git a/QuadTree/QuadTree.fsproj b/QuadTree/QuadTree.fsproj index 438678c..c24b94c 100644 --- a/QuadTree/QuadTree.fsproj +++ b/QuadTree/QuadTree.fsproj @@ -14,6 +14,7 @@ + diff --git a/QuadTree/RedBlackSet.fs b/QuadTree/RedBlackSet.fs new file mode 100644 index 0000000..78d1518 --- /dev/null +++ b/QuadTree/RedBlackSet.fs @@ -0,0 +1,316 @@ +//The following sources were used as a reference: 'Faster, Simpler Red-Black Trees' and Data/Set/RBTree.hs. +module RedBlackSet + +type Color = + | Red + | Black + +type Tree<'T> = + | Empty + | Node of color: Color * left: Tree<'T> * value: 'T * right: Tree<'T> + +let emptySet = Empty + +type private Result<'T> = + | Done of 'T + | ToDo of 'T + +let private blacken tree = + match tree with + | Node(Red, a, x, b) -> Done(Node(Black, a, x, b)) + | _ -> ToDo tree + +let private justTree resultTree = + match resultTree with + | Done t -> t + | ToDo t -> t + +let rec private blackHeight tree = + match tree with + | Empty -> 0 + | Node(Red, l, _, _) -> blackHeight l + | Node(Black, l, _, _) -> 1 + (blackHeight l) + + +let rec contains tree v = + match tree with + | Empty -> false + | Node(_, left, value, right) -> + if value > v then contains left v + elif value < v then contains right v + else true + +let private balance tree = + match tree with + | Node(Black, Node(Red, Node(Red, a, x, b), y, c), z, d) + | Node(Black, Node(Red, a, x, Node(Red, b, y, c)), z, d) + | Node(Black, a, x, Node(Red, Node(Red, b, y, c), z, d)) + | Node(Black, a, x, Node(Red, b, y, Node(Red, c, z, d))) -> + ToDo(Node(Red, Node(Black, a, x, b), y, Node(Black, c, z, d))) + | Node(Black, a, x, b) as n -> Done(n) + | _ -> ToDo(tree) + +let insert tree v = + + let rec insertRec tree v = + match tree with + | Empty -> ToDo(Node(Red, Empty, v, Empty)) + | Node(color, left, value, right) -> + if value > v then + let newLeft = insertRec left v + + match newLeft with + | Done nl -> Done(Node(color, nl, value, right)) + | ToDo nl -> balance (Node(color, nl, value, right)) + elif value < v then + let newRight = insertRec right v + + match newRight with + | Done nr -> Done(Node(color, left, value, nr)) + | ToDo nr -> balance (Node(color, left, value, nr)) + else + Done(tree) + + let newTree = insertRec tree v + newTree |> justTree |> blacken |> justTree + +let delete tree v = + + let balanceDel tree = + match tree with + | Node(color, Node(Red, Node(Red, a, x, b), y, c), z, d) + | Node(color, Node(Red, a, x, Node(Red, b, y, c)), z, d) + | Node(color, a, x, Node(Red, Node(Red, b, y, c), z, d)) + | Node(color, a, x, Node(Red, b, y, Node(Red, c, z, d))) -> + Done(Node(color, Node(Black, a, x, b), y, Node(Black, c, z, d))) + | _ -> blacken tree + + let rec eqL tree = + match tree with + | Node(color, a, x, Node(Black, b, y, c)) -> balanceDel (Node(color, a, x, Node(Red, b, y, c))) + | Node(color, a, x, Node(Red, b, y, c)) -> + let newLeft = eqL (Node(Red, a, x, b)) + + match newLeft with + | Done nl -> Done(Node(Black, nl, y, c)) + | ToDo nl -> ToDo(Node(Black, nl, y, c)) + | _ -> failwith "Impossible pattern" + + let rec eqR tree = + match tree with + | Node(color, Node(Black, a, x, b), y, c) -> balanceDel (Node(color, Node(Red, a, x, b), y, c)) + | Node(color, Node(Red, a, x, b), y, c) -> + let newRight = eqR (Node(Red, b, y, c)) + + match newRight with + | Done nr -> Done(Node(Black, a, x, nr)) + | ToDo nr -> ToDo(Node(Black, a, x, nr)) + | _ -> failwith "Impossible pattern" + + let delCur tree = + + let rec delMin tree = + match tree with + | Node(Red, Empty, x, b) -> (Done b, x) + | Node(Black, Empty, x, b) -> (blacken b, x) + | Node(color, a, x, b) -> + let (an, min) = delMin a + + match an with + | Done t -> (Done(Node(color, t, x, b)), min) + | ToDo t -> (eqL (Node(color, t, x, b)), min) + | _ -> failwith "Impossible pattern" + + match tree with + | Node(Red, a, y, Empty) -> Done a + | Node(Black, a, x, Empty) -> blacken a + | Node(color, a, x, b) -> + let (bn, min) = delMin b + + match bn with + | Done t -> Done(Node(color, a, min, t)) + | ToDo t -> eqR (Node(color, a, min, t)) + | _ -> failwith "Impossible pattern" + + let rec deleteRec tree v = + match tree with + | Empty -> Done(Empty) + | Node(color, left, value, right) -> + if value > v then + let newLeft = deleteRec left v + + match newLeft with + | Done nl -> Done(Node(color, nl, value, right)) + | ToDo nl -> eqL (Node(color, nl, value, right)) + elif value < v then + let newRight = deleteRec right v + + match newRight with + | Done nr -> Done(Node(color, left, value, nr)) + | ToDo nr -> eqR (Node(color, left, value, nr)) + else + delCur tree + + let newTree = deleteRec tree v + newTree |> justTree |> blacken |> justTree + +let private join t1 g t2 = + + let rec joinLT t1 g t2 targetHeight currentHeight = + if targetHeight = currentHeight then + Node(Red, t1, g, t2) + else + match t2 with + | Node(Red, l, x, r) -> + let newLeft = joinLT t1 g l targetHeight currentHeight + Node(Red, newLeft, x, r) |> balance |> justTree + | Node(Black, l, x, r) -> + let newLeft = joinLT t1 g l targetHeight (currentHeight - 1) + Node(Black, newLeft, x, r) |> balance |> justTree + | _ -> failwith "Impossible pattern" + + let rec joinRT t1 g t2 targetHeight currentHeight = + if targetHeight = currentHeight then + Node(Red, t1, g, t2) + else + match t1 with + | Node(Red, l, x, r) -> + let newRight = joinRT t2 g r targetHeight currentHeight + Node(Red, l, x, newRight) |> balance |> justTree + | Node(Black, l, x, r) -> + let newRight = joinRT t2 g r targetHeight (currentHeight - 1) + Node(Black, l, x, newRight) |> balance |> justTree + | _ -> failwith "Impossible pattern" + + let h1 = blackHeight t1 + let h2 = blackHeight t2 + + if h1 = 0 then + insert t2 g + else if h2 = 0 then + insert t1 g + else if h1 < h2 then + let t = joinLT t1 g t2 h1 h2 + + t |> blacken |> justTree + else if h1 > h2 then + let t = joinRT t1 g t2 h2 h1 + + t |> blacken |> justTree + else + Node(Black, t1, g, t2) + +let private merge t1 t2 = + + let rec minimum tree = + match tree with + | Node(_, Empty, x, _) -> x + | Node(_, l, _, _) -> minimum l + | _ -> failwith "Impossible pattern" + + let mergeEQ t1 t2 = + let m = minimum t2 + let t2' = delete t2 m + let h2' = blackHeight t2' + let h1 = blackHeight t1 + + if h1 = h2' then + Node(Red, t1, m, t2') + else + match t1 with + | Node(_, Node(Red, ll, lx, lr), x, r) -> Node(Red, Node(Black, ll, lx, lr), x, Node(Black, r, m, t2')) + | Node(_, l, x, Node(Red, rl, rx, rr)) -> Node(Black, Node(Red, l, x, rl), rx, Node(Red, rr, m, t2')) + | _ -> Node(Black, (justTree (blacken t1)), m, t2') + + let rec mergeLT t1 t2 targetHeight currentHeight = + if targetHeight = currentHeight then + mergeEQ t1 t2 + else + match t2 with + | Node(Red, l, x, r) -> + let newLeft = mergeLT t1 l targetHeight currentHeight + Node(Red, newLeft, x, r) |> balance |> justTree + | Node(Black, l, x, r) -> + let newLeft = mergeLT t1 l targetHeight (currentHeight - 1) + Node(Red, newLeft, x, r) |> balance |> justTree + | _ -> failwith "Impossible pattern" + + let rec mergeRT t1 t2 targetHeight currentHeight = + if targetHeight = currentHeight then + mergeEQ t1 t2 + else + match t1 with + | Node(Red, l, x, r) -> + let newRight = mergeRT r t2 targetHeight currentHeight + Node(Red, l, x, newRight) |> balance |> justTree + | Node(Black, l, x, r) -> + let newRight = mergeRT r t2 targetHeight (currentHeight - 1) + Node(Red, l, x, newRight) |> balance |> justTree + | _ -> failwith "Impossible pattern" + + let h1 = blackHeight t1 + let h2 = blackHeight t2 + + if h1 = 0 then + t2 + else if h2 = 0 then + t1 + else if h1 < h2 then + let t = mergeLT t1 t2 h1 h2 + + t |> blacken |> justTree + else if h1 > h2 then + let t = mergeRT t1 t2 h2 h1 + + t |> blacken |> justTree + else + let t = mergeEQ t1 t2 + t |> blacken |> justTree + +let rec private split kx tree = + match tree with + | Empty -> (Empty, Empty) + | Node(_, l, x, r) -> + if kx < x then + let (lt, gt) = split kx l + (lt, join gt x (justTree (blacken r))) + else if kx > x then + let (lt, gt) = split kx r + (join (justTree (blacken l)) x lt, gt) + else + (justTree (blacken (l)), justTree (blacken (r))) + + +let rec union t1 t2 = + match t1 with + | Empty -> t2 + | _ -> + match t2 with + | Empty -> t1 + | Node(_, l, x, r) -> + let (l', r') = split x t1 + join (union l' l) x (union r' r) + +let rec intersection t1 t2 = + match t1 with + | Empty -> Empty + | _ -> + match t2 with + | Empty -> Empty + | Node(_, l, x, r) -> + let (l', r') = split x t1 + + if contains t1 x then + join (intersection l' l) x (intersection r' r) + else + merge (intersection l' l) (intersection r' r) + +let rec difference t1 t2 = + match t1 with + | Empty -> Empty + | _ -> + match t2 with + | Empty -> t1 + | Node(_, l, x, r) -> + let (l', r') = split x t1 + merge (difference l' l) (difference r' r)