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/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/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..d4e36fa 100644 --- a/src/AVLSet.Library/Library.fs +++ b/src/AVLSet.Library/Library.fs @@ -2,11 +2,11 @@ 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 = +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 maxMinNodesByHeights 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" + | 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 - 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,22 +120,23 @@ 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 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 @@ -146,23 +146,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 - | Empty -> failwith "Unreacheable message" - | Node (h, v, ln, rn) -> + match left with + | 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" - | Node (h, v, ln, rn) -> + | _ -> + match right with + | Empty -> failwith "Unreacheable message 2" + | 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 +170,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 + let maxSet, minSet = Node.maxMinNodesByHeights 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 + let maxSet, minSet = Node.maxMinNodesByHeights 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 + let maxSet, minSet = Node.maxMinNodesByHeights 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 maxSet, minSet = Node.maxMinNodesByHeights set1 set2 let unSet = Tree.copy maxSet - Tree.traversal (fun value set -> Tree.insert value set) unSet minSet + Tree.traverse Tree.insert unSet minSet - 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 + let intersectionTraversal set1 set2 = + let maxSet, minSet = Node.maxMinNodesByHeights set1 set2 + + Tree.traverse + (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 + Tree.traverse Tree.remove diffSet subtrahendSet - let symmDifferenceTraversal set1 set2 = - let maxSet, minSet = Node.maxMinNodes set1 set2 + let symmDifferenceTraversal set1 set2 = + let maxSet, minSet = Node.maxMinNodesByHeights 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.traverse + (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 + let maxSet, minSet = Node.maxMinNodesByHeights 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 + let maxSet, minSet = Node.maxMinNodesByHeights 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 +335,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 + let maxSet, minSet = Node.maxMinNodesByHeights 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..54982a7 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) setA unionSet + && advancedContains (fun v x -> x) 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 else not x) 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 not x else x) 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 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 [] @@ -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) setA unionSet + && advancedContains (fun v x -> x) 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 else not x) 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 not x else x) 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 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 [] @@ -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) setA unionSet + && advancedContains (fun v x -> x) 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 else not x) 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 not x else x) 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 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