From 9949b7a2e5467852e63b50a5249e49febd2d31d6 Mon Sep 17 00:00:00 2001 From: Egor Starikov Date: Thu, 30 Apr 2026 19:11:51 +0300 Subject: [PATCH 1/8] set on red-black tree --- QuadTree.Tests/QuadTree.Tests.fsproj | 1 + QuadTree.Tests/Tests.RedBlackSet.fs | 199 ++++++++++++++++ QuadTree/QuadTree.fsproj | 1 + QuadTree/RedBlackSet.fs | 330 +++++++++++++++++++++++++++ 4 files changed, 531 insertions(+) create mode 100644 QuadTree.Tests/Tests.RedBlackSet.fs create mode 100644 QuadTree/RedBlackSet.fs 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..ea3a79f --- /dev/null +++ b/QuadTree.Tests/Tests.RedBlackSet.fs @@ -0,0 +1,199 @@ +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..1d4ce51 --- /dev/null +++ b/QuadTree/RedBlackSet.fs @@ -0,0 +1,330 @@ +//в качестве референса использовались "Faster, Simpler Red-Black Trees" и 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 + +//join +let 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) + +//merge +let 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 + +//split +let rec 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) From bd58255d92cc65c4a0952e5d05be43c830828bef Mon Sep 17 00:00:00 2001 From: Egor Starikov Date: Thu, 30 Apr 2026 19:32:27 +0300 Subject: [PATCH 2/8] formatted Tests.RedBlackSet.fs --- QuadTree.Tests/Tests.RedBlackSet.fs | 87 ++++++++++++++--------------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/QuadTree.Tests/Tests.RedBlackSet.fs b/QuadTree.Tests/Tests.RedBlackSet.fs index ea3a79f..ad6d4de 100644 --- a/QuadTree.Tests/Tests.RedBlackSet.fs +++ b/QuadTree.Tests/Tests.RedBlackSet.fs @@ -4,54 +4,51 @@ open System open RedBlackSet open Xunit -let rec blHeightInv tree = - match tree with +let rec blHeightInv tree = + match tree with | Empty -> 0 - | Node(color, l, _, r) -> - let lH = blHeightInv l + | 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 + + 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 + match tree with | Empty -> 0 - | Node(_, l, _, r) -> + | 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 + + if lH > rH then + if lH = -1 || rH = -1 || (float (lH + 1) / float (rH + 1) > 2) then -1 - else + else lH - else - if lH = -1 || rH = -1 || (float (rH + 1) / float (lH + 1) > 2) then - -1 - else - rH + else if lH = -1 || rH = -1 || (float (rH + 1) / float (lH + 1) > 2) then + -1 + else + rH let rec blackSonsOfRed tree = - match tree with + match tree with | Empty -> true - | Node(Red, Node(Red, _,_ , _) , _, _) -> false + | Node(Red, Node(Red, _, _, _), _, _) -> false | Node(Red, _, _, Node(Red, _, _, _)) -> false - | Node(_, l, _, r) -> - blackSonsOfRed l && blackSonsOfRed r + | Node(_, l, _, r) -> blackSonsOfRed l && blackSonsOfRed r -let rec numOfElements tree num = +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 + lN + rN + 1 [] -let oneElement() = +let oneElement () = let t1 = emptySet let t2 = insert t1 4 let t3 = insert t2 4 @@ -62,7 +59,7 @@ let oneElement() = Assert.Equal(1, numOfElements t3 0) [] -let insertSomeElem() = +let insertSomeElem () = let t1 = emptySet let t2 = insert t1 5 let t3 = insert t2 9 @@ -77,7 +74,7 @@ let insertSomeElem() = Assert.Equal(6, numOfElements t7 0) [] -let deleteSomeElem() = +let deleteSomeElem () = let t1 = emptySet let t2 = insert t1 5 let t3 = insert t2 9 @@ -94,7 +91,7 @@ let deleteSomeElem() = Assert.Equal(5, numOfElements t9 0) [] -let unionSets() = +let unionSets () = let t1 = emptySet let t2 = insert t1 5 let t3 = insert t2 9 @@ -110,13 +107,13 @@ let unionSets() = let t5' = insert t4' 9 let t6' = insert t5' 5 - let tU = union t7 t6' + let tU = union t7 t6' Assert.NotEqual(-1, heightInv tU) Assert.True(blackSonsOfRed tU) Assert.Equal(9, numOfElements tU 0) [] -let intersectionSets() = +let intersectionSets () = let t1 = emptySet let t2 = insert t1 5 let t3 = insert t2 9 @@ -132,13 +129,13 @@ let intersectionSets() = let t5' = insert t4' 9 let t6' = insert t5' 5 - let tI = intersection t7 t6' + let tI = intersection t7 t6' Assert.NotEqual(-1, heightInv tI) Assert.True(blackSonsOfRed tI) Assert.Equal(2, numOfElements tI 0) [] -let differenceSets() = +let differenceSets () = let t1 = emptySet let t2 = insert t1 5 let t3 = insert t2 9 @@ -154,13 +151,13 @@ let differenceSets() = let t5' = insert t4' 9 let t6' = insert t5' 5 - let tD = difference t7 t6' + let tD = difference t7 t6' Assert.NotEqual(-1, heightInv tD) Assert.True(blackSonsOfRed tD) Assert.Equal(4, numOfElements tD 0) [] -let emptySetProperties() = +let emptySetProperties () = let t = emptySet Assert.False(contains t 0) Assert.Equal(0, numOfElements t 0) @@ -168,32 +165,32 @@ let emptySetProperties() = Assert.True(blackSonsOfRed t) [] -let largeSetInsertion() = - let randomValues = [for i in 1..1000 -> Random().Next(-10000, 10000)] +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 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 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) From 4484c25387019424f709d844bad2fa0dbf7022d4 Mon Sep 17 00:00:00 2001 From: Egor Starikov Date: Fri, 1 May 2026 13:02:21 +0300 Subject: [PATCH 3/8] add private merge, split, join --- QuadTree/RedBlackSet.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/QuadTree/RedBlackSet.fs b/QuadTree/RedBlackSet.fs index 1d4ce51..7d61b4c 100644 --- a/QuadTree/RedBlackSet.fs +++ b/QuadTree/RedBlackSet.fs @@ -163,7 +163,7 @@ let delete tree v = newTree |> justTree |> blacken |> justTree //join -let join t1 g t2 = +let private join t1 g t2 = let rec joinLT t1 g t2 targetHeight currentHeight = if targetHeight = currentHeight then @@ -210,7 +210,7 @@ let join t1 g t2 = Node(Black, t1, g, t2) //merge -let merge t1 t2 = +let private merge t1 t2 = let rec minimum tree = match tree with @@ -278,7 +278,7 @@ let merge t1 t2 = t |> blacken |> justTree //split -let rec split kx tree = +let rec private split kx tree = match tree with | Empty -> (Empty, Empty) | Node(_, l, x, r) -> From 659ddc2120fe630550f4ff4fcb24ab1db0f802fe Mon Sep 17 00:00:00 2001 From: Egor Starikov Date: Tue, 5 May 2026 21:14:07 +0300 Subject: [PATCH 4/8] fix comments --- QuadTree/RedBlackSet.fs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/QuadTree/RedBlackSet.fs b/QuadTree/RedBlackSet.fs index 7d61b4c..78d1518 100644 --- a/QuadTree/RedBlackSet.fs +++ b/QuadTree/RedBlackSet.fs @@ -1,4 +1,4 @@ -//в качестве референса использовались "Faster, Simpler Red-Black Trees" и Data/Set/RBTree.hs +//The following sources were used as a reference: 'Faster, Simpler Red-Black Trees' and Data/Set/RBTree.hs. module RedBlackSet type Color = @@ -11,24 +11,20 @@ type 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 @@ -36,7 +32,6 @@ let rec private blackHeight tree = | Node(Black, l, _, _) -> 1 + (blackHeight l) -//проверка на наличие let rec contains tree v = match tree with | Empty -> false @@ -45,7 +40,6 @@ let rec contains tree 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) @@ -56,7 +50,6 @@ let private balance tree = | Node(Black, a, x, b) as n -> Done(n) | _ -> ToDo(tree) -//вставка let insert tree v = let rec insertRec tree v = @@ -81,7 +74,6 @@ let insert tree v = let newTree = insertRec tree v newTree |> justTree |> blacken |> justTree -//удаление let delete tree v = let balanceDel tree = @@ -162,7 +154,6 @@ let delete tree v = let newTree = deleteRec tree v newTree |> justTree |> blacken |> justTree -//join let private join t1 g t2 = let rec joinLT t1 g t2 targetHeight currentHeight = @@ -209,7 +200,6 @@ let private join t1 g t2 = else Node(Black, t1, g, t2) -//merge let private merge t1 t2 = let rec minimum tree = @@ -277,7 +267,6 @@ let private merge t1 t2 = let t = mergeEQ t1 t2 t |> blacken |> justTree -//split let rec private split kx tree = match tree with | Empty -> (Empty, Empty) @@ -292,7 +281,6 @@ let rec private split kx tree = (justTree (blacken (l)), justTree (blacken (r))) -//объединение let rec union t1 t2 = match t1 with | Empty -> t2 @@ -303,7 +291,6 @@ let rec union t1 t2 = let (l', r') = split x t1 join (union l' l) x (union r' r) -//пересечение let rec intersection t1 t2 = match t1 with | Empty -> Empty @@ -318,7 +305,6 @@ let rec intersection t1 t2 = else merge (intersection l' l) (intersection r' r) -//разность let rec difference t1 t2 = match t1 with | Empty -> Empty From 0c1342f6c6084d1f05bf30b5398bea49ca5075d2 Mon Sep 17 00:00:00 2001 From: Egor Starikov Date: Tue, 5 May 2026 21:22:47 +0300 Subject: [PATCH 5/8] Trigger PR creation after close From fa9b6da525435304b07c3da482fec474ed2b4bc7 Mon Sep 17 00:00:00 2001 From: Egor Starikov Date: Wed, 16 Sep 2026 20:08:21 +0300 Subject: [PATCH 6/8] refactor: use Result --- QuadTree.Tests/Tests.RedBlackSet.fs | 285 +++++++------ QuadTree/RedBlackSet.fs | 630 +++++++++++++++------------- 2 files changed, 508 insertions(+), 407 deletions(-) diff --git a/QuadTree.Tests/Tests.RedBlackSet.fs b/QuadTree.Tests/Tests.RedBlackSet.fs index ad6d4de..0244714 100644 --- a/QuadTree.Tests/Tests.RedBlackSet.fs +++ b/QuadTree.Tests/Tests.RedBlackSet.fs @@ -1,7 +1,8 @@ module RedBlackSet.Tests open System -open RedBlackSet +open RBSet.RBSet +open RBSet open Xunit let rec blHeightInv tree = @@ -22,20 +23,17 @@ let rec heightInv tree = 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 + if lH = -1 || rH = -1 || (float (rH + 1) / float (lH + 1) > 2) then -1 + else if lH > rH then + lH else rH let rec blackSonsOfRed tree = match tree with | Empty -> true - | Node(Red, Node(Red, _, _, _), _, _) -> false + | Node(Red, Node(Red, _, _, _), _, _) | Node(Red, _, _, Node(Red, _, _, _)) -> false | Node(_, l, _, r) -> blackSonsOfRed l && blackSonsOfRed r @@ -49,148 +47,197 @@ let rec numOfElements tree num = [] 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 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(blackSonsOfRed t) + Assert.Equal(1, numOfElements t 0) + | Error e -> Assert.True(false, sprintf "Expect Ok, but get Error: %A" e) [] 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 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(blackSonsOfRed t) + Assert.Equal(6, numOfElements t 0) + | Error e -> Assert.True(false, sprintf "Expect Ok, but get Error: %A" e) [] 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 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(blackSonsOfRed t) + Assert.Equal(5, numOfElements t 0) + | Error e -> Assert.True(false, sprintf "Expect Ok, but get Error: %A" e) [] 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 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(blackSonsOfRed tU) + Assert.Equal(9, numOfElements tU 0) + | Error e -> Assert.True(false, sprintf "Error in union: %A" e) + | _ -> Assert.True(false, sprintf "Error in insert") [] 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 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(blackSonsOfRed tI) + Assert.Equal(2, numOfElements tI 0) + | Error e -> Assert.True(false, sprintf "Error in intersection: %A" e) + | _ -> Assert.True(false, sprintf "Error in insert") [] 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 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(blackSonsOfRed tD) + Assert.Equal(4, numOfElements tD 0) + | Error e -> Assert.True(false, sprintf "Error in difference: %A" e) + | _ -> Assert.True(false, sprintf "Error in insert") [] let emptySetProperties () = - let t = emptySet - Assert.False(contains t 0) + let t = empty + Assert.False(contains 0 t) 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 + let rng = Random() + let randomValues = [ for _ in 1..1000 -> rng.Next(-10000, 10000) ] + + let treeResult = + randomValues |> List.fold (fun acc x -> acc |> Result.bind (add x)) (Ok empty) - Assert.NotEqual(-1, blHeightInv tree) - Assert.True(blackSonsOfRed tree) + match treeResult with + | Ok tree -> + Assert.NotEqual(-1, blHeightInv tree) + Assert.True(blackSonsOfRed tree) - for x in randomValues do - Assert.True(contains tree x) + for x in randomValues do + Assert.True(contains x tree) + | Error err -> Assert.True(false, sprintf "Error in insert: %A" err) [] 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 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.True(false, sprintf "Expect Ok, but get Error") [] 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) + 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(blackSonsOfRed tree) + | Error err -> Assert.True(false, sprintf "Error in insert: %A" err) diff --git a/QuadTree/RedBlackSet.fs b/QuadTree/RedBlackSet.fs index 78d1518..673eea3 100644 --- a/QuadTree/RedBlackSet.fs +++ b/QuadTree/RedBlackSet.fs @@ -1,5 +1,9 @@ //The following sources were used as a reference: 'Faster, Simpler Red-Black Trees' and Data/Set/RBTree.hs. -module RedBlackSet +namespace RBSet + +open Result + +type RBSetError = EmptyNodeWasNotExpected type Color = | Red @@ -9,308 +13,358 @@ 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) +module private Tree = + type private Condition<'T> = + | Done of 'T + | ToDo of 'T - let newTree = insertRec tree v - newTree |> justTree |> blacken |> justTree + let private blacken tree = + match tree with + | Node(Red, a, x, b) -> Done(Node(Black, a, x, b)) + | _ -> ToDo tree -let delete tree v = + let private justTree resultTree = + match resultTree with + | Done t + | ToDo t -> t - 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 = + let rec private blackHeight 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)) + | Empty -> 0 + | Node(Red, l, _, _) -> blackHeight l + | Node(Black, l, _, _) -> 1 + (blackHeight l) - 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 = + let rec contains tree v = 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)) + | Empty -> false + | Node(_, left, value, right) -> + if value > v then contains left v + elif value < v then contains right v + else true - match newRight with - | Done nr -> Done(Node(Black, a, x, nr)) - | ToDo nr -> ToDo(Node(Black, a, x, nr)) - | _ -> failwith "Impossible pattern" + 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 delCur tree = + let insert tree v = - let rec delMin tree = + let rec insertRec tree v = 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 + | Empty -> ToDo(Node(Red, Empty, v, Empty)) + | Node(color, left, value, right) -> + if value > v then + let newLeft = insertRec left v - 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 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 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 newRight with + | Done nr -> Done(Node(color, left, value, nr)) + | ToDo nr -> balance (Node(color, left, value, nr)) + else + Done(tree) - match bn with - | Done t -> Done(Node(color, a, min, t)) - | ToDo t -> eqR (Node(color, a, min, t)) - | _ -> failwith "Impossible pattern" + let newTree = insertRec tree v + newTree |> justTree |> blacken |> justTree |> Ok + + 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 = + 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 |> blacken |> justTree + } + + 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 t2 g r targetHeight currentHeight + return Node(Red, l, x, newRight) |> balance |> justTree + | Node(Black, l, x, r) -> + let! newRight = joinRT t2 g r targetHeight (currentHeight - 1) + return Node(Black, l, x, newRight) |> balance |> justTree + | _ -> return! Error EmptyNodeWasNotExpected + } - 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 + let h2 = blackHeight 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 t |> blacken |> justTree + else if h1 > h2 then + let! t = joinRT t1 g t2 h2 h1 + return t |> blacken |> justTree + else + return Node(Black, t1, g, t2) + } + + let merge t1 t2 = - 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) + let rec minimum tree = + match tree with + | Ok(Node(_, Empty, x, _)) -> Ok x + | Ok(Node(_, l, _, _)) -> minimum (Ok l) + | _ -> Error EmptyNodeWasNotExpected + + let mergeEQ t1 t2 = + resultM { + let! m = minimum (Ok t2) + let! t2' = delete t2 m + let h2' = blackHeight t2' + let h1 = blackHeight t1 + + if h1 = h2' then + return Node(Red, t1, m, t2') + else + match t1 with + | Node(_, Node(Red, ll, lx, lr), x, r) -> + return Node(Red, Node(Black, ll, lx, lr), x, Node(Black, r, m, t2')) + | Node(_, l, x, Node(Red, rl, rx, rr)) -> + return Node(Black, Node(Red, l, x, rl), rx, Node(Red, rr, m, t2')) + | _ -> return Node(Black, (justTree (blacken t1)), m, t2') + } + + let rec mergeLT t1 t2 targetHeight currentHeight = + resultM { + if targetHeight = currentHeight then + return! mergeEQ t1 t2 + else + match t2 with + | Node(Red, l, x, r) -> + let! newLeft = mergeLT t1 l targetHeight currentHeight + return Node(Red, newLeft, x, r) |> balance |> justTree + | Node(Black, l, x, r) -> + let! newLeft = mergeLT t1 l targetHeight (currentHeight - 1) + return Node(Red, newLeft, x, r) |> balance |> justTree + | _ -> return! Error EmptyNodeWasNotExpected + } + + let rec mergeRT t1 t2 targetHeight currentHeight = + resultM { + if targetHeight = currentHeight then + return! mergeEQ t1 t2 + else + match t1 with + | Node(Red, l, x, r) -> + let! newRight = mergeRT r t2 targetHeight currentHeight + return Node(Red, l, x, newRight) |> balance |> justTree + | Node(Black, l, x, r) -> + let! newRight = mergeRT r t2 targetHeight (currentHeight - 1) + return Node(Red, l, x, newRight) |> balance |> justTree + | _ -> return! Error EmptyNodeWasNotExpected + } + + let h1 = blackHeight t1 + let h2 = blackHeight t2 + + resultM { + if h1 = 0 then + return t2 + else if h2 = 0 then + return t1 + else if h1 < h2 then + let! t = mergeLT t1 t2 h1 h2 + return t |> blacken |> justTree + else if h1 > h2 then + let! t = mergeRT t1 t2 h2 h1 + return t |> blacken |> justTree 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) + let! t = mergeEQ t1 t2 + return t |> blacken |> justTree + } + + 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 (justTree (blacken r)) + return lt, t + else if kx > x then + let! lt, gt = split kx r + let! t = join (justTree (blacken l)) x lt + return t, gt + else + return justTree (blacken (l)), justTree (blacken (r)) + } + +module RBSet = + open Tree + + 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 set2 + | _ -> + match set2 with + | Empty -> return set1 + | Node(_, l, x, r) -> + let! l', r' = Tree.split x set1 + let! tl = union l' l + let! tr = union r' r + return! Tree.join tl x 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' = Tree.split x set1 + let! tl = intersection l' l + let! tr = intersection r' r + + if Tree.contains set1 x then + return! Tree.join tl x tr + else + return! Tree.merge tl tr + } + + let rec difference set1 set2 = + resultM { + match set1 with + | Empty -> return Empty + | _ -> + match set2 with + | Empty -> return set1 + | Node(_, l, x, r) -> + let! l', r' = Tree.split x set1 + let! tl = difference l' l + let! tr = difference r' r + return! Tree.merge tl tr + } From 11372c26514132d75d9f50846056b550365195de Mon Sep 17 00:00:00 2001 From: Egor Starikov Date: Wed, 16 Sep 2026 22:35:41 +0300 Subject: [PATCH 7/8] add: benchmarks --- QuadTree.Benchmark/Main.fs | 4 +- QuadTree.Benchmark/QuadTree.Benchmark.fsproj | 1 + QuadTree.Benchmark/RedBlackSet.fs | 160 +++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 QuadTree.Benchmark/RedBlackSet.fs diff --git a/QuadTree.Benchmark/Main.fs b/QuadTree.Benchmark/Main.fs index 61394af..3ee5fd4 100644 --- a/QuadTree.Benchmark/Main.fs +++ b/QuadTree.Benchmark/Main.fs @@ -6,7 +6,9 @@ let main argv = BenchmarkSwitcher [| 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 4edb362..8b708a6 100644 --- a/QuadTree.Benchmark/QuadTree.Benchmark.fsproj +++ b/QuadTree.Benchmark/QuadTree.Benchmark.fsproj @@ -11,6 +11,7 @@ + diff --git a/QuadTree.Benchmark/RedBlackSet.fs b/QuadTree.Benchmark/RedBlackSet.fs new file mode 100644 index 0000000..25c9bf3 --- /dev/null +++ b/QuadTree.Benchmark/RedBlackSet.fs @@ -0,0 +1,160 @@ +namespace QuadTree.Benchmarks.RedBlackSet + +open BenchmarkDotNet.Attributes +open BenchmarkDotNet.Configs +open QuadTree.RBSet +open System.Collections.Generic + +[] +[] +[] +[] +type SingleOpsBenchmark() = + let rnd = System.Random(1234561) + + [] + [] + val mutable public A: int + + [] + val mutable public rndInt: int + + [] + val mutable public setA: RedBlackSet + + [] + member self.Setup() = + self.rndInt <- rnd.Next(self.A + 1, self.A + 1000) + + let dataA = Array.init self.A (fun _ -> rnd.Next()) + + self.setA <- + dataA + |> Array.fold + (fun (set: RedBlackSet) v -> + match RedBlackSet.add v set with + | Ok nextSet -> nextSet + | Error err -> failwithf "Benchmark setup failed: %A" err) + RedBlackSet.empty + + [] + [] + member self.AddingOneElement() = RedBlackSet.add self.rndInt self.setA + + [] + [] + member self.DeletingOneElement() = + RedBlackSet.delete self.rndInt self.setA + + +[] +[] +[] +[] +type FSSetsBenchmark() = + let rnd = System.Random(1234561) + + [] + [] + val mutable public A: int + + [] + [] + val mutable public B: int + + [] + val mutable public RedBlackSetA: RedBlackSet + + [] + val mutable public RedBlackSetB: RedBlackSet + + [] + val mutable public SetA: Set + + [] + val mutable public SetB: Set + + [] + val mutable public HashSetA: HashSet + + [] + val mutable public HashSetB: HashSet + + [] + member self.Setup() = + let dataA = Array.init self.A (fun _ -> rnd.Next()) + + let dataB = Array.init self.B (fun _ -> rnd.Next()) + + self.RedBlackSetA <- + dataA + |> Array.fold + (fun set v -> + match RedBlackSet.add v set with + | Ok s -> s + | Error e -> failwithf "%A" e) + RedBlackSet.empty + + self.RedBlackSetB <- + dataB + |> Array.fold + (fun set v -> + match RedBlackSet.add v set with + | Ok s -> s + | Error e -> failwithf "%A" e) + RedBlackSet.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 + + self.HashSetA <- HashSet() + dataA |> Array.iter (fun v -> self.HashSetA.Add(v) |> ignore) + + self.HashSetB <- HashSet() + dataB |> Array.iter (fun v -> self.HashSetB.Add(v) |> ignore) + + [] + [] + member self.UnionRB() = + RedBlackSet.union self.RedBlackSetA self.RedBlackSetB + + [] + [] + member self.UnionFS() = Set.union self.SetA self.SetB + + [] + [] + member self.UnionHashFS() = + let a = HashSet(self.HashSetA) + a.UnionWith(self.HashSetB) + + [] + [] + member self.IntersectionRB() = + RedBlackSet.intersection self.RedBlackSetA self.RedBlackSetB + + [] + [] + member self.IntersectionFS() = Set.intersect self.SetA self.SetB + + [] + [] + member self.IntersectionHashFS() = + let a = HashSet(self.HashSetA) + a.IntersectWith(self.HashSetB) + + [] + [] + member self.DifferenceRB() = + RedBlackSet.difference self.RedBlackSetA self.RedBlackSetB + + [] + [] + member self.DifferenceFS() = Set.difference self.SetA self.SetB + + [] + [] + member self.DifferenceHashFS() = + let a = HashSet(self.HashSetA) + a.ExceptWith(self.HashSetB) From b7b1e5628cf8b39ee82754538fe0c81d754ad220 Mon Sep 17 00:00:00 2001 From: Egor Starikov Date: Thu, 17 Sep 2026 00:09:18 +0300 Subject: [PATCH 8/8] refactor: change namespace --- QuadTree.Benchmark/Main.fs | 4 ++-- QuadTree.Benchmark/RedBlackSet.fs | 33 ++++++++++++++--------------- QuadTree.Tests/Tests.RedBlackSet.fs | 4 ++-- QuadTree/RedBlackSet.fs | 24 ++++++++++----------- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/QuadTree.Benchmark/Main.fs b/QuadTree.Benchmark/Main.fs index 3ee5fd4..ae6a2e3 100644 --- a/QuadTree.Benchmark/Main.fs +++ b/QuadTree.Benchmark/Main.fs @@ -6,8 +6,8 @@ let main argv = BenchmarkSwitcher [| typeof typeof - typeof - typeof + typeof + typeof typeof |] benchmarks.Run argv |> ignore diff --git a/QuadTree.Benchmark/RedBlackSet.fs b/QuadTree.Benchmark/RedBlackSet.fs index 25c9bf3..8f586cb 100644 --- a/QuadTree.Benchmark/RedBlackSet.fs +++ b/QuadTree.Benchmark/RedBlackSet.fs @@ -2,7 +2,7 @@ namespace QuadTree.Benchmarks.RedBlackSet open BenchmarkDotNet.Attributes open BenchmarkDotNet.Configs -open QuadTree.RBSet +open QuadTree.RBSet open System.Collections.Generic [] @@ -20,7 +20,7 @@ type SingleOpsBenchmark() = val mutable public rndInt: int [] - val mutable public setA: RedBlackSet + val mutable public setA: RBSet [] member self.Setup() = @@ -31,20 +31,19 @@ type SingleOpsBenchmark() = self.setA <- dataA |> Array.fold - (fun (set: RedBlackSet) v -> - match RedBlackSet.add v set with + (fun (set: RBSet) v -> + match RBSet.add v set with | Ok nextSet -> nextSet | Error err -> failwithf "Benchmark setup failed: %A" err) - RedBlackSet.empty + RBSet.empty [] [] - member self.AddingOneElement() = RedBlackSet.add self.rndInt self.setA + member self.AddingOneElement() = RBSet.add self.rndInt self.setA [] [] - member self.DeletingOneElement() = - RedBlackSet.delete self.rndInt self.setA + member self.DeletingOneElement() = RBSet.delete self.rndInt self.setA [] @@ -63,10 +62,10 @@ type FSSetsBenchmark() = val mutable public B: int [] - val mutable public RedBlackSetA: RedBlackSet + val mutable public RedBlackSetA: RBSet [] - val mutable public RedBlackSetB: RedBlackSet + val mutable public RedBlackSetB: RBSet [] val mutable public SetA: Set @@ -90,19 +89,19 @@ type FSSetsBenchmark() = dataA |> Array.fold (fun set v -> - match RedBlackSet.add v set with + match RBSet.add v set with | Ok s -> s | Error e -> failwithf "%A" e) - RedBlackSet.empty + RBSet.empty self.RedBlackSetB <- dataB |> Array.fold (fun set v -> - match RedBlackSet.add v set with + match RBSet.add v set with | Ok s -> s | Error e -> failwithf "%A" e) - RedBlackSet.empty + RBSet.empty self.SetA <- dataA |> Array.fold (fun set v -> Set.add v set) Set.empty @@ -117,7 +116,7 @@ type FSSetsBenchmark() = [] [] member self.UnionRB() = - RedBlackSet.union self.RedBlackSetA self.RedBlackSetB + RBSet.union self.RedBlackSetA self.RedBlackSetB [] [] @@ -132,7 +131,7 @@ type FSSetsBenchmark() = [] [] member self.IntersectionRB() = - RedBlackSet.intersection self.RedBlackSetA self.RedBlackSetB + RBSet.intersection self.RedBlackSetA self.RedBlackSetB [] [] @@ -147,7 +146,7 @@ type FSSetsBenchmark() = [] [] member self.DifferenceRB() = - RedBlackSet.difference self.RedBlackSetA self.RedBlackSetB + RBSet.difference self.RedBlackSetA self.RedBlackSetB [] [] diff --git a/QuadTree.Tests/Tests.RedBlackSet.fs b/QuadTree.Tests/Tests.RedBlackSet.fs index 0244714..b764ab5 100644 --- a/QuadTree.Tests/Tests.RedBlackSet.fs +++ b/QuadTree.Tests/Tests.RedBlackSet.fs @@ -1,8 +1,8 @@ module RedBlackSet.Tests open System -open RBSet.RBSet -open RBSet +open QuadTree.RBSet.RBSet +open QuadTree.RBSet open Xunit let rec blHeightInv tree = diff --git a/QuadTree/RedBlackSet.fs b/QuadTree/RedBlackSet.fs index 673eea3..629ee7e 100644 --- a/QuadTree/RedBlackSet.fs +++ b/QuadTree/RedBlackSet.fs @@ -1,5 +1,5 @@ //The following sources were used as a reference: 'Faster, Simpler Red-Black Trees' and Data/Set/RBTree.hs. -namespace RBSet +namespace QuadTree.RBSet open Result @@ -9,9 +9,9 @@ type Color = | Red | Black -type Tree<'T> = +type RBSet<'T> = | Empty - | Node of color: Color * left: Tree<'T> * value: 'T * right: Tree<'T> + | Node of color: Color * left: RBSet<'T> * value: 'T * right: RBSet<'T> module private Tree = type private Condition<'T> = @@ -28,11 +28,11 @@ module private Tree = | Done t | ToDo t -> t - let rec private blackHeight tree = + let rec private getBlackHeight tree = match tree with | Empty -> 0 - | Node(Red, l, _, _) -> blackHeight l - | Node(Black, l, _, _) -> 1 + (blackHeight l) + | Node(Red, l, _, _) -> getBlackHeight l + | Node(Black, l, _, _) -> 1 + (getBlackHeight l) let rec contains tree v = @@ -202,8 +202,8 @@ module private Tree = | _ -> return! Error EmptyNodeWasNotExpected } - let h1 = blackHeight t1 - let h2 = blackHeight t2 + let h1 = getBlackHeight t1 + let h2 = getBlackHeight t2 resultM { if h1 = 0 then @@ -232,8 +232,8 @@ module private Tree = resultM { let! m = minimum (Ok t2) let! t2' = delete t2 m - let h2' = blackHeight t2' - let h1 = blackHeight t1 + let h2' = getBlackHeight t2' + let h1 = getBlackHeight t1 if h1 = h2' then return Node(Red, t1, m, t2') @@ -276,8 +276,8 @@ module private Tree = | _ -> return! Error EmptyNodeWasNotExpected } - let h1 = blackHeight t1 - let h2 = blackHeight t2 + let h1 = getBlackHeight t1 + let h2 = getBlackHeight t2 resultM { if h1 = 0 then