diff --git a/Algorithms.Tests/DataStructures/Treap.fs b/Algorithms.Tests/DataStructures/Treap.fs new file mode 100644 index 0000000..50b252f --- /dev/null +++ b/Algorithms.Tests/DataStructures/Treap.fs @@ -0,0 +1,95 @@ +namespace Algorithms.Tests.DataStructures + +open Microsoft.VisualStudio.TestTools.UnitTesting +open Algorithms.DataStructures.Treap + +[] +type TreapTests () = + + [] + member this.``Test basic operations``() = + let treap = + empty + |> insert 5 + |> insert 3 + |> insert 7 + + // Test getKthElement (0-based indexing) + Assert.AreEqual(Some 3, getKthElement treap 0u) + Assert.AreEqual(Some 5, getKthElement treap 1u) + Assert.AreEqual(Some 7, getKthElement treap 2u) + Assert.AreEqual(None, getKthElement treap 3u) + + // Test getIndex + Assert.AreEqual(Some 0, getIndex treap 3) + Assert.AreEqual(Some 1, getIndex treap 5) + Assert.AreEqual(Some 2, getIndex treap 7) + Assert.AreEqual(None, getIndex treap 4) + + [] + member this.``Test empty treap``() = + let treap = empty + Assert.AreEqual(None, getKthElement treap 0u) + Assert.AreEqual(None, getIndex treap 5) + + [] + member this.``Test deletion``() = + let mutable treap = + empty + |> insert 5 + |> insert 3 + |> insert 7 + + // Delete middle element + treap <- erase 5 treap + Assert.AreEqual(Some 3, getKthElement treap 0u) + Assert.AreEqual(Some 7, getKthElement treap 1u) + Assert.AreEqual(None, getKthElement treap 2u) + + // Delete non-existent element + treap <- erase 5 treap + Assert.AreEqual(Some 3, getKthElement treap 0u) + Assert.AreEqual(Some 7, getKthElement treap 1u) + + [] + member this.``Test duplicate insertion``() = + let mutable treap = empty + treap <- insert 3 treap + treap <- insert 5 treap + treap <- insert 1 treap + treap <- insert 3 treap + + Assert.AreEqual(Some 1, getKthElement treap 0u) + Assert.AreEqual(Some 3, getKthElement treap 1u) + Assert.AreEqual(Some 5, getKthElement treap 2u) + Assert.AreEqual(None, getKthElement treap 3u) + + treap <- erase 3 treap + Assert.AreEqual(Some 1, getKthElement treap 0u) + Assert.AreEqual(Some 5, getKthElement treap 1u) + Assert.AreEqual(None, getKthElement treap 2u) + + + [] + member this.``Test order preservation``() = + let mutable treap = empty + + // Insert in non-sorted order + treap <- insert 8 treap + treap <- insert 3 treap + treap <- insert 10 treap + treap <- insert 1 treap + treap <- insert 6 treap + treap <- insert 4 treap + treap <- insert 7 treap + treap <- insert 14 treap + treap <- insert 13 treap + + // Verify elements are retrievable in sorted order + for i in 0u..8u do + let expected = + match i with + | 0u -> 1 | 1u -> 3 | 2u -> 4 | 3u -> 6 | 4u -> 7 + | 5u -> 8 | 6u -> 10 | 7u -> 13 | 8u -> 14 + | _ -> failwith "Unexpected index" + Assert.AreEqual(Some expected, getKthElement treap i) diff --git a/Algorithms/DataStructures/Treap.fs b/Algorithms/DataStructures/Treap.fs new file mode 100644 index 0000000..3d9f66c --- /dev/null +++ b/Algorithms/DataStructures/Treap.fs @@ -0,0 +1,124 @@ +namespace Algorithms.DataStructures + +module Treap = + type TreapNode = { + Value: int + Priority: int64 + Size: int + LeftChild: Option + RightChild: Option + } + + module TreapNode = + let create (value: int) : TreapNode = + { + Value = value + Priority = System.Random().NextInt64() + Size = 1 + LeftChild = None + RightChild = None + } + + let getSize (maybeNode: Option) : int = + maybeNode + |> Option.map (fun node -> node.Size) + |> Option.defaultValue 0 + + type TreapNode + with + member this.UpdateLeftChild (leftChild: Option) : TreapNode = + { + this with + LeftChild = leftChild + Size = 1 + TreapNode.getSize leftChild + TreapNode.getSize this.RightChild + } + member this.UpdateRightChild (rightChild: Option) : TreapNode = + { + this with + RightChild = rightChild + Size = 1 + TreapNode.getSize this.LeftChild + TreapNode.getSize rightChild + } + + [] + type Treap = { + Root: Option + } + + let empty : Treap = { Root = None } + + /// Splits treap into two parts based on value + /// Returns (left, right) tuple where: + /// - left contains all nodes with values < split value + /// - right contains all nodes with values >= split value + let rec split (root: Option) (value: int) : Option * Option = + match root with + | None -> + None, None + | Some root -> + if root.Value < value then + // root node belongs to the left + // split the right child of root and update the right child of root + let updatedRightOfRoot, right = split root.RightChild value + root.UpdateRightChild updatedRightOfRoot |> Some, right + else + // root node belongs to the right + // split the left child of root and update the left child of root + let left, updatedLeftOfRoot = split root.LeftChild value + left, root.UpdateLeftChild updatedLeftOfRoot |> Some + + /// Merges two treaps maintaining BST and heap properties + /// Assumes all values in left treap are less than all values in right treap + let rec merge (left: Option) (right: Option) : Option = + match left, right with + | None, right -> right + | left, None -> left + | Some left, Some right -> + if left.Priority < right.Priority then + // left node is the root of the merged treap, merge its right child with right treap + let updatedLeftsRightChild = merge left.RightChild (Some right) + left.UpdateRightChild updatedLeftsRightChild |> Some + else + // right node is the root of the merged treap, merge its left child with left treap + let updatedRightsLeftChild = merge (Some left) right.LeftChild + right.UpdateLeftChild updatedRightsLeftChild |> Some + + // Inserts a new value into the treap, noop if value already exists + let insert (value: int) (treap: Treap) : Treap = + let node = TreapNode.create value + let left, right = split treap.Root value + let _, right = split right (value + 1) + merge (merge left (Some node)) right + |> fun root -> { Root = root } + + let erase (value: int) (treap: Treap) : Treap = + let left, right = split treap.Root value + let _, right = split right (value + 1) + merge left right + |> fun root -> { Root = root } + + /// Gets the kth smallest element in the treap (0-indexed) + /// Returns None if k is out of bounds + let getKthElement (treap: Treap) (k: uint) : Option = + if TreapNode.getSize treap.Root |> uint <= k then + None + else + let rec getKthElementImpl (root: TreapNode) (k: int) : int = + assert (k < root.Size) + if root.Size = 1 then + root.Value + else + if k < TreapNode.getSize root.LeftChild then + getKthElementImpl (root.LeftChild |> Option.get) k + elif k = TreapNode.getSize root.LeftChild then + root.Value + else + getKthElementImpl (root.RightChild |> Option.get) (k - TreapNode.getSize root.LeftChild - 1) + getKthElementImpl (treap.Root |> Option.get) (int k) |> Some + + /// Gets the index of a value in the treap (0 indexed position in sorted order) + /// Returns None if value is not found + let getIndex (treap: Treap) (value: int) : Option = + let left, right = split treap.Root value + let node, _right = split right (value + 1) + node + |> Option.map (fun _ -> TreapNode.getSize left) \ No newline at end of file diff --git a/DIRECTORY.md b/DIRECTORY.md index cd14151..e354294 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -2,6 +2,7 @@ ## Algorithms.Tests * Datastructures + * [Treap](https://github.com/TheAlgorithms/F-Sharp/blob/main/Algorithms.Tests/DataStructures/Treap.fs) * [Trie](https://github.com/TheAlgorithms/F-Sharp/blob/main/Algorithms.Tests/DataStructures/Trie.fs) * Math * [Absmaxtests](https://github.com/TheAlgorithms/F-Sharp/blob/main/Algorithms.Tests/Math/AbsMaxTests.fs) @@ -40,6 +41,7 @@ ## Algorithms * Datastructures + * [Treap](https://github.com/TheAlgorithms/F-Sharp/blob/main/Algorithms/DataStructures/Treap.fs) * [Trie](https://github.com/TheAlgorithms/F-Sharp/blob/main/Algorithms/DataStructures/Trie.fs) * Math * [Abs](https://github.com/TheAlgorithms/F-Sharp/blob/main/Algorithms/Math/Abs.fs)