diff --git a/QuadTree.Benchmark/Main.fs b/QuadTree.Benchmark/Main.fs index 25cfb19..ec4e2b4 100644 --- a/QuadTree.Benchmark/Main.fs +++ b/QuadTree.Benchmark/Main.fs @@ -12,7 +12,11 @@ let main argv = typeof typeof typeof - typeof |] + typeof + typeof + typeof + typeof |] + benchmarks.Run argv |> ignore 0 diff --git a/QuadTree.Benchmark/QuadTree.Benchmark.fsproj b/QuadTree.Benchmark/QuadTree.Benchmark.fsproj index 9fbd475..ba3e0b6 100644 --- a/QuadTree.Benchmark/QuadTree.Benchmark.fsproj +++ b/QuadTree.Benchmark/QuadTree.Benchmark.fsproj @@ -17,6 +17,7 @@ + diff --git a/QuadTree.Benchmark/RedBlackSet.fs b/QuadTree.Benchmark/RedBlackSet.fs new file mode 100644 index 0000000..87cf438 --- /dev/null +++ b/QuadTree.Benchmark/RedBlackSet.fs @@ -0,0 +1,237 @@ +namespace QuadTree.Benchmarks.RedBlackSet + +open BenchmarkDotNet.Attributes +open BenchmarkDotNet.Configs +open QuadTree.RBSet.RBSet +open QuadTree.RBSet +open System.Collections.Generic + +[] +[] +[] +[] +type BatchOpsBenchmark() = + let rnd = System.Random(1234561) + + [] + [] + val mutable public A: int + + [] + [] + val mutable public N: int + + [] + val mutable public data: int[] + + [] + val mutable public toInsert: int[] + + [] + val mutable public existingToDelete: int[] + + [] + val mutable public missingToDelete: int[] + + [] + val mutable public rndInt: int + + [] + val mutable public setA: RBSet + + [] + val mutable public fsSet: Set + + [] + val mutable public initialRB: RBSet + + [] + val mutable public initialFS: Set + + [] + member self.Setup() = + self.data <- Array.init self.A (fun _ -> rnd.Next()) + + self.initialRB <- + self.data + |> Array.fold + (fun (set: RBSet) v -> + match RBSet.add v set with + | Ok nextSet -> nextSet + | Error err -> failwithf "Setup failed: %A" err) + RBSet.empty + + self.initialFS <- self.data |> Array.fold (fun s v -> Set.add v s) Set.empty + + let maxData = if self.data.Length > 0 then Array.max self.data else 0 + let minData = if self.data.Length > 0 then Array.min self.data else 0 + + let insertCount = min self.N self.A + self.toInsert <- Array.init insertCount (fun i -> maxData + 1000 + i) + + let uniqueExisting = + self.data |> Array.distinct |> Array.truncate (min self.N self.A) + + let shuffleRnd = System.Random(42) + let shuffled = Array.copy uniqueExisting + + for i in shuffled.Length - 1 .. -1 .. 1 do + let j = shuffleRnd.Next(i + 1) + let tmp = shuffled.[i] + shuffled.[i] <- shuffled.[j] + shuffled.[j] <- tmp + + self.existingToDelete <- shuffled + + let missingCount = min self.N self.A + self.missingToDelete <- Array.init missingCount (fun i -> minData - 1000 - i) + + self.setA <- self.initialRB + self.fsSet <- self.initialFS + + [] + member self.IterationSetup() = + self.setA <- self.initialRB + self.fsSet <- self.initialFS + + [] + [] + member self.InsertBatchRB() = + self.toInsert + |> Array.fold + (fun s v -> + match RBSet.add v s with + | Ok s' -> s' + | Error _ -> s) + self.setA + + [] + [] + member self.InsertBatchFS() = + self.toInsert |> Array.fold (fun s v -> Set.add v s) self.fsSet + + [] + [] + member self.DeleteExistingBatchRB() = + self.existingToDelete + |> Array.fold + (fun s v -> + match RBSet.delete v s with + | Ok s' -> s' + | Error _ -> s) + self.setA + + [] + [] + member self.DeleteExistingBatchFS() = + self.existingToDelete |> Array.fold (fun s v -> Set.remove v s) self.fsSet + + [] + [] + member self.DeleteMissingBatchRB() = + self.missingToDelete + |> Array.fold + (fun s v -> + match RBSet.delete v s with + | Ok s' -> s' + | Error _ -> s) + self.setA + + [] + [] + member self.DeleteMissingBatchFS() = + self.missingToDelete |> Array.fold (fun s v -> Set.remove v s) self.fsSet + + +[] +[] +[] +[] +type SetsBenchmark() = + let rnd = System.Random(1234561) + + [] + [] + val mutable public A: int + + [] + [] + val mutable public B: int + + [] + val mutable public RedBlackSetA: RBSet + + [] + val mutable public RedBlackSetB: RBSet + + [] + val mutable public SetA: Set + + [] + val mutable public SetB: Set + + [] + member self.Setup() = + let smaller = min self.A self.B + let commonCount = int (float smaller * 0.25) + + let common = Array.init commonCount (fun _ -> rnd.Next()) + + let uniqueACount = self.A - commonCount + let uniqueBCount = self.B - commonCount + + let uniqueA = Array.init uniqueACount (fun _ -> rnd.Next()) + + let uniqueB = Array.init uniqueBCount (fun _ -> rnd.Next()) + + let dataA = Array.append common uniqueA + let dataB = Array.append common uniqueB + + self.RedBlackSetA <- + dataA + |> Array.fold + (fun set v -> + match RBSet.add v set with + | Ok s -> s + | Error e -> failwithf "%A" e) + RBSet.empty + + self.RedBlackSetB <- + dataB + |> Array.fold + (fun set v -> + match RBSet.add v set with + | Ok s -> s + | Error e -> failwithf "%A" e) + RBSet.empty + + self.SetA <- dataA |> Array.fold (fun set v -> Set.add v set) Set.empty + + self.SetB <- dataB |> Array.fold (fun set v -> Set.add v set) Set.empty + + [] + [] + member self.UnionRB() = + RBSet.union self.RedBlackSetA self.RedBlackSetB + + [] + [] + member self.UnionFS() = Set.union self.SetA self.SetB + + [] + [] + member self.IntersectionRB() = + RBSet.intersection self.RedBlackSetA self.RedBlackSetB + + [] + [] + member self.IntersectionFS() = Set.intersect self.SetA self.SetB + + [] + [] + member self.DifferenceRB() = + RBSet.difference self.RedBlackSetA self.RedBlackSetB + + [] + [] + member self.DifferenceFS() = Set.difference self.SetA self.SetB diff --git a/QuadTree.Tests/QuadTree.Tests.fsproj b/QuadTree.Tests/QuadTree.Tests.fsproj index bc76cf2..a8a29ca 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..13478b9 --- /dev/null +++ b/QuadTree.Tests/Tests.RedBlackSet.fs @@ -0,0 +1,494 @@ +module RedBlackSet.Tests + +open System +open QuadTree.RBSet.Tree +open QuadTree.RBSet.RBSet +open QuadTree.RBSet +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 = -1 || rH = -1 || (float (rH + 1) / float (lH + 1) > 2) then + -1 + else if lH > rH then + lH + 1 + else + rH + 1 + +let rec blackChildrenOfRed tree = + match tree with + | Empty -> true + | Node(Red, Node(Red, _, _, _), _, _) + | Node(Red, _, _, Node(Red, _, _, _)) -> false + | Node(_, l, _, r) -> blackChildrenOfRed l && blackChildrenOfRed 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 finalTree = empty |> add 4 |> Result.bind (add 4) + + match finalTree with + | Ok t -> + Assert.True(contains 4 t) + Assert.Equal(1, blHeightInv t) + Assert.NotEqual(-1, heightInv t) + Assert.True(blackChildrenOfRed t) + Assert.Equal(1, numOfElements t 0) + | Error e -> Assert.Fail $"Expect Ok, but get Error: {e}" + +[] +let insertSomeElem () = + let finalTree = + empty + |> add 5 + |> Result.bind (add 9) + |> Result.bind (add -7) + |> Result.bind (add 89) + |> Result.bind (add -27) + |> Result.bind (add 13) + + match finalTree with + | Ok t -> + Assert.True(contains -7 t) + Assert.Equal(2, blHeightInv t) + Assert.NotEqual(-1, heightInv t) + Assert.True(blackChildrenOfRed t) + Assert.Equal(6, numOfElements t 0) + | Error e -> Assert.Fail $"Expect Ok, but get Error: {e}" + +[] +let deleteSomeElem () = + let finalTree = + empty + |> add 5 + |> Result.bind (add 9) + |> Result.bind (add -7) + |> Result.bind (add 89) + |> Result.bind (add -27) + |> Result.bind (add 13) + |> Result.bind (delete 99) + |> Result.bind (delete 13) + + match finalTree with + | Ok t -> + Assert.False(contains 13 t) + Assert.Equal(2, blHeightInv t) + Assert.NotEqual(-1, heightInv t) + Assert.True(blackChildrenOfRed t) + Assert.Equal(5, numOfElements t 0) + | Error e -> Assert.Fail $"Expect Ok, but get Error: {e}" + +[] +let unionSets () = + let finalTree1 = + empty + |> add 5 + |> Result.bind (add 9) + |> Result.bind (add -7) + |> Result.bind (add 89) + |> Result.bind (add -27) + |> Result.bind (add 13) + + let finalTree2 = + empty + |> add 2 + |> Result.bind (add 7) + |> Result.bind (add 21) + |> Result.bind (add 9) + |> Result.bind (add 5) + + match finalTree1, finalTree2 with + | Ok t1, Ok t2 -> + match union t1 t2 with + | Ok tU -> + Assert.NotEqual(-1, heightInv tU) + Assert.True(blackChildrenOfRed tU) + Assert.Equal(9, numOfElements tU 0) + | Error e -> Assert.Fail $"Error in union: {e}" + | _ -> Assert.Fail $"Error in insert" + +[] +let intersectionSets () = + let finalTree1 = + empty + |> add 5 + |> Result.bind (add 9) + |> Result.bind (add -7) + |> Result.bind (add 89) + |> Result.bind (add -27) + |> Result.bind (add 13) + + let finalTree2 = + empty + |> add 2 + |> Result.bind (add 7) + |> Result.bind (add 21) + |> Result.bind (add 9) + |> Result.bind (add 5) + + match finalTree1, finalTree2 with + | Ok t1, Ok t2 -> + match intersection t1 t2 with + | Ok tI -> + Assert.NotEqual(-1, heightInv tI) + Assert.True(blackChildrenOfRed tI) + Assert.Equal(2, numOfElements tI 0) + | Error e -> Assert.Fail $"Error in intersection: {e}" + | _ -> Assert.Fail $"Error in insert" + +[] +let differenceSets () = + let finalTree1 = + empty + |> add 5 + |> Result.bind (add 9) + |> Result.bind (add -7) + |> Result.bind (add 89) + |> Result.bind (add -27) + |> Result.bind (add 13) + + let finalTree2 = + empty + |> add 2 + |> Result.bind (add 7) + |> Result.bind (add 21) + |> Result.bind (add 9) + |> Result.bind (add 5) + + match finalTree1, finalTree2 with + | Ok t1, Ok t2 -> + match difference t1 t2 with + | Ok tD -> + Assert.NotEqual(-1, heightInv tD) + Assert.True(blackChildrenOfRed tD) + Assert.Equal(4, numOfElements tD 0) + | Error e -> Assert.Fail $"Error in difference: {e}" + | _ -> Assert.Fail $"Error in insert" + +[] +let emptySetProperties () = + let t = empty + Assert.False(contains 0 t) + Assert.Equal(0, numOfElements t 0) + Assert.Equal(0, blHeightInv t) + Assert.True(blackChildrenOfRed t) + +[] +let emptyResultOfIntersection () = + let finalTree1 = empty |> add 4 |> Result.bind (add 7) |> Result.bind (add 14) + + let finalTree2 = empty |> add 8 |> Result.bind (add 10) |> Result.bind (add 13) + + match finalTree1, finalTree2 with + | Ok t1, Ok t2 -> + match intersection t1 t2 with + | Ok empty -> Assert.True(true) + | _ -> Assert.Fail $"expected Ok empty" + | _ -> Assert.Fail $"expexcted Ok" + + match finalTree1 with + | Ok t -> + match intersection empty t with + | Ok empty -> Assert.True(true) + | _ -> Assert.Fail $"expected Ok empty" + | _ -> Assert.Fail $"expexcted Ok" + +[] +let emptyResultOfDifference () = + let finalTree1 = empty |> add 4 |> Result.bind (add 7) |> Result.bind (add 14) + + match finalTree1 with + | Ok t -> + match difference empty t with + | Ok empty -> Assert.True(true) + | _ -> Assert.Fail $"expected Ok empty" + | _ -> Assert.Fail $"expexcted Ok" + +[] +let largeSetInsertion () = + let rng = Random() + let randomValues = [ for _ in 1..1000 -> rng.Next(-5000, 5000) ] + + let treeResult = + randomValues |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + match treeResult with + | Ok tree -> + Assert.NotEqual(-1, blHeightInv tree) + Assert.True(blackChildrenOfRed tree) + + for x in randomValues do + Assert.True(contains x tree) + | Error e -> Assert.Fail $"Error in insert: {e}" + +[] +let deleteRoot () = + let finalTree = + empty + |> add 5 + |> Result.bind (add 3) + |> Result.bind (add 7) + |> Result.bind (delete 5) + + match finalTree with + | Ok t -> + Assert.False(contains 5 t) + Assert.True(contains 3 t) + Assert.True(contains 7 t) + Assert.NotEqual(-1, blHeightInv t) + | _ -> Assert.Fail $"Expect Ok, but get Error" + +[] +let complexRedBlackViolations () = + let values = [ 1..20 ] + + let treeResult = + values |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + match treeResult with + | Ok tree -> + Assert.NotEqual(-1, blHeightInv tree) + Assert.True(blackChildrenOfRed tree) + | Error e -> Assert.Fail $"Error in insert: {e}" + +[] +let randomDeletions () = + let rng = Random() + let insertValues = [ for _ in 1..500 -> rng.Next(-5000, 5000) ] + let uniqueInserts = insertValues |> List.distinct + + let treeResult = + insertValues |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + match treeResult with + | Ok tree -> + let deleteValues = uniqueInserts |> List.filter (fun _ -> rng.Next(0, 2) = 0) + + let remaining = uniqueInserts |> List.except deleteValues + + let afterDelete = + deleteValues |> List.fold (fun acc x -> acc |> Result.bind (delete x)) (Ok tree) + + match afterDelete with + | Ok t -> + Assert.NotEqual(-1, blHeightInv t) + Assert.NotEqual(-1, heightInv t) + Assert.True(blackChildrenOfRed t) + + for x in deleteValues do + Assert.False(contains x t, sprintf "Element %d should be deleted" x) + + for x in remaining do + Assert.True(contains x t, sprintf "Element %d should be present" x) + + Assert.Equal(remaining.Length, numOfElements t 0) + | Error e -> Assert.Fail $"Error in delete: {e}" + | Error e -> Assert.Fail $"Error in insert: {e}" + +[] +let randomDeletionsOfMissingElements () = + let rng = Random() + let insertValues = [ for _ in 1..300 -> rng.Next(-3000, 3000) ] + let deleteMissing = [ for _ in 1..300 -> rng.Next(10000, 20000) ] + + let treeResult = + insertValues |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + match treeResult with + | Ok tree -> + let afterDelete = + deleteMissing + |> List.fold (fun acc x -> acc |> Result.bind (delete x)) (Ok tree) + + match afterDelete with + | Ok t -> + Assert.NotEqual(-1, blHeightInv t) + Assert.NotEqual(-1, heightInv t) + Assert.True(blackChildrenOfRed t) + + let expected = insertValues |> List.distinct + + for x in expected do + Assert.True(contains x t, sprintf "Element %d should still be present" x) + + for x in deleteMissing do + Assert.False(contains x t, sprintf "Element %d should not be present" x) + + Assert.Equal(expected.Length, numOfElements t 0) + | Error e -> Assert.Fail $"Error in delete: {e}" + | Error e -> Assert.Fail $"Error in insert: {e}" + +[] +let randomUnion () = + let rng = Random() + let vals1 = [ for _ in 1..300 -> rng.Next(-3000, 3000) ] + let vals2 = [ for _ in 1..300 -> rng.Next(-3000, 3000) ] + + let t1Result = + vals1 |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + let t2Result = + vals2 |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + match t1Result, t2Result with + | Ok t1, Ok t2 -> + match union t1 t2 with + | Ok tU -> + Assert.NotEqual(-1, blHeightInv tU) + Assert.NotEqual(-1, heightInv tU) + Assert.True(blackChildrenOfRed tU) + + let expected = (vals1 @ vals2) |> List.distinct + + for x in expected do + Assert.True(contains x tU, sprintf "Element %d should be in union" x) + + Assert.Equal(expected.Length, numOfElements tU 0) + | Error e -> Assert.Fail $"Error in union: {e}" + | _ -> Assert.Fail $"Error in insert" + +[] +let randomIntersection () = + let rng = Random() + let vals1 = [ for _ in 1..300 -> rng.Next(-3000, 3000) ] + let vals2 = [ for _ in 1..300 -> rng.Next(-3000, 3000) ] + + let t1Result = + vals1 |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + let t2Result = + vals2 |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + match t1Result, t2Result with + | Ok t1, Ok t2 -> + match intersection t1 t2 with + | Ok tI -> + Assert.NotEqual(-1, blHeightInv tI) + Assert.NotEqual(-1, heightInv tI) + Assert.True(blackChildrenOfRed tI) + + let set1 = vals1 |> Set.ofList + let set2 = vals2 |> Set.ofList + let expected = Set.intersect set1 set2 + let allVals = Set.union set1 set2 + let notExpected = Set.difference allVals expected + + for x in expected do + Assert.True(contains x tI, sprintf "Element %d should be in intersection" x) + + for x in notExpected do + Assert.False(contains x tI, sprintf "Element %d should not be in intersection" x) + + Assert.Equal(expected.Count, numOfElements tI 0) + | Error e -> Assert.Fail $"Error in intersection: {e}" + | _ -> Assert.Fail $"Error in insert" + +[] +let randomDifference () = + let rng = Random() + let vals1 = [ for _ in 1..300 -> rng.Next(-3000, 3000) ] + let vals2 = [ for _ in 1..300 -> rng.Next(-3000, 3000) ] + + let t1Result = + vals1 |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + let t2Result = + vals2 |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + + match t1Result, t2Result with + | Ok t1, Ok t2 -> + match difference t1 t2 with + | Ok tD -> + Assert.NotEqual(-1, blHeightInv tD) + Assert.NotEqual(-1, heightInv tD) + Assert.True(blackChildrenOfRed tD) + + let set1 = vals1 |> Set.ofList + let set2 = vals2 |> Set.ofList + let expected = Set.difference set1 set2 + + for x in expected do + Assert.True(contains x tD, sprintf "Element %d should be in difference" x) + + for x in set2 do + Assert.False(contains x tD, sprintf "Element %d should not be in difference" x) + + Assert.Equal(expected.Count, numOfElements tD 0) + | Error e -> Assert.Fail $"Error in difference: {e}" + | _ -> Assert.Fail $"Error in insert" + +[] +let randomMixedOperations () = + let rng = Random() + + let buildRandomSet size = + let values = [ for _ in 1..size -> rng.Next(-5000, 5000) ] + + values + |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) + |> function + | Ok t -> t + | Error e -> failwithf "Insert failed: %A" e + + let t1 = buildRandomSet 400 + let t2 = buildRandomSet 400 + + let combined = + match union t1 t2 with + | Ok u -> u + | Error e -> failwithf "Union failed: %A" e + + let combinedList = + let rec toList tree acc = + match tree with + | Empty -> acc + | Node(_, l, v, r) -> toList l (v :: toList r acc) + + toList combined [] + + let toDelete = combinedList |> List.filter (fun _ -> rng.Next(0, 2) = 0) + + let afterDelete = + toDelete |> List.fold (fun acc x -> acc |> Result.bind (delete x)) (Ok combined) + + match afterDelete with + | Ok final -> + Assert.NotEqual(-1, blHeightInv final) + Assert.NotEqual(-1, heightInv final) + Assert.True(blackChildrenOfRed t1) + Assert.True(blackChildrenOfRed t2) + Assert.True(blackChildrenOfRed final) + + for x in toDelete do + Assert.False(contains x final, sprintf "Deleted element %d found" x) + + let expectedRemaining = combinedList |> List.except toDelete + + for x in expectedRemaining do + Assert.True(contains x final, sprintf "Element %d should be present" x) + + Assert.Equal(expectedRemaining.Length, numOfElements final 0) + | Error e -> Assert.Fail $"Error in mixed ops: {e}" diff --git a/QuadTree/QuadTree.fsproj b/QuadTree/QuadTree.fsproj index abfc6ec..acf35f1 100644 --- a/QuadTree/QuadTree.fsproj +++ b/QuadTree/QuadTree.fsproj @@ -15,6 +15,7 @@ + diff --git a/QuadTree/RedBlackSet.fs b/QuadTree/RedBlackSet.fs new file mode 100644 index 0000000..05fe14c --- /dev/null +++ b/QuadTree/RedBlackSet.fs @@ -0,0 +1,314 @@ +//The following sources were used as a reference: 'Faster, Simpler Red-Black Trees' and Data/Set/RBTree.hs. +namespace QuadTree.RBSet + +open Result + +type RBSetError = EmptyNodeWasNotExpected + +module Tree = + type Color = + | Red + | Black + + type Tree<'T> = + | Empty + | Node of color: Color * left: Tree<'T> * value: 'T * right: Tree<'T> + + type private Condition<'T> = + | Done of 'T + | ToDo of 'T + + let private justTree resultTree = + match resultTree with + | Done t + | ToDo t -> t + + let blackenRoot tree = + match tree with + | Node(Red, a, x, b) -> Node(Black, a, x, b) + | _ -> tree + + let rec private getBlackHeight tree = + match tree with + | Empty -> 0 + | Node(Red, l, _, _) -> getBlackHeight l + | Node(Black, l, _, _) -> 1 + (getBlackHeight 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 |> blackenRoot |> Ok + + let delete tree v = + let blacken tree = + match tree with + | Node(Red, a, x, b) -> Done(Node(Black, a, x, b)) + | _ -> ToDo tree + + 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 = + resultM { + match tree with + | Node(color, a, x, Node(Black, b, y, c)) -> return 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 -> return Done(Node(Black, nl, y, c)) + | ToDo nl -> return ToDo(Node(Black, nl, y, c)) + | _ -> return! Error EmptyNodeWasNotExpected + } + + let rec eqR tree = + resultM { + match tree with + | Node(color, Node(Black, a, x, b), y, c) -> return 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 -> return Done(Node(Black, a, x, nr)) + | ToDo nr -> return ToDo(Node(Black, a, x, nr)) + | _ -> return! Error EmptyNodeWasNotExpected + } + + let delCur tree = + resultM { + let rec delMin tree = + resultM { + match tree with + | Node(Red, Empty, x, b) -> return Done b, x + | Node(Black, Empty, x, b) -> return blacken b, x + | Node(color, a, x, b) -> + let! an, min = delMin a + + match an with + | Done t -> return Done(Node(color, t, x, b)), min + | ToDo t -> + let! t' = eqL (Node(color, t, x, b)) + return t', min + | _ -> return! Error EmptyNodeWasNotExpected + } + + match tree with + | Node(Red, a, y, Empty) -> return Done a + | Node(Black, a, x, Empty) -> return blacken a + | Node(color, a, x, b) -> + let! bn, min = delMin b + + match bn with + | Done t -> return Done(Node(color, a, min, t)) + | ToDo t -> return! eqR (Node(color, a, min, t)) + | _ -> return! Error EmptyNodeWasNotExpected + } + + let rec deleteRec tree v = + resultM { + match tree with + | Empty -> return Done(Empty) + | Node(color, left, value, right) -> + if value > v then + let! newLeft = deleteRec left v + + match newLeft with + | Done nl -> return Done(Node(color, nl, value, right)) + | ToDo nl -> return! eqL (Node(color, nl, value, right)) + elif value < v then + let! newRight = deleteRec right v + + match newRight with + | Done nr -> return Done(Node(color, left, value, nr)) + | ToDo nr -> return! eqR (Node(color, left, value, nr)) + else + return! delCur tree + } + + resultM { + let! t = deleteRec tree v + return t |> justTree |> blackenRoot + } + + let join t1 g t2 = + let rec joinLT t1 g t2 targetHeight currentHeight = + resultM { + if targetHeight = currentHeight then + return Node(Red, t1, g, t2) + else + match t2 with + | Node(Red, l, x, r) -> + let! newLeft = joinLT t1 g l targetHeight currentHeight + return Node(Red, newLeft, x, r) |> balance |> justTree + | Node(Black, l, x, r) -> + let! newLeft = joinLT t1 g l targetHeight (currentHeight - 1) + return Node(Black, newLeft, x, r) |> balance |> justTree + | _ -> return! Error EmptyNodeWasNotExpected + } + + let rec joinRT t1 g t2 targetHeight currentHeight = + resultM { + if targetHeight = currentHeight then + return Node(Red, t1, g, t2) + else + match t1 with + | Node(Red, l, x, r) -> + let! newRight = joinRT r g t2 targetHeight currentHeight + return Node(Red, l, x, newRight) |> balance |> justTree + | Node(Black, l, x, r) -> + let! newRight = joinRT r g t2 targetHeight (currentHeight - 1) + return Node(Black, l, x, newRight) |> balance |> justTree + | _ -> return! Error EmptyNodeWasNotExpected + } + + let h1 = getBlackHeight t1 + let h2 = getBlackHeight t2 + + resultM { + if h1 = 0 then + return! insert t2 g + elif h2 = 0 then + return! insert t1 g + elif h1 < h2 then + let! t = joinLT t1 g t2 h1 h2 + return blackenRoot t + else if h1 > h2 then + let! t = joinRT t1 g t2 h2 h1 + return blackenRoot t + else + return Node(Black, t1, g, t2) + } + + let merge t1 t2 = + resultM { + match t1, t2 with + | Empty, t -> return t + | t, Empty -> return t + | _, _ -> + let rec extractMin tree = + match tree with + | Node(_, Empty, x, _) -> x + | Node(_, l, _, _) -> extractMin l + | Empty -> failwith "extractMin: empty tree" + + let minVal = extractMin t2 + let! t2Rest = delete t2 minVal + return! join (blackenRoot t1) minVal (blackenRoot t2Rest) + } + + let rec split kx tree = + resultM { + match tree with + | Empty -> return Empty, Empty + | Node(_, l, x, r) -> + if kx < x then + let! lt, gt = split kx l + let! t = join gt x (blackenRoot r) + return lt, t + else if kx > x then + let! lt, gt = split kx r + let! t = join (blackenRoot l) x lt + return t, gt + else + return blackenRoot l, blackenRoot r + } + +module RBSet = + open Tree + + type RBSet<'T> = Tree<'T> + let empty = Empty + + let add value set = Tree.insert set value + + let delete value set = Tree.delete set value + + let contains value set = Tree.contains set value + + let rec union set1 set2 = + resultM { + match set1 with + | Empty -> return blackenRoot set2 + | _ -> + match set2 with + | Empty -> return blackenRoot set1 + | Node(_, l, x, r) -> + let! l', r' = split x set1 + let! tl = union l' l + let! tr = union r' r + return! join (blackenRoot tl) x (blackenRoot tr) + } + + let rec intersection set1 set2 = + resultM { + match set1 with + | Empty -> return Empty + | _ -> + match set2 with + | Empty -> return Empty + | Node(_, l, x, r) -> + let! l', r' = split x set1 + let! tl = intersection l' l + let! tr = intersection r' r + + if Tree.contains set1 x then + return! join (blackenRoot tl) x (blackenRoot tr) + else + return! merge (blackenRoot tl) (blackenRoot tr) + } + + let rec difference set1 set2 = + resultM { + match set1 with + | Empty -> return Empty + | _ -> + match set2 with + | Empty -> return blackenRoot set1 + | Node(_, l, x, r) -> + let! l', r' = split x set1 + let! tl = difference l' l + let! tr = difference r' r + return! merge (blackenRoot tl) (blackenRoot tr) + }