From ea6811b25a7d47561606e895223cab3052337ca1 Mon Sep 17 00:00:00 2001 From: Taranukha Leonid Date: Sun, 26 Apr 2026 19:42:17 +0300 Subject: [PATCH 1/3] chore: setup dotnet tools and apply project-wide formatting --- .editorconfig | 21 ++ benchmarks/AVLSet.Benchmarks/Benchmarks.fs | 74 ++-- benchmarks/AVLSet.Benchmarks/Program.fs | 2 +- dotnet-tools.json | 13 + src/AVLSet.Library/Library.fs | 237 +++++++------ tests/AVLSet.UnitTests/Tests.fs | 376 +++++++++------------ 6 files changed, 373 insertions(+), 350 deletions(-) create mode 100644 .editorconfig create mode 100644 dotnet-tools.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d65e3b7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +[*.fs] +fsharp_space_before_lowercase_invocation = true +fsharp_multiline_block_brackets_on_same_line = true +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{fs,fsi,fsx}] +max_line_length = 120 +fsharp_space_before_lowercase_invocation = true +fsharp_multiline_block_brackets_on_same_line = true +fsharp_array_or_list_multiline_formatter = character_width +fsharp_single_line_lambda_with_no_args = true +indent_style = space +indent_size = 4indent_size = 4 diff --git a/benchmarks/AVLSet.Benchmarks/Benchmarks.fs b/benchmarks/AVLSet.Benchmarks/Benchmarks.fs index 7dd4846..42af1e0 100644 --- a/benchmarks/AVLSet.Benchmarks/Benchmarks.fs +++ b/benchmarks/AVLSet.Benchmarks/Benchmarks.fs @@ -1,6 +1,6 @@ namespace AVLSet.Benchmarks -open System.Threading.Tasks +open System.Threading.Tasks open BenchmarkDotNet.Diagnosers open BenchmarkDotNet.Attributes open BenchmarkDotNet.Configs @@ -10,119 +10,123 @@ open AVLSet.Library [] [] [] -[] -[] +[] +[] type SetBenchmarks() = let rnd = System.Random(1234561) [] [] - val mutable public A : int + val mutable public A: int [] [] - val mutable public B : int + val mutable public B: int [] [] - val mutable public DataTypeA : string + val mutable public DataTypeA: string [] [] - val mutable public Threads : int + val mutable public Threads: int [] - val mutable public rndInt : int + val mutable public rndInt: int [] - val mutable public setA : AVLTree + val mutable public setA: AVLTree [] - val mutable public setB : AVLTree + val mutable public setB: AVLTree [] member self.Setup() = - self.rndInt <- rnd.Next(self.A + 1, self.A + 1000) + self.rndInt <- rnd.Next(self.A + 1, self.A + 1000) - let dataA = + let dataA = match self.DataTypeA with | "Random" -> Array.init self.A (fun _ -> rnd.Next()) | _ -> [| 1 .. self.A |] - let dataB = Array.init self.B (fun _ -> rnd.Next()) - + + let dataB = Array.init self.B (fun _ -> rnd.Next()) + self.setA <- dataA |> Array.fold (fun s v -> AVLSet.add v s) AVLSet.empty self.setB <- dataB |> Array.fold (fun s v -> AVLSet.add v s) AVLSet.empty - + [] [] - member self.``Adding one element`` () = AVLSet.add self.rndInt self.setA + member self.``Adding one element``() = AVLSet.add self.rndInt self.setA [] [] - member self.``Deleting one element`` () = AVLSet.delete self.rndInt self.setA + member self.``Deleting one element``() = AVLSet.delete self.rndInt self.setA [] [] - member self.``Sequential union`` () = AVLSet.union self.setA self.setB + member self.``Sequential union``() = AVLSet.union self.setA self.setB [] [] - member self.``Union via tree traversal`` () = AVLSet.unionTraversal self.setA self.setB + member self.``Union via tree traversal``() = + AVLSet.unionTraversal self.setA self.setB [] [] - member self.``Parallel union with threads`` () = + member self.``Parallel union with threads``() = let opts = ParallelOptions() opts.MaxDegreeOfParallelism <- self.Threads - + AVLSet.parallelUnion opts self.setA self.setB [] [] - member self.``Sequential intersection`` () = AVLSet.intersection self.setA self.setB + member self.``Sequential intersection``() = AVLSet.intersection self.setA self.setB [] [] - member self.``Intersection via tree traversal`` () = AVLSet.intersectionTraversal self.setA self.setB + member self.``Intersection via tree traversal``() = + AVLSet.intersectionTraversal self.setA self.setB [] [] - member self.``Parallel intersection with threads`` () = + member self.``Parallel intersection with threads``() = let opts = ParallelOptions() opts.MaxDegreeOfParallelism <- self.Threads - + AVLSet.parallelIntersection opts self.setA self.setB [] [] - member self.``Sequential difference`` () = AVLSet.difference self.setA self.setB + member self.``Sequential difference``() = AVLSet.difference self.setA self.setB [] [] - member self.``Difference via tree traversal`` () = AVLSet.differenceTraversal self.setA self.setB + member self.``Difference via tree traversal``() = + AVLSet.differenceTraversal self.setA self.setB [] [] - member self.``Parallel difference with threads`` () = + member self.``Parallel difference with threads``() = let opts = ParallelOptions() opts.MaxDegreeOfParallelism <- self.Threads - + AVLSet.parallelDifference opts self.setA self.setB [] [] - member self.``Sequential symmetrical difference`` () = AVLSet.symmDifference self.setA self.setB + member self.``Sequential symmetrical difference``() = + AVLSet.symmDifference self.setA self.setB [] [] - member self.``Symmetrical difference via tree traversal`` () = AVLSet.symmDifferenceTraversal self.setA self.setB + member self.``Symmetrical difference via tree traversal``() = + AVLSet.symmDifferenceTraversal self.setA self.setB [] [] - member self.``Parallel symmetrical difference with threads`` () = + member self.``Parallel symmetrical difference with threads``() = let opts = ParallelOptions() opts.MaxDegreeOfParallelism <- self.Threads - + AVLSet.parallelSymmDifference opts self.setA self.setB diff --git a/benchmarks/AVLSet.Benchmarks/Program.fs b/benchmarks/AVLSet.Benchmarks/Program.fs index 93a923d..2c180ba 100644 --- a/benchmarks/AVLSet.Benchmarks/Program.fs +++ b/benchmarks/AVLSet.Benchmarks/Program.fs @@ -4,4 +4,4 @@ open AVLSet.Benchmarks [] let main args = BenchmarkRunner.Run() |> ignore - 0 \ No newline at end of file + 0 diff --git a/dotnet-tools.json b/dotnet-tools.json new file mode 100644 index 0000000..ea1a428 --- /dev/null +++ b/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "fantomas": { + "version": "7.0.5", + "commands": [ + "fantomas" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/src/AVLSet.Library/Library.fs b/src/AVLSet.Library/Library.fs index 9e912f3..3d2db22 100644 --- a/src/AVLSet.Library/Library.fs +++ b/src/AVLSet.Library/Library.fs @@ -6,7 +6,7 @@ type AVLTree<'value> = | Empty | Node of int * 'value * AVLTree<'value> * AVLTree<'value> -module Node = +module Node = let height n = match n with | Empty -> -1 @@ -26,92 +26,91 @@ module Node = match n with | Empty -> failwith "Empty node has no right child" | Node(_, _, _, rn) -> rn - - let maxMinNodes n1 n2 = + + let maxMinNodes n1 n2 = if height n1 >= height n2 then n1, n2 else n2, n1 -module Tree = - let LLrotate n = +module Tree = + let LLrotate n = let ln = Node.leftChild n let lln = Node.leftChild ln let rln = Node.rightChild ln let rn = Node.rightChild n - let rlnNew = Node(max (Node.height rln) (Node.height rn) + 1, - Node.value n, rln, rn) - Node(max (Node.height lln) (Node.height rlnNew) + 1, - Node.value ln, lln, rlnNew) + let rlnNew = Node(max (Node.height rln) (Node.height rn) + 1, Node.value n, rln, rn) + Node(max (Node.height lln) (Node.height rlnNew) + 1, Node.value ln, lln, rlnNew) let RRrotate n = let ln = Node.leftChild n let rn = Node.rightChild n let rrn = Node.rightChild rn let lrn = Node.leftChild rn - let lrnNew = Node(max (Node.height ln) (Node.height lrn) + 1, - Node.value n, ln, lrn) - Node(max (Node.height lrnNew) (Node.height rrn) + 1, - Node.value rn, lrnNew, rrn) + let lrnNew = Node(max (Node.height ln) (Node.height lrn) + 1, Node.value n, ln, lrn) + Node(max (Node.height lrnNew) (Node.height rrn) + 1, Node.value rn, lrnNew, rrn) - let LRrotate n = - let lnNew = RRrotate (Node.leftChild n) + let LRrotate n = + let lnNew = RRrotate(Node.leftChild n) let rn = Node.rightChild n - LLrotate (Node(max (Node.height lnNew) (Node.height rn) + 1, - Node.value n, lnNew, rn)) + LLrotate(Node(max (Node.height lnNew) (Node.height rn) + 1, Node.value n, lnNew, rn)) let RLrotate n = - let rnNew = LLrotate (Node.rightChild n) + let rnNew = LLrotate(Node.rightChild n) let ln = Node.leftChild n - RRrotate (Node(max (Node.height ln) (Node.height rnNew) + 1, - Node.value n, ln, rnNew)) + RRrotate(Node(max (Node.height ln) (Node.height rnNew) + 1, Node.value n, ln, rnNew)) let balance ln rn v = let lnHeight = Node.height ln let rnHeight = Node.height rn + match lnHeight - rnHeight with - | 2 -> - let llnHeight = Node.height (Node.leftChild ln) - let rlnHeight = Node.height (Node.rightChild ln) - if llnHeight >= rlnHeight then - LLrotate (Node(0, v, ln, rn)) - else LRrotate (Node(0, v, ln, rn)) - | -2 -> - let lrnHeight = Node.height (Node.leftChild rn) - let rrnHeight = Node.height (Node.rightChild rn) - if lrnHeight <= rrnHeight then - RRrotate (Node(0, v, ln, rn)) - else RLrotate (Node(0, v, ln, rn)) - | _ -> Node(max lnHeight rnHeight + 1, v, ln, rn) - - let rec minNode n = + | 2 -> + let llnHeight = Node.height (Node.leftChild ln) + let rlnHeight = Node.height (Node.rightChild ln) + + if llnHeight >= rlnHeight then + LLrotate(Node(0, v, ln, rn)) + else + LRrotate(Node(0, v, ln, rn)) + | -2 -> + let lrnHeight = Node.height (Node.leftChild rn) + let rrnHeight = Node.height (Node.rightChild rn) + + if lrnHeight <= rrnHeight then + RRrotate(Node(0, v, ln, rn)) + else + RLrotate(Node(0, v, ln, rn)) + | _ -> Node(max lnHeight rnHeight + 1, v, ln, rn) + + let rec minNode n = match n with | Empty -> failwith "Empty node has no value" | Node(_, v, Empty, rn) -> v, rn - | Node(_, v, ln, rn) -> - let value, lnNew = minNode ln - value, balance lnNew rn v + | Node(_, v, ln, rn) -> + let value, lnNew = minNode ln + value, balance lnNew rn v - let rec insert value n = + let rec insert value n = match n with | Empty -> Node(0, value, Empty, Empty) - | Node(h, v, ln, rn) -> + | Node(h, v, ln, rn) -> match value with | value when value = v -> n - | value when value < v -> + | value when value < v -> let lnNew = insert value ln balance lnNew rn v | _ -> let rnNew = insert value rn balance ln rnNew v - let rec remove value n = + let rec remove value n = match n with | Empty -> Empty - | Node(h, v, ln, rn) -> + | Node(h, v, ln, rn) -> match value with - | value when value = v -> + | value when value = v -> match ln, rn with | Empty, _ -> rn | _, Empty -> ln - | _, _ -> + | _, _ -> let newValue, rnNew = minNode rn balance ln rnNew newValue | value when value < v -> @@ -121,16 +120,16 @@ module Tree = let rnNew = remove value rn balance ln rnNew v - let rec contains value n = + let rec contains value n = match n with | Empty -> false - | Node(h, v, ln, rn) -> + | Node(h, v, ln, rn) -> match value with | value when value = v -> true | value when value < v -> contains value ln | _ -> contains value rn - let rec traversal (func: 'a -> AVLTree<'b> -> AVLTree<'b>) nArg n = + let rec traversal (func: 'a -> AVLTree<'b> -> AVLTree<'b>) nArg n = match n with | Empty -> nArg | Node(_, v, ln, rn) -> @@ -146,23 +145,23 @@ module Tree = let rec join left key right = let leftHeight = Node.height left let rightHeight = Node.height right - match leftHeight - rightHeight with - | diff when abs diff <= 1 -> - Node(max leftHeight rightHeight + 1, key, left, right) + + match leftHeight - rightHeight with + | diff when abs diff <= 1 -> Node(max leftHeight rightHeight + 1, key, left, right) | diff when diff >= 2 -> - match left with + match left with | Empty -> failwith "Unreacheable message" - | Node (h, v, ln, rn) -> + | Node(h, v, ln, rn) -> let rnNew = join rn key right balance ln rnNew v - | _ -> - match right with + | _ -> + match right with | Empty -> failwith "Unreacheable message" - | Node (h, v, ln, rn) -> + | Node(h, v, ln, rn) -> let lnNew = join left key ln balance lnNew rn v - let merge left right = + let merge left right = match left, right with | Empty, _ -> right | _, Empty -> left @@ -170,133 +169,162 @@ module Tree = let key, newRight = minNode right join left key newRight - let rec split key n = + let rec split key n = match n with | Empty -> Empty, Empty, false | Node(_, v, ln, rn) -> match key with - | key when key = v -> - ln, rn, true - | key when key < v -> + | key when key = v -> ln, rn, true + | key when key < v -> let lesser, greater, wasFound = split key ln lesser, join greater v rn, wasFound - | _ -> + | _ -> let lesser, greater, wasFound = split key rn join ln v lesser, greater, wasFound -module AVLSet = +module AVLSet = let empty = Empty - let add value set = - Tree.insert value set + let add value set = Tree.insert value set - let delete value set = - Tree.remove value set + let delete value set = Tree.remove value set - let contains value set = - Tree.contains value set + let contains value set = Tree.contains value set - let copy set = - Tree.copy set + let copy set = Tree.copy set let rec union set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 + match maxSet, minSet with | Empty, _ -> minSet | _, Empty -> maxSet - | Node(_, v, ln, rn), _ -> + | Node(_, v, ln, rn), _ -> let lesser, greater, _ = Tree.split v minSet let leftUnion = union ln lesser let rightUnion = union rn greater Tree.join leftUnion v rightUnion - + let rec intersection set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 + match maxSet, minSet with | Empty, _ -> Empty | _, Empty -> Empty - | Node(_, v, ln, rn), _ -> + | Node(_, v, ln, rn), _ -> let lesser, greater, wasFound = Tree.split v minSet let leftInter = intersection ln lesser let rightInter = intersection rn greater - if wasFound then Tree.join leftInter v rightInter - else Tree.merge leftInter rightInter + + if wasFound then + Tree.join leftInter v rightInter + else + Tree.merge leftInter rightInter let rec difference minuendSet subtrahendSet = match minuendSet, subtrahendSet with | Empty, _ -> Empty | _, Empty -> minuendSet - | Node(_, v, ln, rn), _ -> + | Node(_, v, ln, rn), _ -> let lesser, greater, wasFound = Tree.split v subtrahendSet let leftDiff = difference ln lesser let rightDiff = difference rn greater - if wasFound then Tree.merge leftDiff rightDiff - else Tree.join leftDiff v rightDiff + + if wasFound then + Tree.merge leftDiff rightDiff + else + Tree.join leftDiff v rightDiff let rec symmDifference set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 + match maxSet, minSet with | Empty, _ -> minSet | _, Empty -> maxSet - | Node(_, v, ln, rn), _ -> + | Node(_, v, ln, rn), _ -> let lesser, greater, wasFound = Tree.split v minSet let leftSymm = symmDifference ln lesser let rightSymm = symmDifference rn greater - if wasFound then Tree.merge leftSymm rightSymm - else Tree.join leftSymm v rightSymm + + if wasFound then + Tree.merge leftSymm rightSymm + else + Tree.join leftSymm v rightSymm let unionTraversal set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 let unSet = Tree.copy maxSet - Tree.traversal (fun value set -> Tree.insert value set) unSet minSet + Tree.traversal (fun value set -> Tree.insert value set) unSet minSet - let intersectionTraversal set1 set2 = + let intersectionTraversal set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 - Tree.traversal (fun value set -> - if Tree.contains value maxSet then Tree.insert value set else set) Empty minSet + + Tree.traversal + (fun value set -> + if Tree.contains value maxSet then + Tree.insert value set + else + set) + Empty + minSet let differenceTraversal minuendSet subtrahendSet = let diffSet = Tree.copy minuendSet Tree.traversal (fun value set -> Tree.remove value set) diffSet subtrahendSet - let symmDifferenceTraversal set1 set2 = + let symmDifferenceTraversal set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 let symmSet = Tree.copy maxSet - Tree.traversal (fun value set -> - if Tree.contains value maxSet then Tree.remove value set else Tree.insert value set) symmSet minSet - + + Tree.traversal + (fun value set -> + if Tree.contains value maxSet then + Tree.remove value set + else + Tree.insert value set) + symmSet + minSet + let rec parallelUnion (opts: ParallelOptions) set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 + match maxSet, minSet with - | Empty, _ -> minSet + | Empty, _ -> minSet | _, Empty -> maxSet | Node(_, v, ln, rn), _ -> let lesser, greater, _ = Tree.split v minSet let mutable leftUnion = Empty let mutable rightUnion = Empty + Parallel.Invoke( - opts, + opts, (fun () -> leftUnion <- parallelUnion opts ln lesser), (fun () -> rightUnion <- parallelUnion opts rn greater) ) + Tree.join leftUnion v rightUnion let rec parallelIntersection (opts: ParallelOptions) set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 + match maxSet, minSet with - | Empty, _ -> Empty + | Empty, _ -> Empty | _, Empty -> Empty | Node(_, v, ln, rn), _ -> let lesser, greater, wasFound = Tree.split v minSet let mutable leftInter = Empty let mutable rightInter = Empty + Parallel.Invoke( opts, (fun () -> leftInter <- parallelIntersection opts ln lesser), (fun () -> rightInter <- parallelIntersection opts rn greater) ) - if wasFound then Tree.join leftInter v rightInter - else Tree.merge leftInter rightInter + + if wasFound then + Tree.join leftInter v rightInter + else + Tree.merge leftInter rightInter let rec parallelDifference (opts: ParallelOptions) minuendSet subtrahendSet = match minuendSet, subtrahendSet with @@ -306,27 +334,36 @@ module AVLSet = let lesser, greater, wasFound = Tree.split v subtrahendSet let mutable leftDiff = Empty let mutable rightDiff = Empty + Parallel.Invoke( opts, (fun () -> leftDiff <- parallelDifference opts ln lesser), (fun () -> rightDiff <- parallelDifference opts rn greater) ) - if wasFound then Tree.merge leftDiff rightDiff - else Tree.join leftDiff v rightDiff + + if wasFound then + Tree.merge leftDiff rightDiff + else + Tree.join leftDiff v rightDiff let rec parallelSymmDifference (opts: ParallelOptions) set1 set2 = let maxSet, minSet = Node.maxMinNodes set1 set2 + match maxSet, minSet with - | Empty, _ -> minSet + | Empty, _ -> minSet | _, Empty -> maxSet | Node(_, v, ln, rn), _ -> let lesser, greater, wasFound = Tree.split v minSet let mutable leftSymm = Empty let mutable rightSymm = Empty + Parallel.Invoke( opts, (fun () -> leftSymm <- parallelSymmDifference opts ln lesser), (fun () -> rightSymm <- parallelSymmDifference opts rn greater) ) - if wasFound then Tree.merge leftSymm rightSymm - else Tree.join leftSymm v rightSymm + + if wasFound then + Tree.merge leftSymm rightSymm + else + Tree.join leftSymm v rightSymm diff --git a/tests/AVLSet.UnitTests/Tests.fs b/tests/AVLSet.UnitTests/Tests.fs index 0596e95..d3efef1 100644 --- a/tests/AVLSet.UnitTests/Tests.fs +++ b/tests/AVLSet.UnitTests/Tests.fs @@ -5,36 +5,35 @@ open Xunit open FsUnit.Xunit open AVLSet.Library -module SetTests = +module SetTests = let rec isSetValid n mn mx = match n with | Empty -> true | Node(h, v, ln, rn) -> - let isInBounds = - mn |> Option.forall (fun mn -> v > mn) && - mx |> Option.forall (fun mx -> v < mx) + let isInBounds = + mn |> Option.forall (fun mn -> v > mn) && mx |> Option.forall (fun mx -> v < mx) let lnHeight = Node.height ln let rnHeight = Node.height rn - isInBounds && - h = (max lnHeight rnHeight + 1) && - abs (lnHeight - rnHeight) <= 1 && - isSetValid ln mn (Some v) && - isSetValid rn (Some v) mx - - let rec advancedContains (condition: 'a -> bool -> bool) setOfValues targetSet = + isInBounds + && h = (max lnHeight rnHeight + 1) + && abs (lnHeight - rnHeight) <= 1 + && isSetValid ln mn (Some v) + && isSetValid rn (Some v) mx + + let rec advancedContains (condition: 'a -> bool -> bool) setOfValues targetSet = match setOfValues with | Empty -> true | Node(_, v, ln, rn) -> let lesser, greater, wasFound = Tree.split v targetSet - - condition v wasFound && - advancedContains condition ln lesser && - advancedContains condition rn greater - + + condition v wasFound + && advancedContains condition ln lesser + && advancedContains condition rn greater + [] - let ``Empty tree insertion`` () = + let ``Empty tree insertion`` () = let resultSet = Empty |> AVLSet.add 15 let correctSet = Node(0, 15, Empty, Empty) @@ -51,92 +50,60 @@ module SetTests = [] let ``Insertion without rotation`` () = - let resultSet = - Node(1, 15, - Node(0, 10, Empty, Empty), - Empty) - |> AVLSet.add 20 + let resultSet = Node(1, 15, Node(0, 10, Empty, Empty), Empty) |> AVLSet.add 20 - let correctSet = - Node(1, 15, - Node(0, 10, Empty, Empty), - Node(0, 20, Empty, Empty)) + let correctSet = Node(1, 15, Node(0, 10, Empty, Empty), Node(0, 20, Empty, Empty)) resultSet |> should equal correctSet [] let ``Insertion with height update`` () = - let resultSet = - Node(2, 15, - Node(1, 10, - Node(0, 7, Empty, Empty), - Empty), - Node(0, 20, Empty, Empty)) + let resultSet = + Node(2, 15, Node(1, 10, Node(0, 7, Empty, Empty), Empty), Node(0, 20, Empty, Empty)) |> AVLSet.add 13 - let correctSet = - Node(2, 15, - Node(1, 10, - Node(0, 7, Empty, Empty), - Node(0, 13, Empty, Empty)), - Node(0, 20, Empty, Empty)) + let correctSet = + Node(2, 15, Node(1, 10, Node(0, 7, Empty, Empty), Node(0, 13, Empty, Empty)), Node(0, 20, Empty, Empty)) resultSet |> should equal correctSet [] let ``Left-Left rotation (RR case)`` () = - let resultSet = - Node(1, 15, - Empty, - Node(0, 20, Empty, Empty)) - |> AVLSet.add 25 + let resultSet = Node(1, 15, Empty, Node(0, 20, Empty, Empty)) |> AVLSet.add 25 - let correctSet = - Node(1, 20, - Node(0, 15, Empty, Empty), - Node(0, 25, Empty, Empty)) + let correctSet = Node(1, 20, Node(0, 15, Empty, Empty), Node(0, 25, Empty, Empty)) resultSet |> should equal correctSet [] let ``Right-Left rotation (RL case)`` () = - let resultSet = - Node(2, 15, - Node(0, 10, Empty, Empty), - Node(1, 20, - Node(0, 16, Empty, Empty), - Node(0, 24, Empty, Empty))) + let resultSet = + Node(2, 15, Node(0, 10, Empty, Empty), Node(1, 20, Node(0, 16, Empty, Empty), Node(0, 24, Empty, Empty))) |> AVLSet.add 27 - let correctSet = - Node(2, 20, - Node(1, 15, - Node(0, 10, Empty, Empty), - Node(0, 16, Empty, Empty)), - Node(1, 24, - Empty, - Node(0, 27, Empty, Empty))) + let correctSet = + Node( + 2, + 20, + Node(1, 15, Node(0, 10, Empty, Empty), Node(0, 16, Empty, Empty)), + Node(1, 24, Empty, Node(0, 27, Empty, Empty)) + ) resultSet |> should equal correctSet [] let ``Left-Right rotation (LR case)`` () = - let resultSet = - Node(2, 15, - Node(1, 10, - Node(0, 6, Empty, Empty), - Node(0, 12, Empty, Empty)), - Node(0, 20, Empty, Empty)) + let resultSet = + Node(2, 15, Node(1, 10, Node(0, 6, Empty, Empty), Node(0, 12, Empty, Empty)), Node(0, 20, Empty, Empty)) |> AVLSet.add 14 - let correctSet = - Node(2, 12, - Node(1, 10, - Node(0, 6, Empty, Empty), - Empty), - Node(1, 15, - Node(0, 14, Empty, Empty), - Node(0, 20, Empty, Empty))) + let correctSet = + Node( + 2, + 12, + Node(1, 10, Node(0, 6, Empty, Empty), Empty), + Node(1, 15, Node(0, 14, Empty, Empty), Node(0, 20, Empty, Empty)) + ) resultSet |> should equal correctSet @@ -150,188 +117,148 @@ module SetTests = [] let ``Non-existent element deletion`` () = - let resultSet = - Node(1, 15, - Node(0, 10, Empty, Empty), - Empty) - |> AVLSet.delete 20 + let resultSet = Node(1, 15, Node(0, 10, Empty, Empty), Empty) |> AVLSet.delete 20 - let correctSet = - Node(1, 15, - Node(0, 10, Empty, Empty), - Empty) + let correctSet = Node(1, 15, Node(0, 10, Empty, Empty), Empty) resultSet |> should equal correctSet [] let ``Leaf node deletion`` () = let resultSet = - Node(1, 15, - Node(0, 10, Empty, Empty), - Node(0, 20, Empty, Empty)) + Node(1, 15, Node(0, 10, Empty, Empty), Node(0, 20, Empty, Empty)) |> AVLSet.delete 10 - let correctSet = - Node(1, 15, - Empty, - Node(0, 20, Empty, Empty)) + let correctSet = Node(1, 15, Empty, Node(0, 20, Empty, Empty)) resultSet |> should equal correctSet [] let ``Deletion with single rotation`` () = let resultSet = - Node(2, 15, - Node(1, 10, - Node(0, 6, Empty, Empty), - Node(0, 12, Empty, Empty)), - Node(0, 20, Empty, Empty)) + Node(2, 15, Node(1, 10, Node(0, 6, Empty, Empty), Node(0, 12, Empty, Empty)), Node(0, 20, Empty, Empty)) |> AVLSet.delete 20 let correctSet = - Node(2, 10, - Node(0, 6, Empty, Empty), - Node(1, 15, - Node(0, 12, Empty, Empty), - Empty)) + Node(2, 10, Node(0, 6, Empty, Empty), Node(1, 15, Node(0, 12, Empty, Empty), Empty)) resultSet |> should equal correctSet - + [] let ``Node deletion with one child`` () = let resultSet = - Node(2, 15, - Node(1, 10, - Empty, - Node(0, 12, Empty, Empty)), - Node(1, 20, - Node(0, 16, Empty, Empty), - Node(0, 24, Empty, Empty))) + Node( + 2, + 15, + Node(1, 10, Empty, Node(0, 12, Empty, Empty)), + Node(1, 20, Node(0, 16, Empty, Empty), Node(0, 24, Empty, Empty)) + ) |> AVLSet.delete 10 let correctSet = - Node(2, 15, - Node(0, 12, Empty, Empty), - Node(1, 20, - Node(0, 16, Empty, Empty), - Node(0, 24, Empty, Empty))) + Node(2, 15, Node(0, 12, Empty, Empty), Node(1, 20, Node(0, 16, Empty, Empty), Node(0, 24, Empty, Empty))) resultSet |> should equal correctSet [] let ``Root deletion with successor replacement`` () = let resultSet = - Node(2, 15, - Node(1, 10, - Node(0, 6, Empty, Empty), - Node(0, 12, Empty, Empty)), - Node(1, 20, - Node(0, 16, Empty, Empty), - Node(0, 24, Empty, Empty))) + Node( + 2, + 15, + Node(1, 10, Node(0, 6, Empty, Empty), Node(0, 12, Empty, Empty)), + Node(1, 20, Node(0, 16, Empty, Empty), Node(0, 24, Empty, Empty)) + ) |> AVLSet.delete 15 let correctSet = - Node(2, 16, - Node(1, 10, - Node(0, 6, Empty, Empty), - Node(0, 12, Empty, Empty)), - Node(1, 20, - Empty, - Node(0, 24, Empty, Empty))) + Node( + 2, + 16, + Node(1, 10, Node(0, 6, Empty, Empty), Node(0, 12, Empty, Empty)), + Node(1, 20, Empty, Node(0, 24, Empty, Empty)) + ) resultSet |> should equal correctSet [] let ``Deletion with cascading rebalance`` () = let resultSet = - Node(2, 15, - Node(1, 10, - Node(0, 6, Empty, Empty), - Node(0, 12, Empty, Empty)), - Node(0, 20, Empty, Empty)) + Node(2, 15, Node(1, 10, Node(0, 6, Empty, Empty), Node(0, 12, Empty, Empty)), Node(0, 20, Empty, Empty)) |> AVLSet.delete 15 let correctSet = - Node(2, 10, - Node(0, 6, Empty, Empty), - Node(1, 20, - Node(0, 12, Empty, Empty), - Empty)) + Node(2, 10, Node(0, 6, Empty, Empty), Node(1, 20, Node(0, 12, Empty, Empty), Empty)) resultSet |> should equal correctSet [] let ``Complex multi-level deletion`` () = let resultSet = - Node(3, 15, - Node(1, 10, - Node(0, 6, Empty, Empty), - Node(0, 12, Empty, Empty)), - Node(2, 25, + Node( + 3, + 15, + Node(1, 10, Node(0, 6, Empty, Empty), Node(0, 12, Empty, Empty)), + Node( + 2, + 25, Node(0, 20, Empty, Empty), - Node(1, 30, - Node(0, 27, Empty, Empty), - Node(0, 33, Empty, Empty)))) + Node(1, 30, Node(0, 27, Empty, Empty), Node(0, 33, Empty, Empty)) + ) + ) |> AVLSet.delete 15 let correctSet = - Node(3, 20, - Node(1, 10, - Node(0, 6, Empty, Empty), - Node(0, 12, Empty, Empty)), - Node(2, 30, - Node(1, 25, - Empty, - Node(0, 27, Empty, Empty)), - Node(0, 33, Empty, Empty))) + Node( + 3, + 20, + Node(1, 10, Node(0, 6, Empty, Empty), Node(0, 12, Empty, Empty)), + Node(2, 30, Node(1, 25, Empty, Node(0, 27, Empty, Empty)), Node(0, 33, Empty, Empty)) + ) resultSet |> should equal correctSet [] let ``Search for missing element`` () = let set = - Node(3, 15, - Node(1, 10, - Node(0, 6, Empty, Empty), - Node(0, 12, Empty, Empty)), - Node(2, 25, + Node( + 3, + 15, + Node(1, 10, Node(0, 6, Empty, Empty), Node(0, 12, Empty, Empty)), + Node( + 2, + 25, Node(0, 20, Empty, Empty), - Node(1, 30, - Node(0, 27, Empty, Empty), - Node(0, 33, Empty, Empty)))) + Node(1, 30, Node(0, 27, Empty, Empty), Node(0, 33, Empty, Empty)) + ) + ) AVLSet.contains 100 set |> should be False [] let ``Search for negative value`` () = let set = - Node(3, 15, - Node(2, 5, - Node(1, 0, - Node(0, -3, Empty, Empty), - Node(0, 2, Empty, Empty)), - Node(0, 10, Empty, Empty)), - Node(1, 20, - Node(0, 16, Empty, Empty), - Node(0, 24, Empty, Empty))) + Node( + 3, + 15, + Node(2, 5, Node(1, 0, Node(0, -3, Empty, Empty), Node(0, 2, Empty, Empty)), Node(0, 10, Empty, Empty)), + Node(1, 20, Node(0, 16, Empty, Empty), Node(0, 24, Empty, Empty)) + ) AVLSet.contains -3 set |> should be True [] let ``Tree structure cloning`` () = let set = - Node(3, 15, - Node(2, 5, - Node(1, 0, - Node(0, -3, Empty, Empty), - Node(0, 2, Empty, Empty)), - Node(0, 10, Empty, Empty)), - Node(1, 20, - Node(0, 16, Empty, Empty), - Node(0, 24, Empty, Empty))) + Node( + 3, + 15, + Node(2, 5, Node(1, 0, Node(0, -3, Empty, Empty), Node(0, 2, Empty, Empty)), Node(0, 10, Empty, Empty)), + Node(1, 20, Node(0, 16, Empty, Empty), Node(0, 24, Empty, Empty)) + ) AVLSet.copy set |> should equal set - + let rnd = System.Random(123561) let dataA = Array.init 10000 (fun _ -> rnd.Next()) @@ -353,8 +280,11 @@ module SetTests = let unionSet = AVLSet.union setA setB isSetValid unionSet None None |> should be True - (advancedContains (fun v x -> x = true) setA unionSet && - advancedContains (fun v x -> x = true) setB unionSet) |> should be True + + (advancedContains (fun v x -> x = true) setA unionSet + && advancedContains (fun v x -> x = true) setB unionSet) + |> should be True + AVLSet.union setB setA |> should equal unionSet [] @@ -362,8 +292,10 @@ module SetTests = let intersectionSet = AVLSet.intersection setA setB isSetValid intersectionSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) - setA intersectionSet |> should be True + + advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) setA intersectionSet + |> should be True + AVLSet.intersection setB setA |> should equal intersectionSet [] @@ -371,18 +303,20 @@ module SetTests = let differenceSet = AVLSet.difference setA setB isSetValid differenceSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) - setA differenceSet |> should be True + + advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA differenceSet + |> should be True [] let ``Standard symmetric difference`` () = let symmDiffSet = AVLSet.symmDifference setA setB isSetValid symmDiffSet None None |> should be True - (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) - setA symmDiffSet && - advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) - setB symmDiffSet) |> should be True + + (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA symmDiffSet + && advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) setB symmDiffSet) + |> should be True + AVLSet.symmDifference setB setA |> should equal symmDiffSet [] @@ -390,17 +324,22 @@ module SetTests = let unionSet = AVLSet.unionTraversal setA setB isSetValid unionSet None None |> should be True - (advancedContains (fun v x -> x = true) setA unionSet && - advancedContains (fun v x -> x = true) setB unionSet) |> should be True + + (advancedContains (fun v x -> x = true) setA unionSet + && advancedContains (fun v x -> x = true) setB unionSet) + |> should be True + AVLSet.unionTraversal setB setA |> should equal unionSet - + [] let ``Intersection via tree traversal`` () = let intersectionSet = AVLSet.intersectionTraversal setA setB isSetValid intersectionSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) - setA intersectionSet |> should be True + + advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) setA intersectionSet + |> should be True + AVLSet.intersectionTraversal setB setA |> should equal intersectionSet [] @@ -408,18 +347,20 @@ module SetTests = let differenceSet = AVLSet.differenceTraversal setA setB isSetValid differenceSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) - setA differenceSet |> should be True + + advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA differenceSet + |> should be True [] let ``Symmetric difference via tree traversal`` () = let symmDiffSet = AVLSet.symmDifferenceTraversal setA setB isSetValid symmDiffSet None None |> should be True - (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) - setA symmDiffSet && - advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) - setB symmDiffSet) |> should be True + + (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA symmDiffSet + && advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) setB symmDiffSet) + |> should be True + AVLSet.symmDifferenceTraversal setB setA |> should equal symmDiffSet [] @@ -429,8 +370,11 @@ module SetTests = let unionSet = AVLSet.parallelUnion opts setA setB isSetValid unionSet None None |> should be True - (advancedContains (fun v x -> x = true) setA unionSet && - advancedContains (fun v x -> x = true) setB unionSet) |> should be True + + (advancedContains (fun v x -> x = true) setA unionSet + && advancedContains (fun v x -> x = true) setB unionSet) + |> should be True + AVLSet.parallelUnion opts setB setA |> should equal unionSet [] @@ -440,8 +384,10 @@ module SetTests = let intersectionSet = AVLSet.parallelIntersection opts setA setB isSetValid intersectionSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) - setA intersectionSet |> should be True + + advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) setA intersectionSet + |> should be True + AVLSet.parallelIntersection opts setB setA |> should equal intersectionSet [] @@ -451,8 +397,9 @@ module SetTests = let differenceSet = AVLSet.parallelDifference opts setA setB isSetValid differenceSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) - setA differenceSet |> should be True + + advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA differenceSet + |> should be True [] let ``Parallel set symmetrical difference with threads`` () = @@ -461,8 +408,9 @@ module SetTests = let symmDiffSet = AVLSet.parallelSymmDifference opts setA setB isSetValid symmDiffSet None None |> should be True - (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) - setA symmDiffSet && - advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) - setB symmDiffSet) |> should be True - AVLSet.parallelSymmDifference opts setB setA |> should equal symmDiffSet \ No newline at end of file + + (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA symmDiffSet + && advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) setB symmDiffSet) + |> should be True + + AVLSet.parallelSymmDifference opts setB setA |> should equal symmDiffSet From 7f0370a4ae110720f15cebe2a05437831d4b90ac Mon Sep 17 00:00:00 2001 From: Taranukha Leonid Date: Tue, 28 Apr 2026 21:45:23 +0300 Subject: [PATCH 2/3] chore: add CI workflow, update formatting and minor refactor --- .editorconfig | 21 ------------------ .github/workflows/ci.yml | 34 +++++++++++++++++++++++++++++ src/AVLSet.Library/Library.fs | 37 ++++++++++++++++---------------- tests/AVLSet.UnitTests/Tests.fs | 38 ++++++++++++++++----------------- 4 files changed, 72 insertions(+), 58 deletions(-) delete mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index d65e3b7..0000000 --- a/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -[*.fs] -fsharp_space_before_lowercase_invocation = true -fsharp_multiline_block_brackets_on_same_line = true -root = true - -[*] -indent_style = space -indent_size = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.{fs,fsi,fsx}] -max_line_length = 120 -fsharp_space_before_lowercase_invocation = true -fsharp_multiline_block_brackets_on_same_line = true -fsharp_array_or_list_multiline_formatter = character_width -fsharp_single_line_lambda_with_no_args = true -indent_style = space -indent_size = 4indent_size = 4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2a52513 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: .NET CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Restore tools + run: dotnet tool restore + + - name: Check Formatting + run: dotnet fantomas . --check + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore -c Release + + - name: Run Unit Tests + run: dotnet test tests/AVLSet.UnitTests/AVLSet.UnitTests.fsproj --no-build -c Release diff --git a/src/AVLSet.Library/Library.fs b/src/AVLSet.Library/Library.fs index 3d2db22..f80533b 100644 --- a/src/AVLSet.Library/Library.fs +++ b/src/AVLSet.Library/Library.fs @@ -2,9 +2,9 @@ open System.Threading.Tasks -type AVLTree<'value> = +type AVLTree<'Value> = | Empty - | Node of int * 'value * AVLTree<'value> * AVLTree<'value> + | Node of int * 'Value * AVLTree<'Value> * AVLTree<'Value> module Node = let height n = @@ -27,7 +27,7 @@ module Node = | Empty -> failwith "Empty node has no right child" | Node(_, _, _, rn) -> rn - let maxMinNodes n1 n2 = + let maxMinNodesByHeights n1 n2 = if height n1 >= height n2 then n1, n2 else n2, n1 module Tree = @@ -82,7 +82,7 @@ module Tree = let rec minNode n = match n with - | Empty -> failwith "Empty node has no value" + | Empty -> failwith "minNode: cannot find minimum of an empty node" | Node(_, v, Empty, rn) -> v, rn | Node(_, v, ln, rn) -> let value, lnNew = minNode ln @@ -120,6 +120,7 @@ module Tree = let rnNew = remove value rn balance ln rnNew v + [] let rec contains value n = match n with | Empty -> false @@ -129,7 +130,7 @@ module Tree = | value when value < v -> contains value ln | _ -> contains value rn - let rec traversal (func: 'a -> AVLTree<'b> -> AVLTree<'b>) nArg n = + let rec traversal (func: 'A -> AVLTree<'B> -> AVLTree<'B>) nArg n = match n with | Empty -> nArg | Node(_, v, ln, rn) -> @@ -150,13 +151,13 @@ module Tree = | diff when abs diff <= 1 -> Node(max leftHeight rightHeight + 1, key, left, right) | diff when diff >= 2 -> match left with - | Empty -> failwith "Unreacheable message" + | Empty -> failwith "Unreacheable message 1" | Node(h, v, ln, rn) -> let rnNew = join rn key right balance ln rnNew v | _ -> match right with - | Empty -> failwith "Unreacheable message" + | Empty -> failwith "Unreacheable message 2" | Node(h, v, ln, rn) -> let lnNew = join left key ln balance lnNew rn v @@ -194,7 +195,7 @@ module AVLSet = let copy set = Tree.copy set let rec union set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 match maxSet, minSet with | Empty, _ -> minSet @@ -206,7 +207,7 @@ module AVLSet = Tree.join leftUnion v rightUnion let rec intersection set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 match maxSet, minSet with | Empty, _ -> Empty @@ -236,7 +237,7 @@ module AVLSet = Tree.join leftDiff v rightDiff let rec symmDifference set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 match maxSet, minSet with | Empty, _ -> minSet @@ -252,12 +253,12 @@ module AVLSet = Tree.join leftSymm v rightSymm let unionTraversal set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 let unSet = Tree.copy maxSet - Tree.traversal (fun value set -> Tree.insert value set) unSet minSet + Tree.traversal Tree.insert unSet minSet let intersectionTraversal set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 Tree.traversal (fun value set -> @@ -270,10 +271,10 @@ module AVLSet = let differenceTraversal minuendSet subtrahendSet = let diffSet = Tree.copy minuendSet - Tree.traversal (fun value set -> Tree.remove value set) diffSet subtrahendSet + Tree.traversal Tree.remove diffSet subtrahendSet let symmDifferenceTraversal set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 let symmSet = Tree.copy maxSet Tree.traversal @@ -286,7 +287,7 @@ module AVLSet = minSet let rec parallelUnion (opts: ParallelOptions) set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 match maxSet, minSet with | Empty, _ -> minSet @@ -305,7 +306,7 @@ module AVLSet = Tree.join leftUnion v rightUnion let rec parallelIntersection (opts: ParallelOptions) set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 match maxSet, minSet with | Empty, _ -> Empty @@ -347,7 +348,7 @@ module AVLSet = Tree.join leftDiff v rightDiff let rec parallelSymmDifference (opts: ParallelOptions) set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 match maxSet, minSet with | Empty, _ -> minSet diff --git a/tests/AVLSet.UnitTests/Tests.fs b/tests/AVLSet.UnitTests/Tests.fs index d3efef1..54982a7 100644 --- a/tests/AVLSet.UnitTests/Tests.fs +++ b/tests/AVLSet.UnitTests/Tests.fs @@ -22,7 +22,7 @@ module SetTests = && isSetValid ln mn (Some v) && isSetValid rn (Some v) mx - let rec advancedContains (condition: 'a -> bool -> bool) setOfValues targetSet = + let rec advancedContains (condition: 'A -> bool -> bool) setOfValues targetSet = match setOfValues with | Empty -> true | Node(_, v, ln, rn) -> @@ -281,8 +281,8 @@ module SetTests = isSetValid unionSet None None |> should be True - (advancedContains (fun v x -> x = true) setA unionSet - && advancedContains (fun v x -> x = true) setB unionSet) + (advancedContains (fun v x -> x) setA unionSet + && advancedContains (fun v x -> x) setB unionSet) |> should be True AVLSet.union setB setA |> should equal unionSet @@ -293,7 +293,7 @@ module SetTests = isSetValid intersectionSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) setA intersectionSet + advancedContains (fun v x -> if AVLSet.contains v setB then x else not x) setA intersectionSet |> should be True AVLSet.intersection setB setA |> should equal intersectionSet @@ -304,7 +304,7 @@ module SetTests = isSetValid differenceSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA differenceSet + advancedContains (fun v x -> if AVLSet.contains v setB then not x else x) setA differenceSet |> should be True [] @@ -313,8 +313,8 @@ module SetTests = isSetValid symmDiffSet None None |> should be True - (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA symmDiffSet - && advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) setB symmDiffSet) + (advancedContains (fun v x -> if AVLSet.contains v setB then not x else x) setA symmDiffSet + && advancedContains (fun v x -> if AVLSet.contains v setA then not x else x) setB symmDiffSet) |> should be True AVLSet.symmDifference setB setA |> should equal symmDiffSet @@ -325,8 +325,8 @@ module SetTests = isSetValid unionSet None None |> should be True - (advancedContains (fun v x -> x = true) setA unionSet - && advancedContains (fun v x -> x = true) setB unionSet) + (advancedContains (fun v x -> x) setA unionSet + && advancedContains (fun v x -> x) setB unionSet) |> should be True AVLSet.unionTraversal setB setA |> should equal unionSet @@ -337,7 +337,7 @@ module SetTests = isSetValid intersectionSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) setA intersectionSet + advancedContains (fun v x -> if AVLSet.contains v setB then x else not x) setA intersectionSet |> should be True AVLSet.intersectionTraversal setB setA |> should equal intersectionSet @@ -348,7 +348,7 @@ module SetTests = isSetValid differenceSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA differenceSet + advancedContains (fun v x -> if AVLSet.contains v setB then not x else x) setA differenceSet |> should be True [] @@ -357,8 +357,8 @@ module SetTests = isSetValid symmDiffSet None None |> should be True - (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA symmDiffSet - && advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) setB symmDiffSet) + (advancedContains (fun v x -> if AVLSet.contains v setB then not x else x = true) setA symmDiffSet + && advancedContains (fun v x -> if AVLSet.contains v setA then not x else x) setB symmDiffSet) |> should be True AVLSet.symmDifferenceTraversal setB setA |> should equal symmDiffSet @@ -371,8 +371,8 @@ module SetTests = isSetValid unionSet None None |> should be True - (advancedContains (fun v x -> x = true) setA unionSet - && advancedContains (fun v x -> x = true) setB unionSet) + (advancedContains (fun v x -> x) setA unionSet + && advancedContains (fun v x -> x) setB unionSet) |> should be True AVLSet.parallelUnion opts setB setA |> should equal unionSet @@ -385,7 +385,7 @@ module SetTests = isSetValid intersectionSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = true else x = false) setA intersectionSet + advancedContains (fun v x -> if AVLSet.contains v setB then x else not x) setA intersectionSet |> should be True AVLSet.parallelIntersection opts setB setA |> should equal intersectionSet @@ -398,7 +398,7 @@ module SetTests = isSetValid differenceSet None None |> should be True - advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA differenceSet + advancedContains (fun v x -> if AVLSet.contains v setB then not x else x) setA differenceSet |> should be True [] @@ -409,8 +409,8 @@ module SetTests = isSetValid symmDiffSet None None |> should be True - (advancedContains (fun v x -> if AVLSet.contains v setB then x = false else x = true) setA symmDiffSet - && advancedContains (fun v x -> if AVLSet.contains v setA then x = false else x = true) setB symmDiffSet) + (advancedContains (fun v x -> if AVLSet.contains v setB then not x else x) setA symmDiffSet + && advancedContains (fun v x -> if AVLSet.contains v setA then not x else x) setB symmDiffSet) |> should be True AVLSet.parallelSymmDifference opts setB setA |> should equal symmDiffSet From 3ed656bd76c017e4626df8dd87df5ce57cac27e4 Mon Sep 17 00:00:00 2001 From: Taranukha Leonid Date: Wed, 29 Apr 2026 16:02:58 +0300 Subject: [PATCH 3/3] docs: update README.md --- README.md | 115 +++++++++++++++++++++++++++++++++- src/AVLSet.Library/Library.fs | 14 ++--- 2 files changed, 121 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 206217b..2f8b949 100644 --- a/README.md +++ b/README.md @@ -1 +1,114 @@ -# AVLSetFSharp \ No newline at end of file +# AVLSetFSharp + +![.NET CI](https://github.com/LeoN192/AVLSetFSharp/actions/workflows/ci.yml/badge.svg) +![Formatter](https://img.shields.io/badge/format-Fantomas-blue?logo=fsharp&logoColor=white) +![.NET](https://img.shields.io/badge/.NET-10.0-purple?logo=dotnet&logoColor=white) +![License](https://img.shields.io/badge/license-BSD_3--Clause-blue) + +## Overview + +This repository contains a high-performance, purely functional **Set** data structure implemented using a self-balancing **AVL Tree** in F#. + +Unlike standard library collections, this implementation focuses on efficient set-theoretic operations (Union, Intersection, Difference, Symmetrical Difference) using the **Split/Join** algorithm, providing a solid foundation for both sequential and parallel data processing. + +## Technical Details & Architecture + +The project is built on the principles of immutability and persistent data structures. Every modification returns a new version of the set, while maximizing node sharing to optimize memory usage. + +### Core Algorithms & Complexity +1. **Basic Operations**: `add`, `delete`, `contains` are implemented with $O(\log n)$ time complexity. +2. **Set-theoretic Operations**: `union`, `intersection`, `difference`, `symmetrical difference` utilize the **Split & Join** approach. + * Efficiency: This reduces complexity to $O(m \log (n/m))$ where m is the size of the smaller set. +3. **Parallelism**: Recursive set operations are implemented using `Parallel.Invoke`. + +### Invariants & Balancing +* Balance Factor: For every node, the height difference between left and right subtrees is at most 1. +* Rotations: Four types of rotations (LL, RR, LR, RL) are performed automatically. + +--- + +## Quick Start + +### Requirements +* .NET SDK 10.0+ +* Fantomas + +### 1. Setup & Build +```bash +# Restore local tools (Fantomas) +dotnet tool restore + +# Build the entire solution +dotnet build -c Release +``` + +### 2. Run Tests +```bash +dotnet test +``` + +### 3. Run Benchmarks +```bash +dotnet run -c Release --project benchmarks/AVLSet.Benchmarks +``` + +--- + +## Usage Example + +```fsharp +open AVLSet.Library + +// 1. Create sets from sequences +let setA = [1..10] |> List.fold (fun s v -> AVLSet.add v s) AVLSet.empty +let setB = [5..15] |> List.fold (fun s v -> AVLSet.add v s) AVLSet.empty + +// 2. Perform set operations +let unionSet = AVLSet.union setA setB +let interSet = AVLSet.intersection setA setB + +// 3. Check membership +if AVLSet.contains 7 interSet then + printfn "Intersection contains 7" + +// 4. Parallel operations for large data +let opts = System.Threading.Tasks.ParallelOptions(MaxDegreeOfParallelism = 4) +let largeUnion = AVLSet.parallelUnion opts setA setB +``` + +--- + +## API Reference + +The `AVLSet` module provides a comprehensive interface: + +| Function | Signature | Description | +|:---|:---|:---| +| **add** | `'a -> AVLTree<'a> -> AVLTree<'a>` | Adds an element. | +| **delete** | `'a -> AVLTree<'a> -> AVLTree<'a>` | Removes an element. | +| **contains** | `'a -> AVLTree<'a> -> bool` | Checks membership. | +| **union** | `AVLTree<'a> -> AVLTree<'a> -> AVLTree<'a>` | Standard union ($A \cup B$). | +| **intersection** | `AVLTree<'a> -> AVLTree<'a> -> AVLTree<'a>` | Standard intersection ($A \cap B$). | +| **difference** | `AVLTree<'a> -> AVLTree<'a> -> AVLTree<'a>` | Standard difference ($A \setminus B$). | +| **symmDifference** | `AVLTree<'a> -> AVLTree<'a> -> AVLTree<'a>` | Standard symmetrical difference ($A \vartriangle B$). | +| **parallel(Union/Intersection/Difference/SymmDiff)**| `ParallelOptions -> AVLTree<'a> -> AVLTree<'a> -> AVLTree<'a>` | Multi-threaded set-theoretic operations. | +| **(union/intersection/difference/symmDiff)Traversal**| `AVLTree<'a> -> AVLTree<'a> -> AVLTree<'a>` | Set-theoretic operations via tree traversal. | + +--- + +## Project Structure +```text +/src +└── AVLSet.Library + ├── AVLSet.Library.fsproj +   └── Library.fs +/tests +└── AVLSet.UnitTests + ├── AVLSet.UnitTests.fsproj +   └── Tests.fs +/benchmarks +└── AVLSet.Benchmarks + ├── AVLSet.Benchmarks.fsproj +   ├── Program.fs + └── Bemchmarks.fs +``` \ No newline at end of file diff --git a/src/AVLSet.Library/Library.fs b/src/AVLSet.Library/Library.fs index f80533b..d4e36fa 100644 --- a/src/AVLSet.Library/Library.fs +++ b/src/AVLSet.Library/Library.fs @@ -130,13 +130,13 @@ module Tree = | value when value < v -> contains value ln | _ -> contains value rn - let rec traversal (func: 'A -> AVLTree<'B> -> AVLTree<'B>) nArg n = + let rec traverse (func: 'A -> AVLTree<'B> -> AVLTree<'B>) nArg n = match n with | Empty -> nArg | Node(_, v, ln, rn) -> - let newNArg = traversal func nArg ln + let newNArg = traverse func nArg ln let newNArg2 = func v newNArg - traversal func newNArg2 rn + traverse func newNArg2 rn let rec copy n = match n with @@ -255,12 +255,12 @@ module AVLSet = let unionTraversal set1 set2 = let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 let unSet = Tree.copy maxSet - Tree.traversal Tree.insert unSet minSet + Tree.traverse Tree.insert unSet minSet let intersectionTraversal set1 set2 = let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 - Tree.traversal + Tree.traverse (fun value set -> if Tree.contains value maxSet then Tree.insert value set @@ -271,13 +271,13 @@ module AVLSet = let differenceTraversal minuendSet subtrahendSet = let diffSet = Tree.copy minuendSet - Tree.traversal Tree.remove diffSet subtrahendSet + Tree.traverse Tree.remove diffSet subtrahendSet let symmDifferenceTraversal set1 set2 = let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 let symmSet = Tree.copy maxSet - Tree.traversal + Tree.traverse (fun value set -> if Tree.contains value maxSet then Tree.remove value set