├── .github └── workflows │ └── go.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── avltree ├── avltree.go └── avltree_test.go ├── bloomfilter ├── bloomfilter.go └── bloomfilter_test.go ├── bst ├── bst.go └── bst_test.go ├── collections └── interfaces.go ├── deque ├── dequeue.go └── dequeue_test.go ├── disjointset ├── disjointset.go └── disjointset_test.go ├── go.mod ├── graph ├── graph.go ├── graph_test.go ├── iterator.go └── iterator_test.go ├── internal ├── cmp │ └── cmp.go └── slices │ └── slices.go ├── iterator └── interfaces.go ├── linkedlist ├── iterator.go ├── iterator_test.go ├── linkedlist.go └── linkedlist_test.go ├── priorityqueue ├── priorityqueue.go └── priorityqueue_test.go ├── queue ├── iterator.go ├── iterator_test.go ├── queue.go └── queue_test.go ├── rbtree ├── rbtree.go └── rbtree_test.go ├── ringbuffer ├── ringbuffer.go └── ringbuffer_test.go ├── segmenttree ├── segmenttree.go └── segmenttree_test.go ├── set ├── iterator.go ├── iterator_test.go ├── set.go └── set_test.go ├── skiplist ├── skiplist.go └── skiplist_test.go ├── stack ├── arraystack │ ├── array_stack.go │ └── array_stack_test.go ├── linkedliststack │ ├── linkedlist_stack.go │ └── linkedlist_stack_test.go └── stack.go ├── timedeque ├── time_deque.go └── time_deque_test.go └── trie ├── trie.go └── trie_test.go /.github/workflows/go.yaml: -------------------------------------------------------------------------------- 1 | name: Go CI and Create Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | test: 15 | name: Run Tests 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Check out code 20 | uses: actions/checkout@v3 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v3 24 | with: 25 | go-version: '1.18' 26 | 27 | - name: Run tests 28 | run: go test -count=3 -v ./... 29 | 30 | build: 31 | needs: test 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - name: Check out code 36 | uses: actions/checkout@v3 37 | 38 | - name: Set up Go 39 | uses: actions/setup-go@v3 40 | with: 41 | go-version: '1.18' 42 | 43 | - name: Build 44 | run: go build -v ./... 45 | 46 | create_release: 47 | needs: [test, build] 48 | runs-on: ubuntu-latest 49 | if: startsWith(github.ref, 'refs/tags/v') 50 | 51 | steps: 52 | - name: Checkout code 53 | uses: actions/checkout@v3 54 | 55 | - name: Create Release 56 | id: create_release 57 | uses: actions/create-release@v1 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | with: 61 | tag_name: ${{ github.ref }} 62 | release_name: Release ${{ github.ref }} 63 | draft: false 64 | prerelease: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to go-collections 2 | 3 | First off, thank you for considering contributing to go-collections! It's people like you who make go-collections such a great tool. 4 | 5 | ## Code of Conduct 6 | 7 | By participating in this project, you are expected to uphold our Code of Conduct: 8 | 9 | - Use welcoming and inclusive language 10 | - Be respectful of differing viewpoints and experiences 11 | - Gracefully accept constructive criticism 12 | - Focus on what is best for the community 13 | - Show empathy towards other community members 14 | 15 | ## How Can I Contribute? 16 | 17 | ### Reporting Bugs 18 | 19 | Before creating bug reports, please check the issue list as you might find out that you don't need to create one. When you are creating a bug report, please include as many details as possible: 20 | 21 | 1. **Use a clear and descriptive title** 22 | 2. **Describe the exact steps to reproduce the problem** 23 | 3. **Provide specific examples to demonstrate the steps** 24 | 4. **Describe the behavior you observed after following the steps** 25 | 5. **Explain which behavior you expected to see instead and why** 26 | 6. **Include your Go version (`go version`)** 27 | 7. **Include any relevant code snippets or test cases** 28 | 29 | ```go 30 | // Example of a good bug report code snippet 31 | ds := disjointset.New[int]() 32 | ds.MakeSet(1) 33 | ds.MakeSet(2) 34 | ds.Union(1, 2) 35 | 36 | // Expected: ds.Connected(1, 2) == true 37 | // Actual: ds.Connected(1, 2) == false 38 | ``` 39 | 40 | ### Suggesting Enhancements 41 | 42 | Enhancement suggestions are tracked as GitHub issues. When creating an enhancement suggestion, please include: 43 | 44 | 1. **Use a clear and descriptive title** 45 | 2. **Provide a step-by-step description of the suggested enhancement** 46 | 3. **Provide specific examples to demonstrate the steps** 47 | 4. **Describe the current behavior and explain which behavior you expected to see instead** 48 | 5. **Explain why this enhancement would be useful** 49 | 6. **List some other libraries or applications where this enhancement exists** 50 | 51 | ### Pull Requests 52 | 53 | 1. Fork the repo and create your branch from `main` 54 | 2. If you've added code that should be tested, add tests 55 | 3. If you've changed APIs, update the documentation 56 | 4. Ensure the test suite passes 57 | 5. Make sure your code follows the existing style 58 | 6. Issue that pull request! 59 | 60 | ## Development Setup 61 | 62 | 1. Install Go 1.18 or higher (for generics support) 63 | 2. Fork and clone the repository: 64 | ```bash 65 | git clone https://github.com/yourusername/go-collections.git 66 | cd go-collections 67 | ``` 68 | 3. Install dependencies: 69 | ```bash 70 | go mod download 71 | ``` 72 | 4. Run tests: 73 | ```bash 74 | go test ./... 75 | ``` 76 | 77 | ## Styleguides 78 | 79 | ### Git Commit Messages 80 | 81 | * Use the present tense ("Add feature" not "Added feature") 82 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 83 | * Limit the first line to 72 characters or less 84 | * Reference issues and pull requests liberally after the first line 85 | 86 | ### Go Code Styleguide 87 | 88 | * Follow the official [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) 89 | * Write idiomatic Go code: 90 | ```go 91 | // Good 92 | var items []string 93 | for _, item := range collection { 94 | items = append(items, item) 95 | } 96 | 97 | // Bad 98 | items := make([]string, 0) 99 | for i := 0; i < len(collection); i++ { 100 | items = append(items, collection[i]) 101 | } 102 | ``` 103 | * Use meaningful variable names 104 | * Add comments for exported functions and types 105 | * Maintain existing code style 106 | * Use `go fmt` before committing 107 | * Handle errors appropriately 108 | * Write table-driven tests 109 | 110 | ### Documentation Styleguide 111 | 112 | * Use [Markdown](https://guides.github.com/features/mastering-markdown/) for documentation 113 | * Reference functions and types with backticks: `Queue.Enqueue()` 114 | * Include code examples for new features 115 | * Document performance characteristics 116 | * Update README.md with any necessary changes 117 | * Add doc comments for all exported functions and types: 118 | ```go 119 | // Push adds an item to the top of the stack. 120 | // Returns false if the stack is full. 121 | func (s *Stack[T]) Push(item T) bool { 122 | // Implementation 123 | } 124 | ``` 125 | 126 | ## Testing 127 | 128 | * Write test cases for all new functionality 129 | * Use table-driven tests where appropriate: 130 | ```go 131 | func TestStack(t *testing.T) { 132 | tests := []struct { 133 | name string 134 | input []int 135 | expected int 136 | }{ 137 | {"single item", []int{1}, 1}, 138 | {"multiple items", []int{1, 2, 3}, 3}, 139 | // More test cases... 140 | } 141 | 142 | for _, tt := range tests { 143 | t.Run(tt.name, func(t *testing.T) { 144 | // Test implementation 145 | }) 146 | } 147 | } 148 | ``` 149 | * Aim for high test coverage 150 | * Include both positive and negative test cases 151 | * Test edge cases and error conditions 152 | 153 | ## Questions? 154 | 155 | Feel free to [open an issue](https://github.com/idsulik/go-collections/issues/new) if you have any questions about contributing! 156 | 157 | ## License 158 | 159 | By contributing, you agree that your contributions will be licensed under the MIT License. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Suleiman Dibirov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /avltree/avltree.go: -------------------------------------------------------------------------------- 1 | package avltree 2 | 3 | // Node represents a node in the AVL tree 4 | type Node[T any] struct { 5 | Value T 6 | Left, Right *Node[T] 7 | Height int 8 | } 9 | 10 | // AVLTree represents an AVL tree data structure 11 | type AVLTree[T any] struct { 12 | root *Node[T] 13 | size int 14 | compare func(a, b T) int 15 | } 16 | 17 | // New creates a new AVL tree 18 | func New[T any](compare func(a, b T) int) *AVLTree[T] { 19 | return &AVLTree[T]{ 20 | compare: compare, 21 | } 22 | } 23 | 24 | // getHeight returns the height of a node 25 | func (t *AVLTree[T]) getHeight(node *Node[T]) int { 26 | if node == nil { 27 | return -1 28 | } 29 | return node.Height 30 | } 31 | 32 | // getBalance returns the balance factor of a node 33 | func (t *AVLTree[T]) getBalance(node *Node[T]) int { 34 | if node == nil { 35 | return 0 36 | } 37 | return t.getHeight(node.Left) - t.getHeight(node.Right) 38 | } 39 | 40 | // updateHeight updates the height of a node 41 | func (t *AVLTree[T]) updateHeight(node *Node[T]) { 42 | node.Height = max(t.getHeight(node.Left), t.getHeight(node.Right)) + 1 43 | } 44 | 45 | // rotateRight performs a right rotation 46 | func (t *AVLTree[T]) rotateRight(y *Node[T]) *Node[T] { 47 | x := y.Left 48 | T2 := x.Right 49 | 50 | x.Right = y 51 | y.Left = T2 52 | 53 | t.updateHeight(y) 54 | t.updateHeight(x) 55 | 56 | return x 57 | } 58 | 59 | // rotateLeft performs a left rotation 60 | func (t *AVLTree[T]) rotateLeft(x *Node[T]) *Node[T] { 61 | y := x.Right 62 | T2 := y.Left 63 | 64 | y.Left = x 65 | x.Right = T2 66 | 67 | t.updateHeight(x) 68 | t.updateHeight(y) 69 | 70 | return y 71 | } 72 | 73 | // Insert adds a new value to the AVL tree 74 | func (t *AVLTree[T]) Insert(value T) { 75 | t.root = t.insert(t.root, value) 76 | t.size++ 77 | } 78 | 79 | // insert recursively inserts a value and balances the tree 80 | func (t *AVLTree[T]) insert(node *Node[T], value T) *Node[T] { 81 | if node == nil { 82 | return &Node[T]{Value: value, Height: 0} 83 | } 84 | 85 | comp := t.compare(value, node.Value) 86 | if comp < 0 { 87 | node.Left = t.insert(node.Left, value) 88 | } else if comp > 0 { 89 | node.Right = t.insert(node.Right, value) 90 | } else { 91 | return node // Duplicate value, ignore 92 | } 93 | 94 | t.updateHeight(node) 95 | balance := t.getBalance(node) 96 | 97 | // Left Left Case 98 | if balance > 1 && t.compare(value, node.Left.Value) < 0 { 99 | return t.rotateRight(node) 100 | } 101 | 102 | // Right Right Case 103 | if balance < -1 && t.compare(value, node.Right.Value) > 0 { 104 | return t.rotateLeft(node) 105 | } 106 | 107 | // Left Right Case 108 | if balance > 1 && t.compare(value, node.Left.Value) > 0 { 109 | node.Left = t.rotateLeft(node.Left) 110 | return t.rotateRight(node) 111 | } 112 | 113 | // Right Left Case 114 | if balance < -1 && t.compare(value, node.Right.Value) < 0 { 115 | node.Right = t.rotateRight(node.Right) 116 | return t.rotateLeft(node) 117 | } 118 | 119 | return node 120 | } 121 | 122 | // Delete removes a value from the AVL tree 123 | func (t *AVLTree[T]) Delete(value T) bool { 124 | if t.root == nil { 125 | return false 126 | } 127 | 128 | var deleted bool 129 | t.root, deleted = t.delete(t.root, value) 130 | if deleted { 131 | t.size-- 132 | } 133 | return deleted 134 | } 135 | 136 | // delete recursively deletes a value and balances the tree 137 | func (t *AVLTree[T]) delete(node *Node[T], value T) (*Node[T], bool) { 138 | if node == nil { 139 | return nil, false 140 | } 141 | 142 | comp := t.compare(value, node.Value) 143 | var deleted bool 144 | 145 | if comp < 0 { 146 | node.Left, deleted = t.delete(node.Left, value) 147 | } else if comp > 0 { 148 | node.Right, deleted = t.delete(node.Right, value) 149 | } else { 150 | // Node with only one child or no child 151 | if node.Left == nil { 152 | return node.Right, true 153 | } else if node.Right == nil { 154 | return node.Left, true 155 | } 156 | 157 | // Node with two children 158 | successor := t.findMin(node.Right) 159 | node.Value = successor.Value 160 | node.Right, deleted = t.delete(node.Right, successor.Value) 161 | } 162 | 163 | if !deleted { 164 | return node, false 165 | } 166 | 167 | t.updateHeight(node) 168 | balance := t.getBalance(node) 169 | 170 | // Left Left Case 171 | if balance > 1 && t.getBalance(node.Left) >= 0 { 172 | return t.rotateRight(node), true 173 | } 174 | 175 | // Left Right Case 176 | if balance > 1 && t.getBalance(node.Left) < 0 { 177 | node.Left = t.rotateLeft(node.Left) 178 | return t.rotateRight(node), true 179 | } 180 | 181 | // Right Right Case 182 | if balance < -1 && t.getBalance(node.Right) <= 0 { 183 | return t.rotateLeft(node), true 184 | } 185 | 186 | // Right Left Case 187 | if balance < -1 && t.getBalance(node.Right) > 0 { 188 | node.Right = t.rotateRight(node.Right) 189 | return t.rotateLeft(node), true 190 | } 191 | 192 | return node, true 193 | } 194 | 195 | // findMin returns the node with minimum value in the tree 196 | func (t *AVLTree[T]) findMin(node *Node[T]) *Node[T] { 197 | current := node 198 | for current.Left != nil { 199 | current = current.Left 200 | } 201 | return current 202 | } 203 | 204 | // Search looks for a value in the tree 205 | func (t *AVLTree[T]) Search(value T) bool { 206 | return t.search(t.root, value) 207 | } 208 | 209 | // search recursively searches for a value 210 | func (t *AVLTree[T]) search(node *Node[T], value T) bool { 211 | if node == nil { 212 | return false 213 | } 214 | 215 | comp := t.compare(value, node.Value) 216 | if comp < 0 { 217 | return t.search(node.Left, value) 218 | } else if comp > 0 { 219 | return t.search(node.Right, value) 220 | } 221 | return true 222 | } 223 | 224 | // InOrderTraversal performs an in-order traversal of the tree 225 | func (t *AVLTree[T]) InOrderTraversal(fn func(T)) { 226 | t.inOrder(t.root, fn) 227 | } 228 | 229 | // inOrder performs an in-order traversal from a given node 230 | func (t *AVLTree[T]) inOrder(node *Node[T], fn func(T)) { 231 | if node != nil { 232 | t.inOrder(node.Left, fn) 233 | fn(node.Value) 234 | t.inOrder(node.Right, fn) 235 | } 236 | } 237 | 238 | // Clear removes all elements from the tree 239 | func (t *AVLTree[T]) Clear() { 240 | t.root = nil 241 | t.size = 0 242 | } 243 | 244 | // Len returns the number of nodes in the tree 245 | func (t *AVLTree[T]) Len() int { 246 | return t.size 247 | } 248 | 249 | // IsEmpty returns true if the tree is empty 250 | func (t *AVLTree[T]) IsEmpty() bool { 251 | return t.size == 0 252 | } 253 | 254 | // Height returns the height of the tree 255 | func (t *AVLTree[T]) Height() int { 256 | return t.getHeight(t.root) + 1 257 | } 258 | 259 | func max(a, b int) int { 260 | if a > b { 261 | return a 262 | } 263 | return b 264 | } 265 | -------------------------------------------------------------------------------- /avltree/avltree_test.go: -------------------------------------------------------------------------------- 1 | package avltree 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/idsulik/go-collections/v3/internal/cmp" 7 | ) 8 | 9 | func TestAVLTree(t *testing.T) { 10 | t.Run( 11 | "Basic Operations", func(t *testing.T) { 12 | tree := New[int](cmp.CompareInts) 13 | 14 | // Test Insert and Search 15 | tree.Insert(5) 16 | tree.Insert(3) 17 | tree.Insert(7) 18 | 19 | if !tree.Search(5) { 20 | t.Error("Expected to find 5 in tree") 21 | } 22 | if tree.Search(1) { 23 | t.Error("Did not expect to find 1 in tree") 24 | } 25 | 26 | // Test Len 27 | if size := tree.Len(); size != 3 { 28 | t.Errorf("Expected size 3, got %d", size) 29 | } 30 | }, 31 | ) 32 | 33 | t.Run( 34 | "Balance After Insert", func(t *testing.T) { 35 | tree := New[int](cmp.CompareInts) 36 | 37 | // Left-Left case 38 | tree.Insert(30) 39 | tree.Insert(20) 40 | tree.Insert(10) 41 | 42 | if tree.getHeight(tree.root.Left) != 0 || tree.getHeight(tree.root.Right) != 0 { 43 | t.Error("Tree not properly balanced after left-left case") 44 | } 45 | 46 | tree.Clear() 47 | 48 | // Right-Right case 49 | tree.Insert(10) 50 | tree.Insert(20) 51 | tree.Insert(30) 52 | 53 | if tree.getHeight(tree.root.Left) != 0 || tree.getHeight(tree.root.Right) != 0 { 54 | t.Error("Tree not properly balanced after right-right case") 55 | } 56 | }, 57 | ) 58 | 59 | t.Run( 60 | "Delete Operations", func(t *testing.T) { 61 | tree := New[int](cmp.CompareInts) 62 | 63 | // Insert some values 64 | values := []int{10, 5, 15, 3, 7, 12, 17} 65 | for _, v := range values { 66 | tree.Insert(v) 67 | } 68 | 69 | // Test deletion 70 | if !tree.Delete(5) { 71 | t.Error("Delete should return true for existing value") 72 | } 73 | if tree.Delete(100) { 74 | t.Error("Delete should return false for non-existing value") 75 | } 76 | 77 | // Verify size after deletion 78 | if size := tree.Len(); size != 6 { 79 | t.Errorf("Expected size 6 after deletion, got %d", size) 80 | } 81 | 82 | // Verify the value is actually deleted 83 | if tree.Search(5) { 84 | t.Error("Found deleted value 5 in tree") 85 | } 86 | }, 87 | ) 88 | 89 | t.Run( 90 | "InOrder Traversal", func(t *testing.T) { 91 | tree := New[int](cmp.CompareInts) 92 | values := []int{5, 3, 7, 1, 4, 6, 8} 93 | expected := []int{1, 3, 4, 5, 6, 7, 8} 94 | 95 | for _, v := range values { 96 | tree.Insert(v) 97 | } 98 | 99 | result := make([]int, 0) 100 | tree.InOrderTraversal( 101 | func(v int) { 102 | result = append(result, v) 103 | }, 104 | ) 105 | 106 | if len(result) != len(expected) { 107 | t.Errorf("Expected traversal length %d, got %d", len(expected), len(result)) 108 | } 109 | 110 | for i := range result { 111 | if result[i] != expected[i] { 112 | t.Errorf("At position %d, expected %d, got %d", i, expected[i], result[i]) 113 | } 114 | } 115 | }, 116 | ) 117 | 118 | t.Run( 119 | "Height Calculation", func(t *testing.T) { 120 | tree := New[int](cmp.CompareInts) 121 | 122 | if h := tree.Height(); h != 0 { 123 | t.Errorf("Expected height 0 for empty tree, got %d", h) 124 | } 125 | 126 | tree.Insert(1) 127 | if h := tree.Height(); h != 1 { 128 | t.Errorf("Expected height 1 for single node, got %d", h) 129 | } 130 | 131 | tree.Insert(2) 132 | tree.Insert(3) 133 | if h := tree.Height(); h != 2 { 134 | t.Errorf("Expected height 2 after balancing, got %d", h) 135 | } 136 | }, 137 | ) 138 | 139 | t.Run( 140 | "Clear and IsEmpty", func(t *testing.T) { 141 | tree := New[int](cmp.CompareInts) 142 | 143 | if !tree.IsEmpty() { 144 | t.Error("New tree should be empty") 145 | } 146 | 147 | tree.Insert(1) 148 | tree.Insert(2) 149 | 150 | if tree.IsEmpty() { 151 | t.Error("Tree should not be empty after insertions") 152 | } 153 | 154 | tree.Clear() 155 | 156 | if !tree.IsEmpty() { 157 | t.Error("Tree should be empty after Clear()") 158 | } 159 | 160 | if size := tree.Len(); size != 0 { 161 | t.Errorf("Expected size 0 after Clear(), got %d", size) 162 | } 163 | }, 164 | ) 165 | 166 | t.Run( 167 | "Complex Balancing", func(t *testing.T) { 168 | tree := New[int](cmp.CompareInts) 169 | values := []int{10, 20, 30, 40, 50, 25} 170 | 171 | for _, v := range values { 172 | tree.Insert(v) 173 | if !isBalanced(tree.root) { 174 | t.Errorf("Tree became unbalanced after inserting %d", v) 175 | } 176 | } 177 | 178 | // Delete some values and check balance 179 | deleteValues := []int{30, 40} 180 | for _, v := range deleteValues { 181 | tree.Delete(v) 182 | if !isBalanced(tree.root) { 183 | t.Errorf("Tree became unbalanced after deleting %d", v) 184 | } 185 | } 186 | }, 187 | ) 188 | } 189 | 190 | // Helper function to check if the tree is balanced 191 | func isBalanced(node *Node[int]) bool { 192 | if node == nil { 193 | return true 194 | } 195 | 196 | balance := getNodeHeight(node.Left) - getNodeHeight(node.Right) 197 | if balance < -1 || balance > 1 { 198 | return false 199 | } 200 | 201 | return isBalanced(node.Left) && isBalanced(node.Right) 202 | } 203 | 204 | func getNodeHeight(node *Node[int]) int { 205 | if node == nil { 206 | return -1 207 | } 208 | return node.Height 209 | } 210 | -------------------------------------------------------------------------------- /bloomfilter/bloomfilter.go: -------------------------------------------------------------------------------- 1 | package bloomfilter 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/binary" 6 | "fmt" 7 | "hash" 8 | "hash/fnv" 9 | "math" 10 | ) 11 | 12 | type BloomFilter[T any] struct { 13 | bits []bool 14 | numBits uint 15 | numHash uint 16 | count uint 17 | hasher hash.Hash 18 | } 19 | 20 | func NewBloomFilter[T any](expectedItems uint, falsePositiveProb float64) *BloomFilter[T] { 21 | if expectedItems == 0 { 22 | expectedItems = 1 23 | } 24 | if falsePositiveProb <= 0 { 25 | falsePositiveProb = 0.01 26 | } 27 | 28 | numBits := uint(math.Ceil(-float64(expectedItems) * math.Log(falsePositiveProb) / math.Pow(math.Log(2), 2))) 29 | numHash := uint(math.Ceil(float64(numBits) / float64(expectedItems) * math.Log(2))) 30 | 31 | return &BloomFilter[T]{ 32 | bits: make([]bool, numBits), 33 | numBits: numBits, 34 | numHash: numHash, 35 | hasher: fnv.New64a(), // Using fnv.New64a() for better distribution 36 | } 37 | } 38 | 39 | // hashToUint converts a hash sum to uint 40 | func hashToUint(sum []byte) uint { 41 | // Convert the first 8 bytes of the hash to uint64, then to uint 42 | if len(sum) < 8 { 43 | panic("Hash sum too short") 44 | } 45 | return uint(binary.BigEndian.Uint64(sum[:8])) 46 | } 47 | 48 | // getLocations generates multiple hash locations for an item 49 | func (bf *BloomFilter[T]) getLocations(item T) []uint { 50 | locations := make([]uint, bf.numHash) 51 | itemStr := fmt.Sprintf("%v", item) 52 | 53 | // Calculate SHA-256 hash as a base for location generation 54 | hash := sha256.Sum256([]byte(itemStr)) 55 | h1 := hashToUint(hash[:8]) // Use first 8 bytes for h1 56 | h2 := hashToUint(hash[8:16]) // Use next 8 bytes for h2 57 | 58 | // Generate all hash values using the formula: h1 + i*h2 59 | for i := uint(0); i < bf.numHash; i++ { 60 | locations[i] = (h1 + i*h2) % bf.numBits 61 | } 62 | 63 | return locations 64 | } 65 | 66 | // Add inserts an item into the Bloom Filter. 67 | func (bf *BloomFilter[T]) Add(item T) { 68 | locations := bf.getLocations(item) 69 | for _, loc := range locations { 70 | bf.bits[loc] = true 71 | } 72 | bf.count++ 73 | } 74 | 75 | // Contains tests whether an item might be in the set. 76 | func (bf *BloomFilter[T]) Contains(item T) bool { 77 | locations := bf.getLocations(item) 78 | for _, loc := range locations { 79 | if !bf.bits[loc] { 80 | return false 81 | } 82 | } 83 | return true 84 | } 85 | 86 | // EstimatedFalsePositiveRate returns the estimated false positive rate. 87 | func (bf *BloomFilter[T]) EstimatedFalsePositiveRate() float64 { 88 | if bf.count == 0 { 89 | return 0.0 90 | } 91 | exponent := -float64(bf.numHash) * float64(bf.count) / float64(bf.numBits) 92 | return math.Pow(1-math.Exp(exponent), float64(bf.numHash)) 93 | } 94 | 95 | // Clear removes all items from the Bloom Filter. 96 | func (bf *BloomFilter[T]) Clear() { 97 | bf.bits = make([]bool, bf.numBits) 98 | bf.count = 0 99 | } 100 | 101 | // Len returns the number of items added to the Bloom Filter. 102 | func (bf *BloomFilter[T]) Len() int { 103 | return int(bf.count) 104 | } 105 | 106 | // IsEmpty returns true if no items have been added to the Bloom Filter. 107 | func (bf *BloomFilter[T]) IsEmpty() bool { 108 | return bf.count == 0 109 | } 110 | 111 | // BitSize returns the size of the bit array. 112 | func (bf *BloomFilter[T]) BitSize() uint { 113 | return bf.numBits 114 | } 115 | 116 | // NumberOfHashes returns the number of hash functions. 117 | func (bf *BloomFilter[T]) NumberOfHashes() uint { 118 | return bf.numHash 119 | } 120 | -------------------------------------------------------------------------------- /bloomfilter/bloomfilter_test.go: -------------------------------------------------------------------------------- 1 | package bloomfilter 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestBloomFilter_Basic(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | expectedItems uint 12 | falsePositive float64 13 | itemsToAdd []string 14 | itemsToCheck []string 15 | shouldContain []bool 16 | expectedMinBits uint 17 | }{ 18 | { 19 | name: "Basic operation", 20 | expectedItems: 100, 21 | falsePositive: 0.01, 22 | itemsToAdd: []string{"apple", "banana", "cherry"}, 23 | itemsToCheck: []string{"apple", "banana", "cherry", "date"}, 24 | shouldContain: []bool{true, true, true, false}, 25 | }, 26 | { 27 | name: "Empty filter", 28 | expectedItems: 100, 29 | falsePositive: 0.01, 30 | itemsToAdd: []string{}, 31 | itemsToCheck: []string{"apple"}, 32 | shouldContain: []bool{false}, 33 | }, 34 | { 35 | name: "Single item", 36 | expectedItems: 100, 37 | falsePositive: 0.01, 38 | itemsToAdd: []string{"apple"}, 39 | itemsToCheck: []string{"apple", "banana"}, 40 | shouldContain: []bool{true, false}, 41 | }, 42 | } 43 | 44 | for _, tt := range tests { 45 | t.Run( 46 | tt.name, func(t *testing.T) { 47 | bf := NewBloomFilter[string](tt.expectedItems, tt.falsePositive) 48 | 49 | // Add items 50 | for _, item := range tt.itemsToAdd { 51 | bf.Add(item) 52 | } 53 | 54 | // Check size matches expected items 55 | if bf.Len() != len(tt.itemsToAdd) { 56 | t.Errorf("Expected length %d, got %d", len(tt.itemsToAdd), bf.Len()) 57 | } 58 | 59 | // Check contains 60 | for i, item := range tt.itemsToCheck { 61 | if bf.Contains(item) != tt.shouldContain[i] { 62 | t.Errorf( 63 | "Contains(%s) = %v, want %v", 64 | item, bf.Contains(item), tt.shouldContain[i], 65 | ) 66 | } 67 | } 68 | }, 69 | ) 70 | } 71 | } 72 | 73 | func TestBloomFilter_DifferentTypes(t *testing.T) { 74 | t.Run( 75 | "Integer type", func(t *testing.T) { 76 | bf := NewBloomFilter[int](100, 0.01) 77 | numbers := []int{1, 2, 3, 4, 5} 78 | 79 | for _, n := range numbers { 80 | bf.Add(n) 81 | } 82 | 83 | for _, n := range numbers { 84 | if !bf.Contains(n) { 85 | t.Errorf("Should contain %d", n) 86 | } 87 | } 88 | 89 | if bf.Contains(6) { 90 | t.Error("Should not contain 6") 91 | } 92 | }, 93 | ) 94 | 95 | t.Run( 96 | "Custom struct type", func(t *testing.T) { 97 | type Person struct { 98 | Name string 99 | Age int 100 | } 101 | 102 | bf := NewBloomFilter[Person](100, 0.01) 103 | p1 := Person{"Alice", 30} 104 | p2 := Person{"Bob", 25} 105 | 106 | bf.Add(p1) 107 | bf.Add(p2) 108 | 109 | if !bf.Contains(p1) { 110 | t.Error("Should contain person 1") 111 | } 112 | if !bf.Contains(p2) { 113 | t.Error("Should contain person 2") 114 | } 115 | if bf.Contains(Person{"Charlie", 35}) { 116 | t.Error("Should not contain person 3") 117 | } 118 | }, 119 | ) 120 | } 121 | 122 | func TestBloomFilter_EdgeCases(t *testing.T) { 123 | t.Run( 124 | "Zero expected items", func(t *testing.T) { 125 | bf := NewBloomFilter[string](0, 0.01) 126 | if bf == nil { 127 | t.Error("Should create filter even with zero expected items") 128 | } 129 | bf.Add("test") 130 | if !bf.Contains("test") { 131 | t.Error("Should still function with zero expected items") 132 | } 133 | }, 134 | ) 135 | 136 | t.Run( 137 | "Zero false positive rate", func(t *testing.T) { 138 | bf := NewBloomFilter[string](100, 0) 139 | if bf == nil { 140 | t.Error("Should create filter even with zero false positive rate") 141 | } 142 | bf.Add("test") 143 | if !bf.Contains("test") { 144 | t.Error("Should still function with zero false positive rate") 145 | } 146 | }, 147 | ) 148 | } 149 | 150 | func TestBloomFilter_Operations(t *testing.T) { 151 | t.Run( 152 | "Clear operation", func(t *testing.T) { 153 | bf := NewBloomFilter[string](100, 0.01) 154 | bf.Add("test") 155 | 156 | if !bf.Contains("test") { 157 | t.Error("Should contain 'test' before clear") 158 | } 159 | 160 | bf.Clear() 161 | 162 | if bf.Contains("test") { 163 | t.Error("Should not contain 'test' after clear") 164 | } 165 | 166 | if !bf.IsEmpty() { 167 | t.Error("Should be empty after clear") 168 | } 169 | 170 | if bf.Len() != 0 { 171 | t.Error("Length should be 0 after clear") 172 | } 173 | }, 174 | ) 175 | } 176 | 177 | func TestBloomFilter_FalsePositiveRate(t *testing.T) { 178 | expectedItems := uint(1000) 179 | targetFPR := 0.01 180 | bf := NewBloomFilter[int](expectedItems, targetFPR) 181 | 182 | // Add expectedItems number of items 183 | for i := 0; i < int(expectedItems); i++ { 184 | bf.Add(i) 185 | } 186 | 187 | // Test false positive rate 188 | falsePositives := 0 189 | trials := 10000 190 | for i := int(expectedItems); i < int(expectedItems)+trials; i++ { 191 | if bf.Contains(i) { 192 | falsePositives++ 193 | } 194 | } 195 | 196 | actualFPR := float64(falsePositives) / float64(trials) 197 | estimatedFPR := bf.EstimatedFalsePositiveRate() 198 | 199 | // Allow for some variance in the actual false positive rate 200 | maxAcceptableFPR := targetFPR * 2 201 | if actualFPR > maxAcceptableFPR { 202 | t.Errorf( 203 | "False positive rate too high: got %f, want <= %f", 204 | actualFPR, maxAcceptableFPR, 205 | ) 206 | } 207 | 208 | // Check if estimated FPR is reasonably close to actual FPR 209 | if estimatedFPR < actualFPR/2 || estimatedFPR > actualFPR*2 { 210 | t.Errorf( 211 | "Estimated FPR %f significantly different from actual FPR %f", 212 | estimatedFPR, actualFPR, 213 | ) 214 | } 215 | } 216 | 217 | func BenchmarkBloomFilter(b *testing.B) { 218 | bf := NewBloomFilter[string](1000, 0.01) 219 | 220 | b.Run( 221 | "Add", func(b *testing.B) { 222 | for i := 0; i < b.N; i++ { 223 | bf.Add(fmt.Sprintf("item%d", i)) 224 | } 225 | }, 226 | ) 227 | 228 | b.Run( 229 | "Contains", func(b *testing.B) { 230 | for i := 0; i < b.N; i++ { 231 | bf.Contains(fmt.Sprintf("item%d", i)) 232 | } 233 | }, 234 | ) 235 | } 236 | -------------------------------------------------------------------------------- /bst/bst.go: -------------------------------------------------------------------------------- 1 | package bst 2 | 3 | import "github.com/idsulik/go-collections/v3/internal/cmp" 4 | 5 | // BST represents the Binary Search Tree. 6 | type BST[T cmp.Ordered] struct { 7 | root *node[T] 8 | size int 9 | } 10 | 11 | // node represents each node in the BST. 12 | type node[T cmp.Ordered] struct { 13 | value T 14 | left *node[T] 15 | right *node[T] 16 | } 17 | 18 | // New creates a new empty Binary Search Tree. 19 | func New[T cmp.Ordered]() *BST[T] { 20 | return &BST[T]{} 21 | } 22 | 23 | // Insert adds a value into the BST. 24 | func (bst *BST[T]) Insert(value T) { 25 | bst.root = bst.insert(bst.root, value) 26 | } 27 | 28 | func (bst *BST[T]) insert(n *node[T], value T) *node[T] { 29 | if n == nil { 30 | bst.size++ 31 | return &node[T]{value: value} 32 | } 33 | 34 | cur := n 35 | for cur != nil { 36 | if value < cur.value { 37 | if cur.left == nil { 38 | bst.size++ 39 | cur.left = &node[T]{value: value} 40 | break 41 | } 42 | cur = cur.left 43 | } else if value > cur.value { 44 | if cur.right == nil { 45 | bst.size++ 46 | cur.right = &node[T]{value: value} 47 | break 48 | } 49 | cur = cur.right 50 | } else { 51 | // Value already exists 52 | break 53 | } 54 | } 55 | 56 | return n 57 | } 58 | 59 | // Contains checks if a value exists in the BST. 60 | func (bst *BST[T]) Contains(value T) bool { 61 | return bst.contains(bst.root, value) 62 | } 63 | 64 | func (bst *BST[T]) contains(n *node[T], value T) bool { 65 | cur := n 66 | for cur != nil { 67 | if value < cur.value { 68 | cur = cur.left 69 | } else if value > cur.value { 70 | cur = cur.right 71 | } else { 72 | return true 73 | } 74 | } 75 | 76 | return false 77 | } 78 | 79 | // Remove deletes a value from the BST. 80 | func (bst *BST[T]) Remove(value T) { 81 | var removed bool 82 | bst.root, removed = bst.remove(bst.root, value) 83 | if removed { 84 | bst.size-- 85 | } 86 | } 87 | 88 | func (bst *BST[T]) remove(n *node[T], value T) (*node[T], bool) { 89 | if n == nil { 90 | return nil, false 91 | } 92 | 93 | var removed bool 94 | if value < n.value { 95 | n.left, removed = bst.remove(n.left, value) 96 | } else if value > n.value { 97 | n.right, removed = bst.remove(n.right, value) 98 | } else { 99 | // Node found, remove it 100 | removed = true 101 | if n.left == nil { 102 | return n.right, removed 103 | } else if n.right == nil { 104 | return n.left, removed 105 | } else { 106 | // Node with two children 107 | minRight := bst.min(n.right) 108 | n.value = minRight.value 109 | n.right, _ = bst.remove(n.right, n.value) 110 | } 111 | } 112 | 113 | return n, removed 114 | } 115 | 116 | func (bst *BST[T]) min(n *node[T]) *node[T] { 117 | current := n 118 | for current.left != nil { 119 | current = current.left 120 | } 121 | return current 122 | } 123 | 124 | // InOrderTraversal traverses the BST in order and applies the function fn to each node's value. 125 | func (bst *BST[T]) InOrderTraversal(fn func(T)) { 126 | bst.inOrderTraversal(bst.root, fn) 127 | } 128 | 129 | func (bst *BST[T]) inOrderTraversal(n *node[T], fn func(T)) { 130 | if n != nil { 131 | bst.inOrderTraversal(n.left, fn) 132 | fn(n.value) 133 | bst.inOrderTraversal(n.right, fn) 134 | } 135 | } 136 | 137 | // Len returns the number of nodes in the BST. 138 | func (bst *BST[T]) Len() int { 139 | return bst.size 140 | } 141 | 142 | // IsEmpty checks if the BST is empty. 143 | func (bst *BST[T]) IsEmpty() bool { 144 | return bst.size == 0 145 | } 146 | 147 | // Clear removes all nodes from the BST. 148 | func (bst *BST[T]) Clear() { 149 | bst.root = nil 150 | bst.size = 0 151 | } 152 | -------------------------------------------------------------------------------- /bst/bst_test.go: -------------------------------------------------------------------------------- 1 | package bst 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestInsertAndContains(t *testing.T) { 8 | bst := New[int]() 9 | values := []int{5, 3, 7, 2, 4, 6, 8} 10 | 11 | for _, v := range values { 12 | bst.Insert(v) 13 | } 14 | 15 | for _, v := range values { 16 | if !bst.Contains(v) { 17 | t.Errorf("BST should contain %d", v) 18 | } 19 | } 20 | 21 | if bst.Contains(10) { 22 | t.Errorf("BST should not contain %d", 10) 23 | } 24 | } 25 | 26 | func TestRemove(t *testing.T) { 27 | bst := New[int]() 28 | values := []int{5, 3, 7, 2, 4, 6, 8} 29 | 30 | for _, v := range values { 31 | bst.Insert(v) 32 | } 33 | 34 | bst.Remove(3) 35 | if bst.Contains(3) { 36 | t.Errorf("BST should not contain %d after removal", 3) 37 | } 38 | 39 | bst.Remove(5) 40 | if bst.Contains(5) { 41 | t.Errorf("BST should not contain %d after removal", 5) 42 | } 43 | 44 | bst.Remove(10) // Removing non-existing element should not cause error 45 | } 46 | 47 | func TestInOrderTraversal(t *testing.T) { 48 | bst := New[int]() 49 | values := []int{5, 3, 7, 2, 4, 6, 8} 50 | 51 | for _, v := range values { 52 | bst.Insert(v) 53 | } 54 | 55 | var traversed []int 56 | bst.InOrderTraversal( 57 | func(value int) { 58 | traversed = append(traversed, value) 59 | }, 60 | ) 61 | 62 | expected := []int{2, 3, 4, 5, 6, 7, 8} 63 | 64 | for i, v := range expected { 65 | if traversed[i] != v { 66 | t.Errorf("Expected %d, got %d", v, traversed[i]) 67 | } 68 | } 69 | } 70 | 71 | func TestLenAndIsEmpty(t *testing.T) { 72 | bst := New[int]() 73 | 74 | if !bst.IsEmpty() { 75 | t.Error("BST should be empty") 76 | } 77 | 78 | if bst.Len() != 0 { 79 | t.Errorf("Expected length 0, got %d", bst.Len()) 80 | } 81 | 82 | bst.Insert(1) 83 | 84 | if bst.IsEmpty() { 85 | t.Error("BST should not be empty after insertion") 86 | } 87 | 88 | if bst.Len() != 1 { 89 | t.Errorf("Expected length 1, got %d", bst.Len()) 90 | } 91 | } 92 | 93 | func TestClear(t *testing.T) { 94 | bst := New[int]() 95 | bst.Insert(1) 96 | bst.Insert(2) 97 | bst.Insert(3) 98 | 99 | bst.Clear() 100 | 101 | if !bst.IsEmpty() { 102 | t.Error("BST should be empty after Clear") 103 | } 104 | 105 | if bst.Len() != 0 { 106 | t.Errorf("Expected length 0 after Clear, got %d", bst.Len()) 107 | } 108 | } 109 | 110 | func TestStrings(t *testing.T) { 111 | bst := New[string]() 112 | values := []string{"banana", "apple", "cherry", "date"} 113 | 114 | for _, v := range values { 115 | bst.Insert(v) 116 | } 117 | 118 | if !bst.Contains("apple") { 119 | t.Error("BST should contain 'apple'") 120 | } 121 | 122 | bst.Remove("banana") 123 | if bst.Contains("banana") { 124 | t.Error("BST should not contain 'banana' after removal") 125 | } 126 | 127 | var traversed []string 128 | bst.InOrderTraversal( 129 | func(value string) { 130 | traversed = append(traversed, value) 131 | }, 132 | ) 133 | 134 | expected := []string{"apple", "cherry", "date"} 135 | 136 | for i, v := range expected { 137 | if traversed[i] != v { 138 | t.Errorf("Expected %s, got %s", v, traversed[i]) 139 | } 140 | } 141 | } 142 | 143 | func TestRemoveNodeWithTwoChildren(t *testing.T) { 144 | bst := New[int]() 145 | values := []int{50, 30, 70, 20, 40, 60, 80} 146 | 147 | for _, v := range values { 148 | bst.Insert(v) 149 | } 150 | 151 | bst.Remove(30) // Node with two children 152 | 153 | if bst.Contains(30) { 154 | t.Error("BST should not contain 30 after removal") 155 | } 156 | 157 | var traversed []int 158 | bst.InOrderTraversal( 159 | func(value int) { 160 | traversed = append(traversed, value) 161 | }, 162 | ) 163 | 164 | expected := []int{20, 40, 50, 60, 70, 80} 165 | 166 | for i, v := range expected { 167 | if traversed[i] != v { 168 | t.Errorf("Expected %d, got %d", v, traversed[i]) 169 | } 170 | } 171 | } 172 | 173 | func TestRemoveRoot(t *testing.T) { 174 | bst := New[int]() 175 | values := []int{10, 5, 15, 2, 7, 12, 17} 176 | 177 | for _, v := range values { 178 | bst.Insert(v) 179 | } 180 | 181 | bst.Remove(10) // Remove root node 182 | 183 | if bst.Contains(10) { 184 | t.Error("BST should not contain root node after removal") 185 | } 186 | 187 | if bst.Len() != 6 { 188 | t.Errorf("Expected length 6 after removing root, got %d", bst.Len()) 189 | } 190 | } 191 | 192 | func TestRemoveLeaf(t *testing.T) { 193 | bst := New[int]() 194 | values := []int{10, 5, 15, 2, 7} 195 | 196 | for _, v := range values { 197 | bst.Insert(v) 198 | } 199 | 200 | bst.Remove(2) // Remove leaf node 201 | 202 | if bst.Contains(2) { 203 | t.Error("BST should not contain 2 after removal") 204 | } 205 | 206 | if bst.Len() != 4 { 207 | t.Errorf("Expected length 4 after removing leaf, got %d", bst.Len()) 208 | } 209 | } 210 | 211 | // Test inserting duplicate values 212 | func TestInsertDuplicate(t *testing.T) { 213 | bst := New[int]() 214 | bst.Insert(10) 215 | bst.Insert(10) 216 | 217 | if bst.Len() != 1 { 218 | t.Errorf("Expected length 1 after inserting duplicate, got %d", bst.Len()) 219 | } 220 | 221 | count := 0 222 | bst.InOrderTraversal( 223 | func(value int) { 224 | count++ 225 | }, 226 | ) 227 | 228 | if count != 1 { 229 | t.Errorf("Expected traversal count 1, got %d", count) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /collections/interfaces.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import "github.com/idsulik/go-collections/v3/iterator" 4 | 5 | // Collection is a base interface for all collections. 6 | type Collection[T any] interface { 7 | Len() int 8 | IsEmpty() bool 9 | Clear() 10 | } 11 | 12 | // Set represents a unique collection of elements. 13 | type Set[T comparable] interface { 14 | Collection[T] 15 | Add(item T) 16 | Remove(item T) 17 | Has(item T) bool 18 | Iterator() iterator.Iterator[T] 19 | } 20 | 21 | type Stack[T any] interface { 22 | Collection[T] 23 | Push(item T) 24 | Pop() (T, bool) 25 | Peek() (T, bool) 26 | } 27 | 28 | type Deque[T any] interface { 29 | Collection[T] 30 | PushFront(item T) 31 | PushBack(item T) 32 | PopFront() (T, bool) 33 | PopBack() (T, bool) 34 | } 35 | 36 | type Queue[T any] interface { 37 | Collection[T] 38 | Enqueue(item T) 39 | Dequeue() (T, bool) 40 | Peek() (T, bool) 41 | } 42 | -------------------------------------------------------------------------------- /deque/dequeue.go: -------------------------------------------------------------------------------- 1 | package deque 2 | 3 | const ( 4 | defaultCapacity = 16 // Default initial capacity for the deque 5 | resizeFactor = 2 // Factor by which the deque is resized when full 6 | ) 7 | 8 | type Deque[T any] struct { 9 | buffer []T // Underlying slice to hold elements 10 | head, tail int // Indices for the front and back of the deque 11 | size, capacity int // Current size and maximum capacity of the deque 12 | } 13 | 14 | // New creates a new Deque with the specified initial capacity. 15 | // If the initial capacity is less than 1, it uses the default capacity. 16 | func New[T any](initialCapacity int) *Deque[T] { 17 | if initialCapacity < 1 { 18 | initialCapacity = defaultCapacity 19 | } 20 | 21 | return &Deque[T]{ 22 | buffer: make([]T, initialCapacity), 23 | capacity: initialCapacity, 24 | } 25 | } 26 | 27 | // resize increases the capacity of the deque. 28 | func (d *Deque[T]) resize() { 29 | newCapacity := d.capacity * resizeFactor 30 | d.reallocate(newCapacity) 31 | } 32 | 33 | // reallocate creates a new buffer with the specified capacity and copies elements. 34 | func (d *Deque[T]) reallocate(newCapacity int) { 35 | newBuffer := make([]T, newCapacity) 36 | if d.size > 0 { 37 | if d.tail > d.head { 38 | copy(newBuffer, d.buffer[d.head:d.tail]) 39 | } else { 40 | n := copy(newBuffer, d.buffer[d.head:]) 41 | copy(newBuffer[n:], d.buffer[:d.tail]) 42 | } 43 | } 44 | 45 | d.buffer = newBuffer 46 | d.capacity = newCapacity 47 | d.head = 0 48 | d.tail = d.size 49 | } 50 | 51 | // PushFront inserts an item at the front of the deque. 52 | func (d *Deque[T]) PushFront(item T) { 53 | if d.size == d.capacity { 54 | d.resize() 55 | } 56 | if d.head == 0 { 57 | d.head = d.capacity - 1 58 | } else { 59 | d.head-- 60 | } 61 | d.buffer[d.head] = item 62 | d.size++ 63 | } 64 | 65 | // PushBack inserts an item at the back of the deque. 66 | func (d *Deque[T]) PushBack(item T) { 67 | if d.size == d.capacity { 68 | d.resize() 69 | } 70 | d.buffer[d.tail] = item 71 | d.tail = (d.tail + 1) % d.capacity 72 | d.size++ 73 | } 74 | 75 | // PopFront removes and returns the item at the front of the deque. 76 | // Returns false if the deque is empty. 77 | func (d *Deque[T]) PopFront() (T, bool) { 78 | if d.size == 0 { 79 | var zero T 80 | return zero, false 81 | } 82 | item := d.buffer[d.head] 83 | var zero T 84 | d.buffer[d.head] = zero // Clear reference 85 | d.head = (d.head + 1) % d.capacity 86 | d.size-- 87 | 88 | return item, true 89 | } 90 | 91 | // PopBack removes and returns the item at the back of the deque. 92 | // Returns false if the deque is empty. 93 | func (d *Deque[T]) PopBack() (T, bool) { 94 | if d.size == 0 { 95 | var zero T 96 | return zero, false 97 | } 98 | if d.tail == 0 { 99 | d.tail = d.capacity - 1 100 | } else { 101 | d.tail-- 102 | } 103 | item := d.buffer[d.tail] 104 | var zero T 105 | d.buffer[d.tail] = zero // Clear reference 106 | d.size-- 107 | 108 | return item, true 109 | } 110 | 111 | // PeekFront returns the item at the front of the deque without removing it. 112 | // Returns false if the deque is empty. 113 | func (d *Deque[T]) PeekFront() (T, bool) { 114 | if d.size == 0 { 115 | var zero T 116 | return zero, false 117 | } 118 | return d.buffer[d.head], true 119 | } 120 | 121 | // PeekBack returns the item at the back of the deque without removing it. 122 | // Returns false if the deque is empty. 123 | func (d *Deque[T]) PeekBack() (T, bool) { 124 | if d.size == 0 { 125 | var zero T 126 | return zero, false 127 | } 128 | index := d.tail 129 | if index == 0 { 130 | index = d.capacity - 1 131 | } else { 132 | index-- 133 | } 134 | return d.buffer[index], true 135 | } 136 | 137 | // Len returns the number of elements in the deque. 138 | func (d *Deque[T]) Len() int { 139 | return d.size 140 | } 141 | 142 | // Cap returns the current capacity of the deque. 143 | func (d *Deque[T]) Cap() int { 144 | return d.capacity 145 | } 146 | 147 | // IsEmpty checks if the deque is empty. 148 | func (d *Deque[T]) IsEmpty() bool { 149 | return d.size == 0 150 | } 151 | 152 | // Clear removes all elements from the deque. 153 | func (d *Deque[T]) Clear() { 154 | // Clear references to help GC 155 | for i := range d.buffer { 156 | var zero T 157 | d.buffer[i] = zero 158 | } 159 | d.head = 0 160 | d.tail = 0 161 | d.size = 0 162 | // Reset to default capacity if current capacity is much larger 163 | if d.capacity > defaultCapacity*2 { 164 | d.buffer = make([]T, defaultCapacity) 165 | d.capacity = defaultCapacity 166 | } 167 | } 168 | 169 | // GetItems returns a new slice containing the deque's elements in order. 170 | func (d *Deque[T]) GetItems() []T { 171 | items := make([]T, d.size) 172 | if d.size == 0 { 173 | return items 174 | } 175 | 176 | if d.tail > d.head { 177 | copy(items, d.buffer[d.head:d.tail]) 178 | } else { 179 | n := copy(items, d.buffer[d.head:]) 180 | copy(items[n:], d.buffer[:d.tail]) 181 | } 182 | return items 183 | } 184 | 185 | // Clone returns a deep copy of the deque. 186 | func (d *Deque[T]) Clone() *Deque[T] { 187 | newDeque := &Deque[T]{ 188 | buffer: make([]T, d.capacity), 189 | head: d.head, 190 | tail: d.tail, 191 | size: d.size, 192 | capacity: d.capacity, 193 | } 194 | copy(newDeque.buffer, d.buffer) 195 | return newDeque 196 | } 197 | 198 | func (d *Deque[T]) ForEach(fn func(T)) { 199 | if d.size == 0 { 200 | return 201 | } 202 | 203 | if d.tail > d.head { 204 | for i := d.head; i < d.tail; i++ { 205 | fn(d.buffer[i]) 206 | } 207 | } else { 208 | for i := d.head; i < d.capacity; i++ { 209 | fn(d.buffer[i]) 210 | } 211 | for i := 0; i < d.tail; i++ { 212 | fn(d.buffer[i]) 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /deque/dequeue_test.go: -------------------------------------------------------------------------------- 1 | package deque 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/idsulik/go-collections/v3/internal/slices" 8 | ) 9 | 10 | func TestPushFront(t *testing.T) { 11 | d := New[int](2) 12 | d.PushFront(1) 13 | d.PushFront(2) 14 | 15 | if got := d.Len(); got != 2 { 16 | t.Errorf("Len() = %d; want 2", got) 17 | } 18 | 19 | if got, ok := d.PopFront(); !ok || got != 2 { 20 | t.Errorf("PopFront() = %d, %v; want 2, true", got, ok) 21 | } 22 | 23 | if got, ok := d.PopFront(); !ok || got != 1 { 24 | t.Errorf("PopFront() = %d, %v; want 1, true", got, ok) 25 | } 26 | } 27 | 28 | func TestPushBack(t *testing.T) { 29 | d := New[int](2) 30 | d.PushBack(1) 31 | d.PushBack(2) 32 | 33 | if got := d.Len(); got != 2 { 34 | t.Errorf("Len() = %d; want 2", got) 35 | } 36 | 37 | if got, ok := d.PopFront(); !ok || got != 1 { 38 | t.Errorf("PopFront() = %d, %v; want 1, true", got, ok) 39 | } 40 | 41 | if got, ok := d.PopFront(); !ok || got != 2 { 42 | t.Errorf("PopFront() = %d, %v; want 2, true", got, ok) 43 | } 44 | } 45 | 46 | func TestPopFrontEmpty(t *testing.T) { 47 | d := New[int](0) 48 | if _, ok := d.PopFront(); ok { 49 | t.Errorf("PopFront() should return false for an empty deque") 50 | } 51 | } 52 | 53 | func TestPopBackEmpty(t *testing.T) { 54 | d := New[int](0) 55 | if _, ok := d.PopBack(); ok { 56 | t.Errorf("PopBack() should return false for an empty deque") 57 | } 58 | } 59 | 60 | func TestPopBack(t *testing.T) { 61 | d := New[int](0) 62 | d.PushBack(1) 63 | d.PushBack(2) 64 | 65 | if got := d.Len(); got != 2 { 66 | t.Errorf("Len() = %d; want 2", got) 67 | } 68 | 69 | if got, ok := d.PopBack(); !ok || got != 2 { 70 | t.Errorf("PopBack() = %d, %v; want 2, true", got, ok) 71 | } 72 | 73 | if got, ok := d.PopBack(); !ok || got != 1 { 74 | t.Errorf("PopBack() = %d, %v; want 1, true", got, ok) 75 | } 76 | } 77 | 78 | func TestPeekFront(t *testing.T) { 79 | d := New[int](0) 80 | d.PushBack(1) 81 | d.PushBack(2) 82 | 83 | if got, ok := d.PeekFront(); !ok || got != 1 { 84 | t.Errorf("PeekFront() = %d, %v; want 1, true", got, ok) 85 | } 86 | } 87 | 88 | func TestPeekBack(t *testing.T) { 89 | d := New[int](2) 90 | d.PushBack(1) 91 | d.PushBack(2) 92 | 93 | if got, ok := d.PeekBack(); !ok || got != 2 { 94 | t.Errorf("PeekBack() = %d, %v; want 2, true", got, ok) 95 | } 96 | } 97 | 98 | func TestIsEmpty(t *testing.T) { 99 | d := New[int](1) 100 | if !d.IsEmpty() { 101 | t.Errorf("IsEmpty() = false; want true") 102 | } 103 | 104 | d.PushBack(1) 105 | if d.IsEmpty() { 106 | t.Errorf("IsEmpty() = true; want false") 107 | } 108 | 109 | d.PopFront() 110 | if !d.IsEmpty() { 111 | t.Errorf("IsEmpty() = false; want true") 112 | } 113 | } 114 | 115 | func TestClearBehavior(t *testing.T) { 116 | t.Run( 117 | "capacity reset on clear", func(t *testing.T) { 118 | d := New[int](2) 119 | // Force several capacity increases 120 | for i := 0; i < 100; i++ { 121 | d.PushBack(i) 122 | } 123 | 124 | largeCapacity := d.Cap() 125 | d.Clear() 126 | 127 | if d.Cap() >= largeCapacity { 128 | t.Error("Capacity should be reduced after Clear()") 129 | } 130 | if d.Cap() > defaultCapacity*2 { 131 | t.Error("Capacity should be closer to default after Clear()") 132 | } 133 | }, 134 | ) 135 | } 136 | 137 | func TestCapacity(t *testing.T) { 138 | t.Run( 139 | "initial capacity", func(t *testing.T) { 140 | d := New[int](5) 141 | if got := d.Cap(); got != 5 { 142 | t.Errorf("Cap() = %d; want 5", got) 143 | } 144 | }, 145 | ) 146 | 147 | t.Run( 148 | "default capacity", func(t *testing.T) { 149 | d := New[int](0) 150 | if got := d.Cap(); got != defaultCapacity { 151 | t.Errorf("Cap() = %d; want %d", got, defaultCapacity) 152 | } 153 | }, 154 | ) 155 | 156 | t.Run( 157 | "capacity growth", func(t *testing.T) { 158 | d := New[int](2) 159 | initialCap := d.Cap() 160 | 161 | // Fill beyond initial capacity 162 | for i := 0; i < 3; i++ { 163 | d.PushBack(i) 164 | } 165 | 166 | if got := d.Cap(); got != initialCap*resizeFactor { 167 | t.Errorf("Cap() after growth = %d; want %d", got, initialCap*resizeFactor) 168 | } 169 | }, 170 | ) 171 | } 172 | 173 | func TestGetItems(t *testing.T) { 174 | d := New[int](4) 175 | expected := []int{1, 2, 3, 4} 176 | 177 | for _, v := range expected { 178 | d.PushBack(v) 179 | } 180 | 181 | items := d.GetItems() 182 | 183 | if len(items) != len(expected) { 184 | t.Errorf("GetItems() length = %d; want %d", len(items), len(expected)) 185 | } 186 | 187 | for i, v := range expected { 188 | if items[i] != v { 189 | t.Errorf("GetItems()[%d] = %d; want %d", i, items[i], v) 190 | } 191 | } 192 | } 193 | 194 | func TestClone(t *testing.T) { 195 | d := New[int](4) 196 | original := []int{1, 2, 3} 197 | 198 | for _, v := range original { 199 | d.PushBack(v) 200 | } 201 | 202 | clone := d.Clone() 203 | 204 | t.Run( 205 | "identical content", func(t *testing.T) { 206 | if clone.Len() != d.Len() { 207 | t.Errorf("Clone length = %d; want %d", clone.Len(), d.Len()) 208 | } 209 | 210 | for i := 0; i < d.Len(); i++ { 211 | orig, _ := d.PopFront() 212 | cloned, _ := clone.PopFront() 213 | if orig != cloned { 214 | t.Errorf("Clone mismatch at position %d: got %d; want %d", i, cloned, orig) 215 | } 216 | } 217 | }, 218 | ) 219 | 220 | t.Run( 221 | "independent modification", func(t *testing.T) { 222 | d.PushBack(4) 223 | if d.Len() == clone.Len() { 224 | t.Error("Clone should be independent of original") 225 | } 226 | }, 227 | ) 228 | } 229 | 230 | func TestWraparound(t *testing.T) { 231 | d := New[int](4) 232 | 233 | // Fill the deque 234 | for i := 0; i < 4; i++ { 235 | d.PushBack(i) 236 | } 237 | 238 | // Create wrap-around by removing from front and adding to back 239 | d.PopFront() 240 | d.PopFront() 241 | d.PushBack(4) 242 | d.PushBack(5) 243 | 244 | expected := []int{2, 3, 4, 5} 245 | items := d.GetItems() 246 | 247 | for i, v := range expected { 248 | if items[i] != v { 249 | t.Errorf("Wraparound error: items[%d] = %d; want %d", i, items[i], v) 250 | } 251 | } 252 | } 253 | 254 | func TestOverflowProtection(t *testing.T) { 255 | t.Run( 256 | "new with large capacity", func(t *testing.T) { 257 | defer func() { 258 | if r := recover(); r == nil { 259 | t.Error("Expected panic for too large initial capacity") 260 | } 261 | }() 262 | 263 | New[int](math.MaxInt) 264 | }, 265 | ) 266 | } 267 | 268 | func TestEdgeCases(t *testing.T) { 269 | t.Run( 270 | "rapid push/pop alternation", func(t *testing.T) { 271 | d := New[int](2) 272 | for i := 0; i < 1000; i++ { 273 | d.PushBack(i) 274 | if v, ok := d.PopFront(); !ok || v != i { 275 | t.Errorf("Push/Pop alternation failed at i=%d", i) 276 | } 277 | } 278 | }, 279 | ) 280 | 281 | t.Run( 282 | "mixed front/back operations", func(t *testing.T) { 283 | d := New[int](4) 284 | d.PushFront(1) 285 | d.PushBack(2) 286 | d.PushFront(3) 287 | d.PushBack(4) 288 | 289 | expected := []int{3, 1, 2, 4} 290 | items := d.GetItems() 291 | 292 | for i, v := range expected { 293 | if items[i] != v { 294 | t.Errorf("Mixed operations: items[%d] = %d; want %d", i, items[i], v) 295 | } 296 | } 297 | }, 298 | ) 299 | } 300 | 301 | func TestForEach(t *testing.T) { 302 | d := New[int](4) 303 | 304 | // Test empty deque 305 | var emptyResult []int 306 | d.ForEach( 307 | func(value int) { 308 | emptyResult = append(emptyResult, value) 309 | }, 310 | ) 311 | if len(emptyResult) != 0 { 312 | t.Errorf("Expected empty result, got %v", emptyResult) 313 | } 314 | 315 | // Test non-wrapped deque 316 | d.PushBack(1) 317 | d.PushBack(2) 318 | d.PushBack(3) 319 | var result []int 320 | d.ForEach( 321 | func(value int) { 322 | result = append(result, value) 323 | }, 324 | ) 325 | expected := []int{1, 2, 3} 326 | if !slices.Equal(result, expected) { 327 | t.Errorf("Expected %v, got %v", expected, result) 328 | } 329 | 330 | // Test wrapped deque 331 | d.PushFront(0) 332 | d.PushBack(4) 333 | result = nil 334 | d.ForEach( 335 | func(value int) { 336 | result = append(result, value) 337 | }, 338 | ) 339 | expected = []int{0, 1, 2, 3, 4} 340 | if !slices.Equal(result, expected) { 341 | t.Errorf("Expected %v, got %v", expected, result) 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /disjointset/disjointset.go: -------------------------------------------------------------------------------- 1 | package disjointset 2 | 3 | // DisjointSet represents a disjoint set data structure 4 | type DisjointSet[T comparable] struct { 5 | parent map[T]T 6 | rank map[T]int 7 | } 8 | 9 | // New creates a new DisjointSet instance 10 | func New[T comparable]() *DisjointSet[T] { 11 | return &DisjointSet[T]{ 12 | parent: make(map[T]T), 13 | rank: make(map[T]int), 14 | } 15 | } 16 | 17 | // MakeSet creates a new set containing a single element 18 | func (ds *DisjointSet[T]) MakeSet(x T) { 19 | if _, exists := ds.parent[x]; !exists { 20 | ds.parent[x] = x 21 | ds.rank[x] = 0 22 | } 23 | } 24 | 25 | // Find returns the representative element of the set containing x 26 | // Uses path compression for optimization 27 | func (ds *DisjointSet[T]) Find(x T) T { 28 | if _, exists := ds.parent[x]; !exists { 29 | return x 30 | } 31 | 32 | if ds.parent[x] != x { 33 | ds.parent[x] = ds.Find(ds.parent[x]) // Path compression 34 | } 35 | return ds.parent[x] 36 | } 37 | 38 | // Union merges the sets containing elements x and y 39 | // Uses union by rank for optimization 40 | func (ds *DisjointSet[T]) Union(x, y T) { 41 | rootX := ds.Find(x) 42 | rootY := ds.Find(y) 43 | 44 | if rootX == rootY { 45 | return 46 | } 47 | 48 | // Union by rank 49 | if ds.rank[rootX] < ds.rank[rootY] { 50 | ds.parent[rootX] = rootY 51 | } else if ds.rank[rootX] > ds.rank[rootY] { 52 | ds.parent[rootY] = rootX 53 | } else { 54 | ds.parent[rootY] = rootX 55 | ds.rank[rootX]++ 56 | } 57 | } 58 | 59 | // Connected returns true if elements x and y are in the same set 60 | func (ds *DisjointSet[T]) Connected(x, y T) bool { 61 | return ds.Find(x) == ds.Find(y) 62 | } 63 | 64 | // Clear removes all elements from the disjoint set 65 | func (ds *DisjointSet[T]) Clear() { 66 | ds.parent = make(map[T]T) 67 | ds.rank = make(map[T]int) 68 | } 69 | 70 | // Len returns the number of elements in the disjoint set 71 | func (ds *DisjointSet[T]) Len() int { 72 | return len(ds.parent) 73 | } 74 | 75 | // IsEmpty returns true if the disjoint set contains no elements 76 | func (ds *DisjointSet[T]) IsEmpty() bool { 77 | return len(ds.parent) == 0 78 | } 79 | 80 | // GetSets returns a map of representatives to their set members 81 | func (ds *DisjointSet[T]) GetSets() map[T][]T { 82 | sets := make(map[T][]T) 83 | for element := range ds.parent { 84 | root := ds.Find(element) 85 | sets[root] = append(sets[root], element) 86 | } 87 | return sets 88 | } 89 | -------------------------------------------------------------------------------- /disjointset/disjointset_test.go: -------------------------------------------------------------------------------- 1 | package disjointset 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDisjointSet(t *testing.T) { 8 | t.Run( 9 | "New DisjointSet", func(t *testing.T) { 10 | ds := New[int]() 11 | if !ds.IsEmpty() { 12 | t.Error("New DisjointSet should be empty") 13 | } 14 | }, 15 | ) 16 | 17 | t.Run( 18 | "MakeSet", func(t *testing.T) { 19 | ds := New[int]() 20 | ds.MakeSet(1) 21 | if ds.Find(1) != 1 { 22 | t.Error("MakeSet should create a set with the element as its own representative") 23 | } 24 | }, 25 | ) 26 | 27 | t.Run( 28 | "Union and Find", func(t *testing.T) { 29 | ds := New[int]() 30 | ds.MakeSet(1) 31 | ds.MakeSet(2) 32 | ds.MakeSet(3) 33 | 34 | ds.Union(1, 2) 35 | if !ds.Connected(1, 2) { 36 | t.Error("Elements 1 and 2 should be connected after Union") 37 | } 38 | 39 | ds.Union(2, 3) 40 | if !ds.Connected(1, 3) { 41 | t.Error("Elements 1 and 3 should be connected after Union") 42 | } 43 | }, 44 | ) 45 | 46 | t.Run( 47 | "Connected", func(t *testing.T) { 48 | ds := New[string]() 49 | ds.MakeSet("A") 50 | ds.MakeSet("B") 51 | ds.MakeSet("C") 52 | 53 | if ds.Connected("A", "B") { 54 | t.Error("Elements should not be connected before Union") 55 | } 56 | 57 | ds.Union("A", "B") 58 | if !ds.Connected("A", "B") { 59 | t.Error("Elements should be connected after Union") 60 | } 61 | }, 62 | ) 63 | 64 | t.Run( 65 | "Clear", func(t *testing.T) { 66 | ds := New[int]() 67 | ds.MakeSet(1) 68 | ds.MakeSet(2) 69 | ds.Union(1, 2) 70 | 71 | ds.Clear() 72 | if !ds.IsEmpty() { 73 | t.Error("DisjointSet should be empty after Clear") 74 | } 75 | }, 76 | ) 77 | 78 | t.Run( 79 | "GetSets", func(t *testing.T) { 80 | ds := New[int]() 81 | ds.MakeSet(1) 82 | ds.MakeSet(2) 83 | ds.MakeSet(3) 84 | ds.MakeSet(4) 85 | 86 | ds.Union(1, 2) 87 | ds.Union(3, 4) 88 | 89 | sets := ds.GetSets() 90 | if len(sets) != 2 { 91 | t.Error("Should have exactly 2 distinct sets") 92 | } 93 | 94 | for _, set := range sets { 95 | if len(set) != 2 { 96 | t.Error("Each set should contain exactly 2 elements") 97 | } 98 | } 99 | }, 100 | ) 101 | 102 | t.Run( 103 | "Path Compression", func(t *testing.T) { 104 | ds := New[int]() 105 | ds.MakeSet(1) 106 | ds.MakeSet(2) 107 | ds.MakeSet(3) 108 | 109 | ds.Union(1, 2) 110 | ds.Union(2, 3) 111 | 112 | // After finding 3, the path should be compressed 113 | root := ds.Find(3) 114 | if ds.parent[3] != root { 115 | t.Error("Path compression should make 3 point directly to the root") 116 | } 117 | }, 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/idsulik/go-collections/v3 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /graph/graph.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "github.com/idsulik/go-collections/v3/iterator" 5 | ) 6 | 7 | // Graph represents the graph data structure. 8 | type Graph[T comparable] struct { 9 | directed bool 10 | nodes map[T]*node[T] 11 | } 12 | 13 | // node represents a node in the graph. 14 | type node[T comparable] struct { 15 | value T 16 | edges map[T]*edge[T] 17 | incoming map[T]*edge[T] // For directed graphs 18 | } 19 | 20 | // edge represents an edge between two nodes. 21 | type edge[T comparable] struct { 22 | from *node[T] 23 | to *node[T] 24 | weight float64 25 | } 26 | 27 | // New creates a new Graph. If directed is true, the graph is directed. 28 | func New[T comparable](directed bool) *Graph[T] { 29 | return &Graph[T]{ 30 | directed: directed, 31 | nodes: make(map[T]*node[T]), 32 | } 33 | } 34 | 35 | // AddNode adds a node to the graph and returns true if added, false if it already exists. 36 | func (g *Graph[T]) AddNode(value T) bool { 37 | if _, exists := g.nodes[value]; exists { 38 | return false 39 | } 40 | 41 | g.nodes[value] = &node[T]{ 42 | value: value, 43 | edges: make(map[T]*edge[T]), 44 | incoming: make(map[T]*edge[T]), 45 | } 46 | return true 47 | } 48 | 49 | // AddEdge adds an edge between two nodes with an optional weight and returns true if added, false if it already exists. 50 | func (g *Graph[T]) AddEdge(from, to T, weight float64) bool { 51 | fromNode, fromExists := g.nodes[from] 52 | toNode, toExists := g.nodes[to] 53 | 54 | if !fromExists { 55 | fromNode = &node[T]{ 56 | value: from, 57 | edges: make(map[T]*edge[T]), 58 | incoming: make(map[T]*edge[T]), 59 | } 60 | g.nodes[from] = fromNode 61 | } 62 | 63 | if !toExists { 64 | toNode = &node[T]{ 65 | value: to, 66 | edges: make(map[T]*edge[T]), 67 | incoming: make(map[T]*edge[T]), 68 | } 69 | g.nodes[to] = toNode 70 | } 71 | 72 | if _, exists := fromNode.edges[to]; exists { 73 | return false // Edge already exists 74 | } 75 | 76 | newEdge := &edge[T]{ 77 | from: fromNode, 78 | to: toNode, 79 | weight: weight, 80 | } 81 | 82 | fromNode.edges[to] = newEdge 83 | 84 | if !g.directed { 85 | // For undirected graphs, add the edge in both directions 86 | toNode.edges[from] = &edge[T]{ 87 | from: toNode, 88 | to: fromNode, 89 | weight: weight, 90 | } 91 | } else { 92 | // For directed graphs, update incoming edges 93 | toNode.incoming[from] = newEdge 94 | } 95 | 96 | return true 97 | } 98 | 99 | // RemoveNode removes a node and all connected edges, returns true if removed, false if not found. 100 | func (g *Graph[T]) RemoveNode(value T) bool { 101 | nodeToRemove, exists := g.nodes[value] 102 | if !exists { 103 | return false 104 | } 105 | 106 | // Remove all edges from other nodes to this node 107 | for _, n := range g.nodes { 108 | delete(n.edges, value) 109 | delete(n.incoming, value) 110 | } 111 | 112 | // For directed graphs, remove incoming edges 113 | if g.directed { 114 | for from := range nodeToRemove.incoming { 115 | delete(g.nodes[from].edges, value) 116 | } 117 | } 118 | 119 | delete(g.nodes, value) 120 | return true 121 | } 122 | 123 | // RemoveEdge removes an edge between two nodes and returns true if removed, false if not found. 124 | func (g *Graph[T]) RemoveEdge(from, to T) bool { 125 | fromNode, fromExists := g.nodes[from] 126 | toNode, toExists := g.nodes[to] 127 | 128 | if !fromExists || !toExists { 129 | return false 130 | } 131 | 132 | if _, exists := fromNode.edges[to]; !exists { 133 | return false // Edge does not exist 134 | } 135 | 136 | delete(fromNode.edges, to) 137 | 138 | if !g.directed { 139 | delete(toNode.edges, from) 140 | } else { 141 | delete(toNode.incoming, from) 142 | } 143 | 144 | return true 145 | } 146 | 147 | // Neighbors returns a slice of nodes adjacent to the given node. 148 | func (g *Graph[T]) Neighbors(value T) []T { 149 | node, exists := g.nodes[value] 150 | if !exists { 151 | return nil 152 | } 153 | 154 | neighbors := make([]T, 0, len(node.edges)) 155 | for neighbor := range node.edges { 156 | neighbors = append(neighbors, neighbor) 157 | } 158 | 159 | return neighbors 160 | } 161 | 162 | // HasNode checks if a node exists in the graph. 163 | func (g *Graph[T]) HasNode(value T) bool { 164 | _, exists := g.nodes[value] 165 | return exists 166 | } 167 | 168 | // HasEdge checks if an edge exists between two nodes. 169 | func (g *Graph[T]) HasEdge(from, to T) bool { 170 | fromNode, fromExists := g.nodes[from] 171 | if !fromExists { 172 | return false 173 | } 174 | 175 | _, exists := fromNode.edges[to] 176 | return exists 177 | } 178 | 179 | // GetEdgeWeight returns the weight of the edge between two nodes. 180 | func (g *Graph[T]) GetEdgeWeight(from, to T) (float64, bool) { 181 | if fromNode, fromExists := g.nodes[from]; fromExists { 182 | if edge, exists := fromNode.edges[to]; exists { 183 | return edge.weight, true 184 | } 185 | } 186 | return 0, false 187 | } 188 | 189 | // Traverse performs a breadth-first traversal starting from the given node. 190 | func (g *Graph[T]) Traverse(start T, visit func(T)) { 191 | startNode, exists := g.nodes[start] 192 | if !exists { 193 | return 194 | } 195 | 196 | visited := make(map[T]bool) 197 | queue := []T{startNode.value} 198 | 199 | for len(queue) > 0 { 200 | currentValue := queue[0] 201 | queue = queue[1:] 202 | 203 | if visited[currentValue] { 204 | continue 205 | } 206 | 207 | visited[currentValue] = true 208 | visit(currentValue) 209 | 210 | currentNode := g.nodes[currentValue] 211 | for neighbor := range currentNode.edges { 212 | if !visited[neighbor] { 213 | queue = append(queue, neighbor) 214 | } 215 | } 216 | } 217 | } 218 | 219 | // Nodes returns a slice of all node values in the graph. 220 | func (g *Graph[T]) Nodes() []T { 221 | nodes := make([]T, 0, len(g.nodes)) 222 | for value := range g.nodes { 223 | nodes = append(nodes, value) 224 | } 225 | return nodes 226 | } 227 | 228 | // Edges returns a slice of all edges in the graph. 229 | func (g *Graph[T]) Edges() [][2]T { 230 | var edges [][2]T 231 | seen := make(map[[2]T]bool) 232 | 233 | for fromValue, fromNode := range g.nodes { 234 | for toValue := range fromNode.edges { 235 | edgePair := [2]T{fromValue, toValue} 236 | if g.directed || !seen[edgePair] { 237 | edges = append(edges, edgePair) 238 | if !g.directed { 239 | seen[[2]T{toValue, fromValue}] = true 240 | } 241 | } 242 | } 243 | } 244 | 245 | return edges 246 | } 247 | 248 | func (g *Graph[T]) Iterator() iterator.Iterator[T] { 249 | nodes := g.Nodes() 250 | if len(nodes) == 0 { 251 | return &Iterator[T]{ 252 | visited: make(map[T]bool), 253 | queue: make([]T, 0), 254 | graph: g, 255 | } 256 | } 257 | 258 | return NewIterator(g, nodes[0]) 259 | } 260 | 261 | // ForEach applies a function to each node in the graph. 262 | func (g *Graph[T]) ForEach(fn func(T)) { 263 | for value := range g.nodes { 264 | fn(value) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /graph/graph_test.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAddNode(t *testing.T) { 8 | g := New[int](false) 9 | if !g.AddNode(1) { 10 | t.Error("Expected to add node 1") 11 | } 12 | if !g.AddNode(2) { 13 | t.Error("Expected to add node 2") 14 | } 15 | if g.AddNode(1) { 16 | t.Error("Node 1 should not be added again") 17 | } 18 | 19 | if !g.HasNode(1) || !g.HasNode(2) { 20 | t.Error("Graph should contain nodes 1 and 2") 21 | } 22 | 23 | if len(g.Nodes()) != 2 { 24 | t.Errorf("Expected 2 nodes, got %d", len(g.Nodes())) 25 | } 26 | } 27 | 28 | func TestAddEdge(t *testing.T) { 29 | g := New[int](false) 30 | if !g.AddEdge(1, 2, 1.0) { 31 | t.Error("Expected to add edge between 1 and 2") 32 | } 33 | if g.AddEdge(1, 2, 1.0) { 34 | t.Error("Edge between 1 and 2 should not be added again") 35 | } 36 | 37 | if !g.HasNode(1) || !g.HasNode(2) { 38 | t.Error("Graph should contain nodes 1 and 2 after adding edge") 39 | } 40 | 41 | if !g.HasEdge(1, 2) || !g.HasEdge(2, 1) { 42 | t.Error("Graph should have edge between 1 and 2") 43 | } 44 | 45 | weight, exists := g.GetEdgeWeight(1, 2) 46 | if !exists || weight != 1.0 { 47 | t.Error("Edge weight between 1 and 2 should be 1.0") 48 | } 49 | } 50 | 51 | func TestRemoveNode(t *testing.T) { 52 | g := New[int](false) 53 | g.AddEdge(1, 2, 1.0) 54 | g.AddEdge(2, 3, 2.0) 55 | if !g.RemoveNode(2) { 56 | t.Error("Expected to remove node 2") 57 | } 58 | if g.RemoveNode(2) { 59 | t.Error("Node 2 should not be removed again") 60 | } 61 | 62 | if g.HasNode(2) { 63 | t.Error("Node 2 should have been removed") 64 | } 65 | 66 | if g.HasEdge(1, 2) || g.HasEdge(2, 3) { 67 | t.Error("Edges connected to node 2 should have been removed") 68 | } 69 | } 70 | 71 | func TestRemoveEdge(t *testing.T) { 72 | g := New[int](false) 73 | g.AddEdge(1, 2, 1.0) 74 | if !g.RemoveEdge(1, 2) { 75 | t.Error("Expected to remove edge between 1 and 2") 76 | } 77 | if g.RemoveEdge(1, 2) { 78 | t.Error("Edge between 1 and 2 should not be removed again") 79 | } 80 | 81 | if g.HasEdge(1, 2) || g.HasEdge(2, 1) { 82 | t.Error("Edge between 1 and 2 should have been removed") 83 | } 84 | } 85 | 86 | func TestNeighbors(t *testing.T) { 87 | g := New[int](false) 88 | g.AddEdge(1, 2, 1.0) 89 | g.AddEdge(1, 3, 1.0) 90 | 91 | neighbors := g.Neighbors(1) 92 | expected := map[int]bool{2: true, 3: true} 93 | 94 | if len(neighbors) != 2 { 95 | t.Errorf("Expected 2 neighbors, got %d", len(neighbors)) 96 | } 97 | 98 | for _, n := range neighbors { 99 | if !expected[n] { 100 | t.Errorf("Unexpected neighbor: %d", n) 101 | } 102 | } 103 | } 104 | 105 | func TestDirected(t *testing.T) { 106 | g := New[int](true) 107 | if !g.AddEdge(1, 2, 1.0) { 108 | t.Error("Expected to add directed edge from 1 to 2") 109 | } 110 | 111 | if !g.HasEdge(1, 2) { 112 | t.Error("Directed graph should have edge from 1 to 2") 113 | } 114 | 115 | if g.HasEdge(2, 1) { 116 | t.Error("Directed graph should not have edge from 2 to 1") 117 | } 118 | 119 | neighbors := g.Neighbors(1) 120 | if len(neighbors) != 1 || neighbors[0] != 2 { 121 | t.Error("Neighbors of 1 should be [2]") 122 | } 123 | } 124 | 125 | func TestTraverse(t *testing.T) { 126 | g := New[int](false) 127 | g.AddEdge(1, 2, 1.0) 128 | g.AddEdge(2, 3, 1.0) 129 | g.AddEdge(3, 4, 1.0) 130 | g.AddEdge(4, 5, 1.0) 131 | 132 | visited := make(map[int]bool) 133 | g.Traverse( 134 | 1, func(value int) { 135 | visited[value] = true 136 | }, 137 | ) 138 | 139 | if len(visited) != 5 { 140 | t.Errorf("Expected to visit 5 nodes, visited %d", len(visited)) 141 | } 142 | 143 | for i := 1; i <= 5; i++ { 144 | if !visited[i] { 145 | t.Errorf("Node %d was not visited", i) 146 | } 147 | } 148 | } 149 | 150 | func TestEdges(t *testing.T) { 151 | g := New[int](false) 152 | g.AddEdge(1, 2, 1.0) 153 | g.AddEdge(2, 3, 2.0) 154 | g.AddEdge(3, 1, 3.0) 155 | 156 | edges := g.Edges() 157 | expectedEdges := map[[2]int]bool{ 158 | {1, 2}: true, 159 | {2, 3}: true, 160 | {3, 1}: true, 161 | } 162 | 163 | if len(edges) != len(expectedEdges) { 164 | t.Errorf("Expected %d edges, got %d", len(expectedEdges), len(edges)) 165 | } 166 | 167 | for _, edge := range edges { 168 | // Check both possible directions for undirected edges 169 | if !expectedEdges[[2]int{edge[0], edge[1]}] && !expectedEdges[[2]int{edge[1], edge[0]}] { 170 | t.Errorf("Unexpected edge: %v", edge) 171 | } 172 | } 173 | } 174 | 175 | func TestForEach(t *testing.T) { 176 | g := New[int](false) 177 | g.AddEdge(1, 2, 1.0) 178 | g.AddEdge(2, 3, 2.0) 179 | g.AddEdge(3, 1, 3.0) 180 | 181 | visited := make(map[int]bool) 182 | g.ForEach( 183 | func(value int) { 184 | visited[value] = true 185 | }, 186 | ) 187 | 188 | if len(visited) != 3 { 189 | t.Errorf("Expected to visit 3 nodes, visited %d", len(visited)) 190 | } 191 | 192 | for i := 1; i <= 3; i++ { 193 | if !visited[i] { 194 | t.Errorf("Node %d was not visited", i) 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /graph/iterator.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "github.com/idsulik/go-collections/v3/iterator" 5 | ) 6 | 7 | // Iterator implements iterator.Iterator for Graph using breadth-first traversal 8 | type Iterator[T comparable] struct { 9 | visited map[T]bool 10 | queue []T 11 | graph *Graph[T] 12 | start T 13 | } 14 | 15 | // NewIterator creates a new iterator for breadth-first traversal starting from the given node 16 | func NewIterator[T comparable](g *Graph[T], start T) iterator.Iterator[T] { 17 | it := &Iterator[T]{ 18 | visited: make(map[T]bool), 19 | queue: make([]T, 0), 20 | graph: g, 21 | start: start, 22 | } 23 | 24 | // Only add start node to queue if it exists in the graph 25 | if g.HasNode(start) { 26 | it.queue = append(it.queue, start) 27 | } 28 | 29 | return it 30 | } 31 | 32 | // HasNext returns true if there are more nodes to visit 33 | func (it *Iterator[T]) HasNext() bool { 34 | // Skip nodes that were removed from the graph 35 | for len(it.queue) > 0 && !it.graph.HasNode(it.queue[0]) { 36 | it.queue = it.queue[1:] 37 | } 38 | return len(it.queue) > 0 39 | } 40 | 41 | // Next returns the next node in the breadth-first traversal 42 | func (it *Iterator[T]) Next() (T, bool) { 43 | if !it.HasNext() { 44 | var zero T 45 | return zero, false 46 | } 47 | 48 | current := it.queue[0] 49 | it.queue = it.queue[1:] 50 | it.visited[current] = true 51 | 52 | // Add unvisited neighbors that exist in the graph 53 | for _, neighbor := range it.graph.Neighbors(current) { 54 | if !it.visited[neighbor] && !it.isQueued(neighbor) && it.graph.HasNode(neighbor) { 55 | it.queue = append(it.queue, neighbor) 56 | } 57 | } 58 | 59 | return current, true 60 | } 61 | 62 | // Reset restarts the iteration from the original start node 63 | func (it *Iterator[T]) Reset() { 64 | it.visited = make(map[T]bool) 65 | it.queue = it.queue[:0] 66 | 67 | // Restart from original start node if it exists 68 | if it.graph.HasNode(it.start) { 69 | it.queue = append(it.queue, it.start) 70 | } 71 | } 72 | 73 | // isQueued checks if a node is already in the queue to prevent duplicates 74 | func (it *Iterator[T]) isQueued(node T) bool { 75 | for _, n := range it.queue { 76 | if n == node { 77 | return true 78 | } 79 | } 80 | return false 81 | } 82 | -------------------------------------------------------------------------------- /graph/iterator_test.go: -------------------------------------------------------------------------------- 1 | package graph 2 | 3 | import ( 4 | "sort" 5 | "testing" 6 | ) 7 | 8 | func TestIterator_EmptyGraph(t *testing.T) { 9 | g := New[string](false) 10 | it := NewIterator(g, "") 11 | 12 | t.Run( 13 | "HasNext should return false for empty graph", func(t *testing.T) { 14 | if it.HasNext() { 15 | t.Error("HasNext() should return false for empty graph") 16 | } 17 | }, 18 | ) 19 | 20 | t.Run( 21 | "Next should return zero value and false", func(t *testing.T) { 22 | value, ok := it.Next() 23 | if ok { 24 | t.Error("Next() should return false for empty graph") 25 | } 26 | if value != "" { 27 | t.Errorf("Next() should return zero value for empty graph, got %v", value) 28 | } 29 | }, 30 | ) 31 | } 32 | 33 | func TestIterator_SingleNode(t *testing.T) { 34 | g := New[string](false) 35 | g.AddNode("A") 36 | it := NewIterator(g, "A") 37 | 38 | t.Run( 39 | "HasNext should return true initially", func(t *testing.T) { 40 | if !it.HasNext() { 41 | t.Error("HasNext() should return true when there is a node") 42 | } 43 | }, 44 | ) 45 | 46 | t.Run( 47 | "Next should return node and true", func(t *testing.T) { 48 | value, ok := it.Next() 49 | if !ok { 50 | t.Error("Next() should return true for existing node") 51 | } 52 | if value != "A" { 53 | t.Errorf("Next() returned wrong value, got %v, want 'A'", value) 54 | } 55 | }, 56 | ) 57 | 58 | t.Run( 59 | "HasNext should return false after visiting single node", func(t *testing.T) { 60 | if it.HasNext() { 61 | t.Error("HasNext() should return false after visiting single node") 62 | } 63 | }, 64 | ) 65 | } 66 | 67 | func TestIterator_LinearGraph(t *testing.T) { 68 | g := New[int](false) 69 | // Create a linear graph: 1 - 2 - 3 - 4 70 | edges := [][2]int{{1, 2}, {2, 3}, {3, 4}} 71 | for _, edge := range edges { 72 | g.AddEdge(edge[0], edge[1], 1.0) 73 | } 74 | 75 | t.Run( 76 | "Should visit all nodes in BFS order", func(t *testing.T) { 77 | it := NewIterator(g, 1) 78 | var visited []int 79 | 80 | for it.HasNext() { 81 | value, ok := it.Next() 82 | if !ok { 83 | t.Error("Next() returned false during iteration") 84 | } 85 | visited = append(visited, value) 86 | } 87 | 88 | if len(visited) != 4 { 89 | t.Errorf("Expected 4 nodes to be visited, got %d", len(visited)) 90 | } 91 | 92 | // Check BFS order: 1, 2, 3, 4 93 | expected := []int{1, 2, 3, 4} 94 | for i, v := range expected { 95 | if visited[i] != v { 96 | t.Errorf("Wrong BFS order at position %d: got %d, want %d", i, visited[i], v) 97 | } 98 | } 99 | }, 100 | ) 101 | } 102 | 103 | func TestIterator_CyclicGraph(t *testing.T) { 104 | g := New[string](false) 105 | // Create a cyclic graph: A - B - C - A 106 | edges := [][2]string{{"A", "B"}, {"B", "C"}, {"C", "A"}} 107 | for _, edge := range edges { 108 | g.AddEdge(edge[0], edge[1], 1.0) 109 | } 110 | 111 | t.Run( 112 | "Should handle cycles without infinite loop", func(t *testing.T) { 113 | it := NewIterator(g, "A") 114 | visited := make(map[string]bool) 115 | 116 | for it.HasNext() { 117 | value, _ := it.Next() 118 | visited[value] = true 119 | } 120 | 121 | expected := []string{"A", "B", "C"} 122 | for _, v := range expected { 123 | if !visited[v] { 124 | t.Errorf("Node %s was not visited", v) 125 | } 126 | } 127 | 128 | if len(visited) != 3 { 129 | t.Errorf("Expected 3 unique nodes to be visited, got %d", len(visited)) 130 | } 131 | }, 132 | ) 133 | } 134 | 135 | func TestIterator_DisconnectedGraph(t *testing.T) { 136 | g := New[string](false) 137 | // Create two disconnected components: (A-B) (C-D) 138 | g.AddEdge("A", "B", 1.0) 139 | g.AddEdge("C", "D", 1.0) 140 | 141 | t.Run( 142 | "Should only visit connected component from start node", func(t *testing.T) { 143 | it := NewIterator(g, "A") 144 | visited := make(map[string]bool) 145 | 146 | for it.HasNext() { 147 | value, _ := it.Next() 148 | visited[value] = true 149 | } 150 | 151 | // Should only visit A and B 152 | if !visited["A"] || !visited["B"] { 153 | t.Error("Failed to visit nodes in connected component") 154 | } 155 | if visited["C"] || visited["D"] { 156 | t.Error("Visited nodes in disconnected component") 157 | } 158 | }, 159 | ) 160 | } 161 | 162 | func TestIterator_Reset(t *testing.T) { 163 | g := New[int](false) 164 | edges := [][2]int{{1, 2}, {2, 3}, {3, 4}} 165 | for _, edge := range edges { 166 | g.AddEdge(edge[0], edge[1], 1.0) 167 | } 168 | 169 | t.Run( 170 | "Should allow complete retraversal after reset", func(t *testing.T) { 171 | it := NewIterator(g, 1) 172 | 173 | // First traversal 174 | firstVisit := make([]int, 0) 175 | for it.HasNext() { 176 | value, _ := it.Next() 177 | firstVisit = append(firstVisit, value) 178 | } 179 | 180 | // Reset and second traversal 181 | it.Reset() 182 | secondVisit := make([]int, 0) 183 | for it.HasNext() { 184 | value, _ := it.Next() 185 | secondVisit = append(secondVisit, value) 186 | } 187 | 188 | // Compare both traversals 189 | if len(firstVisit) != len(secondVisit) { 190 | t.Errorf( 191 | "Different number of nodes visited after reset: first %d, second %d", 192 | len(firstVisit), len(secondVisit), 193 | ) 194 | } 195 | 196 | // Sort both slices to compare (since BFS order might vary after reset) 197 | sort.Ints(firstVisit) 198 | sort.Ints(secondVisit) 199 | for i := range firstVisit { 200 | if firstVisit[i] != secondVisit[i] { 201 | t.Errorf( 202 | "Different nodes visited after reset at position %d: first %d, second %d", 203 | i, firstVisit[i], secondVisit[i], 204 | ) 205 | } 206 | } 207 | }, 208 | ) 209 | } 210 | 211 | func TestIterator_DirectedGraph(t *testing.T) { 212 | g := New[int](true) // Create directed graph 213 | // Create a directed path: 1 -> 2 -> 3 214 | g.AddEdge(1, 2, 1.0) 215 | g.AddEdge(2, 3, 1.0) 216 | 217 | t.Run( 218 | "Should respect edge direction", func(t *testing.T) { 219 | it := NewIterator(g, 1) 220 | var visited []int 221 | 222 | for it.HasNext() { 223 | value, _ := it.Next() 224 | visited = append(visited, value) 225 | } 226 | 227 | // Check order: should be able to reach all nodes from 1 228 | expected := []int{1, 2, 3} 229 | if len(visited) != len(expected) { 230 | t.Errorf("Expected %d nodes to be visited, got %d", len(expected), len(visited)) 231 | } 232 | 233 | // Start from node 3 (should not reach 1 or 2) 234 | it = NewIterator(g, 3) 235 | visited = nil 236 | for it.HasNext() { 237 | value, _ := it.Next() 238 | visited = append(visited, value) 239 | } 240 | 241 | if len(visited) != 1 || visited[0] != 3 { 242 | t.Error("Should only visit starting node in directed graph with no outgoing edges") 243 | } 244 | }, 245 | ) 246 | } 247 | 248 | func TestIterator_ModificationDuringIteration(t *testing.T) { 249 | g := New[string](false) 250 | g.AddEdge("A", "B", 1.0) 251 | g.AddEdge("B", "C", 1.0) 252 | 253 | t.Run( 254 | "Should handle graph modifications during iteration", func(t *testing.T) { 255 | it := NewIterator(g, "A") 256 | 257 | // Get first node 258 | first, _ := it.Next() 259 | if first != "A" { 260 | t.Errorf("Expected first node to be 'A', got %s", first) 261 | } 262 | 263 | // Modify graph during iteration 264 | g.AddEdge("B", "D", 1.0) 265 | g.RemoveNode("C") 266 | 267 | // Continue iteration 268 | visited := map[string]bool{first: true} 269 | for it.HasNext() { 270 | value, _ := it.Next() 271 | visited[value] = true 272 | } 273 | 274 | // Should still visit B and D, but not C 275 | if !visited["B"] { 276 | t.Error("Failed to visit node B") 277 | } 278 | if !visited["D"] { 279 | t.Error("Failed to visit new node D") 280 | } 281 | if visited["C"] { 282 | t.Error("Visited removed node C") 283 | } 284 | }, 285 | ) 286 | } 287 | -------------------------------------------------------------------------------- /internal/cmp/cmp.go: -------------------------------------------------------------------------------- 1 | package cmp 2 | 3 | type Ordered interface { 4 | ~int | ~int8 | ~int16 | ~int32 | ~int64 | 5 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | 6 | ~float32 | ~float64 | 7 | ~string 8 | } 9 | 10 | func CompareInts(a, b int) int { 11 | if a < b { 12 | return -1 13 | } 14 | if a > b { 15 | return 1 16 | } 17 | return 0 18 | } 19 | -------------------------------------------------------------------------------- /internal/slices/slices.go: -------------------------------------------------------------------------------- 1 | package slices 2 | 3 | // Equal reports whether two slices are equal: the same length and all 4 | // elements equal. If the lengths are different, Equal returns false. 5 | // Otherwise, the elements are compared in increasing index order, and the 6 | // comparison stops at the first unequal pair. 7 | // Floating point NaNs are not considered equal. 8 | func Equal[S ~[]E, E comparable](s1, s2 S) bool { 9 | if len(s1) != len(s2) { 10 | return false 11 | } 12 | for i := range s1 { 13 | if s1[i] != s2[i] { 14 | return false 15 | } 16 | } 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /iterator/interfaces.go: -------------------------------------------------------------------------------- 1 | package iterator 2 | 3 | // Iterator is a generic interface for iterating over collections 4 | type Iterator[T any] interface { 5 | // HasNext returns true if there are more elements to iterate over 6 | HasNext() bool 7 | 8 | // Next returns the next element in the iteration. 9 | // Second return value is false if there are no more elements. 10 | Next() (T, bool) 11 | 12 | // Reset restarts the iteration from the beginning 13 | Reset() 14 | } 15 | 16 | // Iterable is an interface for collections that can provide an iterator 17 | type Iterable[T any] interface { 18 | // Iterator returns a new iterator for the collection 19 | Iterator() Iterator[T] 20 | } 21 | -------------------------------------------------------------------------------- /linkedlist/iterator.go: -------------------------------------------------------------------------------- 1 | package linkedlist 2 | 3 | import ( 4 | "github.com/idsulik/go-collections/v3/iterator" 5 | ) 6 | 7 | // Iterator implements iterator.Iterator for LinkedList 8 | type Iterator[T any] struct { 9 | current *Node[T] 10 | list *LinkedList[T] 11 | } 12 | 13 | func NewIterator[T any](list *LinkedList[T]) iterator.Iterator[T] { 14 | return &Iterator[T]{list: list, current: list.head} 15 | } 16 | 17 | func (it *Iterator[T]) HasNext() bool { 18 | return it.current != nil 19 | } 20 | 21 | func (it *Iterator[T]) Next() (T, bool) { 22 | if !it.HasNext() { 23 | var zero T 24 | return zero, false 25 | } 26 | value := it.current.Value 27 | it.current = it.current.Next 28 | return value, true 29 | } 30 | 31 | func (it *Iterator[T]) Reset() { 32 | it.current = it.list.head 33 | } 34 | -------------------------------------------------------------------------------- /linkedlist/iterator_test.go: -------------------------------------------------------------------------------- 1 | package linkedlist 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIterator_EmptyList(t *testing.T) { 8 | list := New[int]() 9 | it := NewIterator(list) 10 | 11 | t.Run( 12 | "HasNext should return false", func(t *testing.T) { 13 | if it.HasNext() { 14 | t.Error("HasNext() should return false for empty list") 15 | } 16 | }, 17 | ) 18 | 19 | t.Run( 20 | "Next should return zero value and false", func(t *testing.T) { 21 | value, ok := it.Next() 22 | if ok { 23 | t.Error("Next() should return false for empty list") 24 | } 25 | if value != 0 { 26 | t.Errorf("Next() should return zero value for empty list, got %v", value) 27 | } 28 | }, 29 | ) 30 | } 31 | 32 | func TestIterator_SingleElement(t *testing.T) { 33 | list := New[string]() 34 | list.AddBack("test") 35 | it := NewIterator(list) 36 | 37 | t.Run( 38 | "HasNext should return true initially", func(t *testing.T) { 39 | if !it.HasNext() { 40 | t.Error("HasNext() should return true when there is an element") 41 | } 42 | }, 43 | ) 44 | 45 | t.Run( 46 | "Next should return element and true", func(t *testing.T) { 47 | value, ok := it.Next() 48 | if !ok { 49 | t.Error("Next() should return true for existing element") 50 | } 51 | if value != "test" { 52 | t.Errorf("Next() returned wrong value, got %v, want 'test'", value) 53 | } 54 | }, 55 | ) 56 | 57 | t.Run( 58 | "HasNext should return false after iteration", func(t *testing.T) { 59 | if it.HasNext() { 60 | t.Error("HasNext() should return false after iterating over single element") 61 | } 62 | }, 63 | ) 64 | } 65 | 66 | func TestIterator_MultipleElements(t *testing.T) { 67 | list := New[int]() 68 | expected := []int{1, 2, 3, 4, 5} 69 | 70 | for _, v := range expected { 71 | list.AddBack(v) 72 | } 73 | 74 | t.Run( 75 | "Should iterate over all elements in order", func(t *testing.T) { 76 | it := NewIterator(list) 77 | var actual []int 78 | 79 | for it.HasNext() { 80 | value, ok := it.Next() 81 | if !ok { 82 | t.Error("Next() returned false during iteration") 83 | } 84 | actual = append(actual, value) 85 | } 86 | 87 | if len(actual) != len(expected) { 88 | t.Errorf("Iterator returned wrong number of elements, got %d, want %d", len(actual), len(expected)) 89 | } 90 | 91 | for i := range expected { 92 | if actual[i] != expected[i] { 93 | t.Errorf("Wrong value at position %d, got %d, want %d", i, actual[i], expected[i]) 94 | } 95 | } 96 | }, 97 | ) 98 | } 99 | 100 | func TestIterator_Reset(t *testing.T) { 101 | list := New[int]() 102 | values := []int{1, 2, 3} 103 | for _, v := range values { 104 | list.AddBack(v) 105 | } 106 | 107 | t.Run( 108 | "Should reset to beginning of list", func(t *testing.T) { 109 | it := NewIterator(list) 110 | 111 | // Consume some elements 112 | it.Next() 113 | it.Next() 114 | 115 | // Reset iterator 116 | it.Reset() 117 | 118 | // Verify we're back at the start 119 | value, ok := it.Next() 120 | if !ok { 121 | t.Error("Next() should return true after reset") 122 | } 123 | if value != values[0] { 124 | t.Errorf("After reset, got %d, want %d", value, values[0]) 125 | } 126 | }, 127 | ) 128 | 129 | t.Run( 130 | "Should allow full iteration after reset", func(t *testing.T) { 131 | it := NewIterator(list) 132 | 133 | // Consume all elements 134 | for it.HasNext() { 135 | it.Next() 136 | } 137 | 138 | // Reset and count elements 139 | it.Reset() 140 | count := 0 141 | for it.HasNext() { 142 | _, ok := it.Next() 143 | if !ok { 144 | t.Error("Next() returned false during second iteration") 145 | } 146 | count++ 147 | } 148 | 149 | if count != len(values) { 150 | t.Errorf("Wrong number of elements after reset, got %d, want %d", count, len(values)) 151 | } 152 | }, 153 | ) 154 | } 155 | 156 | func TestIterator_ModificationDuringIteration(t *testing.T) { 157 | list := New[int]() 158 | list.AddBack(1) 159 | list.AddBack(2) 160 | 161 | it := NewIterator(list) 162 | 163 | t.Run( 164 | "Should reflect list state at creation", func(t *testing.T) { 165 | // Start iteration 166 | first, _ := it.Next() 167 | 168 | // Modify list during iteration 169 | list.AddBack(3) 170 | list.RemoveFront() 171 | 172 | // Continue iteration 173 | second, ok := it.Next() 174 | if !ok { 175 | t.Error("Next() should return true for second element") 176 | } 177 | if first != 1 || second != 2 { 178 | t.Errorf("Iterator values changed after list modification, got %d,%d, want 1,2", first, second) 179 | } 180 | }, 181 | ) 182 | } 183 | 184 | func TestIterator_CustomType(t *testing.T) { 185 | type Person struct { 186 | Name string 187 | Age int 188 | } 189 | 190 | list := New[Person]() 191 | people := []Person{ 192 | {"Alice", 25}, 193 | {"Bob", 30}, 194 | } 195 | 196 | for _, p := range people { 197 | list.AddBack(p) 198 | } 199 | 200 | t.Run( 201 | "Should work with custom types", func(t *testing.T) { 202 | it := NewIterator(list) 203 | index := 0 204 | 205 | for it.HasNext() { 206 | person, ok := it.Next() 207 | if !ok { 208 | t.Error("Next() returned false during iteration") 209 | } 210 | if person != people[index] { 211 | t.Errorf("Wrong person at index %d, got %v, want %v", index, person, people[index]) 212 | } 213 | index++ 214 | } 215 | }, 216 | ) 217 | } 218 | 219 | func TestIterator_ConcurrentIteration(t *testing.T) { 220 | list := New[int]() 221 | for i := 1; i <= 3; i++ { 222 | list.AddBack(i) 223 | } 224 | 225 | t.Run( 226 | "Multiple iterators should work independently", func(t *testing.T) { 227 | it1 := NewIterator(list) 228 | it2 := NewIterator(list) 229 | 230 | // Advance first iterator 231 | it1.Next() 232 | 233 | // Second iterator should start from beginning 234 | value, ok := it2.Next() 235 | if !ok { 236 | t.Error("Next() should return true for first element of second iterator") 237 | } 238 | if value != 1 { 239 | t.Errorf("Second iterator got wrong value, got %d, want 1", value) 240 | } 241 | }, 242 | ) 243 | } 244 | -------------------------------------------------------------------------------- /linkedlist/linkedlist.go: -------------------------------------------------------------------------------- 1 | package linkedlist 2 | 3 | import ( 4 | "github.com/idsulik/go-collections/v3/iterator" 5 | ) 6 | 7 | type Node[T any] struct { 8 | Value T 9 | Next *Node[T] 10 | } 11 | 12 | type LinkedList[T any] struct { 13 | head *Node[T] 14 | tail *Node[T] 15 | size int 16 | } 17 | 18 | func New[T any]() *LinkedList[T] { 19 | return &LinkedList[T]{} 20 | } 21 | 22 | func (l *LinkedList[T]) Iterator() iterator.Iterator[T] { 23 | return NewIterator(l) 24 | } 25 | 26 | // ForEach applies a function to each element in the list. 27 | func (l *LinkedList[T]) ForEach(fn func(T)) { 28 | current := l.head 29 | for current != nil { 30 | fn(current.Value) 31 | current = current.Next 32 | } 33 | } 34 | 35 | // AddFront adds a new node with the given value to the front of the list. 36 | func (l *LinkedList[T]) AddFront(value T) { 37 | newNode := &Node[T]{Value: value, Next: l.head} 38 | if l.head == nil { 39 | l.tail = newNode 40 | } 41 | l.head = newNode 42 | l.size++ 43 | } 44 | 45 | // PeekFront returns the value of the node at the front of the list without removing it. 46 | func (l *LinkedList[T]) PeekFront() (T, bool) { 47 | if l.head == nil { 48 | var zero T 49 | return zero, false 50 | } 51 | return l.head.Value, true 52 | } 53 | 54 | // AddBack adds a new node with the given value to the end of the list. 55 | func (l *LinkedList[T]) AddBack(value T) { 56 | newNode := &Node[T]{Value: value} 57 | if l.tail != nil { 58 | l.tail.Next = newNode 59 | } 60 | l.tail = newNode 61 | if l.head == nil { 62 | l.head = newNode 63 | } 64 | l.size++ 65 | } 66 | 67 | // PeekBack returns the value of the node at the end of the list without removing it. 68 | func (l *LinkedList[T]) PeekBack() (T, bool) { 69 | if l.tail == nil { 70 | var zero T 71 | return zero, false 72 | } 73 | return l.tail.Value, true 74 | } 75 | 76 | // RemoveFront removes the node from the front of the list and returns its value. 77 | func (l *LinkedList[T]) RemoveFront() (T, bool) { 78 | if l.head == nil { 79 | var zero T 80 | return zero, false 81 | } 82 | value := l.head.Value 83 | l.head = l.head.Next 84 | if l.head == nil { 85 | l.tail = nil 86 | } 87 | l.size-- 88 | return value, true 89 | } 90 | 91 | // RemoveBack removes the node from the end of the list and returns its value. 92 | func (l *LinkedList[T]) RemoveBack() (T, bool) { 93 | if l.head == nil { 94 | var zero T 95 | return zero, false 96 | } 97 | if l.head == l.tail { 98 | value := l.head.Value 99 | l.head = nil 100 | l.tail = nil 101 | l.size-- 102 | return value, true 103 | } 104 | current := l.head 105 | for current.Next != l.tail { 106 | current = current.Next 107 | } 108 | value := l.tail.Value 109 | l.tail = current 110 | l.tail.Next = nil 111 | l.size-- 112 | return value, true 113 | } 114 | 115 | // IsEmpty checks if the list is empty. 116 | func (l *LinkedList[T]) IsEmpty() bool { 117 | return l.size == 0 118 | } 119 | 120 | // Len returns the number of elements in the list. 121 | func (l *LinkedList[T]) Len() int { 122 | return l.size 123 | } 124 | 125 | // Clear removes all elements from the list. 126 | func (l *LinkedList[T]) Clear() { 127 | l.head = nil 128 | l.tail = nil 129 | l.size = 0 130 | } 131 | 132 | // Iterate iterates over the linked list and applies a function to each node's value 133 | // until the end of the list or the function returns false. 134 | func (l *LinkedList[T]) Iterate(fn func(T) bool) { 135 | current := l.head 136 | for current != nil { 137 | if !fn(current.Value) { 138 | break 139 | } 140 | current = current.Next 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /linkedlist/linkedlist_test.go: -------------------------------------------------------------------------------- 1 | package linkedlist 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAddFront(t *testing.T) { 8 | list := New[int]() 9 | list.AddFront(1) 10 | list.AddFront(2) 11 | 12 | if got := list.Len(); got != 2 { 13 | t.Errorf("Len() = %d; want 2", got) 14 | } 15 | 16 | if got, _ := list.RemoveFront(); got != 2 { 17 | t.Errorf("RemoveFront() = %d; want 2", got) 18 | } 19 | 20 | if got, _ := list.RemoveFront(); got != 1 { 21 | t.Errorf("RemoveFront() = %d; want 1", got) 22 | } 23 | } 24 | 25 | func TestAddBack(t *testing.T) { 26 | list := New[int]() 27 | list.AddBack(1) 28 | list.AddBack(2) 29 | 30 | if got := list.Len(); got != 2 { 31 | t.Errorf("Len() = %d; want 2", got) 32 | } 33 | 34 | if got, _ := list.RemoveFront(); got != 1 { 35 | t.Errorf("RemoveFront() = %d; want 1", got) 36 | } 37 | 38 | if got, _ := list.RemoveFront(); got != 2 { 39 | t.Errorf("RemoveFront() = %d; want 2", got) 40 | } 41 | } 42 | 43 | func TestRemoveFrontEmpty(t *testing.T) { 44 | list := New[int]() 45 | if _, ok := list.RemoveFront(); ok { 46 | t.Errorf("RemoveFront() should return false on empty list") 47 | } 48 | } 49 | 50 | func TestRemoveBackEmpty(t *testing.T) { 51 | list := New[int]() 52 | if _, ok := list.RemoveBack(); ok { 53 | t.Errorf("RemoveBack() should return false on empty list") 54 | } 55 | } 56 | 57 | func TestRemoveBack(t *testing.T) { 58 | list := New[int]() 59 | list.AddBack(1) 60 | list.AddBack(2) 61 | 62 | if got := list.Len(); got != 2 { 63 | t.Errorf("Len() = %d; want 2", got) 64 | } 65 | 66 | if got, _ := list.RemoveBack(); got != 2 { 67 | t.Errorf("RemoveBack() = %d; want 2", got) 68 | } 69 | 70 | if got, _ := list.RemoveBack(); got != 1 { 71 | t.Errorf("RemoveBack() = %d; want 1", got) 72 | } 73 | } 74 | 75 | // TestIterate tests the Iterate method of the LinkedList. 76 | func TestIterate(t *testing.T) { 77 | // Helper function to use as callback in Iterate 78 | calledValues := []int{} 79 | fn := func(value int) bool { 80 | calledValues = append(calledValues, value) 81 | return true 82 | } 83 | 84 | // Create a new linked list and add values 85 | l := New[int]() 86 | l.AddBack(1) 87 | l.AddBack(2) 88 | l.AddBack(3) 89 | 90 | // Call Iterate 91 | l.Iterate(fn) 92 | 93 | // Verify that Iterate visited all nodes and called the function with correct values 94 | expectedValues := []int{1, 2, 3} 95 | if len(calledValues) != len(expectedValues) { 96 | t.Fatalf("Expected %d calls, but got %d", len(expectedValues), len(calledValues)) 97 | } 98 | for i, value := range calledValues { 99 | if value != expectedValues[i] { 100 | t.Errorf("At index %d, expected %d but got %d", i, expectedValues[i], value) 101 | } 102 | } 103 | } 104 | 105 | // TestIterateStopsOnFalse verifies that iteration stops when the function returns false. 106 | func TestIterateStopsOnFalse(t *testing.T) { 107 | // Helper function to use as callback in Iterate 108 | var calledValues []int 109 | fn := func(value int) bool { 110 | calledValues = append(calledValues, value) 111 | return value != 2 // Stop iteration when value equals 2 112 | } 113 | 114 | // Create a new linked list and add values 115 | l := New[int]() 116 | l.AddBack(1) 117 | l.AddBack(2) 118 | l.AddBack(3) 119 | l.AddBack(4) 120 | 121 | // Call Iterate 122 | l.Iterate(fn) 123 | 124 | // Verify that Iterate stopped at value 2 125 | expectedValues := []int{1, 2} 126 | if len(calledValues) != len(expectedValues) { 127 | t.Fatalf("Expected %d calls, but got %d", len(expectedValues), len(calledValues)) 128 | } 129 | for i, value := range calledValues { 130 | if value != expectedValues[i] { 131 | t.Errorf("At index %d, expected %d but got %d", i, expectedValues[i], value) 132 | } 133 | } 134 | } 135 | 136 | // TestIterateEmptyList verifies that Iterate does nothing on an empty list. 137 | func TestIterateEmptyList(t *testing.T) { 138 | // Helper function to use as callback in Iterate 139 | fnCalled := false 140 | fn := func(value int) bool { 141 | fnCalled = true 142 | return true 143 | } 144 | 145 | // Create a new linked list and do not add any values 146 | l := New[int]() 147 | 148 | // Call Iterate 149 | l.Iterate(fn) 150 | 151 | // Verify that Iterate did not call the function 152 | if fnCalled { 153 | t.Errorf("Function should not have been called on an empty list") 154 | } 155 | } 156 | 157 | func TestSize(t *testing.T) { 158 | list := New[int]() 159 | if got := list.Len(); got != 0 { 160 | t.Errorf("Len() = %d; want 0", got) 161 | } 162 | 163 | list.AddBack(1) 164 | if got := list.Len(); got != 1 { 165 | t.Errorf("Len() = %d; want 1", got) 166 | } 167 | 168 | list.AddBack(2) 169 | if got := list.Len(); got != 2 { 170 | t.Errorf("Len() = %d; want 2", got) 171 | } 172 | 173 | list.RemoveFront() 174 | if got := list.Len(); got != 1 { 175 | t.Errorf("Len() = %d; want 1", got) 176 | } 177 | 178 | list.RemoveFront() 179 | if got := list.Len(); got != 0 { 180 | t.Errorf("Len() = %d; want 0", got) 181 | } 182 | } 183 | 184 | func TestClear(t *testing.T) { 185 | list := New[int]() 186 | list.AddBack(1) 187 | list.AddBack(2) 188 | 189 | list.Clear() 190 | if got := list.Len(); got != 0 { 191 | t.Errorf("Len() = %d; want 0", got) 192 | } 193 | } 194 | 195 | func TestForEach(t *testing.T) { 196 | list := New[int]() 197 | list.AddBack(1) 198 | list.AddBack(2) 199 | list.AddBack(3) 200 | 201 | var sum int 202 | list.ForEach( 203 | func(value int) { 204 | sum += value 205 | }, 206 | ) 207 | 208 | if sum != 6 { 209 | t.Errorf("ForEach() = %d; want 6", sum) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /priorityqueue/priorityqueue.go: -------------------------------------------------------------------------------- 1 | package priorityqueue 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/idsulik/go-collections/v3/internal/cmp" 8 | ) 9 | 10 | type PriorityQueue[T any] struct { 11 | items []T 12 | less func(a, b T) bool 13 | equals func(a, b T) bool 14 | } 15 | 16 | // Option is a function that configures a PriorityQueue. 17 | type Option[T any] func(*PriorityQueue[T]) 18 | 19 | // WithLess sets a custom less function for the PriorityQueue. 20 | func WithLess[T any](less func(a, b T) bool) Option[T] { 21 | return func(pq *PriorityQueue[T]) { 22 | pq.less = less 23 | } 24 | } 25 | 26 | // WithEquals sets a custom equals function for the PriorityQueue. 27 | func WithEquals[T any](equals func(a, b T) bool) Option[T] { 28 | return func(pq *PriorityQueue[T]) { 29 | pq.equals = equals 30 | } 31 | } 32 | 33 | // New creates a new PriorityQueue with the provided comparison function. 34 | func New[T any](less func(a, b T) bool) *PriorityQueue[T] { 35 | return &PriorityQueue[T]{ 36 | items: []T{}, 37 | less: less, 38 | equals: func(a, b T) bool { 39 | // Since we can't use == with generic types, we marshal both items 40 | // to JSON and compare the results 41 | jsonA, _ := json.Marshal(a) 42 | jsonB, _ := json.Marshal(b) 43 | return string(jsonA) == string(jsonB) 44 | }, 45 | } 46 | } 47 | 48 | // NewOrdered creates a new PriorityQueue with Ordered elements. 49 | func NewOrdered[T cmp.Ordered]() *PriorityQueue[T] { 50 | return &PriorityQueue[T]{ 51 | items: []T{}, 52 | less: func(a, b T) bool { return a < b }, 53 | equals: func(a, b T) bool { return a == b }, 54 | } 55 | } 56 | 57 | func ApplyOptions[T any](pq *PriorityQueue[T], opts ...Option[T]) { 58 | for _, opt := range opts { 59 | opt(pq) 60 | } 61 | } 62 | 63 | // Push adds an item to the priority queue. 64 | func (pq *PriorityQueue[T]) Push(item T) { 65 | pq.items = append(pq.items, item) 66 | pq.up(len(pq.items) - 1) 67 | } 68 | 69 | // Pop removes and returns the highest priority item from the queue. 70 | func (pq *PriorityQueue[T]) Pop() (T, bool) { 71 | return pq.PopFunc(func(T) bool { return true }) 72 | } 73 | 74 | // PopFunc removes and returns the first item that satisfies the given function. 75 | func (pq *PriorityQueue[T]) PopFunc(fn func(T) bool) (T, bool) { 76 | for i, v := range pq.items { 77 | if fn(v) { 78 | last := len(pq.items) - 1 79 | pq.items[i] = pq.items[last] 80 | pq.items = pq.items[:last] 81 | pq.down(i) 82 | return v, true 83 | } 84 | } 85 | var zero T 86 | return zero, false 87 | } 88 | 89 | // Peek returns the highest priority item without removing it. 90 | func (pq *PriorityQueue[T]) Peek() (T, bool) { 91 | if len(pq.items) == 0 { 92 | var zero T 93 | return zero, false 94 | } 95 | return pq.items[0], true 96 | } 97 | 98 | // Len returns the number of items in the priority queue. 99 | func (pq *PriorityQueue[T]) Len() int { 100 | return len(pq.items) 101 | } 102 | 103 | // LenFunc returns the number of items in the priority queue that satisfy the given function. 104 | func (pq *PriorityQueue[T]) LenFunc(fn func(T) bool) int { 105 | count := 0 106 | for _, v := range pq.items { 107 | if fn(v) { 108 | count++ 109 | } 110 | } 111 | return count 112 | } 113 | 114 | // IsEmpty checks if the priority queue is empty. 115 | func (pq *PriorityQueue[T]) IsEmpty() bool { 116 | return len(pq.items) == 0 117 | } 118 | 119 | // Clear removes all items from the priority queue. 120 | func (pq *PriorityQueue[T]) Clear() { 121 | pq.items = []T{} 122 | } 123 | 124 | // MarshalJSON implements json.Marshaler interface 125 | func (pq *PriorityQueue[T]) MarshalJSON() ([]byte, error) { 126 | if pq == nil { 127 | return nil, fmt.Errorf("cannot marshal nil PriorityQueue") 128 | } 129 | 130 | return json.Marshal(pq.items) 131 | } 132 | 133 | // UnmarshalJSON implements json.Unmarshaler interface 134 | func (pq *PriorityQueue[T]) UnmarshalJSON(data []byte) error { 135 | // Check if the priority queue is initialized 136 | if pq == nil { 137 | return fmt.Errorf("cannot unmarshal into nil PriorityQueue") 138 | } 139 | 140 | var items []T 141 | if err := json.Unmarshal(data, &items); err != nil { 142 | return err 143 | } 144 | 145 | pq.items = items 146 | // Heapify the entire queue 147 | for i := len(pq.items)/2 - 1; i >= 0; i-- { 148 | pq.down(i) 149 | } 150 | return nil 151 | } 152 | 153 | // GetFunc returns the first item that satisfies the given function 154 | func (pq *PriorityQueue[T]) GetFunc(fn func(T) bool) T { 155 | for _, v := range pq.items { 156 | if fn(v) { 157 | return v 158 | } 159 | } 160 | var zero T 161 | return zero 162 | } 163 | 164 | // Contains checks if an item exists in the queue 165 | // Note: This is an O(n) operation 166 | func (pq *PriorityQueue[T]) Contains(item T) bool { 167 | for _, v := range pq.items { 168 | if pq.equals(item, v) { 169 | return true 170 | } 171 | } 172 | return false 173 | } 174 | 175 | func (pq *PriorityQueue[T]) ContainsFunc(fn func(T) bool) bool { 176 | for _, v := range pq.items { 177 | if fn(v) { 178 | return true 179 | } 180 | } 181 | return false 182 | } 183 | 184 | // PushIfAbsent adds an item to the queue only if it's not already present 185 | // Returns true if the item was added, false if it was already present 186 | func (pq *PriorityQueue[T]) PushIfAbsent(item T) bool { 187 | if pq.Contains(item) { 188 | return false 189 | } 190 | 191 | pq.Push(item) 192 | return true 193 | } 194 | 195 | // RemoveFunc removes the first item that satisfies the given function 196 | func (pq *PriorityQueue[T]) RemoveFunc(fn func(T) bool) bool { 197 | for i, v := range pq.items { 198 | if fn(v) { 199 | // Remove the item by swapping with the last element and removing the last 200 | last := len(pq.items) - 1 201 | pq.items[i] = pq.items[last] 202 | pq.items = pq.items[:last] 203 | 204 | // Restore heap property 205 | if i < last { 206 | pq.down(i) 207 | pq.up(i) 208 | } 209 | return true 210 | } 211 | } 212 | return false 213 | } 214 | 215 | // Remove removes the first occurrence of the specified item from the queue 216 | // Returns true if the item was found and removed, false otherwise 217 | func (pq *PriorityQueue[T]) Remove(item T) bool { 218 | return pq.RemoveFunc( 219 | func(v T) bool { 220 | return pq.equals(item, v) 221 | }, 222 | ) 223 | } 224 | 225 | func (pq *PriorityQueue[T]) Clone() *PriorityQueue[T] { 226 | return &PriorityQueue[T]{ 227 | items: append([]T(nil), pq.items...), 228 | less: pq.less, 229 | equals: pq.equals, 230 | } 231 | } 232 | 233 | // Keys returns a slice of all items in the queue, maintaining heap order 234 | func (pq *PriorityQueue[T]) Keys() []T { 235 | result := make([]T, len(pq.items)) 236 | copy(result, pq.items) 237 | return result 238 | } 239 | 240 | // Vals is an alias for Keys() for compatibility 241 | func (pq *PriorityQueue[T]) Vals() []T { 242 | return pq.Keys() 243 | } 244 | 245 | // up restores the heap property by moving the item at index i up. 246 | func (pq *PriorityQueue[T]) up(i int) { 247 | for { 248 | parent := (i - 1) / 2 249 | if i == 0 || !pq.less(pq.items[i], pq.items[parent]) { 250 | break 251 | } 252 | pq.items[i], pq.items[parent] = pq.items[parent], pq.items[i] 253 | i = parent 254 | } 255 | } 256 | 257 | // down restores the heap property by moving the item at index i down. 258 | func (pq *PriorityQueue[T]) down(i int) { 259 | n := len(pq.items) 260 | for { 261 | left := 2*i + 1 262 | right := 2*i + 2 263 | smallest := i 264 | 265 | if left < n && pq.less(pq.items[left], pq.items[smallest]) { 266 | smallest = left 267 | } 268 | if right < n && pq.less(pq.items[right], pq.items[smallest]) { 269 | smallest = right 270 | } 271 | if smallest == i { 272 | break 273 | } 274 | pq.items[i], pq.items[smallest] = pq.items[smallest], pq.items[i] 275 | i = smallest 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /priorityqueue/priorityqueue_test.go: -------------------------------------------------------------------------------- 1 | package priorityqueue 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func TestPriorityQueueOptions(t *testing.T) { 9 | t.Run( 10 | "Custom Equals Function", func(t *testing.T) { 11 | type Person struct { 12 | ID int 13 | Name string 14 | Age int 15 | } 16 | 17 | pq := New( 18 | func(a, b Person) bool { 19 | return a.Age < b.Age 20 | }, 21 | ) 22 | 23 | // Add custom equals that only compares IDs 24 | ApplyOptions( 25 | pq, WithEquals( 26 | func(a, b Person) bool { 27 | return a.ID == b.ID 28 | }, 29 | ), 30 | ) 31 | 32 | p1 := Person{ID: 1, Name: "Alice", Age: 30} 33 | p2 := Person{ID: 1, Name: "Alice Updated", Age: 31} // Same ID, different age 34 | p3 := Person{ID: 2, Name: "Bob", Age: 25} 35 | 36 | pq.Push(p1) 37 | 38 | // Should return false because ID already exists 39 | if pq.PushIfAbsent(p2) { 40 | t.Error("Should not allow push of person with same ID") 41 | } 42 | 43 | // Should allow push of person with different ID 44 | if !pq.PushIfAbsent(p3) { 45 | t.Error("Should allow push of person with different ID") 46 | } 47 | }, 48 | ) 49 | 50 | t.Run( 51 | "Ordered Type With Custom Equals", func(t *testing.T) { 52 | pq := NewOrdered[int]() 53 | 54 | // Override default equals 55 | ApplyOptions( 56 | pq, WithEquals( 57 | func(a, b int) bool { 58 | // Consider numbers equal if they have the same parity 59 | return (a % 2) == (b % 2) 60 | }, 61 | ), 62 | ) 63 | 64 | pq.Push(1) 65 | pq.Push(3) 66 | 67 | // Should not add 5 as it's considered equal to 1 (both odd) 68 | if pq.PushIfAbsent(5) { 69 | t.Error("Should not add 5 as it's considered equal to existing odd number") 70 | } 71 | 72 | // Should add 2 as it's even 73 | if !pq.PushIfAbsent(2) { 74 | t.Error("Should add 2 as no even numbers exist") 75 | } 76 | }, 77 | ) 78 | 79 | t.Run( 80 | "Update Less Function", func(t *testing.T) { 81 | pq := NewOrdered[int]() 82 | 83 | // Change from min-heap to max-heap 84 | ApplyOptions( 85 | pq, WithLess( 86 | func(a, b int) bool { 87 | return a > b 88 | }, 89 | ), 90 | ) 91 | 92 | nums := []int{1, 3, 2, 5, 4} 93 | for _, n := range nums { 94 | pq.Push(n) 95 | } 96 | 97 | // Should now pop in descending order 98 | expected := []int{5, 4, 3, 2, 1} 99 | for _, exp := range expected { 100 | if val, ok := pq.Pop(); !ok || val != exp { 101 | t.Errorf("Expected %d, got %d", exp, val) 102 | } 103 | } 104 | }, 105 | ) 106 | 107 | t.Run( 108 | "Multiple Options", func(t *testing.T) { 109 | pq := NewOrdered[int]() 110 | 111 | ApplyOptions( 112 | pq, 113 | WithLess( 114 | func(a, b int) bool { 115 | return a > b // max-heap 116 | }, 117 | ), 118 | WithEquals( 119 | func(a, b int) bool { 120 | return a/10 == b/10 // equal if same tens digit 121 | }, 122 | ), 123 | ) 124 | 125 | pq.Push(11) 126 | 127 | // Should not add 15 as it's in the same tens group as 11 128 | if pq.PushIfAbsent(15) { 129 | t.Error("Should not add 15 as it's in same tens group as 11") 130 | } 131 | 132 | // Should add 21 as it's in different tens group 133 | if !pq.PushIfAbsent(21) { 134 | t.Error("Should add 21 as it's in different tens group") 135 | } 136 | 137 | // First pop should be 21 due to max-heap property 138 | if val, ok := pq.Pop(); !ok || val != 21 { 139 | t.Errorf("Expected 21, got %d", val) 140 | } 141 | }, 142 | ) 143 | } 144 | 145 | func TestPriorityQueue(t *testing.T) { 146 | less := func(a, b int) bool { 147 | return a < b // Min-heap: smaller numbers have higher priority 148 | } 149 | pq := New(less) 150 | 151 | pq.Push(5) 152 | pq.Push(3) 153 | pq.Push(4) 154 | pq.Push(1) 155 | pq.Push(2) 156 | 157 | expectedOrder := []int{1, 2, 3, 4, 5} 158 | 159 | for i, expected := range expectedOrder { 160 | item, ok := pq.Pop() 161 | if !ok { 162 | t.Fatalf("Expected item %d but got none", expected) 163 | } 164 | if item != expected { 165 | t.Errorf("Test %d: Expected %d, got %d", i+1, expected, item) 166 | } 167 | } 168 | 169 | // The queue should now be empty 170 | if !pq.IsEmpty() { 171 | t.Error("Priority queue should be empty after popping all elements") 172 | } 173 | } 174 | 175 | func TestPeek(t *testing.T) { 176 | less := func(a, b int) bool { 177 | return a > b // Max-heap: larger numbers have higher priority 178 | } 179 | pq := New(less) 180 | 181 | pq.Push(10) 182 | pq.Push(30) 183 | pq.Push(20) 184 | 185 | item, ok := pq.Peek() 186 | if !ok { 187 | t.Fatal("Expected to peek an item but got none") 188 | } 189 | if item != 30 { 190 | t.Errorf("Expected top item to be 30, got %d", item) 191 | } 192 | 193 | // Ensure the item was not removed 194 | if pq.Len() != 3 { 195 | t.Errorf("Expected queue length to be 3, got %d", pq.Len()) 196 | } 197 | } 198 | 199 | func TestIsEmptyAndLen(t *testing.T) { 200 | less := func(a, b int) bool { 201 | return a < b 202 | } 203 | pq := New(less) 204 | 205 | if !pq.IsEmpty() { 206 | t.Error("Newly created priority queue should be empty") 207 | } 208 | if pq.Len() != 0 { 209 | t.Errorf("Expected length 0, got %d", pq.Len()) 210 | } 211 | 212 | pq.Push(1) 213 | if pq.IsEmpty() { 214 | t.Error("Priority queue should not be empty after pushing an element") 215 | } 216 | if pq.Len() != 1 { 217 | t.Errorf("Expected length 1, got %d", pq.Len()) 218 | } 219 | } 220 | 221 | func TestLenFunc(t *testing.T) { 222 | pq := NewOrdered[int]() 223 | 224 | pq.Push(1) 225 | pq.Push(2) 226 | pq.Push(3) 227 | 228 | count := pq.LenFunc( 229 | func(v int) bool { 230 | return v%2 == 0 231 | }, 232 | ) 233 | if count != 1 { 234 | t.Errorf("Expected 1 even number, got %d", count) 235 | } 236 | } 237 | 238 | func TestClear(t *testing.T) { 239 | less := func(a, b int) bool { 240 | return a < b 241 | } 242 | pq := New(less) 243 | 244 | pq.Push(1) 245 | pq.Push(2) 246 | pq.Push(3) 247 | 248 | pq.Clear() 249 | 250 | if !pq.IsEmpty() { 251 | t.Error("Priority queue should be empty after Clear") 252 | } 253 | if pq.Len() != 0 { 254 | t.Errorf("Expected length 0 after Clear, got %d", pq.Len()) 255 | } 256 | } 257 | 258 | type Task struct { 259 | name string 260 | priority int 261 | } 262 | 263 | func TestPopCustomStruct(t *testing.T) { 264 | less := func(a, b Task) bool { 265 | return a.priority < b.priority 266 | } 267 | pq := New(less) 268 | 269 | pq.Push(Task{name: "Task 1", priority: 3}) 270 | pq.Push(Task{name: "Task 2", priority: 1}) 271 | pq.Push(Task{name: "Task 3", priority: 2}) 272 | 273 | expectedOrder := []string{"Task 2", "Task 3", "Task 1"} 274 | 275 | for i, expectedName := range expectedOrder { 276 | task, ok := pq.Pop() 277 | if !ok { 278 | t.Fatalf("Expected task %s but got none", expectedName) 279 | } 280 | if task.name != expectedName { 281 | t.Errorf("Test %d: Expected %s, got %s", i+1, expectedName, task.name) 282 | } 283 | } 284 | } 285 | 286 | func TestPopEmpty(t *testing.T) { 287 | less := func(a, b int) bool { 288 | return a < b 289 | } 290 | pq := New(less) 291 | 292 | _, ok := pq.Pop() 293 | if ok { 294 | t.Error("Expected Pop to return false on empty queue") 295 | } 296 | } 297 | 298 | func TestPopFunc(t *testing.T) { 299 | pq := NewOrdered[int]() 300 | 301 | pq.Push(1) 302 | pq.Push(2) 303 | pq.Push(3) 304 | 305 | item, ok := pq.PopFunc( 306 | func(v int) bool { 307 | return v == 2 308 | }, 309 | ) 310 | 311 | if !ok { 312 | t.Error("Expected to find and remove 2") 313 | } 314 | if item != 2 { 315 | t.Errorf("Expected 2, got %d", item) 316 | } 317 | 318 | item, ok = pq.PopFunc( 319 | func(v int) bool { 320 | return v == 4 321 | }, 322 | ) 323 | if ok { 324 | t.Error("Did not expect to find 4") 325 | } 326 | } 327 | 328 | func TestPeekEmpty(t *testing.T) { 329 | less := func(a, b int) bool { 330 | return a < b 331 | } 332 | pq := New(less) 333 | 334 | _, ok := pq.Peek() 335 | if ok { 336 | t.Error("Expected Peek to return false on empty queue") 337 | } 338 | } 339 | 340 | func TestEqualPriority(t *testing.T) { 341 | type Item struct { 342 | value string 343 | priority int 344 | } 345 | less := func(a, b Item) bool { 346 | return a.priority < b.priority 347 | } 348 | pq := New(less) 349 | 350 | pq.Push(Item{value: "Item 1", priority: 1}) 351 | pq.Push(Item{value: "Item 2", priority: 1}) 352 | pq.Push(Item{value: "Item 3", priority: 1}) 353 | 354 | items := make(map[string]bool) 355 | for i := 0; i < 3; i++ { 356 | item, ok := pq.Pop() 357 | if !ok { 358 | t.Fatal("Expected an item but got none") 359 | } 360 | items[item.value] = true 361 | } 362 | 363 | if len(items) != 3 { 364 | t.Errorf("Expected 3 unique items, got %d", len(items)) 365 | } 366 | } 367 | 368 | func TestLargeDataSet(t *testing.T) { 369 | less := func(a, b int) bool { 370 | return a < b 371 | } 372 | pq := New(less) 373 | 374 | const numElements = 10000 375 | 376 | // Push elements in reverse order 377 | for i := numElements; i > 0; i-- { 378 | pq.Push(i) 379 | } 380 | 381 | // Pop elements and ensure they are in ascending order 382 | for i := 1; i <= numElements; i++ { 383 | item, ok := pq.Pop() 384 | if !ok { 385 | t.Fatalf("Expected item %d but got none", i) 386 | } 387 | if item != i { 388 | t.Errorf("Expected %d, got %d", i, item) 389 | } 390 | } 391 | } 392 | 393 | func TestMarshalUnmarshal(t *testing.T) { 394 | less := func(a, b int) bool { 395 | return a < b 396 | } 397 | pq := New(less) 398 | 399 | data, err := pq.MarshalJSON() 400 | if err != nil { 401 | t.Fatalf("Failed to marshal: %v", err) 402 | } 403 | 404 | // Add some items 405 | items := []int{5, 3, 4, 1, 2} 406 | for _, item := range items { 407 | pq.Push(item) 408 | } 409 | 410 | // Marshal 411 | data, err = json.Marshal(pq) 412 | if err != nil { 413 | t.Fatalf("Failed to marshal: %v", err) 414 | } 415 | 416 | // Create new queue and unmarshal 417 | newPQ := New(less) 418 | err = json.Unmarshal(data, newPQ) 419 | if err != nil { 420 | t.Fatalf("Failed to unmarshal: %v", err) 421 | } 422 | 423 | // Verify contents 424 | for i := 1; i <= 5; i++ { 425 | item, ok := newPQ.Pop() 426 | if !ok || item != i { 427 | t.Errorf("Expected %d, got %d", i, item) 428 | } 429 | } 430 | } 431 | 432 | func TestMarshalNil(t *testing.T) { 433 | var pq *PriorityQueue[int] 434 | //goland:noinspection GoDfaNilDereference 435 | _, err := pq.MarshalJSON() 436 | if err == nil { 437 | t.Error("Expected unmarshal into nil PriorityQueue to fail") 438 | } 439 | } 440 | 441 | func TestUnmarshalNil(t *testing.T) { 442 | var pq *PriorityQueue[int] 443 | //goland:noinspection GoDfaNilDereference 444 | err := pq.UnmarshalJSON([]byte(`[1,2,3]`)) 445 | if err == nil { 446 | t.Error("Expected unmarshal into nil PriorityQueue to fail") 447 | } 448 | } 449 | 450 | func TestGetFunc(t *testing.T) { 451 | pq := NewOrdered[int]() 452 | 453 | pq.Push(1) 454 | pq.Push(2) 455 | pq.Push(3) 456 | 457 | item := pq.GetFunc(func(v int) bool { return v == 2 }) 458 | if item != 2 { 459 | t.Errorf("Expected 2, got %d", item) 460 | } 461 | 462 | item = pq.GetFunc(func(v int) bool { return v == 4 }) 463 | if item != 0 { 464 | t.Errorf("Expected 0, got %d", item) 465 | } 466 | } 467 | 468 | func TestContains(t *testing.T) { 469 | less := func(a, b int) bool { 470 | return a < b 471 | } 472 | pq := New(less) 473 | 474 | pq.Push(1) 475 | pq.Push(2) 476 | pq.Push(3) 477 | 478 | if !pq.Contains(2) { 479 | t.Error("Expected to find 2 in queue") 480 | } 481 | 482 | if pq.Contains(4) { 483 | t.Error("Did not expect to find 4 in queue") 484 | } 485 | } 486 | 487 | func TestContainsFunc(t *testing.T) { 488 | less := func(a, b int) bool { 489 | return a < b 490 | } 491 | pq := New(less) 492 | 493 | pq.Push(1) 494 | pq.Push(2) 495 | pq.Push(3) 496 | 497 | if !pq.ContainsFunc(func(v int) bool { return v == 2 }) { 498 | t.Error("Expected to find 2 in queue") 499 | } 500 | 501 | if pq.ContainsFunc(func(v int) bool { return v == 4 }) { 502 | t.Error("Did not expect to find 4 in queue") 503 | } 504 | } 505 | 506 | func TestPushIfAbsent(t *testing.T) { 507 | less := func(a, b int) bool { 508 | return a < b 509 | } 510 | pq := New(less) 511 | 512 | // First push should succeed 513 | if !pq.PushIfAbsent(1) { 514 | t.Error("First push should succeed") 515 | } 516 | 517 | // Second push of same value should fail 518 | if pq.PushIfAbsent(1) { 519 | t.Error("Second push should fail") 520 | } 521 | 522 | if pq.Len() != 1 { 523 | t.Errorf("Expected length 1, got %d", pq.Len()) 524 | } 525 | } 526 | 527 | func TestRemoveFunc(t *testing.T) { 528 | pq := NewOrdered[int]() 529 | 530 | pq.Push(1) 531 | pq.Push(2) 532 | pq.Push(3) 533 | 534 | // Remove existing item 535 | if !pq.RemoveFunc(func(v int) bool { return v == 2 }) { 536 | t.Error("Expected to remove 2") 537 | } 538 | 539 | // Try to remove non-existent item 540 | if pq.RemoveFunc(func(v int) bool { return v == 4 }) { 541 | t.Error("Should not be able to remove non-existent item") 542 | } 543 | 544 | // Verify remaining items 545 | expected := []int{1, 3} 546 | for _, exp := range expected { 547 | item, ok := pq.Pop() 548 | if !ok || item != exp { 549 | t.Errorf("Expected %d, got %d", exp, item) 550 | } 551 | } 552 | } 553 | 554 | func TestRemove(t *testing.T) { 555 | less := func(a, b int) bool { 556 | return a < b 557 | } 558 | pq := New(less) 559 | 560 | pq.Push(1) 561 | pq.Push(2) 562 | pq.Push(3) 563 | 564 | // Remove existing item 565 | if !pq.Remove(2) { 566 | t.Error("Expected to remove 2") 567 | } 568 | 569 | // Try to remove non-existent item 570 | if pq.Remove(4) { 571 | t.Error("Should not be able to remove non-existent item") 572 | } 573 | 574 | // Verify remaining items 575 | expected := []int{1, 3} 576 | for _, exp := range expected { 577 | item, ok := pq.Pop() 578 | if !ok || item != exp { 579 | t.Errorf("Expected %d, got %d", exp, item) 580 | } 581 | } 582 | } 583 | 584 | func TestKeysAndVals(t *testing.T) { 585 | less := func(a, b int) bool { 586 | return a < b 587 | } 588 | pq := New(less) 589 | 590 | items := []int{5, 3, 4, 1, 2} 591 | for _, item := range items { 592 | pq.Push(item) 593 | } 594 | 595 | keys := pq.Keys() 596 | if len(keys) != 5 { 597 | t.Errorf("Expected 5 keys, got %d", len(keys)) 598 | } 599 | 600 | vals := pq.Vals() 601 | if len(vals) != 5 { 602 | t.Errorf("Expected 5 values, got %d", len(vals)) 603 | } 604 | 605 | // Verify that modifying the returned slices doesn't affect the queue 606 | keys[0] = 100 607 | if pq.items[0] == 100 { 608 | t.Error("Modifying returned keys should not affect queue") 609 | } 610 | } 611 | 612 | func TestClone(t *testing.T) { 613 | less := func(a, b int) bool { 614 | return a < b 615 | } 616 | pq := New(less) 617 | 618 | pq.Push(1) 619 | pq.Push(2) 620 | pq.Push(3) 621 | 622 | clone := pq.Clone() 623 | 624 | // Verify that the clone has the same contents 625 | for i := 1; i <= 3; i++ { 626 | if !clone.Contains(i) { 627 | t.Errorf("Expected to find %d in clone", i) 628 | } 629 | } 630 | 631 | // Modify the original queue 632 | pq.Pop() 633 | 634 | // Verify that the clone is unaffected 635 | if clone.Len() != 3 { 636 | t.Errorf("Expected length 3, got %d", clone.Len()) 637 | } 638 | } 639 | -------------------------------------------------------------------------------- /queue/iterator.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "github.com/idsulik/go-collections/v3/iterator" 5 | ) 6 | 7 | // Iterator implements iterator.Iterator for Queue 8 | type Iterator[T any] struct { 9 | current int 10 | items []T 11 | } 12 | 13 | // NewIterator creates a new iterator for the queue 14 | func NewIterator[T any](q *Queue[T]) iterator.Iterator[T] { 15 | it := &Iterator[T]{ 16 | current: 0, 17 | items: make([]T, q.Len()), 18 | } 19 | 20 | // Take a snapshot of current queue items 21 | // This ensures modifications to the queue won't affect iteration 22 | if q.Len() > 0 { 23 | copy(it.items, q.GetItems()) 24 | } 25 | 26 | return it 27 | } 28 | 29 | // HasNext returns true if there are more elements to iterate over 30 | func (it *Iterator[T]) HasNext() bool { 31 | return it.current < len(it.items) 32 | } 33 | 34 | // Next returns the next element in the iteration 35 | // Returns the zero value and false if there are no more elements 36 | func (it *Iterator[T]) Next() (T, bool) { 37 | if !it.HasNext() { 38 | var zero T 39 | return zero, false 40 | } 41 | 42 | value := it.items[it.current] 43 | it.current++ 44 | return value, true 45 | } 46 | 47 | // Reset restarts iteration from the beginning 48 | // Uses the same snapshot of items from when iterator was created 49 | func (it *Iterator[T]) Reset() { 50 | it.current = 0 51 | } 52 | -------------------------------------------------------------------------------- /queue/iterator_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIterator_EmptyQueue(t *testing.T) { 8 | q := New[int](5) 9 | it := NewIterator(q) 10 | 11 | t.Run( 12 | "HasNext should return false for empty queue", func(t *testing.T) { 13 | if it.HasNext() { 14 | t.Error("HasNext() should return false for empty queue") 15 | } 16 | }, 17 | ) 18 | 19 | t.Run( 20 | "Next should return zero value and false", func(t *testing.T) { 21 | value, ok := it.Next() 22 | if ok { 23 | t.Error("Next() should return false for empty queue") 24 | } 25 | if value != 0 { 26 | t.Errorf("Next() should return zero value for empty queue, got %v", value) 27 | } 28 | }, 29 | ) 30 | } 31 | 32 | func TestIterator_SingleElement(t *testing.T) { 33 | q := New[string](5) 34 | q.Enqueue("test") 35 | it := NewIterator(q) 36 | 37 | t.Run( 38 | "HasNext should return true initially", func(t *testing.T) { 39 | if !it.HasNext() { 40 | t.Error("HasNext() should return true when there is an element") 41 | } 42 | }, 43 | ) 44 | 45 | t.Run( 46 | "Next should return element and true", func(t *testing.T) { 47 | value, ok := it.Next() 48 | if !ok { 49 | t.Error("Next() should return true for existing element") 50 | } 51 | if value != "test" { 52 | t.Errorf("Next() returned wrong value, got %v, want 'test'", value) 53 | } 54 | }, 55 | ) 56 | 57 | t.Run( 58 | "HasNext should return false after iteration", func(t *testing.T) { 59 | if it.HasNext() { 60 | t.Error("HasNext() should return false after iterating over single element") 61 | } 62 | }, 63 | ) 64 | } 65 | 66 | func TestIterator_MultipleElements(t *testing.T) { 67 | q := New[int](5) 68 | expected := []int{1, 2, 3, 4, 5} 69 | 70 | for _, v := range expected { 71 | q.Enqueue(v) 72 | } 73 | 74 | t.Run( 75 | "Should iterate over all elements in order", func(t *testing.T) { 76 | it := NewIterator(q) 77 | var actual []int 78 | 79 | for it.HasNext() { 80 | value, ok := it.Next() 81 | if !ok { 82 | t.Error("Next() returned false during iteration") 83 | } 84 | actual = append(actual, value) 85 | } 86 | 87 | if len(actual) != len(expected) { 88 | t.Errorf("Iterator returned wrong number of elements, got %d, want %d", len(actual), len(expected)) 89 | } 90 | 91 | for i := range expected { 92 | if actual[i] != expected[i] { 93 | t.Errorf("Wrong value at position %d, got %d, want %d", i, actual[i], expected[i]) 94 | } 95 | } 96 | }, 97 | ) 98 | } 99 | 100 | func TestIterator_Reset(t *testing.T) { 101 | q := New[int](5) 102 | values := []int{1, 2, 3} 103 | for _, v := range values { 104 | q.Enqueue(v) 105 | } 106 | 107 | t.Run( 108 | "Should reset to beginning of queue", func(t *testing.T) { 109 | it := NewIterator(q) 110 | 111 | // Consume some elements 112 | it.Next() 113 | it.Next() 114 | 115 | // Reset iterator 116 | it.Reset() 117 | 118 | // Verify we're back at the start 119 | value, ok := it.Next() 120 | if !ok { 121 | t.Error("Next() should return true after reset") 122 | } 123 | if value != values[0] { 124 | t.Errorf("After reset, got %d, want %d", value, values[0]) 125 | } 126 | }, 127 | ) 128 | 129 | t.Run( 130 | "Should allow full iteration after reset", func(t *testing.T) { 131 | it := NewIterator(q) 132 | 133 | // Consume all elements 134 | for it.HasNext() { 135 | it.Next() 136 | } 137 | 138 | // Reset and count elements 139 | it.Reset() 140 | count := 0 141 | for it.HasNext() { 142 | _, ok := it.Next() 143 | if !ok { 144 | t.Error("Next() returned false during second iteration") 145 | } 146 | count++ 147 | } 148 | 149 | if count != len(values) { 150 | t.Errorf("Wrong number of elements after reset, got %d, want %d", count, len(values)) 151 | } 152 | }, 153 | ) 154 | } 155 | 156 | func TestQueueIterator_ModificationDuringIteration(t *testing.T) { 157 | q := New[int](5) 158 | q.Enqueue(1) 159 | q.Enqueue(2) 160 | 161 | it := NewIterator(q) 162 | 163 | t.Run( 164 | "Should maintain original snapshot during iteration and reset", func(t *testing.T) { 165 | // Start iteration 166 | first, _ := it.Next() 167 | 168 | // Modify queue during iteration 169 | q.Enqueue(3) 170 | q.Dequeue() // Removes 1 171 | 172 | // Continue iteration - should see original snapshot 173 | second, ok := it.Next() 174 | if !ok { 175 | t.Error("Next() should return true for second element") 176 | } 177 | if first != 1 || second != 2 { 178 | t.Errorf("Iterator values changed after queue modification, got %d,%d, want 1,2", first, second) 179 | } 180 | 181 | // Reset and verify we still see original snapshot 182 | it.Reset() 183 | first, _ = it.Next() 184 | second, _ = it.Next() 185 | if first != 1 || second != 2 { 186 | t.Errorf("Iterator values changed after reset, got %d,%d, want 1,2", first, second) 187 | } 188 | }, 189 | ) 190 | 191 | t.Run( 192 | "New iterator should get its own snapshot", func(t *testing.T) { 193 | anotherIt := NewIterator(q) 194 | 195 | var values []int 196 | for anotherIt.HasNext() { 197 | v, _ := anotherIt.Next() 198 | values = append(values, v) 199 | } 200 | 201 | if len(values) != 2 || values[0] != 2 || values[1] != 3 { 202 | t.Errorf("New iterator got wrong values, got %v, want [2,3]", values) 203 | } 204 | }, 205 | ) 206 | } 207 | 208 | func TestIterator_CustomType(t *testing.T) { 209 | type Person struct { 210 | Name string 211 | Age int 212 | } 213 | 214 | q := New[Person](5) 215 | people := []Person{ 216 | {"Alice", 25}, 217 | {"Bob", 30}, 218 | } 219 | 220 | for _, p := range people { 221 | q.Enqueue(p) 222 | } 223 | 224 | t.Run( 225 | "Should work with custom types", func(t *testing.T) { 226 | it := NewIterator(q) 227 | index := 0 228 | 229 | for it.HasNext() { 230 | person, ok := it.Next() 231 | if !ok { 232 | t.Error("Next() returned false during iteration") 233 | } 234 | if person != people[index] { 235 | t.Errorf("Wrong person at index %d, got %v, want %v", index, person, people[index]) 236 | } 237 | index++ 238 | } 239 | }, 240 | ) 241 | } 242 | 243 | func TestIterator_ConcurrentIteration(t *testing.T) { 244 | q := New[int](5) 245 | for i := 1; i <= 3; i++ { 246 | q.Enqueue(i) 247 | } 248 | 249 | t.Run( 250 | "Multiple iterators should work independently", func(t *testing.T) { 251 | it1 := NewIterator(q) 252 | it2 := NewIterator(q) 253 | 254 | // Advance first iterator 255 | it1.Next() 256 | 257 | // Second iterator should start from beginning 258 | value, ok := it2.Next() 259 | if !ok { 260 | t.Error("Next() should return true for first element of second iterator") 261 | } 262 | if value != 1 { 263 | t.Errorf("Second iterator got wrong value, got %d, want 1", value) 264 | } 265 | }, 266 | ) 267 | } 268 | 269 | func TestIterator_CapacityExceeded(t *testing.T) { 270 | q := New[int](5) 271 | values := []int{1, 2, 3, 4, 5} 272 | 273 | for _, v := range values { 274 | q.Enqueue(v) 275 | } 276 | 277 | t.Run( 278 | "Should handle queue resizing", func(t *testing.T) { 279 | it := NewIterator(q) 280 | var actual []int 281 | 282 | for it.HasNext() { 283 | value, ok := it.Next() 284 | if !ok { 285 | t.Error("Next() returned false during iteration") 286 | } 287 | actual = append(actual, value) 288 | } 289 | 290 | if len(actual) != len(values) { 291 | t.Errorf("Wrong number of elements after queue resize, got %d, want %d", len(actual), len(values)) 292 | } 293 | 294 | for i := range values { 295 | if actual[i] != values[i] { 296 | t.Errorf("Wrong value at position %d after queue resize, got %d, want %d", i, actual[i], values[i]) 297 | } 298 | } 299 | }, 300 | ) 301 | } 302 | -------------------------------------------------------------------------------- /queue/queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "github.com/idsulik/go-collections/v3/deque" 5 | "github.com/idsulik/go-collections/v3/iterator" 6 | ) 7 | 8 | type Queue[T any] struct { 9 | d *deque.Deque[T] 10 | } 11 | 12 | // New creates and returns a new, empty queue. 13 | func New[T any](initialCapacity int) *Queue[T] { 14 | return &Queue[T]{ 15 | d: deque.New[T](initialCapacity), 16 | } 17 | } 18 | 19 | // Enqueue adds an item to the end of the queue. 20 | func (q *Queue[T]) Enqueue(item T) { 21 | q.d.PushBack(item) 22 | } 23 | 24 | // Dequeue removes and returns the item at the front of the queue. 25 | // Returns false if the queue is empty. 26 | func (q *Queue[T]) Dequeue() (T, bool) { 27 | return q.d.PopFront() 28 | } 29 | 30 | // Peek returns the item at the front of the queue without removing it. 31 | // Returns false if the queue is empty. 32 | func (q *Queue[T]) Peek() (T, bool) { 33 | return q.d.PeekFront() 34 | } 35 | 36 | // Len returns the number of items currently in the queue. 37 | func (q *Queue[T]) Len() int { 38 | return q.d.Len() 39 | } 40 | 41 | // IsEmpty checks if the queue is empty. 42 | func (q *Queue[T]) IsEmpty() bool { 43 | return q.d.IsEmpty() 44 | } 45 | 46 | // Clear removes all items from the queue, leaving it empty. 47 | func (q *Queue[T]) Clear() { 48 | q.d.Clear() 49 | } 50 | 51 | // Iterator returns a new iterator for the queue. 52 | func (q *Queue[T]) Iterator() iterator.Iterator[T] { 53 | return NewIterator(q) 54 | } 55 | 56 | // ForEach applies a function to each item in the queue. 57 | func (q *Queue[T]) ForEach(fn func(T)) { 58 | q.d.ForEach(fn) 59 | } 60 | 61 | // GetItems returns a slice of all items in the queue. 62 | func (q *Queue[T]) GetItems() []T { 63 | return q.d.GetItems() 64 | } 65 | -------------------------------------------------------------------------------- /queue/queue_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // TestNewQueue tests the creation of a new queue with initial capacity. 8 | func TestNewQueue(t *testing.T) { 9 | q := New[int](10) 10 | 11 | if got := q.Len(); got != 0 { 12 | t.Errorf("Len() = %d; want 0", got) 13 | } 14 | if !q.IsEmpty() { 15 | t.Errorf("IsEmpty() = false; want true") 16 | } 17 | } 18 | 19 | // TestEnqueue tests adding items to the queue. 20 | func TestEnqueue(t *testing.T) { 21 | q := New[int](10) 22 | q.Enqueue(1) 23 | q.Enqueue(2) 24 | q.Enqueue(3) 25 | 26 | if got := q.Len(); got != 3 { 27 | t.Errorf("Len() = %d; want 3", got) 28 | } 29 | if got, ok := q.Dequeue(); !ok || got != 1 { 30 | t.Errorf("Dequeue() = %d, %v; want 1, true", got, ok) 31 | } 32 | } 33 | 34 | // TestDequeueEmpty tests dequeueing from an empty queue. 35 | func TestDequeueEmpty(t *testing.T) { 36 | q := New[int](10) 37 | if _, ok := q.Dequeue(); ok { 38 | t.Errorf("Dequeue() should return false for an empty queue") 39 | } 40 | } 41 | 42 | // TestPeek tests peeking at the front of the queue. 43 | func TestPeek(t *testing.T) { 44 | q := New[int](10) 45 | q.Enqueue(1) 46 | q.Enqueue(2) 47 | 48 | if got, ok := q.Peek(); !ok || got != 1 { 49 | t.Errorf("Peek() = %d, %v; want 1, true", got, ok) 50 | } 51 | 52 | // Ensure Peek does not remove the item 53 | if got, ok := q.Peek(); !ok || got != 1 { 54 | t.Errorf("Peek() = %d, %v; want 1, true after re-peeking", got, ok) 55 | } 56 | } 57 | 58 | // TestPeekEmpty tests peeking into an empty queue. 59 | func TestPeekEmpty(t *testing.T) { 60 | q := New[int](10) 61 | if _, ok := q.Peek(); ok { 62 | t.Errorf("Peek() should return false for an empty queue") 63 | } 64 | } 65 | 66 | // TestLen tests the length of the queue. 67 | func TestLen(t *testing.T) { 68 | q := New[int](10) 69 | if got := q.Len(); got != 0 { 70 | t.Errorf("Len() = %d; want 0", got) 71 | } 72 | 73 | q.Enqueue(1) 74 | q.Enqueue(2) 75 | if got := q.Len(); got != 2 { 76 | t.Errorf("Len() = %d; want 2", got) 77 | } 78 | } 79 | 80 | // TestIsEmpty tests checking if the queue is empty. 81 | func TestIsEmpty(t *testing.T) { 82 | q := New[int](10) 83 | if !q.IsEmpty() { 84 | t.Errorf("IsEmpty() = false; want true") 85 | } 86 | 87 | q.Enqueue(1) 88 | if q.IsEmpty() { 89 | t.Errorf("IsEmpty() = true; want false") 90 | } 91 | 92 | q.Dequeue() 93 | if !q.IsEmpty() { 94 | t.Errorf("IsEmpty() = false; want true after Dequeue") 95 | } 96 | } 97 | 98 | // TestClear tests clearing the queue. 99 | func TestClear(t *testing.T) { 100 | q := New[int](10) 101 | q.Enqueue(1) 102 | q.Enqueue(2) 103 | q.Clear() 104 | 105 | if !q.IsEmpty() { 106 | t.Errorf("IsEmpty() = false; want true after Clear") 107 | } 108 | if got := q.Len(); got != 0 { 109 | t.Errorf("Len() = %d; want 0 after Clear", got) 110 | } 111 | } 112 | 113 | func TestForEach(t *testing.T) { 114 | q := New[int](10) 115 | q.Enqueue(1) 116 | q.Enqueue(2) 117 | 118 | sum := 0 119 | q.ForEach( 120 | func(item int) { 121 | sum += item 122 | }, 123 | ) 124 | 125 | if sum != 3 { 126 | t.Errorf("ForEach() = %d; want 3", sum) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /rbtree/rbtree.go: -------------------------------------------------------------------------------- 1 | // Package rbtree implements a Red-Black Tree data structure 2 | package rbtree 3 | 4 | // color represents the color of a node in the Red-Black tree 5 | type color bool 6 | 7 | const ( 8 | Black color = true 9 | Red color = false 10 | ) 11 | 12 | // Node represents a node in the Red-Black tree 13 | type node[T any] struct { 14 | value T 15 | color color 16 | left *node[T] 17 | right *node[T] 18 | parent *node[T] 19 | } 20 | 21 | // RedBlackTree represents a Red-Black tree data structure 22 | type RedBlackTree[T any] struct { 23 | root *node[T] 24 | size int 25 | compare func(a, b T) int 26 | } 27 | 28 | // New creates a new Red-Black tree 29 | func New[T any](compare func(a, b T) int) *RedBlackTree[T] { 30 | return &RedBlackTree[T]{ 31 | compare: compare, 32 | } 33 | } 34 | 35 | // Len returns the number of nodes in the tree 36 | func (t *RedBlackTree[T]) Len() int { 37 | return t.size 38 | } 39 | 40 | // IsEmpty returns true if the tree is empty 41 | func (t *RedBlackTree[T]) IsEmpty() bool { 42 | return t.size == 0 43 | } 44 | 45 | // Clear removes all nodes from the tree 46 | func (t *RedBlackTree[T]) Clear() { 47 | t.root = nil 48 | t.size = 0 49 | } 50 | 51 | // Insert adds a value to the tree if it doesn't already exist 52 | func (t *RedBlackTree[T]) Insert(value T) { 53 | // First check if value already exists 54 | if t.Search(value) { 55 | return // Don't insert duplicates 56 | } 57 | 58 | newNode := &node[T]{ 59 | value: value, 60 | color: Red, 61 | } 62 | 63 | if t.root == nil { 64 | t.root = newNode 65 | t.size++ 66 | t.insertFixup(newNode) 67 | return 68 | } 69 | 70 | current := t.root 71 | var parent *node[T] 72 | 73 | for current != nil { 74 | parent = current 75 | cmp := t.compare(value, current.value) 76 | if cmp == 0 { 77 | return // Double-check for duplicates 78 | } else if cmp < 0 { 79 | current = current.left 80 | } else { 81 | current = current.right 82 | } 83 | } 84 | 85 | newNode.parent = parent 86 | if t.compare(value, parent.value) < 0 { 87 | parent.left = newNode 88 | } else { 89 | parent.right = newNode 90 | } 91 | 92 | t.size++ 93 | t.insertFixup(newNode) 94 | } 95 | 96 | // insertFixup maintains Red-Black properties after insertion 97 | func (t *RedBlackTree[T]) insertFixup(n *node[T]) { 98 | if n.parent == nil { 99 | n.color = Black 100 | return 101 | } 102 | 103 | for n.parent != nil && n.parent.color == Red { 104 | if n.parent == n.parent.parent.left { 105 | uncle := n.parent.parent.right 106 | if uncle != nil && uncle.color == Red { 107 | n.parent.color = Black 108 | uncle.color = Black 109 | n.parent.parent.color = Red 110 | n = n.parent.parent 111 | } else { 112 | if n == n.parent.right { 113 | n = n.parent 114 | t.rotateLeft(n) 115 | } 116 | n.parent.color = Black 117 | n.parent.parent.color = Red 118 | t.rotateRight(n.parent.parent) 119 | } 120 | } else { 121 | uncle := n.parent.parent.left 122 | if uncle != nil && uncle.color == Red { 123 | n.parent.color = Black 124 | uncle.color = Black 125 | n.parent.parent.color = Red 126 | n = n.parent.parent 127 | } else { 128 | if n == n.parent.left { 129 | n = n.parent 130 | t.rotateRight(n) 131 | } 132 | n.parent.color = Black 133 | n.parent.parent.color = Red 134 | t.rotateLeft(n.parent.parent) 135 | } 136 | } 137 | if n == t.root { 138 | break 139 | } 140 | } 141 | t.root.color = Black 142 | } 143 | 144 | // rotateLeft performs a left rotation around the given node 145 | func (t *RedBlackTree[T]) rotateLeft(x *node[T]) { 146 | y := x.right 147 | x.right = y.left 148 | if y.left != nil { 149 | y.left.parent = x 150 | } 151 | y.parent = x.parent 152 | if x.parent == nil { 153 | t.root = y 154 | } else if x == x.parent.left { 155 | x.parent.left = y 156 | } else { 157 | x.parent.right = y 158 | } 159 | y.left = x 160 | x.parent = y 161 | } 162 | 163 | // rotateRight performs a right rotation around the given node 164 | func (t *RedBlackTree[T]) rotateRight(y *node[T]) { 165 | x := y.left 166 | y.left = x.right 167 | if x.right != nil { 168 | x.right.parent = y 169 | } 170 | x.parent = y.parent 171 | if y.parent == nil { 172 | t.root = x 173 | } else if y == y.parent.right { 174 | y.parent.right = x 175 | } else { 176 | y.parent.left = x 177 | } 178 | x.right = y 179 | y.parent = x 180 | } 181 | 182 | // Search checks if a value exists in the tree 183 | func (t *RedBlackTree[T]) Search(value T) bool { 184 | current := t.root 185 | for current != nil { 186 | cmp := t.compare(value, current.value) 187 | if cmp == 0 { 188 | return true 189 | } else if cmp < 0 { 190 | current = current.left 191 | } else { 192 | current = current.right 193 | } 194 | } 195 | return false 196 | } 197 | 198 | // InOrderTraversal visits all nodes in ascending order 199 | func (t *RedBlackTree[T]) InOrderTraversal(fn func(T)) { 200 | var inorder func(*node[T]) 201 | inorder = func(n *node[T]) { 202 | if n == nil { 203 | return 204 | } 205 | inorder(n.left) 206 | fn(n.value) 207 | inorder(n.right) 208 | } 209 | inorder(t.root) 210 | } 211 | 212 | // Delete removes a value from the tree 213 | func (t *RedBlackTree[T]) Delete(value T) bool { 214 | node := t.findNode(value) 215 | if node == nil { 216 | return false 217 | } 218 | 219 | t.deleteNode(node) 220 | t.size-- 221 | return true 222 | } 223 | 224 | // findNode finds the node containing the given value 225 | func (t *RedBlackTree[T]) findNode(value T) *node[T] { 226 | current := t.root 227 | for current != nil { 228 | cmp := t.compare(value, current.value) 229 | if cmp == 0 { 230 | return current 231 | } else if cmp < 0 { 232 | current = current.left 233 | } else { 234 | current = current.right 235 | } 236 | } 237 | return nil 238 | } 239 | 240 | // deleteNode removes the given node from the tree 241 | func (t *RedBlackTree[T]) deleteNode(n *node[T]) { 242 | var x, y *node[T] 243 | 244 | if n.left == nil || n.right == nil { 245 | y = n 246 | } else { 247 | y = t.successor(n) 248 | } 249 | 250 | if y.left != nil { 251 | x = y.left 252 | } else { 253 | x = y.right 254 | } 255 | 256 | if x != nil { 257 | x.parent = y.parent 258 | } 259 | 260 | if y.parent == nil { 261 | t.root = x 262 | } else if y == y.parent.left { 263 | y.parent.left = x 264 | } else { 265 | y.parent.right = x 266 | } 267 | 268 | if y != n { 269 | n.value = y.value 270 | } 271 | 272 | if y.color == Black { 273 | t.deleteFixup(x, y.parent) 274 | } 275 | } 276 | 277 | // successor returns the next larger node 278 | func (t *RedBlackTree[T]) successor(n *node[T]) *node[T] { 279 | if n.right != nil { 280 | return t.minimum(n.right) 281 | } 282 | y := n.parent 283 | for y != nil && n == y.right { 284 | n = y 285 | y = y.parent 286 | } 287 | return y 288 | } 289 | 290 | // minimum returns the node with the smallest value in the subtree 291 | func (t *RedBlackTree[T]) minimum(n *node[T]) *node[T] { 292 | current := n 293 | for current.left != nil { 294 | current = current.left 295 | } 296 | return current 297 | } 298 | 299 | // deleteFixup maintains Red-Black properties after deletion 300 | func (t *RedBlackTree[T]) deleteFixup(n *node[T], parent *node[T]) { 301 | for n != t.root && (n == nil || n.color == Black) { 302 | if n == parent.left { 303 | w := parent.right 304 | if w.color == Red { 305 | w.color = Black 306 | parent.color = Red 307 | t.rotateLeft(parent) 308 | w = parent.right 309 | } 310 | if (w.left == nil || w.left.color == Black) && 311 | (w.right == nil || w.right.color == Black) { 312 | w.color = Red 313 | n = parent 314 | parent = n.parent 315 | } else { 316 | if w.right == nil || w.right.color == Black { 317 | if w.left != nil { 318 | w.left.color = Black 319 | } 320 | w.color = Red 321 | t.rotateRight(w) 322 | w = parent.right 323 | } 324 | w.color = parent.color 325 | parent.color = Black 326 | if w.right != nil { 327 | w.right.color = Black 328 | } 329 | t.rotateLeft(parent) 330 | n = t.root 331 | break 332 | } 333 | } else { 334 | w := parent.left 335 | if w.color == Red { 336 | w.color = Black 337 | parent.color = Red 338 | t.rotateRight(parent) 339 | w = parent.left 340 | } 341 | if (w.right == nil || w.right.color == Black) && 342 | (w.left == nil || w.left.color == Black) { 343 | w.color = Red 344 | n = parent 345 | parent = n.parent 346 | } else { 347 | if w.left == nil || w.left.color == Black { 348 | if w.right != nil { 349 | w.right.color = Black 350 | } 351 | w.color = Red 352 | t.rotateLeft(w) 353 | w = parent.left 354 | } 355 | w.color = parent.color 356 | parent.color = Black 357 | if w.left != nil { 358 | w.left.color = Black 359 | } 360 | t.rotateRight(parent) 361 | n = t.root 362 | break 363 | } 364 | } 365 | } 366 | if n != nil { 367 | n.color = Black 368 | } 369 | } 370 | 371 | // Height returns the height of the tree 372 | func (t *RedBlackTree[T]) Height() int { 373 | var height func(*node[T]) int 374 | height = func(n *node[T]) int { 375 | if n == nil { 376 | return -1 377 | } 378 | leftHeight := height(n.left) 379 | rightHeight := height(n.right) 380 | if leftHeight > rightHeight { 381 | return leftHeight + 1 382 | } 383 | return rightHeight + 1 384 | } 385 | return height(t.root) 386 | } 387 | -------------------------------------------------------------------------------- /rbtree/rbtree_test.go: -------------------------------------------------------------------------------- 1 | package rbtree 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | 9 | "github.com/idsulik/go-collections/v3/internal/cmp" 10 | ) 11 | 12 | // verifyRedBlackProperties checks if the tree maintains Red-Black properties 13 | func verifyRedBlackProperties[T any](t *RedBlackTree[T]) bool { 14 | if t.root == nil { 15 | return true 16 | } 17 | 18 | // Property 1: Root must be black 19 | if t.root.color != Black { 20 | return false 21 | } 22 | 23 | // Check other properties recursively 24 | blackHeight, valid := verifyNodeProperties(t.root, nil) 25 | return valid && blackHeight >= 0 26 | } 27 | 28 | // verifyNodeProperties checks Red-Black properties for a node and its subtrees 29 | func verifyNodeProperties[T any](n *node[T], parent *node[T]) (int, bool) { 30 | if n == nil { 31 | return 0, true // Nil nodes are considered black 32 | } 33 | 34 | // Check parent pointer 35 | if n.parent != parent { 36 | return -1, false 37 | } 38 | 39 | // Property 2: No red node has a red child 40 | if n.color == Red && parent != nil && parent.color == Red { 41 | return -1, false 42 | } 43 | 44 | // Check left subtree 45 | leftBlackHeight, leftValid := verifyNodeProperties(n.left, n) 46 | if !leftValid { 47 | return -1, false 48 | } 49 | 50 | // Check right subtree 51 | rightBlackHeight, rightValid := verifyNodeProperties(n.right, n) 52 | if !rightValid { 53 | return -1, false 54 | } 55 | 56 | // Property 5: All paths must have same number of black nodes 57 | if leftBlackHeight != rightBlackHeight { 58 | return -1, false 59 | } 60 | 61 | // Calculate black height 62 | blackHeight := leftBlackHeight 63 | if n.color == Black { 64 | blackHeight++ 65 | } 66 | 67 | return blackHeight, true 68 | } 69 | 70 | func TestNewRedBlackTree(t *testing.T) { 71 | tree := New[int](cmp.CompareInts) 72 | if tree == nil { 73 | t.Error("Expected non-nil tree") 74 | } 75 | if !tree.IsEmpty() { 76 | t.Error("Expected empty tree") 77 | } 78 | if tree.Len() != 0 { 79 | t.Errorf("Expected size 0, got %d", tree.Len()) 80 | } 81 | } 82 | 83 | func TestRedBlackTree_Insert(t *testing.T) { 84 | tests := []struct { 85 | name string 86 | values []int 87 | }{ 88 | {"Empty", []int{}}, 89 | {"Single Value", []int{1}}, 90 | {"Ascending Order", []int{1, 2, 3, 4, 5}}, 91 | {"Descending Order", []int{5, 4, 3, 2, 1}}, 92 | {"Random Order", []int{3, 1, 4, 5, 2}}, 93 | {"Duplicates", []int{1, 2, 2, 3, 1}}, 94 | } 95 | 96 | for _, tt := range tests { 97 | t.Run( 98 | tt.name, func(t *testing.T) { 99 | tree := New[int](cmp.CompareInts) 100 | uniqueValues := make(map[int]bool) 101 | 102 | for _, v := range tt.values { 103 | tree.Insert(v) 104 | uniqueValues[v] = true 105 | 106 | // Verify Red-Black properties after each insertion 107 | if !verifyRedBlackProperties(tree) { 108 | t.Error("Red-Black properties violated after insertion") 109 | } 110 | } 111 | 112 | // Check size 113 | expectedSize := len(uniqueValues) 114 | if tree.Len() != expectedSize { 115 | t.Errorf("Expected size %d, got %d", expectedSize, tree.Len()) 116 | } 117 | 118 | // Verify all values are present 119 | for v := range uniqueValues { 120 | if !tree.Search(v) { 121 | t.Errorf("Value %d not found after insertion", v) 122 | } 123 | } 124 | }, 125 | ) 126 | } 127 | } 128 | 129 | func TestRedBlackTree_Delete(t *testing.T) { 130 | tests := []struct { 131 | name string 132 | insertOrder []int 133 | deleteOrder []int 134 | expectedSize int 135 | }{ 136 | { 137 | name: "Delete Root", 138 | insertOrder: []int{1}, 139 | deleteOrder: []int{1}, 140 | expectedSize: 0, 141 | }, 142 | { 143 | name: "Delete Leaf", 144 | insertOrder: []int{2, 1, 3}, 145 | deleteOrder: []int{1}, 146 | expectedSize: 2, 147 | }, 148 | { 149 | name: "Delete Internal Node", 150 | insertOrder: []int{2, 1, 3, 4}, 151 | deleteOrder: []int{3}, 152 | expectedSize: 3, 153 | }, 154 | { 155 | name: "Delete All", 156 | insertOrder: []int{1, 2, 3, 4, 5}, 157 | deleteOrder: []int{1, 2, 3, 4, 5}, 158 | expectedSize: 0, 159 | }, 160 | } 161 | 162 | for _, tt := range tests { 163 | t.Run( 164 | tt.name, func(t *testing.T) { 165 | tree := New[int](cmp.CompareInts) 166 | 167 | // Insert values 168 | for _, v := range tt.insertOrder { 169 | tree.Insert(v) 170 | } 171 | 172 | // Delete values 173 | for _, v := range tt.deleteOrder { 174 | if !tree.Delete(v) { 175 | t.Errorf("Failed to delete value %d", v) 176 | } 177 | 178 | // Verify Red-Black properties after each deletion 179 | if !verifyRedBlackProperties(tree) { 180 | t.Error("Red-Black properties violated after deletion") 181 | } 182 | } 183 | 184 | // Check final size 185 | if tree.Len() != tt.expectedSize { 186 | t.Errorf("Expected size %d, got %d", tt.expectedSize, tree.Len()) 187 | } 188 | 189 | // Verify deleted values are gone 190 | for _, v := range tt.deleteOrder { 191 | if tree.Search(v) { 192 | t.Errorf("Value %d still present after deletion", v) 193 | } 194 | } 195 | }, 196 | ) 197 | } 198 | } 199 | 200 | func TestRedBlackTree_InOrderTraversal(t *testing.T) { 201 | tests := []struct { 202 | name string 203 | values []int 204 | expected []int 205 | }{ 206 | { 207 | name: "Empty Tree", 208 | values: []int{}, 209 | expected: []int{}, 210 | }, 211 | { 212 | name: "Single Node", 213 | values: []int{1}, 214 | expected: []int{1}, 215 | }, 216 | { 217 | name: "Multiple Nodes", 218 | values: []int{5, 3, 7, 1, 9, 4, 6, 8, 2}, 219 | expected: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 220 | }, 221 | } 222 | 223 | for _, tt := range tests { 224 | t.Run( 225 | tt.name, func(t *testing.T) { 226 | tree := New[int](cmp.CompareInts) 227 | for _, v := range tt.values { 228 | tree.Insert(v) 229 | } 230 | 231 | var result []int 232 | tree.InOrderTraversal( 233 | func(v int) { 234 | result = append(result, v) 235 | }, 236 | ) 237 | 238 | if len(result) != len(tt.expected) { 239 | t.Errorf("Expected length %d, got %d", len(tt.expected), len(result)) 240 | } 241 | 242 | for i := range result { 243 | if result[i] != tt.expected[i] { 244 | t.Errorf("Expected %v at index %d, got %v", tt.expected[i], i, result[i]) 245 | } 246 | } 247 | }, 248 | ) 249 | } 250 | } 251 | 252 | func TestRedBlackTree_Height(t *testing.T) { 253 | tests := []struct { 254 | name string 255 | values []int 256 | expectedHeight int 257 | }{ 258 | {"Empty Tree", []int{}, -1}, 259 | {"Single Node", []int{1}, 0}, 260 | {"Two Nodes", []int{1, 2}, 1}, 261 | {"Three Nodes", []int{2, 1, 3}, 1}, 262 | {"Multiple Nodes", []int{5, 3, 7, 1, 9, 4, 6, 8, 2}, 3}, 263 | } 264 | 265 | for _, tt := range tests { 266 | t.Run( 267 | tt.name, func(t *testing.T) { 268 | tree := New[int](cmp.CompareInts) 269 | for _, v := range tt.values { 270 | tree.Insert(v) 271 | } 272 | 273 | height := tree.Height() 274 | if height != tt.expectedHeight { 275 | t.Errorf("Expected height %d, got %d", tt.expectedHeight, height) 276 | } 277 | }, 278 | ) 279 | } 280 | } 281 | 282 | func TestRedBlackTree_Clear(t *testing.T) { 283 | tree := New[int](cmp.CompareInts) 284 | values := []int{5, 3, 7, 1, 9} 285 | 286 | for _, v := range values { 287 | tree.Insert(v) 288 | } 289 | 290 | tree.Clear() 291 | 292 | if !tree.IsEmpty() { 293 | t.Error("Tree should be empty after Clear()") 294 | } 295 | if tree.Len() != 0 { 296 | t.Errorf("Expected size 0 after Clear(), got %d", tree.Len()) 297 | } 298 | if tree.Height() != -1 { 299 | t.Errorf("Expected height -1 after Clear(), got %d", tree.Height()) 300 | } 301 | 302 | // Verify no values remain 303 | for _, v := range values { 304 | if tree.Search(v) { 305 | t.Errorf("Value %d still present after Clear()", v) 306 | } 307 | } 308 | } 309 | 310 | func TestRedBlackTree_RandomOperations(t *testing.T) { 311 | tree := New[int](cmp.CompareInts) 312 | rng := rand.New(rand.NewSource(time.Now().UnixNano())) 313 | 314 | operations := 1000 315 | maxValue := 100 316 | values := make(map[int]bool) 317 | 318 | for i := 0; i < operations; i++ { 319 | value := rng.Intn(maxValue) 320 | if rng.Float32() < 0.7 { // 70% insertions 321 | tree.Insert(value) 322 | values[value] = true 323 | } else { // 30% deletions 324 | tree.Delete(value) 325 | delete(values, value) 326 | } 327 | 328 | // Verify Red-Black properties 329 | if !verifyRedBlackProperties(tree) { 330 | t.Errorf("Red-Black properties violated after operation %d", i) 331 | } 332 | 333 | // Verify size matches unique values 334 | if tree.Len() != len(values) { 335 | t.Errorf( 336 | "Len mismatch at operation %d: expected %d, got %d", 337 | i, len(values), tree.Len(), 338 | ) 339 | } 340 | 341 | // Verify all values are present 342 | for v := range values { 343 | if !tree.Search(v) { 344 | t.Errorf("Value %d missing at operation %d", v, i) 345 | } 346 | } 347 | } 348 | } 349 | 350 | func BenchmarkRedBlackTree(b *testing.B) { 351 | benchmarks := []struct { 352 | name string 353 | size int 354 | }{ 355 | {"Small", 100}, 356 | {"Medium", 1000}, 357 | {"Large", 10000}, 358 | } 359 | 360 | for _, bm := range benchmarks { 361 | b.Run( 362 | fmt.Sprintf("Insert_%s", bm.name), func(b *testing.B) { 363 | for i := 0; i < b.N; i++ { 364 | tree := New[int](cmp.CompareInts) 365 | for j := 0; j < bm.size; j++ { 366 | tree.Insert(j) 367 | } 368 | } 369 | }, 370 | ) 371 | 372 | b.Run( 373 | fmt.Sprintf("Search_%s", bm.name), func(b *testing.B) { 374 | tree := New[int](cmp.CompareInts) 375 | for i := 0; i < bm.size; i++ { 376 | tree.Insert(i) 377 | } 378 | b.ResetTimer() 379 | for i := 0; i < b.N; i++ { 380 | tree.Search(rand.Intn(bm.size)) 381 | } 382 | }, 383 | ) 384 | 385 | b.Run( 386 | fmt.Sprintf("Delete_%s", bm.name), func(b *testing.B) { 387 | values := make([]int, bm.size) 388 | for i := range values { 389 | values[i] = i 390 | } 391 | b.ResetTimer() 392 | for i := 0; i < b.N; i++ { 393 | b.StopTimer() 394 | tree := New[int](cmp.CompareInts) 395 | for _, v := range values { 396 | tree.Insert(v) 397 | } 398 | b.StartTimer() 399 | for _, v := range values { 400 | tree.Delete(v) 401 | } 402 | } 403 | }, 404 | ) 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /ringbuffer/ringbuffer.go: -------------------------------------------------------------------------------- 1 | package ringbuffer 2 | 3 | // RingBuffer represents a circular buffer of fixed size 4 | type RingBuffer[T any] struct { 5 | buffer []T 6 | size int 7 | head int // points to the next write position 8 | tail int // points to the next read position 9 | count int // number of elements currently in buffer 10 | } 11 | 12 | // New creates a new RingBuffer with the specified capacity 13 | func New[T any](capacity int) *RingBuffer[T] { 14 | if capacity <= 0 { 15 | capacity = 1 16 | } 17 | return &RingBuffer[T]{ 18 | buffer: make([]T, capacity), 19 | size: capacity, 20 | } 21 | } 22 | 23 | // Write adds an item to the buffer, overwriting the oldest item if the buffer is full 24 | func (r *RingBuffer[T]) Write(item T) bool { 25 | if r.count == r.size { 26 | return false // Buffer is full 27 | } 28 | 29 | r.buffer[r.head] = item 30 | r.head = (r.head + 1) % r.size 31 | r.count++ 32 | return true 33 | } 34 | 35 | // Read removes and returns the oldest item from the buffer 36 | func (r *RingBuffer[T]) Read() (T, bool) { 37 | var zero T 38 | if r.count == 0 { 39 | return zero, false // Buffer is empty 40 | } 41 | 42 | item := r.buffer[r.tail] 43 | r.tail = (r.tail + 1) % r.size 44 | r.count-- 45 | return item, true 46 | } 47 | 48 | // Peek returns the oldest item without removing it 49 | func (r *RingBuffer[T]) Peek() (T, bool) { 50 | var zero T 51 | if r.count == 0 { 52 | return zero, false 53 | } 54 | return r.buffer[r.tail], true 55 | } 56 | 57 | // IsFull returns true if the buffer is at capacity 58 | func (r *RingBuffer[T]) IsFull() bool { 59 | return r.count == r.size 60 | } 61 | 62 | // IsEmpty returns true if the buffer contains no items 63 | func (r *RingBuffer[T]) IsEmpty() bool { 64 | return r.count == 0 65 | } 66 | 67 | // Cap returns the total capacity of the buffer 68 | func (r *RingBuffer[T]) Cap() int { 69 | return r.size 70 | } 71 | 72 | // Len returns the current number of items in the buffer 73 | func (r *RingBuffer[T]) Len() int { 74 | return r.count 75 | } 76 | 77 | // Clear removes all items from the buffer 78 | func (r *RingBuffer[T]) Clear() { 79 | r.head = 0 80 | r.tail = 0 81 | r.count = 0 82 | } 83 | -------------------------------------------------------------------------------- /ringbuffer/ringbuffer_test.go: -------------------------------------------------------------------------------- 1 | package ringbuffer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRingBuffer(t *testing.T) { 8 | t.Run( 9 | "New buffer creation", func(t *testing.T) { 10 | rb := New[int](5) 11 | if rb.Cap() != 5 { 12 | t.Errorf("Expected capacity of 5, got %d", rb.Cap()) 13 | } 14 | if !rb.IsEmpty() { 15 | t.Error("New buffer should be empty") 16 | } 17 | }, 18 | ) 19 | 20 | t.Run( 21 | "Writing and reading", func(t *testing.T) { 22 | rb := New[int](3) 23 | 24 | // Write test 25 | if !rb.Write(1) { 26 | t.Error("Write should succeed on empty buffer") 27 | } 28 | if rb.Len() != 1 { 29 | t.Errorf("Expected length 1, got %d", rb.Len()) 30 | } 31 | 32 | // Read test 33 | val, ok := rb.Read() 34 | if !ok || val != 1 { 35 | t.Errorf("Expected to read 1, got %d", val) 36 | } 37 | if !rb.IsEmpty() { 38 | t.Error("Buffer should be empty after reading") 39 | } 40 | }, 41 | ) 42 | 43 | t.Run( 44 | "Buffer full behavior", func(t *testing.T) { 45 | rb := New[int](2) 46 | 47 | rb.Write(1) 48 | rb.Write(2) 49 | 50 | if !rb.IsFull() { 51 | t.Error("Buffer should be full") 52 | } 53 | 54 | if rb.Write(3) { 55 | t.Error("Write should fail when buffer is full") 56 | } 57 | }, 58 | ) 59 | 60 | t.Run( 61 | "Peek operation", func(t *testing.T) { 62 | rb := New[int](2) 63 | rb.Write(1) 64 | 65 | val, ok := rb.Peek() 66 | if !ok || val != 1 { 67 | t.Errorf("Expected to peek 1, got %d", val) 68 | } 69 | if rb.Len() != 1 { 70 | t.Error("Peek should not remove items") 71 | } 72 | }, 73 | ) 74 | 75 | t.Run( 76 | "Clear operation", func(t *testing.T) { 77 | rb := New[int](2) 78 | rb.Write(1) 79 | rb.Write(2) 80 | 81 | rb.Clear() 82 | if !rb.IsEmpty() { 83 | t.Error("Buffer should be empty after clear") 84 | } 85 | if rb.Len() != 0 { 86 | t.Errorf("Expected length 0 after clear, got %d", rb.Len()) 87 | } 88 | }, 89 | ) 90 | 91 | t.Run( 92 | "Circular behavior", func(t *testing.T) { 93 | rb := New[int](3) 94 | 95 | // Fill the buffer 96 | rb.Write(1) 97 | rb.Write(2) 98 | rb.Write(3) 99 | 100 | // Read two items 101 | rb.Read() 102 | rb.Read() 103 | 104 | // Write two more 105 | rb.Write(4) 106 | rb.Write(5) 107 | 108 | // Check the sequence 109 | val1, _ := rb.Read() 110 | val2, _ := rb.Read() 111 | val3, _ := rb.Read() 112 | 113 | if val1 != 3 || val2 != 4 || val3 != 5 { 114 | t.Error("Circular buffer not maintaining correct order") 115 | } 116 | }, 117 | ) 118 | } 119 | -------------------------------------------------------------------------------- /segmenttree/segmenttree.go: -------------------------------------------------------------------------------- 1 | package segmenttree 2 | 3 | // Operation represents a function type for segment tree operations 4 | type Operation[T any] func(T, T) T 5 | 6 | // SegmentTree provides efficient range query operations 7 | type SegmentTree[T any] struct { 8 | tree []T 9 | size int 10 | identity T 11 | combine Operation[T] 12 | } 13 | 14 | // NewSegmentTree creates a new segment tree from given array 15 | func NewSegmentTree[T any](arr []T, identity T, combine Operation[T]) *SegmentTree[T] { 16 | n := len(arr) 17 | tree := make([]T, 4*n) // 4*n is enough to store the segment tree 18 | st := &SegmentTree[T]{ 19 | tree: tree, 20 | size: n, 21 | identity: identity, 22 | combine: combine, 23 | } 24 | st.build(arr, 0, 0, n-1) 25 | return st 26 | } 27 | 28 | // build constructs the segment tree 29 | func (st *SegmentTree[T]) build(arr []T, node int, start, end int) T { 30 | if start == end { 31 | st.tree[node] = arr[start] 32 | return st.tree[node] 33 | } 34 | 35 | mid := (start + end) / 2 36 | leftVal := st.build(arr, 2*node+1, start, mid) 37 | rightVal := st.build(arr, 2*node+2, mid+1, end) 38 | st.tree[node] = st.combine(leftVal, rightVal) 39 | return st.tree[node] 40 | } 41 | 42 | // Update updates a value at given index 43 | func (st *SegmentTree[T]) Update(index int, value T) { 44 | st.updateRecursive(0, 0, st.size-1, index, value) 45 | } 46 | 47 | func (st *SegmentTree[T]) updateRecursive(node int, start, end, index int, value T) { 48 | if start == end { 49 | st.tree[node] = value 50 | return 51 | } 52 | 53 | mid := (start + end) / 2 54 | if index <= mid { 55 | st.updateRecursive(2*node+1, start, mid, index, value) 56 | } else { 57 | st.updateRecursive(2*node+2, mid+1, end, index, value) 58 | } 59 | st.tree[node] = st.combine(st.tree[2*node+1], st.tree[2*node+2]) 60 | } 61 | 62 | // Query returns the result of the operation in range [left, right] 63 | func (st *SegmentTree[T]) Query(left, right int) T { 64 | return st.queryRecursive(0, 0, st.size-1, left, right) 65 | } 66 | 67 | func (st *SegmentTree[T]) queryRecursive(node int, start, end, left, right int) T { 68 | if right < start || left > end { 69 | return st.identity 70 | } 71 | if left <= start && end <= right { 72 | return st.tree[node] 73 | } 74 | 75 | mid := (start + end) / 2 76 | leftVal := st.queryRecursive(2*node+1, start, mid, left, right) 77 | rightVal := st.queryRecursive(2*node+2, mid+1, end, left, right) 78 | return st.combine(leftVal, rightVal) 79 | } 80 | -------------------------------------------------------------------------------- /segmenttree/segmenttree_test.go: -------------------------------------------------------------------------------- 1 | package segmenttree 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestSegmentTree(t *testing.T) { 9 | // Test case 1: Range Sum 10 | t.Run( 11 | "Range Sum", func(t *testing.T) { 12 | arr := []int{1, 3, 5, 7, 9, 11} 13 | st := NewSegmentTree(arr, 0, func(a, b int) int { return a + b }) 14 | 15 | tests := []struct { 16 | left, right int 17 | want int 18 | }{ 19 | {0, 2, 9}, // 1 + 3 + 5 20 | {1, 4, 24}, // 3 + 5 + 7 + 9 21 | {0, 5, 36}, // sum of all elements 22 | {3, 3, 7}, // single element 23 | } 24 | 25 | for _, tt := range tests { 26 | got := st.Query(tt.left, tt.right) 27 | if got != tt.want { 28 | t.Errorf("Query(%d, %d) = %d; want %d", tt.left, tt.right, got, tt.want) 29 | } 30 | } 31 | 32 | // Test update 33 | st.Update(2, 6) // Change 5 to 6 34 | if got := st.Query(0, 2); got != 10 { 35 | t.Errorf("After update, Query(0, 2) = %d; want 10", got) 36 | } 37 | }, 38 | ) 39 | 40 | // Test case 2: Range Minimum 41 | t.Run( 42 | "Range Minimum", func(t *testing.T) { 43 | arr := []float64{3.5, 1.2, 4.8, 2.3, 5.4} 44 | st := NewSegmentTree(arr, math.Inf(1), func(a, b float64) float64 { return math.Min(a, b) }) 45 | 46 | tests := []struct { 47 | left, right int 48 | want float64 49 | }{ 50 | {0, 2, 1.2}, 51 | {2, 4, 2.3}, 52 | {0, 4, 1.2}, 53 | } 54 | 55 | for _, tt := range tests { 56 | got := st.Query(tt.left, tt.right) 57 | if got != tt.want { 58 | t.Errorf("Query(%d, %d) = %f; want %f", tt.left, tt.right, got, tt.want) 59 | } 60 | } 61 | }, 62 | ) 63 | } 64 | 65 | func BenchmarkSegmentTree(b *testing.B) { 66 | arr := make([]int, 10000) 67 | for i := range arr { 68 | arr[i] = i 69 | } 70 | st := NewSegmentTree(arr, 0, func(a, b int) int { return a + b }) 71 | 72 | b.Run( 73 | "Query", func(b *testing.B) { 74 | for i := 0; i < b.N; i++ { 75 | st.Query(100, 9900) 76 | } 77 | }, 78 | ) 79 | 80 | b.Run( 81 | "Update", func(b *testing.B) { 82 | for i := 0; i < b.N; i++ { 83 | st.Update(5000, i) 84 | } 85 | }, 86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /set/iterator.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "github.com/idsulik/go-collections/v3/iterator" 5 | ) 6 | 7 | type Iterator[T any] struct { 8 | current int 9 | items []T 10 | } 11 | 12 | func NewIterator[T any](items []T) iterator.Iterator[T] { 13 | return &Iterator[T]{items: items} 14 | } 15 | 16 | func (it *Iterator[T]) HasNext() bool { 17 | return it.current < len(it.items) 18 | } 19 | 20 | func (it *Iterator[T]) Next() (T, bool) { 21 | if !it.HasNext() { 22 | var zero T 23 | return zero, false 24 | } 25 | 26 | value := it.items[it.current] 27 | it.current++ 28 | return value, true 29 | } 30 | 31 | func (it *Iterator[T]) Reset() { 32 | it.current = 0 33 | } 34 | -------------------------------------------------------------------------------- /set/iterator_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIterator_EmptySet(t *testing.T) { 8 | it := NewIterator([]int{}) 9 | 10 | t.Run( 11 | "HasNext should return false for empty set", func(t *testing.T) { 12 | if it.HasNext() { 13 | t.Error("HasNext() should return false for empty set") 14 | } 15 | }, 16 | ) 17 | 18 | t.Run( 19 | "Next should return zero value and false", func(t *testing.T) { 20 | value, ok := it.Next() 21 | if ok { 22 | t.Error("Next() should return false for empty set") 23 | } 24 | if value != 0 { 25 | t.Errorf("Next() should return zero value for empty set, got %v", value) 26 | } 27 | }, 28 | ) 29 | } 30 | 31 | func TestIterator_SingleElement(t *testing.T) { 32 | it := NewIterator([]string{"test"}) 33 | 34 | t.Run( 35 | "HasNext should return true initially", func(t *testing.T) { 36 | if !it.HasNext() { 37 | t.Error("HasNext() should return true when there is an element") 38 | } 39 | }, 40 | ) 41 | 42 | t.Run( 43 | "Next should return element and true", func(t *testing.T) { 44 | value, ok := it.Next() 45 | if !ok { 46 | t.Error("Next() should return true for existing element") 47 | } 48 | if value != "test" { 49 | t.Errorf("Next() returned wrong value, got %v, want 'test'", value) 50 | } 51 | }, 52 | ) 53 | 54 | t.Run( 55 | "HasNext should return false after iteration", func(t *testing.T) { 56 | if it.HasNext() { 57 | t.Error("HasNext() should return false after iterating over single element") 58 | } 59 | }, 60 | ) 61 | } 62 | 63 | func TestIterator_MultipleElements(t *testing.T) { 64 | items := []int{1, 2, 3, 4, 5} 65 | it := NewIterator(items) 66 | 67 | t.Run( 68 | "Should iterate over all elements in order", func(t *testing.T) { 69 | var actual []int 70 | 71 | for it.HasNext() { 72 | value, ok := it.Next() 73 | if !ok { 74 | t.Error("Next() returned false during iteration") 75 | } 76 | actual = append(actual, value) 77 | } 78 | 79 | if len(actual) != len(items) { 80 | t.Errorf("Iterator returned wrong number of elements, got %d, want %d", len(actual), len(items)) 81 | } 82 | 83 | for i := range items { 84 | if actual[i] != items[i] { 85 | t.Errorf("Wrong value at position %d, got %d, want %d", i, actual[i], items[i]) 86 | } 87 | } 88 | }, 89 | ) 90 | } 91 | 92 | func TestIterator_Reset(t *testing.T) { 93 | items := []int{1, 2, 3} 94 | it := NewIterator(items) 95 | 96 | t.Run( 97 | "Should reset to beginning", func(t *testing.T) { 98 | // Consume some elements 99 | it.Next() 100 | it.Next() 101 | 102 | // Reset iterator 103 | it.Reset() 104 | 105 | // Verify we're back at the start 106 | value, ok := it.Next() 107 | if !ok { 108 | t.Error("Next() should return true after reset") 109 | } 110 | if value != items[0] { 111 | t.Errorf("After reset, got %d, want %d", value, items[0]) 112 | } 113 | }, 114 | ) 115 | 116 | t.Run( 117 | "Should allow full iteration after reset", func(t *testing.T) { 118 | it.Reset() 119 | count := 0 120 | for it.HasNext() { 121 | value, ok := it.Next() 122 | if !ok { 123 | t.Error("Next() returned false during iteration") 124 | } 125 | if value != items[count] { 126 | t.Errorf("Wrong value at position %d, got %d, want %d", count, value, items[count]) 127 | } 128 | count++ 129 | } 130 | 131 | if count != len(items) { 132 | t.Errorf("Wrong number of elements after reset, got %d, want %d", count, len(items)) 133 | } 134 | }, 135 | ) 136 | 137 | t.Run( 138 | "Should handle multiple resets", func(t *testing.T) { 139 | it.Reset() 140 | first1, _ := it.Next() 141 | it.Reset() 142 | first2, _ := it.Next() 143 | 144 | if first1 != first2 { 145 | t.Errorf("Different first values after multiple resets: got %d and %d", first1, first2) 146 | } 147 | }, 148 | ) 149 | } 150 | 151 | func TestIterator_CustomType(t *testing.T) { 152 | type Person struct { 153 | Name string 154 | Age int 155 | } 156 | 157 | people := []Person{ 158 | {"Alice", 25}, 159 | {"Bob", 30}, 160 | } 161 | 162 | t.Run( 163 | "Should work with custom types", func(t *testing.T) { 164 | it := NewIterator(people) 165 | index := 0 166 | 167 | for it.HasNext() { 168 | person, ok := it.Next() 169 | if !ok { 170 | t.Error("Next() returned false during iteration") 171 | } 172 | if person != people[index] { 173 | t.Errorf("Wrong person at index %d, got %v, want %v", index, person, people[index]) 174 | } 175 | index++ 176 | } 177 | }, 178 | ) 179 | } 180 | 181 | func TestIterator_NilSlice(t *testing.T) { 182 | var items []int 183 | it := NewIterator(items) 184 | 185 | t.Run( 186 | "Should handle nil slice", func(t *testing.T) { 187 | if it.HasNext() { 188 | t.Error("HasNext() should return false for nil slice") 189 | } 190 | 191 | value, ok := it.Next() 192 | if ok { 193 | t.Error("Next() should return false for nil slice") 194 | } 195 | if value != 0 { 196 | t.Errorf("Next() should return zero value for nil slice, got %v", value) 197 | } 198 | }, 199 | ) 200 | } 201 | 202 | func TestIterator_ConcurrentIteration(t *testing.T) { 203 | items := []int{1, 2, 3} 204 | 205 | t.Run( 206 | "Multiple iterators should work independently", func(t *testing.T) { 207 | it1 := NewIterator(items) 208 | it2 := NewIterator(items) 209 | 210 | // Advance first iterator 211 | it1.Next() 212 | 213 | // Second iterator should start from beginning 214 | value, ok := it2.Next() 215 | if !ok { 216 | t.Error("Next() should return true for first element of second iterator") 217 | } 218 | if value != 1 { 219 | t.Errorf("Second iterator got wrong value, got %d, want 1", value) 220 | } 221 | }, 222 | ) 223 | } 224 | 225 | func TestIterator_BoundaryConditions(t *testing.T) { 226 | items := []int{42} 227 | it := NewIterator(items) 228 | 229 | t.Run( 230 | "Should handle boundary conditions", func(t *testing.T) { 231 | // Call Next() at the boundary 232 | value, ok := it.Next() 233 | if !ok || value != 42 { 234 | t.Errorf("First Next() failed, got %d, %v", value, ok) 235 | } 236 | 237 | // Should be at the end now 238 | if it.HasNext() { 239 | t.Error("HasNext() should be false after last element") 240 | } 241 | 242 | // Call Next() past the end 243 | value, ok = it.Next() 244 | if ok { 245 | t.Error("Next() should return false when past end") 246 | } 247 | if value != 0 { 248 | t.Errorf("Next() should return zero value when past end, got %d", value) 249 | } 250 | 251 | // Reset and verify we can iterate again 252 | it.Reset() 253 | if !it.HasNext() { 254 | t.Error("HasNext() should be true after reset") 255 | } 256 | }, 257 | ) 258 | } 259 | -------------------------------------------------------------------------------- /set/set.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/idsulik/go-collections/v3/iterator" 7 | ) 8 | 9 | // Set represents a set of unique items. 10 | type Set[T comparable] struct { 11 | items map[T]struct{} 12 | // Store NaN values separately since NaN != NaN but maps treat NaN keys as equal 13 | hasNaN bool 14 | } 15 | 16 | // New creates and returns a new, empty set. 17 | func New[T comparable]() *Set[T] { 18 | return &Set[T]{ 19 | items: make(map[T]struct{}), 20 | hasNaN: false, 21 | } 22 | } 23 | 24 | // isNaN checks if the value is a NaN float 25 | func isNaN[T comparable](v T) bool { 26 | // Type assert to check if T is float32 or float64 27 | switch val := any(v).(type) { 28 | case float32: 29 | return math.IsNaN(float64(val)) 30 | case float64: 31 | return math.IsNaN(val) 32 | default: 33 | return false 34 | } 35 | } 36 | 37 | // Add adds an item to the set. 38 | func (s *Set[T]) Add(item T) { 39 | if isNaN(item) { 40 | s.hasNaN = true 41 | return 42 | } 43 | s.items[item] = struct{}{} 44 | } 45 | 46 | // Remove removes an item from the set. 47 | func (s *Set[T]) Remove(item T) { 48 | if isNaN(item) { 49 | s.hasNaN = false 50 | return 51 | } 52 | delete(s.items, item) 53 | } 54 | 55 | // Has returns true if the set contains the specified item. 56 | func (s *Set[T]) Has(item T) bool { 57 | if isNaN(item) { 58 | return s.hasNaN 59 | } 60 | _, ok := s.items[item] 61 | return ok 62 | } 63 | 64 | // Clear removes all items from the set. 65 | func (s *Set[T]) Clear() { 66 | s.items = make(map[T]struct{}) 67 | s.hasNaN = false 68 | } 69 | 70 | // Len returns the number of items in the set. 71 | func (s *Set[T]) Len() int { 72 | count := len(s.items) 73 | if s.hasNaN { 74 | count++ 75 | } 76 | return count 77 | } 78 | 79 | // IsEmpty returns true if the set is empty. 80 | func (s *Set[T]) IsEmpty() bool { 81 | return len(s.items) == 0 && !s.hasNaN 82 | } 83 | 84 | // Elements returns a slice containing all items in the set. 85 | func (s *Set[T]) Elements() []T { 86 | elements := make([]T, 0, s.Len()) 87 | for item := range s.items { 88 | elements = append(elements, item) 89 | } 90 | // Add NaN if present 91 | if s.hasNaN { 92 | var nan T 93 | switch any(nan).(type) { 94 | case float32: 95 | elements = append(elements, any(float32(math.NaN())).(T)) 96 | case float64: 97 | elements = append(elements, any(math.NaN()).(T)) 98 | } 99 | } 100 | return elements 101 | } 102 | 103 | // AddAll adds multiple items to the set. 104 | func (s *Set[T]) AddAll(items ...T) { 105 | for _, item := range items { 106 | s.Add(item) 107 | } 108 | } 109 | 110 | // RemoveAll removes multiple items from the set. 111 | func (s *Set[T]) RemoveAll(items ...T) { 112 | for _, item := range items { 113 | s.Remove(item) 114 | } 115 | } 116 | 117 | // Diff returns a new set containing items that are in the receiver set but not in the other set. 118 | func (s *Set[T]) Diff(other *Set[T]) *Set[T] { 119 | out := New[T]() 120 | for item := range s.items { 121 | if !other.Has(item) { 122 | out.Add(item) 123 | } 124 | } 125 | s.handleNan(other, out) 126 | return out 127 | } 128 | 129 | // Intersect returns a new set containing items that are in both the receiver set and the other set. 130 | func (s *Set[T]) Intersect(other *Set[T]) *Set[T] { 131 | out := New[T]() 132 | for item := range s.items { 133 | if other.Has(item) { 134 | out.Add(item) 135 | } 136 | } 137 | s.handleNan(other, out) 138 | return out 139 | } 140 | 141 | // Union returns a new set containing items that are in either the receiver set or the other set. 142 | func (s *Set[T]) Union(other *Set[T]) *Set[T] { 143 | out := New[T]() 144 | for item := range s.items { 145 | out.Add(item) 146 | } 147 | for item := range other.items { 148 | out.Add(item) 149 | } 150 | 151 | s.handleNan(other, out) 152 | return out 153 | } 154 | 155 | // IsSubset returns true if the receiver set is a subset of the other set. 156 | func (s *Set[T]) IsSubset(other *Set[T]) bool { 157 | if s.hasNaN && !other.hasNaN { 158 | return false 159 | } 160 | for item := range s.items { 161 | if !other.Has(item) { 162 | return false 163 | } 164 | } 165 | return true 166 | } 167 | 168 | // IsSuperset returns true if the receiver set is a superset of the other set. 169 | func (s *Set[T]) IsSuperset(other *Set[T]) bool { 170 | return other.IsSubset(s) 171 | } 172 | 173 | // Equal returns true if the receiver set is equal to the other set. 174 | func (s *Set[T]) Equal(other *Set[T]) bool { 175 | if s.Len() != other.Len() { 176 | return false 177 | } 178 | if s.hasNaN != other.hasNaN { 179 | return false 180 | } 181 | for item := range s.items { 182 | if !other.Has(item) { 183 | return false 184 | } 185 | } 186 | 187 | return true 188 | } 189 | 190 | func (s *Set[T]) handleNan(other *Set[T], out *Set[T]) { 191 | if s.hasNaN || other.hasNaN { 192 | var nan T 193 | switch any(nan).(type) { 194 | case float32: 195 | out.Add(any(float32(math.NaN())).(T)) 196 | case float64: 197 | out.Add(any(math.NaN()).(T)) 198 | } 199 | } 200 | } 201 | 202 | // Iterator returns a new iterator for the set. 203 | func (s *Set[T]) Iterator() iterator.Iterator[T] { 204 | return NewIterator(s.Elements()) 205 | } 206 | -------------------------------------------------------------------------------- /set/set_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "sort" 7 | "testing" 8 | ) 9 | 10 | // TestNew checks the creation of a new Set. 11 | func TestNew(t *testing.T) { 12 | s := New[int]() 13 | if s == nil { 14 | t.Error("Expected new set to be non-nil") 15 | } 16 | } 17 | 18 | // TestAdd checks adding elements to the Set. 19 | func TestAdd(t *testing.T) { 20 | t.Run( 21 | "regular integers", func(t *testing.T) { 22 | s := New[int]() 23 | s.Add(1) 24 | if !s.Has(1) { 25 | t.Errorf("Expected 1 to be in the set") 26 | } 27 | }, 28 | ) 29 | 30 | t.Run( 31 | "float64 NaN handling", func(t *testing.T) { 32 | s := New[float64]() 33 | nan1 := math.NaN() 34 | nan2 := math.NaN() 35 | s.Add(nan1) 36 | if !s.Has(nan2) { 37 | t.Error("Expected NaN to be in the set") 38 | } 39 | if s.Len() != 1 { 40 | t.Errorf("Expected length 1, got %d", s.Len()) 41 | } 42 | }, 43 | ) 44 | 45 | t.Run( 46 | "float32 NaN handling", func(t *testing.T) { 47 | s := New[float32]() 48 | nan1 := float32(math.NaN()) 49 | nan2 := float32(math.NaN()) 50 | s.Add(nan1) 51 | if !s.Has(nan2) { 52 | t.Error("Expected NaN to be in the set") 53 | } 54 | if s.Len() != 1 { 55 | t.Errorf("Expected length 1, got %d", s.Len()) 56 | } 57 | }, 58 | ) 59 | } 60 | 61 | // TestRemove checks removing elements from the Set. 62 | func TestRemove(t *testing.T) { 63 | t.Run( 64 | "regular integers", func(t *testing.T) { 65 | s := New[int]() 66 | s.Add(1) 67 | s.Remove(1) 68 | if s.Has(1) { 69 | t.Errorf("Expected 1 to be removed from the set") 70 | } 71 | }, 72 | ) 73 | 74 | t.Run( 75 | "float64 NaN handling", func(t *testing.T) { 76 | s := New[float64]() 77 | nan1 := math.NaN() 78 | nan2 := math.NaN() 79 | s.Add(nan1) 80 | s.Remove(nan2) 81 | if s.Has(nan1) { 82 | t.Error("Expected NaN to be removed from the set") 83 | } 84 | }, 85 | ) 86 | } 87 | 88 | // TestHas checks if the Set has an element. 89 | func TestHas(t *testing.T) { 90 | t.Run( 91 | "regular integers", func(t *testing.T) { 92 | s := New[int]() 93 | s.Add(1) 94 | if !s.Has(1) { 95 | t.Errorf("Expected 1 to be in the set") 96 | } 97 | }, 98 | ) 99 | 100 | t.Run( 101 | "float64 NaN handling", func(t *testing.T) { 102 | s := New[float64]() 103 | nan1 := math.NaN() 104 | nan2 := math.NaN() 105 | s.Add(nan1) 106 | if !s.Has(nan2) { 107 | t.Error("Expected NaN to be in the set") 108 | } 109 | }, 110 | ) 111 | } 112 | 113 | // TestClear checks clearing the Set. 114 | func TestClear(t *testing.T) { 115 | t.Run( 116 | "regular integers", func(t *testing.T) { 117 | s := New[int]() 118 | s.Add(1) 119 | s.Add(2) 120 | s.Clear() 121 | if s.Len() != 0 { 122 | t.Errorf("Expected set to be empty, got size %d", s.Len()) 123 | } 124 | }, 125 | ) 126 | 127 | t.Run( 128 | "with NaN values", func(t *testing.T) { 129 | s := New[float64]() 130 | s.Add(1.0) 131 | s.Add(math.NaN()) 132 | s.Clear() 133 | if s.Len() != 0 { 134 | t.Errorf("Expected set to be empty, got size %d", s.Len()) 135 | } 136 | if s.Has(math.NaN()) { 137 | t.Error("Expected NaN to be cleared from the set") 138 | } 139 | }, 140 | ) 141 | } 142 | 143 | // TestLen checks the size of the Set. 144 | func TestLen(t *testing.T) { 145 | t.Run( 146 | "regular integers", func(t *testing.T) { 147 | s := New[int]() 148 | s.Add(1) 149 | s.Add(2) 150 | if s.Len() != 2 { 151 | t.Errorf("Expected size 2, got %d", s.Len()) 152 | } 153 | }, 154 | ) 155 | 156 | t.Run( 157 | "with NaN values", func(t *testing.T) { 158 | s := New[float64]() 159 | s.Add(1.0) 160 | s.Add(math.NaN()) 161 | s.Add(math.NaN()) // Adding NaN twice shouldn't increase length 162 | if s.Len() != 2 { 163 | t.Errorf("Expected size 2, got %d", s.Len()) 164 | } 165 | }, 166 | ) 167 | } 168 | 169 | // TestElements checks if Elements returns a correct slice of the set's elements. 170 | func TestElements(t *testing.T) { 171 | t.Run( 172 | "regular integers", func(t *testing.T) { 173 | s := New[int]() 174 | s.Add(1) 175 | s.Add(2) 176 | expected := []int{1, 2} 177 | actual := s.Elements() 178 | sort.Slice(actual, func(i, j int) bool { return actual[i] < actual[j] }) 179 | 180 | if !reflect.DeepEqual(actual, expected) { 181 | t.Errorf("Expected elements %v, got %v", expected, actual) 182 | } 183 | }, 184 | ) 185 | 186 | t.Run( 187 | "with NaN values", func(t *testing.T) { 188 | s := New[float64]() 189 | s.Add(1.0) 190 | s.Add(math.NaN()) 191 | elements := s.Elements() 192 | if len(elements) != 2 { 193 | t.Errorf("Expected 2 elements, got %d", len(elements)) 194 | } 195 | nanCount := 0 196 | regularCount := 0 197 | for _, e := range elements { 198 | if math.IsNaN(e) { 199 | nanCount++ 200 | } else { 201 | regularCount++ 202 | } 203 | } 204 | if nanCount != 1 || regularCount != 1 { 205 | t.Errorf("Expected 1 NaN and 1 regular value, got %d NaN and %d regular", nanCount, regularCount) 206 | } 207 | }, 208 | ) 209 | } 210 | 211 | // TestSet operations with NaN values 212 | func TestSetOperationsWithNaN(t *testing.T) { 213 | t.Run( 214 | "Diff with NaN", func(t *testing.T) { 215 | s1 := New[float64]() 216 | s2 := New[float64]() 217 | s1.Add(1.0) 218 | s1.Add(math.NaN()) 219 | s2.Add(1.0) 220 | 221 | diff := s1.Diff(s2) 222 | if diff.Len() != 1 || !diff.Has(math.NaN()) { 223 | t.Error("Expected diff to contain only NaN") 224 | } 225 | }, 226 | ) 227 | 228 | t.Run( 229 | "Intersect with NaN", func(t *testing.T) { 230 | s1 := New[float64]() 231 | s2 := New[float64]() 232 | s1.Add(math.NaN()) 233 | s2.Add(math.NaN()) 234 | 235 | intersect := s1.Intersect(s2) 236 | if intersect.Len() != 1 || !intersect.Has(math.NaN()) { 237 | t.Error("Expected intersection to contain NaN") 238 | } 239 | }, 240 | ) 241 | 242 | t.Run( 243 | "Union with NaN", func(t *testing.T) { 244 | s1 := New[float64]() 245 | s2 := New[float64]() 246 | s1.Add(1.0) 247 | s1.Add(math.NaN()) 248 | s2.Add(2.0) 249 | 250 | union := s1.Union(s2) 251 | if union.Len() != 3 || !union.Has(math.NaN()) { 252 | t.Errorf("Expected union to contain 3 elements including NaN, got %d elements", union.Len()) 253 | } 254 | }, 255 | ) 256 | 257 | t.Run( 258 | "IsSubset with NaN", func(t *testing.T) { 259 | s1 := New[float64]() 260 | s2 := New[float64]() 261 | s1.Add(math.NaN()) 262 | s2.Add(math.NaN()) 263 | s2.Add(1.0) 264 | 265 | if !s1.IsSubset(s2) { 266 | t.Error("Expected s1 to be subset of s2") 267 | } 268 | 269 | s1.Add(2.0) 270 | if s1.IsSubset(s2) { 271 | t.Error("Expected s1 not to be subset of s2") 272 | } 273 | }, 274 | ) 275 | 276 | t.Run( 277 | "Equal with NaN", func(t *testing.T) { 278 | s1 := New[float64]() 279 | s2 := New[float64]() 280 | s1.Add(1.0) 281 | s1.Add(math.NaN()) 282 | s2.Add(1.0) 283 | s2.Add(math.NaN()) 284 | 285 | if !s1.Equal(s2) { 286 | t.Error("Expected sets to be equal") 287 | } 288 | 289 | s2.Add(2.0) 290 | if s1.Equal(s2) { 291 | t.Error("Expected sets not to be equal") 292 | } 293 | }, 294 | ) 295 | } 296 | 297 | // TestInfinityHandling checks if the set properly handles infinity values 298 | func TestInfinityHandling(t *testing.T) { 299 | s := New[float64]() 300 | posInf := math.Inf(1) 301 | negInf := math.Inf(-1) 302 | 303 | s.Add(posInf) 304 | s.Add(negInf) 305 | 306 | if !s.Has(posInf) { 307 | t.Error("Expected set to contain positive infinity") 308 | } 309 | 310 | if !s.Has(negInf) { 311 | t.Error("Expected set to contain negative infinity") 312 | } 313 | 314 | if s.Len() != 2 { 315 | t.Errorf("Expected length 2, got %d", s.Len()) 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /skiplist/skiplist.go: -------------------------------------------------------------------------------- 1 | package skiplist 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/idsulik/go-collections/v3/internal/cmp" 8 | ) 9 | 10 | // SkipList represents the Skip List. 11 | type SkipList[T cmp.Ordered] struct { 12 | maxLevel int 13 | level int 14 | p float64 15 | header *node[T] 16 | length int 17 | randSrc *rand.Rand 18 | } 19 | 20 | type node[T cmp.Ordered] struct { 21 | value T 22 | next []*node[T] 23 | } 24 | 25 | // New creates a new empty Skip List. 26 | func New[T cmp.Ordered](maxLevel int, p float64) *SkipList[T] { 27 | return &SkipList[T]{ 28 | maxLevel: maxLevel, 29 | level: 1, 30 | p: p, 31 | header: &node[T]{next: make([]*node[T], maxLevel)}, 32 | randSrc: rand.New(rand.NewSource(time.Now().UnixNano())), 33 | } 34 | } 35 | 36 | // Insert adds a value into the Skip List. 37 | func (sl *SkipList[T]) Insert(value T) { 38 | update := make([]*node[T], sl.maxLevel) 39 | current := sl.header 40 | 41 | // Find positions to update 42 | for i := sl.level - 1; i >= 0; i-- { 43 | for current.next[i] != nil && current.next[i].value < value { 44 | current = current.next[i] 45 | } 46 | update[i] = current 47 | } 48 | 49 | // Check if value already exists 50 | if current.next[0] != nil && current.next[0].value == value { 51 | return // Do not insert duplicates 52 | } 53 | 54 | // Generate a random level for the new node 55 | newLevel := sl.randomLevel() 56 | if newLevel > sl.level { 57 | for i := sl.level; i < newLevel; i++ { 58 | update[i] = sl.header 59 | } 60 | sl.level = newLevel 61 | } 62 | 63 | // Create new node 64 | newNode := &node[T]{ 65 | value: value, 66 | next: make([]*node[T], newLevel), 67 | } 68 | 69 | // Insert node and update pointers 70 | for i := 0; i < newLevel; i++ { 71 | newNode.next[i] = update[i].next[i] 72 | update[i].next[i] = newNode 73 | } 74 | 75 | sl.length++ 76 | } 77 | 78 | // Search checks if a value exists in the Skip List. 79 | func (sl *SkipList[T]) Search(value T) bool { 80 | current := sl.header 81 | for i := sl.level - 1; i >= 0; i-- { 82 | for current.next[i] != nil && current.next[i].value < value { 83 | current = current.next[i] 84 | } 85 | } 86 | current = current.next[0] 87 | return current != nil && current.value == value 88 | } 89 | 90 | // Delete removes a value from the Skip List. 91 | func (sl *SkipList[T]) Delete(value T) { 92 | update := make([]*node[T], sl.maxLevel) 93 | current := sl.header 94 | 95 | // Find positions to update 96 | for i := sl.level - 1; i >= 0; i-- { 97 | for current.next[i] != nil && current.next[i].value < value { 98 | current = current.next[i] 99 | } 100 | update[i] = current 101 | } 102 | 103 | current = current.next[0] 104 | if current != nil && current.value == value { 105 | for i := 0; i < sl.level; i++ { 106 | if update[i].next[i] != current { 107 | break 108 | } 109 | update[i].next[i] = current.next[i] 110 | } 111 | 112 | // Adjust the level if necessary 113 | for sl.level > 1 && sl.header.next[sl.level-1] == nil { 114 | sl.level-- 115 | } 116 | sl.length-- 117 | } 118 | } 119 | 120 | // Len returns the number of elements in the Skip List. 121 | func (sl *SkipList[T]) Len() int { 122 | return sl.length 123 | } 124 | 125 | // IsEmpty checks if the Skip List is empty. 126 | func (sl *SkipList[T]) IsEmpty() bool { 127 | return sl.length == 0 128 | } 129 | 130 | // Clear removes all elements from the Skip List. 131 | func (sl *SkipList[T]) Clear() { 132 | sl.header = &node[T]{next: make([]*node[T], sl.maxLevel)} 133 | sl.level = 1 134 | sl.length = 0 135 | } 136 | 137 | // randomLevel generates a random level for a new node. 138 | func (sl *SkipList[T]) randomLevel() int { 139 | level := 1 140 | for sl.randSrc.Float64() < sl.p && level < sl.maxLevel { 141 | level++ 142 | } 143 | return level 144 | } 145 | -------------------------------------------------------------------------------- /skiplist/skiplist_test.go: -------------------------------------------------------------------------------- 1 | package skiplist 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // Test basic insertion and search 8 | func TestInsertAndSearch(t *testing.T) { 9 | sl := New[int](16, 0.5) 10 | values := []int{3, 6, 7, 9, 12, 19, 17, 26, 21, 25} 11 | 12 | for _, v := range values { 13 | sl.Insert(v) 14 | } 15 | 16 | for _, v := range values { 17 | if !sl.Search(v) { 18 | t.Errorf("SkipList should contain %d", v) 19 | } 20 | } 21 | 22 | if sl.Search(100) { 23 | t.Errorf("SkipList should not contain %d", 100) 24 | } 25 | } 26 | 27 | // Test deletion 28 | func TestDelete(t *testing.T) { 29 | sl := New[int](16, 0.5) 30 | values := []int{3, 6, 7, 9, 12, 19, 17} 31 | 32 | for _, v := range values { 33 | sl.Insert(v) 34 | } 35 | 36 | sl.Delete(6) 37 | if sl.Search(6) { 38 | t.Errorf("SkipList should not contain %d after deletion", 6) 39 | } 40 | 41 | sl.Delete(9) 42 | if sl.Search(9) { 43 | t.Errorf("SkipList should not contain %d after deletion", 9) 44 | } 45 | 46 | sl.Delete(100) // Deleting non-existing element should not cause error 47 | } 48 | 49 | // Test Len and IsEmpty 50 | func TestLenAndIsEmpty(t *testing.T) { 51 | sl := New[int](16, 0.5) 52 | 53 | if !sl.IsEmpty() { 54 | t.Error("SkipList should be empty") 55 | } 56 | 57 | if sl.Len() != 0 { 58 | t.Errorf("Expected length 0, got %d", sl.Len()) 59 | } 60 | 61 | sl.Insert(1) 62 | 63 | if sl.IsEmpty() { 64 | t.Error("SkipList should not be empty after insertion") 65 | } 66 | 67 | if sl.Len() != 1 { 68 | t.Errorf("Expected length 1, got %d", sl.Len()) 69 | } 70 | } 71 | 72 | // Test Clear 73 | func TestClear(t *testing.T) { 74 | sl := New[int](16, 0.5) 75 | sl.Insert(1) 76 | sl.Insert(2) 77 | sl.Insert(3) 78 | 79 | sl.Clear() 80 | 81 | if !sl.IsEmpty() { 82 | t.Error("SkipList should be empty after Clear") 83 | } 84 | 85 | if sl.Len() != 0 { 86 | t.Errorf("Expected length 0 after Clear, got %d", sl.Len()) 87 | } 88 | } 89 | 90 | // Test with strings 91 | func TestStrings(t *testing.T) { 92 | sl := New[string](16, 0.5) 93 | values := []string{"apple", "banana", "cherry", "date", "elderberry"} 94 | 95 | for _, v := range values { 96 | sl.Insert(v) 97 | } 98 | 99 | if !sl.Search("banana") { 100 | t.Error("SkipList should contain 'banana'") 101 | } 102 | 103 | sl.Delete("cherry") 104 | if sl.Search("cherry") { 105 | t.Error("SkipList should not contain 'cherry' after deletion") 106 | } 107 | 108 | if sl.Len() != 4 { 109 | t.Errorf("Expected length 4 after deletion, got %d", sl.Len()) 110 | } 111 | } 112 | 113 | // Test inserting duplicate values 114 | func TestInsertDuplicate(t *testing.T) { 115 | sl := New[int](16, 0.5) 116 | sl.Insert(10) 117 | sl.Insert(10) 118 | 119 | if sl.Len() != 1 { 120 | t.Errorf("Expected length 1 after inserting duplicate, got %d", sl.Len()) 121 | } 122 | } 123 | 124 | // Test large dataset 125 | func TestLargeDataSet(t *testing.T) { 126 | sl := New[int](32, 0.5) 127 | const numElements = 10000 128 | 129 | for i := 0; i < numElements; i++ { 130 | sl.Insert(i) 131 | } 132 | 133 | if sl.Len() != numElements { 134 | t.Errorf("Expected length %d, got %d", numElements, sl.Len()) 135 | } 136 | 137 | for i := 0; i < numElements; i++ { 138 | if !sl.Search(i) { 139 | t.Errorf("SkipList should contain %d", i) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /stack/arraystack/array_stack.go: -------------------------------------------------------------------------------- 1 | package arraystack 2 | 3 | // ArrayStack represents a LIFO (last-in, first-out) stack implemented using a slice. 4 | type ArrayStack[T any] struct { 5 | items []T 6 | } 7 | 8 | // New creates and returns a new, empty stack with the specified initial capacity. 9 | func New[T any](initialCapacity int) *ArrayStack[T] { 10 | return &ArrayStack[T]{ 11 | items: make([]T, 0, initialCapacity), 12 | } 13 | } 14 | 15 | // Push adds an item to the top of the stack. 16 | func (s *ArrayStack[T]) Push(item T) { 17 | s.items = append(s.items, item) 18 | } 19 | 20 | // Pop removes and returns the item from the top of the stack. 21 | // Returns false if the stack is empty. 22 | func (s *ArrayStack[T]) Pop() (T, bool) { 23 | if s.IsEmpty() { 24 | var zero T 25 | return zero, false 26 | } 27 | 28 | index := len(s.items) - 1 29 | item := s.items[index] 30 | s.items[index] = *new(T) // remove reference 31 | s.items = s.items[:index] 32 | 33 | return item, true 34 | } 35 | 36 | // Peek returns the item at the top of the stack without removing it. 37 | // Returns false if the stack is empty. 38 | func (s *ArrayStack[T]) Peek() (T, bool) { 39 | if s.IsEmpty() { 40 | var zero T 41 | return zero, false 42 | } 43 | 44 | return s.items[len(s.items)-1], true 45 | } 46 | 47 | // Len returns the number of items currently in the stack. 48 | func (s *ArrayStack[T]) Len() int { 49 | return len(s.items) 50 | } 51 | 52 | // IsEmpty checks if the stack is empty. 53 | func (s *ArrayStack[T]) IsEmpty() bool { 54 | return len(s.items) == 0 55 | } 56 | 57 | // Clear removes all items from the stack, leaving it empty. 58 | func (s *ArrayStack[T]) Clear() { 59 | for i := range s.items { 60 | var zero T 61 | s.items[i] = zero 62 | } 63 | s.items = s.items[:0] 64 | } 65 | -------------------------------------------------------------------------------- /stack/arraystack/array_stack_test.go: -------------------------------------------------------------------------------- 1 | package arraystack 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // TestNew tests the creation of a new stack with an initial capacity. 8 | func TestNew(t *testing.T) { 9 | // Create a new stack with an initial capacity of 10 10 | s := New[int](10) 11 | 12 | if got := s.Len(); got != 0 { 13 | t.Errorf("Len() = %d; want 0", got) 14 | } 15 | if !s.IsEmpty() { 16 | t.Errorf("IsEmpty() = false; want true") 17 | } 18 | } 19 | 20 | // TestArrayStackPush tests adding items to the stack. 21 | func TestArrayStackPush(t *testing.T) { 22 | // Create a new stack with an initial capacity of 10 23 | s := New[int](10) 24 | s.Push(1) 25 | s.Push(2) 26 | s.Push(3) 27 | 28 | if got := s.Len(); got != 3 { 29 | t.Errorf("Len() = %d; want 3", got) 30 | } 31 | if got, ok := s.Pop(); !ok || got != 3 { 32 | t.Errorf("Pop() = %d, %v; want 3, true", got, ok) 33 | } 34 | } 35 | 36 | // TestArrayStackPopEmpty tests popping from an empty stack. 37 | func TestArrayStackPopEmpty(t *testing.T) { 38 | // Create a new stack with an initial capacity of 10 39 | s := New[int](10) 40 | if _, ok := s.Pop(); ok { 41 | t.Errorf("Pop() should return false for an empty stack") 42 | } 43 | } 44 | 45 | // TestArrayStackPeek tests peeking at the top of the stack. 46 | func TestArrayStackPeek(t *testing.T) { 47 | // Create a new stack with an initial capacity of 10 48 | s := New[int](10) 49 | s.Push(1) 50 | s.Push(2) 51 | 52 | if got, ok := s.Peek(); !ok || got != 2 { 53 | t.Errorf("Peek() = %d, %v; want 2, true", got, ok) 54 | } 55 | 56 | // Ensure Peek does not remove the item 57 | if got, ok := s.Peek(); !ok || got != 2 { 58 | t.Errorf("Peek() = %d, %v; want 2, true after re-peeking", got, ok) 59 | } 60 | } 61 | 62 | // TestArrayStackPeekEmpty tests peeking into an empty stack. 63 | func TestArrayStackPeekEmpty(t *testing.T) { 64 | // Create a new stack with an initial capacity of 10 65 | s := New[int](10) 66 | if _, ok := s.Peek(); ok { 67 | t.Errorf("Peek() should return false for an empty stack") 68 | } 69 | } 70 | 71 | // TestArrayStackLen tests the length of the stack. 72 | func TestArrayStackLen(t *testing.T) { 73 | // Create a new stack with an initial capacity of 10 74 | s := New[int](10) 75 | if got := s.Len(); got != 0 { 76 | t.Errorf("Len() = %d; want 0", got) 77 | } 78 | 79 | s.Push(1) 80 | s.Push(2) 81 | if got := s.Len(); got != 2 { 82 | t.Errorf("Len() = %d; want 2", got) 83 | } 84 | } 85 | 86 | // TestArrayStackIsEmpty tests checking if the stack is empty. 87 | func TestArrayStackIsEmpty(t *testing.T) { 88 | // Create a new stack with an initial capacity of 10 89 | s := New[int](10) 90 | if !s.IsEmpty() { 91 | t.Errorf("IsEmpty() = false; want true") 92 | } 93 | 94 | s.Push(1) 95 | if s.IsEmpty() { 96 | t.Errorf("IsEmpty() = true; want false") 97 | } 98 | 99 | s.Pop() 100 | if !s.IsEmpty() { 101 | t.Errorf("IsEmpty() = false; want true after Pop") 102 | } 103 | } 104 | 105 | // TestArrayStackClear tests clearing the stack. 106 | func TestArrayStackClear(t *testing.T) { 107 | // Create a new stack with an initial capacity of 10 108 | s := New[int](10) 109 | s.Push(1) 110 | s.Push(2) 111 | s.Clear() 112 | 113 | if !s.IsEmpty() { 114 | t.Errorf("IsEmpty() = false; want true after Clear") 115 | } 116 | if got := s.Len(); got != 0 { 117 | t.Errorf("Len() = %d; want 0 after Clear", got) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /stack/linkedliststack/linkedlist_stack.go: -------------------------------------------------------------------------------- 1 | package linkedliststack 2 | 3 | import "github.com/idsulik/go-collections/v3/linkedlist" 4 | 5 | // LinkedListStack represents a LIFO (last-in, first-out) stack implemented using a linked list. 6 | type LinkedListStack[T any] struct { 7 | linkedList *linkedlist.LinkedList[T] 8 | } 9 | 10 | // New creates and returns a new, empty linked list stack. 11 | func New[T any]() *LinkedListStack[T] { 12 | return &LinkedListStack[T]{ 13 | linkedList: linkedlist.New[T](), 14 | } 15 | } 16 | 17 | // Push adds an item to the top of the stack. 18 | func (s *LinkedListStack[T]) Push(item T) { 19 | s.linkedList.AddFront(item) 20 | } 21 | 22 | // Pop removes and returns the item from the top of the stack. 23 | // Returns false if the stack is empty. 24 | func (s *LinkedListStack[T]) Pop() (T, bool) { 25 | return s.linkedList.RemoveFront() 26 | } 27 | 28 | // Peek returns the item at the top of the stack without removing it. 29 | // Returns false if the stack is empty. 30 | func (s *LinkedListStack[T]) Peek() (T, bool) { 31 | return s.linkedList.PeekFront() 32 | } 33 | 34 | // Len returns the number of items currently in the stack. 35 | func (s *LinkedListStack[T]) Len() int { 36 | return s.linkedList.Len() 37 | } 38 | 39 | // IsEmpty checks if the stack is empty. 40 | func (s *LinkedListStack[T]) IsEmpty() bool { 41 | return s.linkedList.IsEmpty() 42 | } 43 | 44 | // Clear removes all items from the stack, leaving it empty. 45 | func (s *LinkedListStack[T]) Clear() { 46 | s.linkedList.Clear() 47 | } 48 | -------------------------------------------------------------------------------- /stack/linkedliststack/linkedlist_stack_test.go: -------------------------------------------------------------------------------- 1 | package linkedliststack 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // TestNew tests the creation of a new stack with an initial capacity. 8 | func TestNew(t *testing.T) { 9 | // Create a new stack with an initial capacity of 10 | s := New[int]() 11 | 12 | if got := s.Len(); got != 0 { 13 | t.Errorf("Len() = %d; want 0", got) 14 | } 15 | if !s.IsEmpty() { 16 | t.Errorf("IsEmpty() = false; want true") 17 | } 18 | } 19 | 20 | // TestLinkedListStackPush tests adding items to the stack. 21 | func TestLinkedListStackPush(t *testing.T) { 22 | // Create a new stack with an initial capacity of 23 | s := New[int]() 24 | s.Push(1) 25 | s.Push(2) 26 | s.Push(3) 27 | 28 | if got := s.Len(); got != 3 { 29 | t.Errorf("Len() = %d; want 3", got) 30 | } 31 | if got, ok := s.Pop(); !ok || got != 3 { 32 | t.Errorf("Pop() = %d, %v; want 3, true", got, ok) 33 | } 34 | } 35 | 36 | // TestLinkedListStackPopEmpty tests popping from an empty stack. 37 | func TestLinkedListStackPopEmpty(t *testing.T) { 38 | // Create a new stack with an initial capacity of 39 | s := New[int]() 40 | if _, ok := s.Pop(); ok { 41 | t.Errorf("Pop() should return false for an empty stack") 42 | } 43 | } 44 | 45 | // TestLinkedListStackPeek tests peeking at the top of the stack. 46 | func TestLinkedListStackPeek(t *testing.T) { 47 | // Create a new stack with an initial capacity of 48 | s := New[int]() 49 | s.Push(1) 50 | s.Push(2) 51 | 52 | if got, ok := s.Peek(); !ok || got != 2 { 53 | t.Errorf("Peek() = %d, %v; want 2, true", got, ok) 54 | } 55 | 56 | // Ensure Peek does not remove the item 57 | if got, ok := s.Peek(); !ok || got != 2 { 58 | t.Errorf("Peek() = %d, %v; want 2, true after re-peeking", got, ok) 59 | } 60 | } 61 | 62 | // TestLinkedListStackPeekEmpty tests peeking into an empty stack. 63 | func TestLinkedListStackPeekEmpty(t *testing.T) { 64 | // Create a new stack with an initial capacity of 65 | s := New[int]() 66 | if _, ok := s.Peek(); ok { 67 | t.Errorf("Peek() should return false for an empty stack") 68 | } 69 | } 70 | 71 | // TestLinkedListStackLen tests the length of the stack. 72 | func TestLinkedListStackLen(t *testing.T) { 73 | // Create a new stack with an initial capacity of 74 | s := New[int]() 75 | if got := s.Len(); got != 0 { 76 | t.Errorf("Len() = %d; want 0", got) 77 | } 78 | 79 | s.Push(1) 80 | s.Push(2) 81 | if got := s.Len(); got != 2 { 82 | t.Errorf("Len() = %d; want 2", got) 83 | } 84 | } 85 | 86 | // TestLinkedListStackIsEmpty tests checking if the stack is empty. 87 | func TestLinkedListStackIsEmpty(t *testing.T) { 88 | // Create a new stack with an initial capacity of 89 | s := New[int]() 90 | if !s.IsEmpty() { 91 | t.Errorf("IsEmpty() = false; want true") 92 | } 93 | 94 | s.Push(1) 95 | if s.IsEmpty() { 96 | t.Errorf("IsEmpty() = true; want false") 97 | } 98 | 99 | s.Pop() 100 | if !s.IsEmpty() { 101 | t.Errorf("IsEmpty() = false; want true after Pop") 102 | } 103 | } 104 | 105 | // TestLinkedListStackClear tests clearing the stack. 106 | func TestLinkedListStackClear(t *testing.T) { 107 | // Create a new stack with an initial capacity of 108 | s := New[int]() 109 | s.Push(1) 110 | s.Push(2) 111 | s.Clear() 112 | 113 | if !s.IsEmpty() { 114 | t.Errorf("IsEmpty() = false; want true after Clear") 115 | } 116 | if got := s.Len(); got != 0 { 117 | t.Errorf("Len() = %d; want 0 after Clear", got) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /stack/stack.go: -------------------------------------------------------------------------------- 1 | package stack 2 | 3 | // Stack is an interface representing a LIFO (last-in, first-out) stack. 4 | // deprecated Use github.com/idsulik/go-collections/v3/collections.Stack instead. 5 | type Stack[T any] interface { 6 | // Push adds an item to the top of the stack. 7 | Push(item T) 8 | 9 | // Pop removes and returns the item from the top of the stack. 10 | // Returns false if the stack is empty. 11 | Pop() (T, bool) 12 | 13 | // Peek returns the item at the top of the stack without removing it. 14 | // Returns false if the stack is empty. 15 | Peek() (T, bool) 16 | 17 | // Len returns the number of items currently in the stack. 18 | Len() int 19 | 20 | // IsEmpty checks if the stack is empty. 21 | IsEmpty() bool 22 | 23 | // Clear removes all items from the stack, leaving it empty. 24 | Clear() 25 | } 26 | -------------------------------------------------------------------------------- /timedeque/time_deque.go: -------------------------------------------------------------------------------- 1 | package timedeque 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/idsulik/go-collections/v3/deque" 7 | ) 8 | 9 | const defaultCapacity = 16 10 | 11 | // TimedItem wraps an item with its insertion timestamp 12 | type TimedItem[T any] struct { 13 | Value T 14 | Timestamp time.Time 15 | } 16 | 17 | // TimedDeque extends the Deque with time-to-live functionality 18 | type TimedDeque[T any] struct { 19 | deque *deque.Deque[TimedItem[T]] 20 | ttl time.Duration 21 | } 22 | 23 | // New creates a new TimedDeque with the specified TTL 24 | func New[T any](ttl time.Duration) *TimedDeque[T] { 25 | return &TimedDeque[T]{ 26 | deque: deque.New[TimedItem[T]](defaultCapacity), 27 | ttl: ttl, 28 | } 29 | } 30 | 31 | // NewWithCapacity creates a new TimedDeque with the specified TTL and capacity 32 | func NewWithCapacity[T any](ttl time.Duration, capacity int) *TimedDeque[T] { 33 | return &TimedDeque[T]{ 34 | deque: deque.New[TimedItem[T]](capacity), 35 | ttl: ttl, 36 | } 37 | } 38 | 39 | // PushFront adds an item to the front of the deque with the current timestamp 40 | func (td *TimedDeque[T]) PushFront(item T) { 41 | td.deque.PushFront( 42 | TimedItem[T]{ 43 | Value: item, 44 | Timestamp: time.Now(), 45 | }, 46 | ) 47 | } 48 | 49 | // PushBack adds an item to the back of the deque with the current timestamp 50 | func (td *TimedDeque[T]) PushBack(item T) { 51 | td.deque.PushBack( 52 | TimedItem[T]{ 53 | Value: item, 54 | Timestamp: time.Now(), 55 | }, 56 | ) 57 | } 58 | 59 | // PopFront removes and returns the item at the front of the deque 60 | // First removes any expired items from the front 61 | func (td *TimedDeque[T]) PopFront() (T, bool) { 62 | td.removeExpiredFront() 63 | 64 | if td.deque.IsEmpty() { 65 | var zero T 66 | return zero, false 67 | } 68 | 69 | item, ok := td.deque.PopFront() 70 | if !ok { 71 | var zero T 72 | return zero, false 73 | } 74 | 75 | return item.Value, true 76 | } 77 | 78 | // PopBack removes and returns the item at the back of the deque 79 | // First removes any expired items from the front 80 | func (td *TimedDeque[T]) PopBack() (T, bool) { 81 | td.removeExpiredFront() 82 | 83 | if td.deque.IsEmpty() { 84 | var zero T 85 | return zero, false 86 | } 87 | 88 | item, ok := td.deque.PopBack() 89 | if !ok { 90 | var zero T 91 | return zero, false 92 | } 93 | 94 | return item.Value, true 95 | } 96 | 97 | // PeekFront returns the item at the front of the deque without removing it 98 | // First removes any expired items from the front 99 | func (td *TimedDeque[T]) PeekFront() (T, bool) { 100 | td.removeExpiredFront() 101 | 102 | if td.deque.IsEmpty() { 103 | var zero T 104 | return zero, false 105 | } 106 | 107 | item, ok := td.deque.PeekFront() 108 | if !ok { 109 | var zero T 110 | return zero, false 111 | } 112 | 113 | return item.Value, true 114 | } 115 | 116 | // PeekBack returns the item at the back of the deque without removing it 117 | // First removes any expired items from the front 118 | func (td *TimedDeque[T]) PeekBack() (T, bool) { 119 | td.removeExpiredFront() 120 | 121 | if td.deque.IsEmpty() { 122 | var zero T 123 | return zero, false 124 | } 125 | 126 | item, ok := td.deque.PeekBack() 127 | if !ok { 128 | var zero T 129 | return zero, false 130 | } 131 | 132 | return item.Value, true 133 | } 134 | 135 | // Len returns the number of items in the deque after removing expired items 136 | func (td *TimedDeque[T]) Len() int { 137 | td.removeExpiredFront() 138 | return td.deque.Len() 139 | } 140 | 141 | // Cap returns the current capacity of the deque 142 | func (td *TimedDeque[T]) Cap() int { 143 | return td.deque.Cap() 144 | } 145 | 146 | // IsEmpty checks if the deque is empty after removing expired items 147 | func (td *TimedDeque[T]) IsEmpty() bool { 148 | td.removeExpiredFront() 149 | return td.deque.IsEmpty() 150 | } 151 | 152 | // Clear removes all items from the deque 153 | func (td *TimedDeque[T]) Clear() { 154 | // Preserve the capacity of the underlying deque 155 | capacity := td.deque.Cap() 156 | td.deque = deque.New[TimedItem[T]](capacity) 157 | } 158 | 159 | // GetItems returns a slice containing all non-expired items in the deque 160 | func (td *TimedDeque[T]) GetItems() []T { 161 | td.removeExpiredFront() 162 | timedItems := td.deque.GetItems() 163 | items := make([]T, len(timedItems)) 164 | 165 | for i, timedItem := range timedItems { 166 | items[i] = timedItem.Value 167 | } 168 | 169 | return items 170 | } 171 | 172 | // Clone returns a deep copy of the TimedDeque 173 | func (td *TimedDeque[T]) Clone() *TimedDeque[T] { 174 | return &TimedDeque[T]{ 175 | deque: td.deque.Clone(), 176 | ttl: td.ttl, 177 | } 178 | } 179 | 180 | // SetTTL updates the time-to-live duration and removes expired items 181 | func (td *TimedDeque[T]) SetTTL(ttl time.Duration) { 182 | td.ttl = ttl 183 | td.removeExpiredFront() 184 | } 185 | 186 | // GetTTL returns the current time-to-live duration 187 | func (td *TimedDeque[T]) GetTTL() time.Duration { 188 | return td.ttl 189 | } 190 | 191 | // IsExpired checks if an item with the given timestamp has expired 192 | func (td *TimedDeque[T]) IsExpired(timestamp time.Time) bool { 193 | // If TTL is zero or negative, items never expire 194 | if td.ttl <= 0 { 195 | return false 196 | } 197 | return time.Since(timestamp) > td.ttl 198 | } 199 | 200 | // removeExpiredFront removes expired items from the front of the deque 201 | func (td *TimedDeque[T]) removeExpiredFront() { 202 | // If TTL is zero or negative, items never expire 203 | if td.ttl <= 0 { 204 | return 205 | } 206 | 207 | for !td.deque.IsEmpty() { 208 | frontItem, ok := td.deque.PeekFront() 209 | if !ok { 210 | break 211 | } 212 | 213 | if time.Since(frontItem.Timestamp) > td.ttl { 214 | td.deque.PopFront() 215 | } else { 216 | break 217 | } 218 | } 219 | } 220 | 221 | // RemoveExpired removes all expired items from the deque 222 | // This is more thorough than removeExpiredFront but has O(n) complexity 223 | func (td *TimedDeque[T]) RemoveExpired() { 224 | if td.deque.IsEmpty() { 225 | return 226 | } 227 | 228 | // First remove from front (optimization) 229 | td.removeExpiredFront() 230 | 231 | // If ttl is 0, all items are kept forever 232 | if td.ttl <= 0 { 233 | return 234 | } 235 | 236 | // Check if there are any expired items in the middle or back 237 | // We'll rebuild the deque if necessary 238 | timedItems := td.deque.GetItems() 239 | now := time.Now() 240 | hasExpired := false 241 | 242 | for _, item := range timedItems { 243 | if now.Sub(item.Timestamp) > td.ttl { 244 | hasExpired = true 245 | break 246 | } 247 | } 248 | 249 | if !hasExpired { 250 | return 251 | } 252 | 253 | // Rebuild the deque without expired items 254 | newDeque := deque.New[TimedItem[T]](td.deque.Cap()) 255 | for _, item := range timedItems { 256 | if now.Sub(item.Timestamp) <= td.ttl { 257 | newDeque.PushBack(item) 258 | } 259 | } 260 | 261 | td.deque = newDeque 262 | } 263 | -------------------------------------------------------------------------------- /timedeque/time_deque_test.go: -------------------------------------------------------------------------------- 1 | package timedeque 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestTimedDequeBasicOperations(t *testing.T) { 9 | // Create a timed deque with a relatively long TTL to test basic operations 10 | td := New[int](time.Hour) 11 | 12 | // Test initial state 13 | if !td.IsEmpty() { 14 | t.Error("New TimedDeque should be empty") 15 | } 16 | 17 | if td.Len() != 0 { 18 | t.Errorf("Expected length 0, got %d", td.Len()) 19 | } 20 | 21 | // Test PushBack and PeekFront 22 | td.PushBack(1) 23 | td.PushBack(2) 24 | 25 | if td.IsEmpty() { 26 | t.Error("TimedDeque should not be empty after pushing items") 27 | } 28 | 29 | if td.Len() != 2 { 30 | t.Errorf("Expected length 2, got %d", td.Len()) 31 | } 32 | 33 | val, ok := td.PeekFront() 34 | if !ok || val != 1 { 35 | t.Errorf("PeekFront() = %d, %v; want 1, true", val, ok) 36 | } 37 | 38 | // Test PushFront and PeekBack 39 | td.PushFront(3) 40 | 41 | val, ok = td.PeekFront() 42 | if !ok || val != 3 { 43 | t.Errorf("PeekFront() after PushFront = %d, %v; want 3, true", val, ok) 44 | } 45 | 46 | val, ok = td.PeekBack() 47 | if !ok || val != 2 { 48 | t.Errorf("PeekBack() = %d, %v; want 2, true", val, ok) 49 | } 50 | 51 | // Test PopFront 52 | val, ok = td.PopFront() 53 | if !ok || val != 3 { 54 | t.Errorf("PopFront() = %d, %v; want 3, true", val, ok) 55 | } 56 | 57 | if td.Len() != 2 { 58 | t.Errorf("Expected length 2 after PopFront, got %d", td.Len()) 59 | } 60 | 61 | // Test PopBack 62 | val, ok = td.PopBack() 63 | if !ok || val != 2 { 64 | t.Errorf("PopBack() = %d, %v; want 2, true", val, ok) 65 | } 66 | 67 | if td.Len() != 1 { 68 | t.Errorf("Expected length 1 after PopBack, got %d", td.Len()) 69 | } 70 | 71 | // Test Clear 72 | td.Clear() 73 | if !td.IsEmpty() { 74 | t.Error("TimedDeque should be empty after Clear") 75 | } 76 | } 77 | 78 | func TestTimedDequeExpiry(t *testing.T) { 79 | // Create a timed deque with a short TTL 80 | shortTTL := 50 * time.Millisecond 81 | td := New[string](shortTTL) 82 | 83 | // Add items 84 | td.PushBack("item1") 85 | td.PushBack("item2") 86 | 87 | // Check initial state 88 | if td.Len() != 2 { 89 | t.Errorf("Expected initial length 2, got %d", td.Len()) 90 | } 91 | 92 | // Wait for items to expire 93 | time.Sleep(shortTTL + 10*time.Millisecond) 94 | 95 | // Check if items expired correctly 96 | if !td.IsEmpty() { 97 | t.Errorf("Expected TimedDeque to be empty after TTL, but has %d items", td.Len()) 98 | } 99 | 100 | // Test adding new items after expiry 101 | td.PushBack("new-item") 102 | 103 | if td.Len() != 1 { 104 | t.Errorf("Expected length 1 after adding new item, got %d", td.Len()) 105 | } 106 | 107 | val, ok := td.PeekFront() 108 | if !ok || val != "new-item" { 109 | t.Errorf("PeekFront() = %s, %v; want new-item, true", val, ok) 110 | } 111 | } 112 | 113 | func TestTimedDequeMixedExpiry(t *testing.T) { 114 | // Create a timed deque with a medium TTL 115 | mediumTTL := 100 * time.Millisecond 116 | td := New[int](mediumTTL) 117 | 118 | // Add initial items 119 | td.PushBack(1) 120 | td.PushBack(2) 121 | 122 | // Wait for half the TTL 123 | time.Sleep(mediumTTL / 2) 124 | 125 | // Add more items 126 | td.PushBack(3) 127 | td.PushBack(4) 128 | 129 | // At this point, no items should have expired yet 130 | if td.Len() != 4 { 131 | t.Errorf("Expected all 4 items to still be valid, got %d", td.Len()) 132 | } 133 | 134 | // Wait for the first batch to expire, but not the second 135 | time.Sleep(mediumTTL/2 + 10*time.Millisecond) 136 | 137 | // Now the first two items should have expired 138 | if td.Len() != 2 { 139 | t.Errorf("Expected 2 items to remain valid, got %d", td.Len()) 140 | } 141 | 142 | val, ok := td.PeekFront() 143 | if !ok || val != 3 { 144 | t.Errorf("PeekFront() = %d, %v; want 3, true", val, ok) 145 | } 146 | 147 | // Wait for all items to expire 148 | time.Sleep(mediumTTL) 149 | 150 | if !td.IsEmpty() { 151 | t.Error("Expected all items to expire") 152 | } 153 | } 154 | 155 | func TestTimedDequeZeroTTL(t *testing.T) { 156 | // Zero or negative TTL means items never expire 157 | td := New[int](0) 158 | 159 | td.PushBack(1) 160 | td.PushBack(2) 161 | 162 | // Wait a bit 163 | time.Sleep(50 * time.Millisecond) 164 | 165 | // Items should still be there 166 | if td.Len() != 2 { 167 | t.Errorf("Expected items not to expire with zero TTL, got length %d", td.Len()) 168 | } 169 | 170 | // Test with negative TTL 171 | td = New[int](-1 * time.Second) 172 | 173 | td.PushBack(1) 174 | 175 | // Wait a bit 176 | time.Sleep(50 * time.Millisecond) 177 | 178 | // Items should still be there 179 | if td.Len() != 1 { 180 | t.Errorf("Expected items not to expire with negative TTL, got length %d", td.Len()) 181 | } 182 | } 183 | 184 | func TestTimedDequeChangeTTL(t *testing.T) { 185 | // Start with a long TTL 186 | td := New[int](time.Hour) 187 | 188 | td.PushBack(1) 189 | td.PushBack(2) 190 | 191 | // Change to a short TTL 192 | shortTTL := 50 * time.Millisecond 193 | td.SetTTL(shortTTL) 194 | 195 | // TTL should be updated 196 | if td.GetTTL() != shortTTL { 197 | t.Errorf("Expected TTL to be %v, got %v", shortTTL, td.GetTTL()) 198 | } 199 | 200 | // Wait for items to expire with new TTL 201 | time.Sleep(shortTTL + 10*time.Millisecond) 202 | 203 | // Items should have expired with the new TTL 204 | if !td.IsEmpty() { 205 | t.Errorf("Expected items to expire after changing TTL, got length %d", td.Len()) 206 | } 207 | } 208 | 209 | func TestTimedDequeGetItems(t *testing.T) { 210 | td := New[string](time.Hour) 211 | 212 | // Add some items 213 | items := []string{"item1", "item2", "item3"} 214 | for _, item := range items { 215 | td.PushBack(item) 216 | } 217 | 218 | // Get all items 219 | gotItems := td.GetItems() 220 | 221 | if len(gotItems) != len(items) { 222 | t.Errorf("Expected %d items, got %d", len(items), len(gotItems)) 223 | } 224 | 225 | for i, item := range items { 226 | if gotItems[i] != item { 227 | t.Errorf("At index %d, expected %s, got %s", i, item, gotItems[i]) 228 | } 229 | } 230 | } 231 | 232 | func TestTimedDequeRemoveExpired(t *testing.T) { 233 | // Create a timed deque with a short TTL 234 | shortTTL := 50 * time.Millisecond 235 | td := New[int](shortTTL) 236 | 237 | // Add initial items 238 | td.PushBack(1) 239 | td.PushBack(2) 240 | 241 | // Wait for partial expiry 242 | time.Sleep(shortTTL / 2) 243 | 244 | // Add more items 245 | td.PushBack(3) 246 | td.PushFront(0) // This goes at the front but should not expire yet 247 | 248 | // Wait a bit more so the first items expire but not the new ones 249 | time.Sleep(shortTTL/2 + 10*time.Millisecond) 250 | 251 | // Call explicit RemoveExpired 252 | td.RemoveExpired() 253 | 254 | // Only the last two items should remain 255 | if td.Len() != 2 { 256 | t.Errorf("Expected 2 items after RemoveExpired, got %d", td.Len()) 257 | } 258 | 259 | items := td.GetItems() 260 | if len(items) != 2 { 261 | t.Errorf("Expected 2 items in the slice, got %d", len(items)) 262 | } else { 263 | // The items should be 0 and 3 (in that order) 264 | if items[0] != 0 || items[1] != 3 { 265 | t.Errorf("Expected items [0, 3], got %v", items) 266 | } 267 | } 268 | } 269 | 270 | func TestTimedDequeClone(t *testing.T) { 271 | // Create a timed deque with some items 272 | td := New[int](time.Hour) 273 | td.PushBack(1) 274 | td.PushBack(2) 275 | 276 | // Clone it 277 | clone := td.Clone() 278 | 279 | // Verify the clone has the same items 280 | if clone.Len() != td.Len() { 281 | t.Errorf("Clone length %d differs from original %d", clone.Len(), td.Len()) 282 | } 283 | 284 | // Verify the clone has the same TTL 285 | if clone.GetTTL() != td.GetTTL() { 286 | t.Errorf("Clone TTL %v differs from original %v", clone.GetTTL(), td.GetTTL()) 287 | } 288 | 289 | // Modify the original 290 | td.PushBack(3) 291 | td.SetTTL(time.Minute) 292 | 293 | // Verify the clone is independent 294 | if clone.Len() == td.Len() { 295 | t.Error("Clone should be independent of original") 296 | } 297 | 298 | if clone.GetTTL() == td.GetTTL() { 299 | t.Error("Clone TTL should be independent of original") 300 | } 301 | } 302 | 303 | func TestTimedDequeCapacity(t *testing.T) { 304 | // Test with custom capacity 305 | td := NewWithCapacity[int](time.Hour, 100) 306 | 307 | if cap := td.Cap(); cap < 100 { 308 | t.Errorf("Expected capacity at least 100, got %d", cap) 309 | } 310 | 311 | // Add a lot of items 312 | for i := 0; i < 50; i++ { 313 | td.PushBack(i) 314 | } 315 | 316 | // Clear and verify capacity is preserved 317 | td.Clear() 318 | 319 | if td.Len() != 0 { 320 | t.Errorf("Expected length 0 after Clear, got %d", td.Len()) 321 | } 322 | 323 | if cap := td.Cap(); cap < 100 { 324 | t.Errorf("Expected capacity at least 100 after Clear, got %d", cap) 325 | } 326 | } 327 | 328 | func TestTimedDequeEmptyOperations(t *testing.T) { 329 | td := New[string](time.Hour) 330 | 331 | // Operations on empty deque 332 | val, ok := td.PopFront() 333 | if ok || val != "" { 334 | t.Errorf("PopFront() on empty deque = %s, %v; want \"\", false", val, ok) 335 | } 336 | 337 | val, ok = td.PopBack() 338 | if ok || val != "" { 339 | t.Errorf("PopBack() on empty deque = %s, %v; want \"\", false", val, ok) 340 | } 341 | 342 | val, ok = td.PeekFront() 343 | if ok || val != "" { 344 | t.Errorf("PeekFront() on empty deque = %s, %v; want \"\", false", val, ok) 345 | } 346 | 347 | val, ok = td.PeekBack() 348 | if ok || val != "" { 349 | t.Errorf("PeekBack() on empty deque = %s, %v; want \"\", false", val, ok) 350 | } 351 | } 352 | 353 | func TestTimedDequeCustomTypes(t *testing.T) { 354 | type Person struct { 355 | Name string 356 | Age int 357 | } 358 | 359 | td := New[Person](time.Hour) 360 | 361 | p1 := Person{"Alice", 30} 362 | p2 := Person{"Bob", 25} 363 | 364 | td.PushBack(p1) 365 | td.PushBack(p2) 366 | 367 | person, ok := td.PeekFront() 368 | if !ok || person.Name != "Alice" { 369 | t.Errorf("PeekFront() = %+v, %v; want %+v, true", person, ok, p1) 370 | } 371 | 372 | // Pop and verify 373 | person, ok = td.PopFront() 374 | if !ok || person.Name != "Alice" { 375 | t.Errorf("PopFront() = %+v, %v; want %+v, true", person, ok, p1) 376 | } 377 | 378 | person, ok = td.PopFront() 379 | if !ok || person.Name != "Bob" { 380 | t.Errorf("Second PopFront() = %+v, %v; want %+v, true", person, ok, p2) 381 | } 382 | } 383 | 384 | func BenchmarkTimedDequePushPop(b *testing.B) { 385 | td := New[int](time.Hour) 386 | 387 | b.ResetTimer() 388 | for i := 0; i < b.N; i++ { 389 | td.PushBack(i) 390 | td.PopFront() 391 | } 392 | } 393 | 394 | func BenchmarkTimedDequeRemoveExpired(b *testing.B) { 395 | td := New[int](time.Millisecond * 100) 396 | 397 | // Add a bunch of items 398 | for i := 0; i < 1000; i++ { 399 | td.PushBack(i) 400 | } 401 | 402 | // Wait for some to expire 403 | time.Sleep(time.Millisecond * 50) 404 | 405 | // Add more items 406 | for i := 0; i < 1000; i++ { 407 | td.PushBack(i + 1000) 408 | } 409 | 410 | b.ResetTimer() 411 | for i := 0; i < b.N; i++ { 412 | td.RemoveExpired() 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /trie/trie.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | // Node represents each node in the Trie 4 | type Node struct { 5 | children map[rune]*Node 6 | isEnd bool 7 | } 8 | 9 | // Trie represents the Trie structure 10 | type Trie struct { 11 | root *Node 12 | } 13 | 14 | // New initializes a new Trie 15 | func New() *Trie { 16 | return &Trie{root: newNode()} 17 | } 18 | 19 | // newNode initializes a new Trie node 20 | func newNode() *Node { 21 | return &Node{children: make(map[rune]*Node)} 22 | } 23 | 24 | // Insert Adds a word to the Trie. 25 | func (t *Trie) Insert(words string) { 26 | current := t.root 27 | for _, char := range words { 28 | if _, found := current.children[char]; !found { 29 | current.children[char] = newNode() 30 | } 31 | current = current.children[char] 32 | } 33 | current.isEnd = true 34 | } 35 | 36 | // Search searches for a word in the Trie and returns true if the word exists 37 | func (t *Trie) Search(word string) bool { 38 | current := t.root 39 | for _, char := range word { 40 | if _, found := current.children[char]; !found { 41 | return false 42 | } 43 | current = current.children[char] 44 | } 45 | return current.isEnd 46 | } 47 | 48 | // StartsWith checks if there is any word in the Trie that starts with the given prefix 49 | func (t *Trie) StartsWith(prefix string) bool { 50 | current := t.root 51 | for _, char := range prefix { 52 | if _, found := current.children[char]; !found { 53 | return false 54 | } 55 | current = current.children[char] 56 | } 57 | return true 58 | } 59 | -------------------------------------------------------------------------------- /trie/trie_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import "testing" 4 | 5 | func TestTrie_InsertAndSearch(t *testing.T) { 6 | tr := New() 7 | 8 | // Test inserting and searching for a word 9 | tr.Insert("hello") 10 | if !tr.Search("hello") { 11 | t.Errorf("Expected 'hello' to be found in the Trie") 12 | } 13 | 14 | // Test searching for a word that does not exist 15 | if tr.Search("hell") { 16 | t.Errorf("Expected 'hell' not to be found in the Trie") 17 | } 18 | 19 | // Test searching for a word that partially matches an existing word 20 | if tr.Search("helloo") { 21 | t.Errorf("Expected 'helloo' not to be found in the Trie") 22 | } 23 | } 24 | 25 | func TestTrie_StartsWith(t *testing.T) { 26 | tr := New() 27 | 28 | // Test prefix search 29 | tr.Insert("hello") 30 | if !tr.StartsWith("hel") { 31 | t.Errorf("Expected Trie to have words starting with 'hel'") 32 | } 33 | 34 | if !tr.StartsWith("hello") { 35 | t.Errorf("Expected Trie to have words starting with 'hello'") 36 | } 37 | 38 | if tr.StartsWith("helloo") { 39 | t.Errorf("Expected Trie not to have words starting with 'helloo'") 40 | } 41 | 42 | if tr.StartsWith("hez") { 43 | t.Errorf("Expected Trie not to have words starting with 'hez'") 44 | } 45 | } 46 | 47 | func TestTrie_InsertMultipleWords(t *testing.T) { 48 | tr := New() 49 | 50 | // Insert multiple words 51 | tr.Insert("hello") 52 | tr.Insert("helium") 53 | 54 | // Test searching for multiple words 55 | if !tr.Search("hello") { 56 | t.Errorf("Expected 'hello' to be found in the Trie") 57 | } 58 | 59 | if !tr.Search("helium") { 60 | t.Errorf("Expected 'helium' to be found in the Trie") 61 | } 62 | 63 | if tr.Search("helix") { 64 | t.Errorf("Expected 'helix' not to be found in the Trie") 65 | } 66 | } 67 | 68 | func TestTrie_EmptyString(t *testing.T) { 69 | tr := New() 70 | 71 | // Test inserting and searching for an empty string 72 | tr.Insert("") 73 | if !tr.Search("") { 74 | t.Errorf("Expected empty string to be found in the Trie") 75 | } 76 | 77 | // Test prefix search with empty string 78 | if !tr.StartsWith("") { 79 | t.Errorf("Expected Trie to have words starting with empty string") 80 | } 81 | } 82 | --------------------------------------------------------------------------------