├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── doc └── DOCUMENTATION.md ├── index.ts ├── jest.config.ts ├── lib ├── base-collection.ts ├── binary-search-tree.ts ├── deque.ts ├── graph.ts ├── hash-table.ts ├── hash-utils.ts ├── heap.ts ├── linked-list.ts ├── map.ts ├── matrix.ts ├── priority-queue.ts ├── queue.ts ├── red-black-tree.ts ├── set.ts ├── stack.ts └── utils.ts ├── package-lock.json ├── package.json ├── tests ├── binary-search-tree.test.ts ├── deque.test.ts ├── hash-table.test.ts ├── hash-utils.test.ts ├── heap.test.ts ├── linked-list.test.ts ├── map.test.ts ├── matrix.test.ts ├── priority-queue.test.ts ├── queue.test.ts ├── red-black-tree.test.ts ├── set.test.ts ├── stack.test.ts └── utils.test.ts ├── tsconfig.json └── types └── index.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended" 5 | ], 6 | "parser": "babel-eslint", 7 | "overrides": [{ 8 | "files": ["**/*.ts"], 9 | "parser": "@typescript-eslint/parser", 10 | "plugins": ["@typescript-eslint"], 11 | "rules": { 12 | "no-unused-vars": "off", 13 | "@typescript-eslint/no-unused-vars": "warn", 14 | "no-useless-constructor": "off", 15 | "@typescript-eslint/no-useless-constructor": "error" 16 | } 17 | }], 18 | "settings": { 19 | // The following settings seem to be necessary in order for the import 20 | // plugin to work correctly with @typescript-eslint/parser. 21 | // https://medium.com/@myylow/how-to-keep-the-airbnb-eslint-config-when-moving-to-typescript-1abb26adb5c6 22 | "import/extensions": [".js", ".ts"], 23 | "import/parsers": { 24 | "@typescript-eslint/parser": [".ts"] 25 | }, 26 | "import/resolver": { 27 | "node": { 28 | "extensions": [".js", ".ts"] 29 | } 30 | } 31 | }, 32 | "rules": { 33 | "semi": ["error", "always"], 34 | "quotes": ["error", "single"], 35 | "camelcase": "off", 36 | "import/extensions": "off", 37 | "no-multiple-empty-lines": ["error", { "max": 3, "maxEOF": 1 }], 38 | "prefer-promise-reject-errors": "off", 39 | "comma-dangle": "off", 40 | "no-console": "off", 41 | "no-new-object": "error", 42 | "no-plusplus": "off", 43 | "object-shorthand": ["error", "never"], 44 | "no-shadow": ["error", {"allow": ["error"]}], 45 | "no-use-before-define": [2, {"functions": false, "classes": true}], 46 | "prefer-template": "off", 47 | "prefer-destructuring": "off", 48 | "object-curly-spacing": "off", 49 | "dot-notation": "off", 50 | "lines-between-class-members": "off", 51 | "no-param-reassign": "off", 52 | "no-await-in-loop": "off", 53 | "no-restricted-globals": "off", 54 | "no-underscore-dangle": "off", 55 | "operator-linebreak": ["error", "after"], 56 | "no-constant-condition": ["error", { "checkLoops": false }], 57 | "no-restricted-syntax": ["error", { 58 | "selector": "ForInStatement", 59 | "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." 60 | }, { 61 | "selector": "LabeledStatement", 62 | "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." 63 | }, { 64 | "selector": "WithStatement", 65 | "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." 66 | }] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | *.swp 4 | .DS_Store 5 | /.cache 6 | *.js.map 7 | *.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Artiom Baloian 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript Data Structure Library 2 | [![NPM](https://img.shields.io/npm/v/typescript-ds-lib?label=npm%20package&color=limegreen)](https://www.npmjs.com/package/typescript-ds-lib) 3 | 4 | TypeScript data structure implementations without external dependencies. Why to use this library? 5 | 6 | - Fully Tested 7 | - Fast 8 | - 0 Dependencies 9 | - Lightweight 10 | - Comes with Comparator (for custom types) 11 | - `equals()` method provided for all data structures 12 | - Well documented 13 | 14 | 15 | ## Install 16 | ``` 17 | npm install typescript-ds-lib 18 | ``` 19 | 20 | 21 | ## Usage 22 | ```typescript 23 | import { Stack } from 'typescript-ds-lib'; 24 | 25 | const stack: Stack = new Stack(); 26 | 27 | stack.push(1); 28 | stack.push(2); 29 | stack.push(3); 30 | 31 | console.log(stack.top()); // 3 32 | stack.pop(); 33 | console.log(stack.top()); // 2 34 | console.log(stack.size()); // 2 35 | ``` 36 | 37 | 38 | ## Documentation and Examples 39 | See the [documentation](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md) for more details and examples. 40 | 41 | 42 | ## Data Structures 43 | - Binary Search Tree 44 | - Deque 45 | - Hash Table (unordered map) 46 | - Heap 47 | - Linked List 48 | - Map 49 | - Matrix 50 | - Priority Queue 51 | - Queue 52 | - Red-Black Tree 53 | - Set 54 | - Stack 55 | - Graph (coming soon) 56 | 57 | 58 | ## `Map-Set` vs Native JavaScript `Map-Set` 59 | The library's `Map` and `Set` data structures are implemented as Red-Black Tree and Binary Search Tree respectively. 60 | Native JavaScript `Map` and `Set` are implemented as Hash Table and Hash Set respectively. 61 | 62 | When to use the library's `Map` and `Set`? 63 | - If CPU consumption is important for you as RBT and BST do not do any kind of CPU intensive hashing. 64 | - If your goal is to have a balanced tree with O(log n) complexity for all the operations in Map. 65 | - If memory efficiency is important for you as RBT and BST are more memory efficient than Hash Table and HashSet. 66 | 67 | You can consider the library's `Map` and `Set` as ordered map and set, and native JavaScript `Map` and `Set` as unordered map and set. 68 | 69 | 70 | ## Contributions 71 | Contributions are welcome and can be made by submitting GitHub pull requests 72 | to this repository. 73 | 74 | 75 | ## License 76 | This source code is available to everyone under the standard 77 | [MIT LICENSE](https://github.com/baloian/marcal/blob/master/LICENSE). 78 | -------------------------------------------------------------------------------- /doc/DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | # TypeScript Data Structure Library Documentation 2 | This library provides a collection of commonly used data structures implemented in TypeScript. Each data structure is designed to be type-safe and efficient. 3 | - [Binary Search Tree](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#binary-search-tree) 4 | - [Deque](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#deque) 5 | - [Hash Table](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#hash-table) 6 | - [Heap](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#heap) 7 | - [Linked List](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#linked-list) 8 | - [Map](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#map) 9 | - [Matrix](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#matrix) 10 | - [Priority Queue](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#priority-queue) 11 | - [Queue](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#queue) 12 | - [Red-Black Tree](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#red-black-tree) 13 | - [Set](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#set) 14 | - [Stack](https://github.com/baloian/typescript-ds-lib/blob/master/doc/DOCUMENTATION.md#stack) 15 | 16 | # Binary Search Tree 17 | A binary search tree (BST) implementation in TypeScript that stores values in an ordered tree structure, with smaller values to the left and larger values to the right. 18 | 19 | ## Constructor 20 | `constructor(comparator: Comparator = (a: T, b: T) => a < b)` - Creates a new binary search tree with the given comparator. 21 | 22 | ## Methods 23 | `insert(value: T)` - Inserts a new value into the binary search tree. 24 | `find(value: T)` - Searches for a value in the binary search tree. 25 | `min()` - Returns the minimum value stored in the tree. 26 | `max()` - Returns the maximum value stored in the tree. 27 | `remove(value: T)` - Removes a value from the binary search tree if it exists. 28 | `forEach(callback: (element: T) => void, traversal: 'inorder' | 'preorder' | 'postorder' = 'inorder')` - Executes a callback function for each element in the BST. 29 | `isEmpty()` - Checks if the binary search tree is empty. 30 | `clear()` - Removes all nodes from the binary search tree. 31 | `size()` - Returns the total number of nodes in the binary search tree. 32 | `equals(other: BinarySearchTree): boolean` - Checks if this binary search tree is equal to another binary search tree. 33 | 34 | ### Example Usage 35 | ```typescript 36 | import { BinarySearchTree } from 'typescript-ds-lib'; 37 | 38 | const bst: BinarySearchTree = new BinarySearchTree(); 39 | 40 | bst.insert(10); 41 | bst.insert(5); 42 | bst.insert(15); 43 | 44 | console.log(bst.find(5)); // true 45 | console.log(bst.min()); // 5 46 | console.log(bst.max()); // 15 47 | ``` 48 | 49 | 50 | # Deque 51 | A double-ended queue (Deque) implementation in TypeScript that allows insertion and deletion of elements from both ends of the queue. 52 | 53 | ## Constructor 54 | `constructor()` - Creates a new deque. 55 | 56 | ## Methods 57 | `pushFront(value: T)` - Adds an element to the front of the deque. 58 | `pushBack(value: T)` - Adds an element to the back of the deque. 59 | `popFront()` - Removes and returns the front element of the deque. 60 | `popBack()` - Removes and returns the back element of the deque. 61 | `front()` - Returns the front element without removing it. 62 | `back()` - Returns the back element without removing it. 63 | `isEmpty()` - Checks if the deque is empty. 64 | `clear()` - Removes all elements from the deque. 65 | `size()` - Returns the total number of elements in the deque. 66 | `equals(other: Deque): boolean` - Checks if this deque is equal to another deque. 67 | 68 | ### Example Usage 69 | ```typescript 70 | import { Deque } from 'typescript-ds-lib'; 71 | 72 | const deque: Deque = new Deque(); 73 | 74 | deque.pushBack(1); // deque: [1] 75 | deque.pushFront(2); // deque: [2,1] 76 | deque.pushBack(3); // deque: [2,1,3] 77 | 78 | console.log(deque.front()); // 2 79 | console.log(deque.back()); // 3 80 | console.log(deque.size()); // 3 81 | 82 | deque.popFront(); // deque: [1,3] 83 | deque.popBack(); // deque: [1] 84 | ``` 85 | 86 | 87 | # Hash Table 88 | A hash table implementation in TypeScript that provides efficient key-value pair storage and retrieval using a hash function for indexing. 89 | 90 | **NOTE:** If a key is an instance of a class that provides a `hashCode()` and/or `equals()` methods, those methods will be used for generating the hash value and comparing keys instead of the default hashing algorithm. This allows for custom hash implementations for complex key types. 91 | 92 | 93 | ## Constructor 94 | `constructor(capacity: number = 4096)` - Creates a new hash table with the specified capacity. 95 | 96 | ## Methods 97 | `insert(key: K, value: V)` - Inserts or updates a key-value pair in the hash table. 98 | `get(key: K)` - Retrieves the value associated with the given key. 99 | `remove(key: K)` - Removes a key-value pair from the hash table. 100 | `forEach(callback: (key: K, value: V) => void)` - Executes a callback function for each key-value pair in the hash table. 101 | `isEmpty()` - Checks if the hash table is empty. 102 | `clear()` - Removes all key-value pairs from the hash table. 103 | `size()` - Returns the number of key-value pairs in the hash table. 104 | `equals(other: HashTable): boolean` - Checks if this hash table is equal to another hash table. 105 | 106 | ### Example Usage 107 | ```typescript 108 | import { HashTable } from 'typescript-ds-lib'; 109 | 110 | const hashTable: HashTable = new HashTable(); 111 | 112 | hashTable.insert('one', 1); 113 | hashTable.insert('two', 2); 114 | hashTable.insert('three', 3); 115 | 116 | console.log(hashTable.get('two')); // 2 117 | console.log(hashTable.isEmpty()); // false 118 | console.log(hashTable.size()); // 3 119 | 120 | hashTable.remove("one"); 121 | ``` 122 | 123 | 124 | # Heap 125 | A heap implementation in TypeScript that maintains elements in a heap structure, where each element has a priority value determining its position in the heap. 126 | 127 | ## Constructor 128 | `constructor(comparator: Comparator = (a: T, b: T) => a < b)` - Creates a new heap with the given comparator. 129 | 130 | ## Methods 131 | `push(value: T)` - Adds an element to the heap. 132 | `pop()` - Removes and returns the element with the highest priority. 133 | `top()` - Returns the element with the highest priority without removing it. 134 | `isEmpty()` - Checks if the heap is empty. 135 | `clear()` - Removes all elements from the heap. 136 | `size()` - Returns the number of elements in the heap. 137 | `equals(other: Heap): boolean` - Checks if this heap is equal to another heap. 138 | 139 | # Linked List 140 | A singly linked list implementation in TypeScript that stores elements in a sequence of nodes, where each node points to the next node in the sequence. 141 | 142 | ## Constructor 143 | `constructor()` - Creates a new linked list. 144 | 145 | ## Methods 146 | `pushBack(value: T)` - Adds a new element to the end of the linked list. 147 | `pushFront(value: T)` - Adds a new element to the beginning of the linked list. 148 | `popBack()` - Removes and returns the last element from the linked list. 149 | `popFront()` - Removes and returns the first element from the linked list. 150 | `front()` - Returns the first element without removing it. 151 | `back()` - Returns the last element without removing it. 152 | `insert(value: T, position: number)` - Inserts a new element at the specified position. 153 | `insertBefore(value: T, condition: (element: T) => boolean)` - Inserts a new element before the first element that satisfies the condition. 154 | `insertAfter(value: T, condition: (element: T) => boolean)` - Inserts a new element after the first element that satisfies the condition. 155 | `removeIf(condition: (element: T) => boolean)` - Removes all elements that satisfy the condition. 156 | `removeAt(position: number)` - Removes the element at the specified position. 157 | `forEach(callback: (element: T) => void)` - Applies a function to each element of the linked list. 158 | `get(position: number)` - Returns the element at the specified position. 159 | `isEmpty()` - Checks if the linked list is empty. 160 | `clear()` - Removes all elements from the linked list. 161 | `size()` - Returns the total number of elements in the linked list. 162 | `equals(other: LinkedList): boolean` - Checks if this linked list is equal to another linked list. 163 | 164 | ### Example Usage 165 | ```typescript 166 | import { LinkedList } from 'typescript-ds-lib'; 167 | 168 | const list: LinkedList = new LinkedList(); 169 | 170 | list.pushBack(1); // list: [1] 171 | list.pushBack(2); // list: [1,2] 172 | list.pushFront(0); // list: [0,1,2] 173 | 174 | console.log(list.get(1)); // 1 175 | console.log(list.size()); // 3 176 | 177 | list.removeAt(0); // list: [1,2] 178 | list.removeAt(1); // list: [1] 179 | ``` 180 | 181 | 182 | # Map 183 | A map implementation in TypeScript that maintains key-value pairs in sorted order based on the keys, implemented as a red-black tree. 184 | 185 | ## Constructor 186 | `constructor(comparator: Comparator = (a: K, b: K) => a < b)` - Creates a new map with the given comparator. 187 | 188 | ## Methods 189 | `insert(key: K, value: V)` - Inserts or updates a key-value pair in the map. 190 | `find(key: K)` - Retrieves the value associated with the given key. 191 | `remove(key: K)` - Removes a key-value pair from the map. 192 | `forEach(callback: (key: K, value: V) => void)` - Executes a callback function for each key-value pair in the map. 193 | `isEmpty()` - Checks if the map is empty. 194 | `clear()` - Removes all key-value pairs from the map. 195 | `size()` - Returns the number of key-value pairs in the map. 196 | `equals(other: Map): boolean` - Checks if this map is equal to another map. 197 | 198 | ### Example Usage 199 | ```typescript 200 | import { Map as OrderedMap } from 'typescript-ds-lib'; 201 | 202 | const map: OrderedMap = new OrderedMap(); 203 | 204 | map.insert('apple', 1); 205 | map.insert('banana', 2); 206 | map.insert('cherry', 3); 207 | 208 | console.log(map.find('banana')); // 2 209 | console.log(map.find('apple')); // 1 210 | console.log(map.size()); // 3 211 | 212 | map.remove('apple'); 213 | console.log(map.size()); // 2 214 | ``` 215 | 216 | 217 | # Priority Queue 218 | A priority queue implementation in TypeScript that maintains elements in a heap structure, where each element has a priority value determining its position in the queue. 219 | 220 | ## Constructor 221 | `constructor(comparator: Comparator = (a: T, b: T) => a > b)` - Creates a new priority queue with the given comparator . 222 | 223 | ## Methods 224 | `push(value: T)` - Adds an element to the priority queue. 225 | `pop()` - Removes and returns the element with the highest priority. 226 | `front()` - Returns the highest priority element without removing it. 227 | `isEmpty()` - Checks if the priority queue is empty. 228 | `clear()` - Removes all elements from the priority queue. 229 | `size()` - Returns the number of elements in the priority queue. 230 | `equals(other: PriorityQueue): boolean` - Checks if this priority queue is equal to another priority queue. 231 | 232 | ### Example Usage 233 | ```typescript 234 | import { PriorityQueue } from 'typescript-ds-lib'; 235 | 236 | const pq: PriorityQueue = new PriorityQueue(); 237 | 238 | pq.push(10); // Will be third priority 239 | pq.push(30); // Will be first priority 240 | pq.push(20); // Will be second priority 241 | 242 | console.log(pq.front()); // 30 (highest value has highest priority) 243 | console.log(pq.size()); // 3 244 | 245 | pq.pop(); // Removes 30 246 | console.log(pq.front()); // 20 (next highest value) 247 | ``` 248 | 249 | 250 | # Queue 251 | A queue implementation in TypeScript that follows the First-In-First-Out (FIFO) principle, where elements are added to the back and removed from the front of the queue. 252 | 253 | ## Constructor 254 | `constructor()` - Creates a new queue. 255 | 256 | ## Methods 257 | `push(value: T)` - Adds an element to the back of the queue. 258 | `pop()` - Removes and returns the element from the front of the queue. 259 | `front()` - Returns the front element without removing it. 260 | `isEmpty()` - Checks if the queue is empty. 261 | `clear()` - Removes all elements from the queue. 262 | `size()` - Returns the number of elements in the queue. 263 | `equals(other: Queue): boolean` - Checks if this queue is equal to another queue. 264 | 265 | ### Example Usage 266 | ```typescript 267 | import { Queue } from 'typescript-ds-lib'; 268 | 269 | const queue: Queue = new Queue(); 270 | 271 | queue.push(1); // queue: [1] 272 | queue.push(2); // queue: [1,2] 273 | queue.push(3); // queue: [1,2,3] 274 | 275 | console.log(queue.front()); // 1 276 | console.log(queue.size()); // 3 277 | 278 | queue.pop(); // queue: [2,3] 279 | console.log(queue.front()); // 2 280 | ``` 281 | 282 | 283 | # Red-Black Tree 284 | A self-balancing binary search tree (Red-Black key-value Tree) implementation in TypeScript that maintains balance through color properties and rotation operations, ensuring O(log n) time complexity for basic operations. 285 | 286 | ## Constructor 287 | `constructor(comparator: Comparator = (a: K, b: K) => a < b)` - Creates a new red-black tree with the given comparator. 288 | 289 | ## Methods 290 | `insert(key: K, value: V)` - Inserts a new key-value pair into the tree while maintaining red-black properties. 291 | `remove(key: K)` - Removes a key-value pair from the tree if it exists while maintaining red-black properties. 292 | `forEach(callback: (key: K, value: V) => void)` - Executes a callback function for each key-value pair in the tree. 293 | `find(key: K)` - Searches for a key in the tree and returns true if found. 294 | `min()` - Returns the minimum value stored in the tree. 295 | `max()` - Returns the maximum value stored in the tree. 296 | `isEmpty()` - Checks if the tree is empty. 297 | `clear()` - Removes all nodes from the tree. 298 | `size()` - Returns the total number of nodes in the tree. 299 | `equals(other: RedBlackTree): boolean` - Checks if this red-black tree is equal to another red-black tree. 300 | 301 | ### Example Usage 302 | ```typescript 303 | import { RedBlackTree } from 'typescript-ds-lib'; 304 | 305 | const rbt: RedBlackTree = new RedBlackTree(); 306 | 307 | rbt.insert(10, "ten"); 308 | rbt.insert(5, "five"); 309 | rbt.insert(15, "fifteen"); 310 | rbt.insert(3, "three"); 311 | rbt.insert(7, "seven"); 312 | 313 | console.log(rbt.find(5)); // "five" 314 | console.log(rbt.min()); // "three" 315 | console.log(rbt.max()); // "fifteen" 316 | console.log(rbt.size()); // 5 317 | 318 | rbt.remove(5); 319 | console.log(rbt.find(5)); // undefined 320 | ``` 321 | 322 | 323 | # Set 324 | A set implementation in TypeScript that maintains a collection of unique elements, implemented as a binary search tree. 325 | 326 | ## Constructor 327 | `constructor(comparator: Comparator = (a: T, b: T) => a < b)` - Creates a new set with the given comparator. 328 | 329 | ## Methods 330 | `insert(value: T)` - Adds a new element to the set if it doesn't already exist. 331 | `insertList(values: T[])` - Adds multiple elements to the set if they don't already exist. 332 | `remove(value: T)` - Removes an element from the set if it exists. 333 | `find(value: T)` - Checks if an element exists in the set. 334 | `has(value: T)` - Checks if an element exists in the set. 335 | `forEach(callback: (element: T) => void)` - Executes a callback function for each element in the set. 336 | `isEmpty()` - Checks if the set is empty. 337 | `clear()` - Removes all elements from the set. 338 | `size()` - Returns the number of elements in the set. 339 | `equals(other: Set): boolean` - Checks if this set is equal to another set. 340 | 341 | ### Example Usage 342 | ```typescript 343 | import { Set as OrderedSet } from 'typescript-ds-lib'; 344 | 345 | const set: OrderedSet = new OrderedSet(); 346 | 347 | set.insert(1); // set: {1} 348 | set.insert(2); // set: {1,2} 349 | set.insert(2); // set: {1,2} (no duplicate added) 350 | set.insert(3); // set: {1,2,3} 351 | 352 | console.log(set.has(2)); // true 353 | console.log(set.size()); // 3 354 | 355 | set.remove(2); // set: {1,3} 356 | console.log(set.has(2)); // false 357 | 358 | // Iterate through elements 359 | set.forEach(element => { 360 | console.log(element); // Prints: 1, 3 361 | }); 362 | ``` 363 | 364 | 365 | # Stack 366 | A stack implementation in TypeScript that follows the Last-In-First-Out (LIFO) principle, where elements are added and removed from the same end of the stack. 367 | 368 | ## Constructor 369 | `constructor()` - Creates a new stack. 370 | 371 | ## Methods 372 | `push(value: T)` - Adds an element to the top of the stack. 373 | `pop()` - Removes and returns the element from the top of the stack. 374 | `top()` - Returns the top element without removing it. 375 | `isEmpty()` - Checks if the stack is empty. 376 | `clear()` - Removes all elements from the stack. 377 | `size()` - Returns the number of elements in the stack. 378 | `equals(other: Stack): boolean` - Checks if this stack is equal to another stack. 379 | 380 | ### Example Usage 381 | ```typescript 382 | import { Stack } from 'typescript-ds-lib'; 383 | 384 | const stack: Stack = new Stack(); 385 | 386 | stack.push(1); // stack: [1] 387 | stack.push(2); // stack: [1,2] 388 | stack.push(3); // stack: [1,2,3] 389 | 390 | console.log(stack.top()); // 3 391 | console.log(stack.size()); // 3 392 | 393 | stack.pop(); // stack: [1,2] 394 | console.log(stack.top()); // 2 395 | ``` 396 | 397 | 398 | # Matrix 399 | A matrix implementation in TypeScript that provides operations for matrix manipulation, such as addition, multiplication, and transposition. 400 | 401 | ## Constructor 402 | `constructor(rows: number, cols: number)` - Creates a new matrix with the specified number of rows and columns, initialized with undefined. 403 | 404 | ## Methods 405 | `get(row: number, col: number): T | undefined` - Returns the element at the specified row and column, or undefined if out of bounds. 406 | `set(row: number, col: number, value: T): void` - Sets the element at the specified row and column to the given value. Ignores if out of bounds. 407 | `add(matrix: Matrix): Matrix` - Adds another matrix to this matrix and returns the result. Matrices must have same dimensions. 408 | `multiply(matrix: Matrix): Matrix` - Multiplies this matrix by another matrix and returns the result. Number of columns in first matrix must equal rows in second. 409 | `transpose(): Matrix` - Returns a new matrix that is the transpose of this matrix (rows become columns and vice versa). 410 | `fill(value: T): void` - Fills the entire matrix with the given value. 411 | `clone(): Matrix` - Returns a deep copy of the matrix with all values copied. 412 | `toArray(): T[][]` - Returns a copy of the internal 2D array representation. 413 | `getRow(row: number): T[]` - Returns a copy of the specified row as an array. Returns empty array if invalid row. 414 | `getColumn(col: number): T[]` - Returns a copy of the specified column as an array. Returns empty array if invalid column. 415 | `setRow(row: number, values: T[]): void` - Sets the specified row to the given array. Values array length must match columns. 416 | `setColumn(col: number, values: T[]): void` - Sets the specified column to the given array. Values array length must match rows. 417 | `resize(rows: number, cols: number): void` - Resizes the matrix to new dimensions, preserving existing values where possible. 418 | `map(fn: (value: T, row: number, col: number) => T): Matrix` - Returns a new matrix with the function applied to each element. 419 | `forEach(fn: (value: T, row: number, col: number) => void): void` - Executes the function for each element in the matrix. 420 | `swapRows(row1: number, row2: number): void` - Swaps the positions of two rows in the matrix. 421 | `swapColumns(col1: number, col2: number): void` - Swaps the positions of two columns in the matrix. 422 | `submatrix(startRow: number, startCol: number, endRow: number, endCol: number): Matrix` - Extracts a submatrix from the given bounds. 423 | `insertMatrix(other: Matrix, startRow: number, startCol: number): void` - Inserts another matrix into this matrix at the specified position. 424 | `getDiagonal(): T[]` - Returns the diagonal elements of the matrix as an array. 425 | `setDiagonal(values: T[]): void` - Sets the diagonal elements of the matrix from the given array. 426 | `trace(): T` - Calculates and returns the sum of diagonal elements. 427 | `isSquare(): boolean` - Checks if the matrix has equal number of rows and columns. 428 | `isSymmetric(): boolean` - Checks if the matrix is symmetric (equal to its transpose). 429 | `scalarMultiply(scalar: number): Matrix` - Multiplies each element in the matrix by a scalar value. 430 | `subtract(other: Matrix): Matrix` - Subtracts another matrix from this matrix and returns the result. 431 | `equals(other: Matrix): boolean` - Checks if this matrix is equal to another matrix. 432 | `isEmpty(): boolean` - Checks if the matrix is empty. 433 | `clear(): void` - Resets all elements in the matrix to undefined. 434 | `size(): number` - Returns the total number of elements in the matrix (rows * columns). 435 | 436 | ### Example Usage 437 | ```typescript 438 | import { Matrix } from 'typescript-ds-lib'; 439 | 440 | const matrix: Matrix = new Matrix(3, 3, 0); 441 | 442 | matrix.set(0, 0, 1); 443 | matrix.set(1, 1, 2); 444 | matrix.set(2, 2, 3); 445 | 446 | console.log(matrix.get(1, 1)); // 2 447 | console.log(matrix.size()); // { rows: 3, cols: 3 } 448 | 449 | const transposed = matrix.transpose(); 450 | console.log(transposed.get(0, 0)); // 1 451 | ``` 452 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './types'; 2 | export { BinarySearchTree } from './lib/binary-search-tree'; 3 | export { Deque } from './lib/deque'; 4 | export { HashTable } from './lib/hash-table'; 5 | export { Heap } from './lib/heap'; 6 | export { LinkedList } from './lib/linked-list'; 7 | export { Map } from './lib/map'; 8 | export { Matrix } from './lib/matrix'; 9 | export { PriorityQueue } from './lib/priority-queue'; 10 | export { Queue } from './lib/queue'; 11 | export { RedBlackTree } from './lib/red-black-tree'; 12 | export { Set } from './lib/set'; 13 | export { Stack } from './lib/stack'; 14 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types'; 2 | 3 | const config: Config.InitialOptions = { 4 | verbose: true, 5 | transform: { 6 | '^.+\\.(ts|tsx)$': 'ts-jest' 7 | }, 8 | testPathIgnorePatterns: ['/node_modules/', '/dist/'] 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /lib/base-collection.ts: -------------------------------------------------------------------------------- 1 | export abstract class BaseCollection { 2 | /** 3 | * Checks if the collection is empty. Returns true if the collection is empty, false otherwise. 4 | */ 5 | abstract isEmpty(): boolean; 6 | 7 | /** 8 | * Returns the number of elements in the collection. 9 | */ 10 | abstract size(): number; 11 | 12 | /** 13 | * Removes all elements from the collection. 14 | */ 15 | abstract clear(): void; 16 | 17 | /** 18 | * Checks if two collections are equal. 19 | */ 20 | abstract equals(other: BaseCollection): boolean; 21 | } 22 | -------------------------------------------------------------------------------- /lib/binary-search-tree.ts: -------------------------------------------------------------------------------- 1 | import { Comparator } from '../types'; 2 | import { BaseCollection } from './base-collection'; 3 | 4 | 5 | export interface BinarySearchTree { 6 | insert(element: T): void; 7 | remove(element: T): void; 8 | find(element: T): boolean; 9 | min(): T | undefined; 10 | max(): T | undefined; 11 | forEach(callback: (element: T) => void, traversal?: 'inorder' | 'preorder' | 'postorder'): void; 12 | } 13 | 14 | 15 | class TreeNode { 16 | value: T; 17 | left: TreeNode | null; 18 | right: TreeNode | null; 19 | 20 | constructor(value: T) { 21 | this.value = value; 22 | this.left = null; 23 | this.right = null; 24 | } 25 | } 26 | 27 | 28 | export class BinarySearchTree extends BaseCollection implements BinarySearchTree { 29 | private root: TreeNode | null; 30 | private comparator: Comparator; 31 | private nodeCount: number; 32 | 33 | constructor(comparator: Comparator = (a: T, b: T) => a < b) { 34 | super(); 35 | this.root = null; 36 | this.comparator = comparator; 37 | this.nodeCount = 0; 38 | } 39 | 40 | /** 41 | * Inserts a value into the BST if it doesn't already exist 42 | */ 43 | insert(value: T): void { 44 | const newNode = new TreeNode(value); 45 | if (!this.root) { 46 | this.root = newNode; 47 | this.nodeCount++; 48 | return; 49 | } 50 | let current = this.root; 51 | while (true) { 52 | if (this.comparator(value, current.value)) { 53 | if (current.left === null) { 54 | current.left = newNode; 55 | this.nodeCount++; 56 | break; 57 | } 58 | current = current.left; 59 | } else if (this.isEqual(value, current.value)) { 60 | return; 61 | } else { 62 | if (current.right === null) { 63 | current.right = newNode; 64 | this.nodeCount++; 65 | break; 66 | } 67 | current = current.right; 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Searches for a value in the BST. Returns true if found, false otherwise 74 | */ 75 | find(value: T): boolean { 76 | let current = this.root; 77 | while (current !== null) { 78 | if (this.isEqual(value, current.value)) { 79 | return true; 80 | } 81 | if (this.comparator(value, current.value)) { 82 | current = current.left; 83 | } else { 84 | current = current.right; 85 | } 86 | } 87 | return false; 88 | } 89 | 90 | /** 91 | * Returns the minimum value in the BST, or undefined if tree is empty 92 | */ 93 | min(): T | undefined { 94 | if (!this.root) return undefined; 95 | let current = this.root; 96 | while (current.left !== null) { 97 | current = current.left; 98 | } 99 | return current.value; 100 | } 101 | 102 | /** 103 | * Returns the maximum value in the BST, or undefined if tree is empty 104 | */ 105 | max(): T | undefined { 106 | if (!this.root) return undefined; 107 | let current = this.root; 108 | while (current.right !== null) { 109 | current = current.right; 110 | } 111 | return current.value; 112 | } 113 | 114 | /** 115 | * Removes a value from the BST if it exists 116 | */ 117 | remove(value: T): void { 118 | this.root = this.removeNode(this.root, value); 119 | } 120 | 121 | private removeNode(node: TreeNode | null, value: T): TreeNode | null { 122 | if (node === null) return null; 123 | if (this.comparator(value, node.value)) { 124 | node.left = this.removeNode(node.left, value); 125 | return node; 126 | } else if (this.comparator(node.value, value)) { 127 | node.right = this.removeNode(node.right, value); 128 | return node; 129 | } else { 130 | // Node to delete found 131 | this.nodeCount--; 132 | // Case 1: Leaf node 133 | if (node.left === null && node.right === null) { 134 | return null; 135 | } 136 | // Case 2: Node with one child 137 | if (node.left === null) return node.right; 138 | if (node.right === null) return node.left; 139 | // Case 3: Node with two children 140 | const minNode = this.findMin(node.right); 141 | node.value = minNode.value; 142 | node.right = this.removeNode(node.right, minNode.value); 143 | this.nodeCount++; // Increment back since the recursive call decremented 144 | return node; 145 | } 146 | } 147 | 148 | private findMin(node: TreeNode): TreeNode { 149 | let current = node; 150 | while (current.left !== null) { 151 | current = current.left; 152 | } 153 | return current; 154 | } 155 | 156 | private isEqual(a: T, b: T): boolean { 157 | // Two values are equal if neither is less than the other 158 | return !this.comparator(a, b) && !this.comparator(b, a); 159 | } 160 | 161 | /** 162 | * Executes a callback function for each element in the BST in-order traversal 163 | */ 164 | forEach(callback: (element: T) => void, traversal: 'inorder' | 'preorder' | 'postorder' = 'inorder'): void { 165 | switch (traversal) { 166 | case 'inorder': 167 | this.inorderTraversal(this.root, callback); 168 | break; 169 | case 'preorder': 170 | this.preorderTraversal(this.root, callback); 171 | break; 172 | case 'postorder': 173 | this.postorderTraversal(this.root, callback); 174 | break; 175 | default: 176 | this.inorderTraversal(this.root, callback); 177 | } 178 | } 179 | 180 | private inorderTraversal(node: TreeNode | null, callback: (element: T) => void): void { 181 | if (node === null) return; 182 | this.inorderTraversal(node.left, callback); 183 | callback(node.value); 184 | this.inorderTraversal(node.right, callback); 185 | } 186 | 187 | private preorderTraversal(node: TreeNode | null, callback: (element: T) => void): void { 188 | if (node === null) return; 189 | callback(node.value); 190 | this.preorderTraversal(node.left, callback); 191 | this.preorderTraversal(node.right, callback); 192 | } 193 | 194 | private postorderTraversal(node: TreeNode | null, callback: (element: T) => void): void { 195 | if (node === null) return; 196 | this.postorderTraversal(node.left, callback); 197 | this.postorderTraversal(node.right, callback); 198 | callback(node.value); 199 | } 200 | 201 | /** 202 | * Returns true if the BST is empty, false otherwise 203 | */ 204 | isEmpty(): boolean { 205 | return this.root === null; 206 | } 207 | 208 | /** 209 | * Removes all nodes from the BST 210 | */ 211 | clear(): void { 212 | this.root = null; 213 | this.nodeCount = 0; 214 | } 215 | 216 | /** 217 | * Returns the number of nodes in the BST. 218 | */ 219 | size(): number { 220 | return this.nodeCount; 221 | } 222 | 223 | /** 224 | * Checks if two BSTs are equal. 225 | */ 226 | equals(other: BinarySearchTree): boolean { 227 | if (!other || !(other instanceof BinarySearchTree)) return false; 228 | if (this.size() !== other.size()) return false; 229 | return this.areTreesEqual(this.root, other.root); 230 | } 231 | 232 | private areTreesEqual(node1: TreeNode | null, node2: TreeNode | null): boolean { 233 | if (node1 === null && node2 === null) return true; 234 | if (node1 === null || node2 === null) return false; 235 | return this.isEqual(node1.value, node2.value) && 236 | this.areTreesEqual(node1.left, node2.left) && 237 | this.areTreesEqual(node1.right, node2.right); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /lib/deque.ts: -------------------------------------------------------------------------------- 1 | import { LinkedList } from './linked-list'; 2 | import { BaseCollection } from './base-collection'; 3 | 4 | 5 | export interface Deque { 6 | pushFront(element: T): void; 7 | pushBack(element: T): void; 8 | popFront(): T | undefined; 9 | popBack(): T | undefined; 10 | front(): T | undefined; 11 | back(): T | undefined; 12 | } 13 | 14 | 15 | export class Deque extends BaseCollection implements Deque { 16 | private items: LinkedList; 17 | 18 | constructor() { 19 | super(); 20 | this.items = new LinkedList(); 21 | } 22 | 23 | /** 24 | * Adds an element to the front of the deque 25 | */ 26 | pushFront(element: T): void { 27 | this.items.pushFront(element); 28 | } 29 | 30 | /** 31 | * Adds an element to the back of the deque 32 | */ 33 | pushBack(element: T): void { 34 | this.items.pushBack(element); 35 | } 36 | 37 | /** 38 | * Removes and returns the front element from the deque, or undefined if deque is empty 39 | */ 40 | popFront(): T | undefined { 41 | return this.items.popFront(); 42 | } 43 | 44 | /** 45 | * Removes and returns the back element from the deque, or undefined if deque is empty 46 | */ 47 | popBack(): T | undefined { 48 | return this.items.popBack(); 49 | } 50 | 51 | /** 52 | * Returns the front element without removing it, or undefined if deque is empty 53 | */ 54 | front(): T | undefined { 55 | return this.items.get(0); 56 | } 57 | 58 | /** 59 | * Returns the back element without removing it, or undefined if deque is empty 60 | */ 61 | back(): T | undefined { 62 | return this.items.get(this.items.size() - 1); 63 | } 64 | 65 | /** 66 | * Returns true if the deque is empty, false otherwise 67 | */ 68 | isEmpty(): boolean { 69 | return this.items.isEmpty(); 70 | } 71 | 72 | /** 73 | * Returns the number of elements in the deque 74 | */ 75 | size(): number { 76 | return this.items.size(); 77 | } 78 | 79 | /** 80 | * Removes all elements from the deque 81 | */ 82 | clear(): void { 83 | this.items.clear(); 84 | } 85 | 86 | /** 87 | * Checks if two deques are equal. 88 | */ 89 | equals(other: Deque): boolean { 90 | if (!other || !(other instanceof Deque)) return false; 91 | return this.items.equals(other.items); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/graph.ts: -------------------------------------------------------------------------------- 1 | export interface Graph { 2 | addVertex(vertex: V): void; 3 | removeVertex(vertex: V): void; 4 | addEdge(from: V, to: V, weight?: W): void; 5 | removeEdge(from: V, to: V): void; 6 | getNeighbors(vertex: V): V[]; 7 | getEdgeWeight(from: V, to: V): W; 8 | getVertices(): V[]; 9 | hasVertex(vertex: V): boolean; 10 | hasEdge(from: V, to: V): boolean; 11 | vertexCount(): number; 12 | edgeCount(): number; 13 | clear(): void; 14 | } 15 | -------------------------------------------------------------------------------- /lib/hash-table.ts: -------------------------------------------------------------------------------- 1 | import { HashUtils } from './hash-utils'; 2 | import { BaseCollection } from './base-collection'; 3 | import { Utils } from './utils'; 4 | 5 | 6 | export interface HashTable { 7 | insert(key: K, value: V): void; 8 | get(key: K): V | undefined; 9 | remove(key: K): boolean; 10 | forEach(callback: (key: K, value: V) => void): void; 11 | } 12 | 13 | 14 | class HashNode { 15 | key: K; 16 | value: V; 17 | next: HashNode | null; 18 | 19 | constructor(key: K, value: V) { 20 | this.key = key; 21 | this.value = value; 22 | this.next = null; 23 | } 24 | } 25 | 26 | 27 | export class HashTable extends BaseCollection implements HashTable { 28 | private table: Array | null>; 29 | private count: number; 30 | private readonly capacity: number; 31 | 32 | constructor(capacity: number = 4096) { 33 | super(); 34 | // Handle negative or zero capacity by using default capacity 35 | this.capacity = capacity <= 0 ? 4096 : capacity; 36 | this.table = new Array(this.capacity).fill(null); 37 | this.count = 0; 38 | } 39 | 40 | insert(key: K, value: V): void { 41 | const index: number = HashUtils.hash(key, this.capacity); 42 | // Handle empty bucket case. 43 | if (!this.table[index]) { 44 | this.table[index] = new HashNode(key, value); 45 | this.count++; 46 | return; 47 | } 48 | // Check first node for key match. If it matches, update the value. 49 | if (Utils.equals(this.table[index]!.key, key)) { 50 | this.table[index]!.value = value; 51 | return; 52 | } 53 | // Traverse chain to find key or last node. If it matches, update the value. 54 | let current: HashNode | null = this.table[index]; 55 | while (current?.next) { 56 | if (Utils.equals(current.next.key, key)) { 57 | current.next.value = value; 58 | return; 59 | } 60 | current = current.next; 61 | } 62 | // Key not found, append new node. 63 | current.next = new HashNode(key, value); 64 | this.count++; 65 | } 66 | 67 | get(key: K): V | undefined { 68 | const index: number = HashUtils.hash(key, this.capacity); 69 | let current: HashNode | null = this.table[index]; 70 | while (current) { 71 | if (Utils.equals(current.key, key)) { 72 | return current.value; 73 | } 74 | current = current.next; 75 | } 76 | return undefined; 77 | } 78 | 79 | remove(key: K): boolean { 80 | const index: number = HashUtils.hash(key, this.capacity); 81 | let current: HashNode | null = this.table[index]; 82 | let prev: HashNode | null = null; 83 | while (current) { 84 | if (Utils.equals(current.key, key)) { 85 | if (prev) { 86 | prev.next = current.next; 87 | } else { 88 | this.table[index] = current.next; 89 | } 90 | this.count--; 91 | return true; 92 | } 93 | prev = current; 94 | current = current.next; 95 | } 96 | return false; 97 | } 98 | 99 | forEach(callback: (key: K, value: V) => void): void { 100 | for (const node of this.table) { 101 | let current: HashNode | null = node; 102 | while (current) { 103 | callback(current.key, current.value); 104 | current = current.next; 105 | } 106 | } 107 | } 108 | 109 | size(): number { 110 | return this.count; 111 | } 112 | 113 | isEmpty(): boolean { 114 | return this.count === 0; 115 | } 116 | 117 | clear(): void { 118 | this.table = new Array(this.capacity).fill(null); 119 | this.count = 0; 120 | } 121 | 122 | /** 123 | * Checks if two hash tables are equal. 124 | */ 125 | equals(other: HashTable): boolean { 126 | if (!other || !(other instanceof HashTable)) { 127 | return false; 128 | } 129 | if (this.size() !== other.size()) { 130 | return false; 131 | } 132 | // Check each key-value pair in this table exists in other table 133 | let isEqual = true; 134 | this.forEach((key, value) => { 135 | const otherValue = other.get(key); 136 | if (!Utils.equals(value, otherValue)) { 137 | isEqual = false; 138 | } 139 | }); 140 | return isEqual; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/hash-utils.ts: -------------------------------------------------------------------------------- 1 | export class HashUtils { 2 | private static valueToString(value: V): string { 3 | if (value === null || value === undefined) return 'null'; 4 | let stringKey: string; 5 | switch (typeof value) { 6 | case 'number': 7 | stringKey = value.toString(); 8 | break; 9 | case 'object': 10 | if (typeof (value as any).toString === 'function') { 11 | stringKey = (value as any).toString(); 12 | } else { 13 | stringKey = JSON.stringify(value); 14 | } 15 | break; 16 | case 'string': 17 | stringKey = value; 18 | break; 19 | case 'function': 20 | stringKey = value.toString(); 21 | break; 22 | case 'symbol': 23 | stringKey = value.toString(); 24 | break; 25 | default: 26 | stringKey = String(value); 27 | } 28 | return stringKey; 29 | } 30 | 31 | // Thomas Wang, Integer Hash Functions. 32 | static wangHash32(key: number): number { 33 | key = key >>> 0; 34 | key = ~key + (key << 15); 35 | key = key ^ (key >>> 12); 36 | key = key + (key << 2); 37 | key = key ^ (key >>> 4); 38 | // Ensure multiplication wraps to 32 bits 39 | key = ((key << 11) + (key << 3) + key) >>> 0; 40 | key = key ^ (key >>> 16); 41 | return key >>> 0; 42 | } 43 | 44 | /* 45 | * DJB2a (variant using xor rather than +) hash algorithm. 46 | * See: http://www.cse.yorku.ca/~oz/hash.html 47 | */ 48 | static djb2aHash(str: string): number { 49 | let hash: number = 5381; 50 | for (let i = 0; i < str.length; i++) { 51 | hash = ((hash << 5) + hash) ^ str.charCodeAt(i); 52 | } 53 | // Convert the hash to an unsigned 32-bit integer to match C's unsigned long. 54 | return hash >>> 0; 55 | } 56 | 57 | static hash(key: K, capacity: number): number { 58 | if (key && typeof (key as any).hashCode === 'function') { 59 | const hashValue = (key as any).hashCode(); 60 | return typeof hashValue === 'number' ? hashValue % capacity : 61 | HashUtils.djb2aHash(String(hashValue)) % capacity; 62 | } 63 | if (typeof key === 'number' && Number.isSafeInteger(key)) { 64 | return HashUtils.wangHash32(key) % capacity; 65 | } 66 | const stringKey: string = this.valueToString(key); 67 | return HashUtils.djb2aHash(stringKey) % capacity; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/heap.ts: -------------------------------------------------------------------------------- 1 | import { Comparator } from '../types'; 2 | import { BaseCollection } from './base-collection'; 3 | import { Utils } from './utils'; 4 | 5 | 6 | export interface Heap { 7 | push(element: T): void; 8 | pop(): T | undefined; 9 | top(): T | undefined; 10 | } 11 | 12 | 13 | export class Heap extends BaseCollection implements Heap { 14 | private items: T[]; 15 | private comparator: Comparator; 16 | 17 | constructor(comparator: Comparator = (a: T, b: T) => a < b) { 18 | super(); 19 | this.items = []; 20 | this.comparator = comparator; 21 | } 22 | 23 | /** 24 | * Adds an element to the heap. 25 | */ 26 | push(element: T): void { 27 | this.items.push(element); 28 | this.heapifyUp(this.items.length - 1); 29 | } 30 | 31 | /** 32 | * Removes and returns the root element from the heap, or undefined if heap is empty. 33 | */ 34 | pop(): T | undefined { 35 | if (this.isEmpty()) { 36 | return undefined; 37 | } 38 | const root: T = this.items[0]; 39 | const lastElement: T = this.items.pop()!; 40 | if (!this.isEmpty()) { 41 | this.items[0] = lastElement; 42 | this.heapifyDown(0); 43 | } 44 | return root; 45 | } 46 | 47 | /** 48 | * Returns the root element without removing it, or undefined if heap is empty. 49 | */ 50 | top(): T | undefined { 51 | return this.items[0]; 52 | } 53 | 54 | /** 55 | * Checks if the heap is empty. Returns true if empty, false otherwise. 56 | */ 57 | isEmpty(): boolean { 58 | return this.items.length === 0; 59 | } 60 | 61 | /** 62 | * Returns the number of elements in the heap. 63 | */ 64 | size(): number { 65 | return this.items.length; 66 | } 67 | 68 | /** 69 | * Removes all elements from the heap. 70 | */ 71 | clear(): void { 72 | this.items = []; 73 | } 74 | 75 | /** 76 | * Moves an element up the heap to its correct position. 77 | */ 78 | private heapifyUp(index: number): void { 79 | while (index > 0) { 80 | const parentIndex: number = Math.floor((index - 1) / 2); 81 | if (this.comparator(this.items[index], this.items[parentIndex])) { 82 | [this.items[index], this.items[parentIndex]] = 83 | [this.items[parentIndex], this.items[index]]; 84 | index = parentIndex; 85 | } else { 86 | break; 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * Moves an element down the heap to its correct position. 93 | */ 94 | private heapifyDown(index: number): void { 95 | while (true) { 96 | let smallestIndex: number = index; 97 | const leftChild: number = 2 * index + 1; 98 | const rightChild: number = 2 * index + 2; 99 | if (leftChild < this.items.length && 100 | this.comparator(this.items[leftChild], this.items[smallestIndex])) { 101 | smallestIndex = leftChild; 102 | } 103 | if (rightChild < this.items.length && 104 | this.comparator(this.items[rightChild], this.items[smallestIndex])) { 105 | smallestIndex = rightChild; 106 | } 107 | if (smallestIndex === index) { 108 | break; 109 | } 110 | [this.items[index], this.items[smallestIndex]] = 111 | [this.items[smallestIndex], this.items[index]]; 112 | index = smallestIndex; 113 | } 114 | } 115 | 116 | /** 117 | * Checks if two heaps are equal. 118 | */ 119 | equals(other: Heap): boolean { 120 | if (!other || !(other instanceof Heap)) return false; 121 | if (this.size() !== other.size()) return false; 122 | for (let i = 0; i < this.items.length; i++) { 123 | if (!Utils.equals(this.items[i], other.items[i])) return false; 124 | } 125 | return true; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/linked-list.ts: -------------------------------------------------------------------------------- 1 | import { BaseCollection } from './base-collection'; 2 | import { Utils } from './utils'; 3 | 4 | 5 | export interface LinkedList { 6 | pushBack(element: T): void; 7 | pushFront(element: T): void; 8 | popBack(): T | undefined; 9 | popFront(): T | undefined; 10 | front(): T | undefined; 11 | back(): T | undefined; 12 | insert(element: T, position: number): boolean; 13 | insertBefore(element: T, condition: (element: T) => boolean): boolean; 14 | insertAfter(element: T, condition: (element: T) => boolean): boolean; 15 | removeIf(condition: (element: T) => boolean): boolean; 16 | removeAt(position: number): T | undefined; 17 | forEach(callback: (element: T) => void): void; 18 | get(position: number): T | undefined; 19 | } 20 | 21 | 22 | class Node { 23 | value: T; 24 | next: Node | null; 25 | 26 | constructor(value: T) { 27 | this.value = value; 28 | this.next = null; 29 | } 30 | } 31 | 32 | 33 | export class LinkedList extends BaseCollection implements LinkedList { 34 | private head: Node | null; 35 | private tail: Node | null; 36 | private length: number; 37 | 38 | constructor() { 39 | super(); 40 | this.head = null; 41 | this.tail = null; 42 | this.length = 0; 43 | } 44 | 45 | /** 46 | * Returns the first element in the list without removing it, or undefined if list is empty 47 | */ 48 | front(): T | undefined { 49 | return this.head?.value; 50 | } 51 | 52 | /** 53 | * Returns the last element in the list without removing it, or undefined if list is empty 54 | */ 55 | back(): T | undefined { 56 | return this.tail?.value; 57 | } 58 | 59 | /** 60 | * Adds an element to the end of the list 61 | */ 62 | pushBack(element: T): void { 63 | const newNode: Node = new Node(element); 64 | if (!this.head) { 65 | this.head = newNode; 66 | this.tail = newNode; 67 | } else { 68 | this.tail!.next = newNode; 69 | this.tail = newNode; 70 | } 71 | this.length++; 72 | } 73 | 74 | /** 75 | * Adds an element to the front of the list 76 | */ 77 | pushFront(element: T): void { 78 | const newNode: Node = new Node(element); 79 | if (!this.head) { 80 | this.head = newNode; 81 | this.tail = newNode; 82 | } else { 83 | newNode.next = this.head; 84 | this.head = newNode; 85 | } 86 | this.length++; 87 | } 88 | 89 | /** 90 | * Removes and returns the last element from the list, or undefined if list is empty 91 | */ 92 | popBack(): T | undefined { 93 | if (!this.head) { 94 | return undefined; 95 | } 96 | if (this.length === 1) { 97 | const value = this.head.value; 98 | this.head = null; 99 | this.tail = null; 100 | this.length = 0; 101 | return value; 102 | } 103 | let current: Node | null = this.head; 104 | while (current?.next !== this.tail) { 105 | current = current!.next; 106 | } 107 | const value = this.tail!.value; 108 | current.next = null; 109 | this.tail = current; 110 | this.length--; 111 | return value; 112 | } 113 | 114 | /** 115 | * Removes and returns the first element from the list, or undefined if list is empty 116 | */ 117 | popFront(): T | undefined { 118 | if (!this.head) { 119 | return undefined; 120 | } 121 | const value = this.head.value; 122 | this.head = this.head.next; 123 | this.length--; 124 | if (this.length === 0) { 125 | this.tail = null; 126 | } 127 | return value; 128 | } 129 | 130 | /** 131 | * Inserts an element at the specified position. Returns true if successful, false if position is invalid 132 | */ 133 | insert(element: T, position: number): boolean { 134 | if (position < 0 || position > this.length) { 135 | return false; 136 | } 137 | if (position === 0) { 138 | this.pushFront(element); 139 | return true; 140 | } 141 | if (position === this.length) { 142 | this.pushBack(element); 143 | return true; 144 | } 145 | const newNode: Node = new Node(element); 146 | let current: Node | null = this.head; 147 | let prev: Node | null = null; 148 | let index: number = 0; 149 | while (index < position) { 150 | prev = current; 151 | current = current!.next; 152 | index++; 153 | } 154 | prev!.next = newNode; 155 | newNode.next = current; 156 | this.length++; 157 | return true; 158 | } 159 | 160 | /** 161 | * Inserts an element before the first element that satisfies the condition. Returns true if successful, false if no matching element found 162 | */ 163 | insertBefore(element: T, condition: (element: T) => boolean): boolean { 164 | if (!this.head) { 165 | return false; 166 | } 167 | if (condition(this.head.value)) { 168 | this.pushFront(element); 169 | return true; 170 | } 171 | let current: Node | null = this.head; 172 | while (current?.next !== null) { 173 | if (condition(current.next.value)) { 174 | const newNode: Node = new Node(element); 175 | newNode.next = current.next; 176 | current.next = newNode; 177 | this.length++; 178 | return true; 179 | } 180 | current = current.next; 181 | } 182 | return false; 183 | } 184 | 185 | /** 186 | * Inserts an element after the first element that satisfies the condition. Returns true if successful, false if no matching element found 187 | */ 188 | insertAfter(element: T, condition: (element: T) => boolean): boolean { 189 | let current: Node | null = this.head; 190 | while (current !== null) { 191 | if (condition(current.value)) { 192 | const newNode: Node = new Node(element); 193 | newNode.next = current.next; 194 | current.next = newNode; 195 | if (current === this.tail) { 196 | this.tail = newNode; 197 | } 198 | this.length++; 199 | return true; 200 | } 201 | current = current.next; 202 | } 203 | return false; 204 | } 205 | 206 | /** 207 | * Removes the first element that satisfies the predicate. Returns true if an element was removed, false otherwise. 208 | */ 209 | removeIf(condition: (element: T) => boolean): boolean { 210 | let current: Node | null = this.head; 211 | let prev: Node | null = null; 212 | while (current !== null) { 213 | if (condition(current.value)) { 214 | if (prev === null) { 215 | this.head = current.next; 216 | } else { 217 | prev.next = current.next; 218 | } 219 | if (current === this.tail) { 220 | this.tail = prev; 221 | } 222 | this.length--; 223 | return true; 224 | } 225 | prev = current; 226 | current = current!.next; 227 | } 228 | return false; 229 | } 230 | 231 | /** 232 | * Removes and returns the element at the specified position. Returns undefined if position is invalid 233 | */ 234 | removeAt(position: number): T | undefined { 235 | if (position < 0 || position >= this.length) { 236 | return undefined; 237 | } 238 | let current: Node | null = this.head; 239 | if (position === 0) { 240 | this.head = current!.next; 241 | if (this.length === 1) { 242 | this.tail = null; 243 | } 244 | this.length--; 245 | return current!.value; 246 | } 247 | 248 | let prev: Node | null = null; 249 | let index: number = 0; 250 | while (index < position) { 251 | prev = current; 252 | current = current!.next; 253 | index++; 254 | } 255 | prev!.next = current!.next; 256 | if (position === this.length - 1) { 257 | this.tail = prev; 258 | } 259 | this.length--; 260 | return current!.value; 261 | } 262 | 263 | /** 264 | * Executes a provided function once for each element in the list 265 | */ 266 | forEach(callback: (element: T) => void): void { 267 | let current: Node | null = this.head; 268 | while (current !== null) { 269 | callback(current.value); 270 | current = current.next; 271 | } 272 | } 273 | 274 | /** 275 | * Returns the element at the specified position without removing it. Returns undefined if position is invalid 276 | */ 277 | get(position: number): T | undefined { 278 | if (position < 0 || position >= this.length) { 279 | return undefined; 280 | } 281 | // Optimize for front element 282 | if (position === 0) return this.head!.value; 283 | // Optimize for back element 284 | if (position === this.length - 1) return this.tail!.value; 285 | let current: Node | null = this.head; 286 | let index: number = 0; 287 | while (index < position) { 288 | current = current!.next; 289 | index++; 290 | } 291 | return current!.value; 292 | } 293 | 294 | /** 295 | * Returns true if the list is empty, false otherwise 296 | */ 297 | isEmpty(): boolean { 298 | return this.length === 0; 299 | } 300 | 301 | /** 302 | * Returns the number of elements in the list 303 | */ 304 | size(): number { 305 | return this.length; 306 | } 307 | 308 | /** 309 | * Removes all elements from the list 310 | */ 311 | clear(): void { 312 | this.head = null; 313 | this.tail = null; 314 | this.length = 0; 315 | } 316 | 317 | /** 318 | * Checks if two lists are equal. 319 | */ 320 | equals(other: LinkedList): boolean { 321 | if (!other || !(other instanceof LinkedList)) return false; 322 | if (this.size() !== other.size()) return false; 323 | let current: Node | null = this.head; 324 | let otherCurrent: Node | null = other.head; 325 | while (current !== null) { 326 | if (!Utils.equals(current.value, otherCurrent!.value)) return false; 327 | current = current.next; 328 | otherCurrent = otherCurrent!.next; 329 | } 330 | return true; 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /lib/map.ts: -------------------------------------------------------------------------------- 1 | import { Comparator } from '../types'; 2 | import { RedBlackTree } from './red-black-tree'; 3 | import { BaseCollection } from './base-collection'; 4 | 5 | 6 | export interface Map { 7 | insert(key: K, value: V): void; 8 | find(key: K): V | undefined; 9 | delete(key: K): void; 10 | remove(key: K): void; 11 | forEach(callback: (key: K, value: V) => void): void; 12 | } 13 | 14 | 15 | export class Map extends BaseCollection implements Map { 16 | private rbTree: RedBlackTree; 17 | 18 | constructor(comparator: Comparator = (a: K, b: K) => a < b) { 19 | super(); 20 | this.rbTree = new RedBlackTree(comparator); 21 | } 22 | 23 | insert(key: K, value: V): void { 24 | this.rbTree.insert(key, value); 25 | } 26 | 27 | find(key: K): V | undefined { 28 | return this.rbTree.find(key); 29 | } 30 | 31 | delete(key: K): void { 32 | this.rbTree.remove(key); 33 | } 34 | 35 | remove(key: K): void { 36 | this.rbTree.remove(key); 37 | } 38 | 39 | clear(): void { 40 | this.rbTree.clear(); 41 | } 42 | 43 | size(): number { 44 | return this.rbTree.size(); 45 | } 46 | 47 | isEmpty(): boolean { 48 | return this.rbTree.isEmpty(); 49 | } 50 | 51 | forEach(callback: (key: K, value: V) => void): void { 52 | this.rbTree.forEach(callback); 53 | } 54 | 55 | equals(other: Map): boolean { 56 | if (!other || !(other instanceof Map)) return false; 57 | return this.rbTree.equals(other.rbTree); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/matrix.ts: -------------------------------------------------------------------------------- 1 | import { BaseCollection } from './base-collection'; 2 | 3 | 4 | export interface Matrix { 5 | // Basic operations 6 | get(row: number, col: number): T | undefined; 7 | set(row: number, col: number, value: T): void; 8 | rows(): number; 9 | columns(): number; 10 | 11 | // Bulk operations 12 | fill(value: T): void; 13 | resize(rows: number, cols: number): void; 14 | 15 | isSquare(): boolean; 16 | isSymmetric(): boolean; 17 | 18 | // Row/Column operations 19 | getRow(row: number): T[]; 20 | getColumn(col: number): T[]; 21 | setRow(row: number, values: T[]): void; 22 | setColumn(col: number, values: T[]): void; 23 | swapRows(row1: number, row2: number): void; 24 | swapColumns(col1: number, col2: number): void; 25 | 26 | // Matrix transformations 27 | transpose(): Matrix; 28 | add(other: Matrix): Matrix; 29 | subtract(other: Matrix): Matrix; 30 | multiply(other: Matrix): Matrix; 31 | scalarMultiply(scalar: number): Matrix; 32 | 33 | // Element-wise operations 34 | map(fn: (value: T, row: number, col: number) => T): Matrix; 35 | forEach(fn: (value: T, row: number, col: number) => void): void; 36 | 37 | // Utility methods 38 | clone(): Matrix; 39 | toArray(): T[][]; 40 | equals(other: Matrix): boolean; 41 | 42 | // Submatrix operations 43 | submatrix(startRow: number, startCol: number, endRow: number, endCol: number): Matrix; 44 | insertMatrix(other: Matrix, startRow: number, startCol: number): void; 45 | 46 | // Diagonal operations 47 | getDiagonal(): T[]; 48 | setDiagonal(values: T[]): void; 49 | trace(): T; 50 | } 51 | 52 | 53 | export class Matrix extends BaseCollection implements Matrix { 54 | private data: T[][]; 55 | private numRows: number; 56 | private numCols: number; 57 | 58 | constructor(rows: number, cols: number) { 59 | super(); 60 | this.numRows = rows; 61 | this.numCols = cols; 62 | this.data = Array(rows).fill(null).map(() => Array(cols).fill(undefined)); 63 | } 64 | 65 | /** 66 | * Gets the value at the specified position. The value at [row,col] or undefined if out of bounds. 67 | */ 68 | get(row: number, col: number): T | undefined { 69 | if (!this.isValidPosition(row, col)) return undefined; 70 | return this.data[row][col]; 71 | } 72 | 73 | /** 74 | * Sets a value at the specified position. 75 | */ 76 | set(row: number, col: number, value: T): void { 77 | if (!this.isValidPosition(row, col)) return; 78 | this.data[row][col] = value; 79 | } 80 | 81 | /** 82 | * Returns the number of rows in the matrix. 83 | */ 84 | rows(): number { 85 | return this.numRows; 86 | } 87 | 88 | /** 89 | * Returns the number of columns in the matrix. 90 | */ 91 | columns(): number { 92 | return this.numCols; 93 | } 94 | 95 | /** 96 | * Fills the entire matrix with a value. 97 | */ 98 | fill(value: T): void { 99 | this.data = Array(this.numRows).fill(null).map(() => Array(this.numCols).fill(value)); 100 | } 101 | 102 | /** 103 | * Clears the matrix by setting all elements to undefined. 104 | */ 105 | clear(): void { 106 | this.data = Array(this.numRows).fill(null).map(() => Array(this.numCols).fill(undefined)); 107 | } 108 | 109 | /** 110 | * Checks if the matrix is empty (has zero dimensions). 111 | */ 112 | isEmpty(): boolean { 113 | return this.numRows === 0 || this.numCols === 0; 114 | } 115 | 116 | /** 117 | * Returns the total number of elements in the matrix. 118 | */ 119 | size(): number { 120 | return this.numRows * this.numCols; 121 | } 122 | 123 | /** 124 | * Checks if the matrix is square (same number of rows and columns). 125 | */ 126 | isSquare(): boolean { 127 | return this.numRows === this.numCols; 128 | } 129 | 130 | /** 131 | * Checks if the matrix is symmetric (equal to its transpose). 132 | */ 133 | isSymmetric(): boolean { 134 | if (!this.isSquare()) return false; 135 | for (let i = 0; i < this.numRows; i++) { 136 | for (let j = 0; j < i; j++) { 137 | if (this.data[i][j] !== this.data[j][i]) return false; 138 | } 139 | } 140 | return true; 141 | } 142 | 143 | /** 144 | * Creates a new matrix that is the transpose of this matrix. 145 | */ 146 | transpose(): Matrix { 147 | const result = new Matrix(this.numCols, this.numRows); 148 | for (let i = 0; i < this.numRows; i++) { 149 | for (let j = 0; j < this.numCols; j++) { 150 | result.set(j, i, this.data[i][j]); 151 | } 152 | } 153 | return result; 154 | } 155 | 156 | /** 157 | * Adds another matrix to this one. 158 | */ 159 | add(other: Matrix): Matrix { 160 | if (this.numRows !== other.rows() || this.numCols !== other.columns()) { 161 | throw new Error('Matrix dimensions must match for addition'); 162 | } 163 | const result = new Matrix(this.numRows, this.numCols); 164 | for (let i = 0; i < this.numRows; i++) { 165 | for (let j = 0; j < this.numCols; j++) { 166 | const sum = (this.data[i][j] as any) + (other.get(i, j) as any); 167 | result.set(i, j, sum as T); 168 | } 169 | } 170 | return result; 171 | } 172 | 173 | /** 174 | * Subtracts another matrix from this one. 175 | */ 176 | subtract(other: Matrix): Matrix { 177 | if (this.numRows !== other.rows() || this.numCols !== other.columns()) { 178 | throw new Error('Matrix dimensions must match for subtraction'); 179 | } 180 | const result = new Matrix(this.numRows, this.numCols); 181 | for (let i = 0; i < this.numRows; i++) { 182 | for (let j = 0; j < this.numCols; j++) { 183 | const diff = (this.data[i][j] as any) - (other.get(i, j) as any); 184 | result.set(i, j, diff as T); 185 | } 186 | } 187 | return result; 188 | } 189 | 190 | /** 191 | * Multiplies this matrix with another matrix. 192 | */ 193 | multiply(other: Matrix): Matrix { 194 | if (this.numCols !== other.rows()) { 195 | throw new Error('Matrix dimensions must be compatible for multiplication'); 196 | } 197 | const result = new Matrix(this.numRows, other.columns()); 198 | for (let i = 0; i < this.numRows; i++) { 199 | for (let j = 0; j < other.columns(); j++) { 200 | let sum: any = 0; 201 | for (let k = 0; k < this.numCols; k++) { 202 | sum += (this.data[i][k] as any) * (other.get(k, j) as any); 203 | } 204 | result.set(i, j, sum as T); 205 | } 206 | } 207 | return result; 208 | } 209 | 210 | /** 211 | * Multiplies the matrix by a scalar value. 212 | */ 213 | scalarMultiply(scalar: number): Matrix { 214 | const result = new Matrix(this.numRows, this.numCols); 215 | for (let i = 0; i < this.numRows; i++) { 216 | for (let j = 0; j < this.numCols; j++) { 217 | const product = (this.data[i][j] as any) * scalar; 218 | result.set(i, j, product as T); 219 | } 220 | } 221 | return result; 222 | } 223 | 224 | /** 225 | * Applies a function to each element and returns a new matrix. 226 | */ 227 | map(fn: (value: T, row: number, col: number) => T): Matrix { 228 | const result = new Matrix(this.numRows, this.numCols); 229 | for (let i = 0; i < this.numRows; i++) { 230 | for (let j = 0; j < this.numCols; j++) { 231 | result.set(i, j, fn(this.data[i][j], i, j)); 232 | } 233 | } 234 | return result; 235 | } 236 | 237 | /** 238 | * Executes a function for each element in the matrix. 239 | */ 240 | forEach(fn: (value: T, row: number, col: number) => void): void { 241 | for (let i = 0; i < this.numRows; i++) { 242 | for (let j = 0; j < this.numCols; j++) { 243 | fn(this.data[i][j], i, j); 244 | } 245 | } 246 | } 247 | 248 | /** 249 | * Creates a deep copy of this matrix. 250 | */ 251 | clone(): Matrix { 252 | const result = new Matrix(this.numRows, this.numCols); 253 | for (let i = 0; i < this.numRows; i++) { 254 | for (let j = 0; j < this.numCols; j++) { 255 | result.set(i, j, this.data[i][j]); 256 | } 257 | } 258 | return result; 259 | } 260 | 261 | /** 262 | * Converts the matrix to a 2D array. 263 | */ 264 | toArray(): T[][] { 265 | return this.data.map(row => [...row]); 266 | } 267 | 268 | /** 269 | * Checks if this matrix equals another matrix. 270 | */ 271 | equals(other: Matrix): boolean { 272 | if (this.numRows !== other.rows() || this.numCols !== other.columns()) { 273 | return false; 274 | } 275 | for (let i = 0; i < this.numRows; i++) { 276 | for (let j = 0; j < this.numCols; j++) { 277 | if (this.data[i][j] !== other.get(i, j)) return false; 278 | } 279 | } 280 | return true; 281 | } 282 | 283 | /** 284 | * Gets a copy of the specified row. 285 | */ 286 | getRow(row: number): T[] { 287 | if (row < 0 || row >= this.numRows) { 288 | return []; 289 | } 290 | return [...this.data[row]]; 291 | } 292 | 293 | /** 294 | * Gets a copy of the specified column. 295 | */ 296 | getColumn(col: number): T[] { 297 | if (col < 0 || col >= this.numCols) { 298 | return []; 299 | } 300 | return this.data.map(row => row[col]); 301 | } 302 | 303 | /** 304 | * Sets values for an entire row. 305 | */ 306 | setRow(row: number, values: T[]): void { 307 | if (row < 0 || row >= this.numRows || values.length !== this.numCols) { 308 | return; 309 | } 310 | this.data[row] = [...values]; 311 | } 312 | 313 | /** 314 | * Sets values for an entire column. 315 | */ 316 | setColumn(col: number, values: T[]): void { 317 | if (col < 0 || col >= this.numCols || values.length !== this.numRows) { 318 | return; 319 | } 320 | for (let i = 0; i < this.numRows; i++) { 321 | this.data[i][col] = values[i]; 322 | } 323 | } 324 | 325 | /** 326 | * Swaps two rows in the matrix. 327 | */ 328 | swapRows(row1: number, row2: number): void { 329 | if (!this.isValidPosition(row1, 0) || !this.isValidPosition(row2, 0)) return; 330 | [this.data[row1], this.data[row2]] = [this.data[row2], this.data[row1]]; 331 | } 332 | 333 | /** 334 | * Swaps two columns in the matrix. 335 | */ 336 | swapColumns(col1: number, col2: number): void { 337 | if (!this.isValidPosition(0, col1) || !this.isValidPosition(0, col2)) return; 338 | for (let i = 0; i < this.numRows; i++) { 339 | [this.data[i][col1], this.data[i][col2]] = [this.data[i][col2], this.data[i][col1]]; 340 | } 341 | } 342 | 343 | /** 344 | * Extracts a submatrix from this matrix. 345 | */ 346 | submatrix(startRow: number, startCol: number, endRow: number, endCol: number): Matrix { 347 | if (!this.isValidPosition(startRow, startCol) || !this.isValidPosition(endRow, endCol)) { 348 | throw new Error('Invalid submatrix bounds'); 349 | } 350 | const rows = endRow - startRow + 1; 351 | const cols = endCol - startCol + 1; 352 | const result = new Matrix(rows, cols); 353 | for (let i = 0; i < rows; i++) { 354 | for (let j = 0; j < cols; j++) { 355 | result.set(i, j, this.data[startRow + i][startCol + j]); 356 | } 357 | } 358 | return result; 359 | } 360 | 361 | /** 362 | * Inserts another matrix into this matrix at the specified position. 363 | */ 364 | insertMatrix(other: Matrix, startRow: number, startCol: number): void { 365 | if (!this.isValidPosition(startRow, startCol)) return; 366 | const maxRows = Math.min(other.rows(), this.numRows - startRow); 367 | const maxCols = Math.min(other.columns(), this.numCols - startCol); 368 | for (let i = 0; i < maxRows; i++) { 369 | for (let j = 0; j < maxCols; j++) { 370 | this.data[startRow + i][startCol + j] = other.get(i, j)!; 371 | } 372 | } 373 | } 374 | 375 | /** 376 | * Gets the diagonal elements of the matrix. 377 | */ 378 | getDiagonal(): T[] { 379 | const size = Math.min(this.numRows, this.numCols); 380 | const result: T[] = []; 381 | for (let i = 0; i < size; i++) { 382 | result.push(this.data[i][i]); 383 | } 384 | return result; 385 | } 386 | 387 | /** 388 | * Sets the diagonal elements of the matrix. 389 | */ 390 | setDiagonal(values: T[]): void { 391 | const size = Math.min(this.numRows, this.numCols, values.length); 392 | for (let i = 0; i < size; i++) { 393 | this.data[i][i] = values[i]; 394 | } 395 | } 396 | 397 | /** 398 | * Calculates the trace (sum of diagonal elements) of the matrix. 399 | */ 400 | trace(): T { 401 | if (!this.isSquare() || this.isEmpty()) { 402 | throw new Error('Trace is only defined for non-empty square matrices'); 403 | } 404 | return this.getDiagonal().reduce((sum: any, val: any) => sum + val); 405 | } 406 | 407 | /** 408 | * Resizes the matrix to new dimensions, preserving existing values where possible. 409 | */ 410 | resize(rows: number, cols: number): void { 411 | const newData: T[][] = Array(rows).fill(null).map(() => Array(cols).fill(undefined)); 412 | const minRows = Math.min(rows, this.numRows); 413 | const minCols = Math.min(cols, this.numCols); 414 | for (let i = 0; i < minRows; i++) { 415 | for (let j = 0; j < minCols; j++) { 416 | newData[i][j] = this.data[i][j]; 417 | } 418 | } 419 | this.data = newData; 420 | this.numRows = rows; 421 | this.numCols = cols; 422 | } 423 | 424 | private isValidPosition(row: number, col: number): boolean { 425 | return row >= 0 && row < this.numRows && col >= 0 && col < this.numCols; 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /lib/priority-queue.ts: -------------------------------------------------------------------------------- 1 | import { Heap } from './heap'; 2 | import { BaseCollection } from './base-collection'; 3 | import { Comparator } from '../types'; 4 | 5 | 6 | export interface PriorityQueue { 7 | push(element: T, priority: number): void; 8 | pop(): T | undefined; 9 | front(): T | undefined; 10 | } 11 | 12 | 13 | export class PriorityQueue extends BaseCollection implements PriorityQueue { 14 | private heap: Heap; 15 | 16 | constructor(comparator: Comparator = (a: T, b: T) => a > b) { 17 | super(); 18 | this.heap = new Heap(comparator); 19 | } 20 | 21 | /** 22 | * Adds an element with a priority to the queue. 23 | * Lower priority numbers have higher precedence. 24 | */ 25 | push(element: T): void { 26 | this.heap.push(element); 27 | } 28 | 29 | /** 30 | * Removes and returns the highest priority element from the queue, or undefined if queue is empty. 31 | */ 32 | pop(): T | undefined { 33 | return this.heap.pop(); 34 | } 35 | 36 | /** 37 | * Returns the highest priority element without removing it, or undefined if queue is empty. 38 | */ 39 | front(): T | undefined { 40 | return this.heap.top(); 41 | } 42 | 43 | /** 44 | * Checks if the queue is empty. Returns true if empty, false otherwise. 45 | */ 46 | isEmpty(): boolean { 47 | return this.heap.isEmpty(); 48 | } 49 | 50 | /** 51 | * Returns the number of elements in the queue. 52 | */ 53 | size(): number { 54 | return this.heap.size(); 55 | } 56 | 57 | /** 58 | * Removes all elements from the queue. 59 | */ 60 | clear(): void { 61 | this.heap.clear(); 62 | } 63 | 64 | /** 65 | * Checks if two priority queues are equal. 66 | */ 67 | equals(other: PriorityQueue): boolean { 68 | if (!other || !(other instanceof PriorityQueue)) return false; 69 | return this.heap.equals(other.heap); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/queue.ts: -------------------------------------------------------------------------------- 1 | import { LinkedList } from './linked-list'; 2 | import { BaseCollection } from './base-collection'; 3 | 4 | 5 | export interface Queue { 6 | push(element: T): void; 7 | pop(): T | undefined; 8 | front(): T | undefined; 9 | } 10 | 11 | 12 | export class Queue extends BaseCollection implements Queue { 13 | private items: LinkedList; 14 | 15 | constructor() { 16 | super(); 17 | this.items = new LinkedList(); 18 | } 19 | 20 | /** 21 | * Adds an element to the back of the queue. 22 | */ 23 | push(element: T): void { 24 | this.items.pushBack(element); 25 | } 26 | 27 | /** 28 | * Removes and returns the front element from the queue, or undefined if queue is empty. 29 | */ 30 | pop(): T | undefined { 31 | return this.items.popFront(); 32 | } 33 | 34 | /** 35 | * Returns the front element of the queue without removing it, or undefined if queue is empty. 36 | */ 37 | front(): T | undefined { 38 | return this.items.get(0); 39 | } 40 | 41 | /** 42 | * Checks if the queue is empty. Returns true if the queue is empty, false otherwise. 43 | */ 44 | isEmpty(): boolean { 45 | return this.items.isEmpty(); 46 | } 47 | 48 | /** 49 | * Returns the number of elements in the queue. 50 | */ 51 | size(): number { 52 | return this.items.size(); 53 | } 54 | 55 | /** 56 | * Removes all elements from the queue. 57 | */ 58 | clear(): void { 59 | this.items.clear(); 60 | } 61 | 62 | /** 63 | * Checks if two queues are equal. 64 | */ 65 | equals(other: Queue): boolean { 66 | if (!other || !(other instanceof Queue)) return false; 67 | return this.items.equals(other.items); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/red-black-tree.ts: -------------------------------------------------------------------------------- 1 | import { Comparator } from '../types'; 2 | import { BaseCollection } from './base-collection'; 3 | import { Utils } from './utils'; 4 | 5 | 6 | export interface RedBlackTree { 7 | insert(key: K, value: V): void; 8 | remove(key: K): void; 9 | find(key: K): V | undefined; 10 | min(): V | undefined; 11 | max(): V | undefined; 12 | forEach(callback: (key: K, value: V) => void): void; 13 | } 14 | 15 | 16 | enum Color { 17 | RED, 18 | BLACK 19 | } 20 | 21 | 22 | class RBNode { 23 | key: K; 24 | value: V; 25 | color: Color; 26 | left: RBNode | null; 27 | right: RBNode | null; 28 | parent: RBNode | null; 29 | 30 | constructor(key: K, value: V) { 31 | this.key = key; 32 | this.value = value; 33 | this.color = Color.RED; 34 | this.left = null; 35 | this.right = null; 36 | this.parent = null; 37 | } 38 | } 39 | 40 | 41 | export class RedBlackTree extends BaseCollection implements RedBlackTree { 42 | private root: RBNode | null; 43 | private nodeCount: number; 44 | private comparator: Comparator; 45 | 46 | constructor(comparator: Comparator = (a: K, b: K) => a < b) { 47 | super(); 48 | this.root = null; 49 | this.nodeCount = 0; 50 | this.comparator = comparator; 51 | } 52 | 53 | private rotateLeft(node: RBNode): void { 54 | const rightChild: RBNode | null = node.right!; 55 | node.right = rightChild.left; 56 | if (rightChild.left !== null) { 57 | rightChild.left.parent = node; 58 | } 59 | rightChild.parent = node.parent; 60 | if (node.parent === null) { 61 | this.root = rightChild; 62 | } else if (node === node.parent.left) { 63 | node.parent.left = rightChild; 64 | } else { 65 | node.parent.right = rightChild; 66 | } 67 | rightChild.left = node; 68 | node.parent = rightChild; 69 | } 70 | 71 | private rotateRight(node: RBNode): void { 72 | const leftChild: RBNode | null = node.left!; 73 | node.left = leftChild.right; 74 | if (leftChild.right !== null) { 75 | leftChild.right.parent = node; 76 | } 77 | leftChild.parent = node.parent; 78 | if (node.parent === null) { 79 | this.root = leftChild; 80 | } else if (node === node.parent.right) { 81 | node.parent.right = leftChild; 82 | } else { 83 | node.parent.left = leftChild; 84 | } 85 | leftChild.right = node; 86 | node.parent = leftChild; 87 | } 88 | 89 | private fixInsert(node: RBNode): void { 90 | while (node !== this.root && node.parent?.color === Color.RED) { 91 | if (node.parent === node.parent.parent?.left) { 92 | const uncle: RBNode | null = node.parent.parent.right; 93 | 94 | if (uncle?.color === Color.RED) { 95 | node.parent.color = Color.BLACK; 96 | uncle.color = Color.BLACK; 97 | node.parent.parent.color = Color.RED; 98 | node = node.parent.parent; 99 | } else { 100 | if (node === node.parent.right) { 101 | node = node.parent; 102 | this.rotateLeft(node); 103 | } 104 | node.parent!.color = Color.BLACK; 105 | node.parent!.parent!.color = Color.RED; 106 | this.rotateRight(node.parent!.parent!); 107 | } 108 | } else { 109 | const uncle: RBNode | null | undefined = node.parent.parent?.left; 110 | if (uncle?.color === Color.RED) { 111 | node.parent.color = Color.BLACK; 112 | uncle.color = Color.BLACK; 113 | node.parent.parent!.color = Color.RED; 114 | node = node.parent.parent!; 115 | } else { 116 | if (node === node.parent.left) { 117 | node = node.parent; 118 | this.rotateRight(node); 119 | } 120 | node.parent!.color = Color.BLACK; 121 | node.parent!.parent!.color = Color.RED; 122 | this.rotateLeft(node.parent!.parent!); 123 | } 124 | } 125 | } 126 | this.root!.color = Color.BLACK; 127 | } 128 | 129 | insert(key: K, value: V): void { 130 | const newNode: RBNode = new RBNode(key, value); 131 | let parent: RBNode | null = null; 132 | let current: RBNode | null = this.root; 133 | while (current !== null) { 134 | parent = current; 135 | if (this.comparator(key, current.key)) { 136 | current = current.left; 137 | } else if (this.comparator(current.key, key)) { 138 | current = current.right; 139 | } else { 140 | // Key already exists, update value 141 | current.value = value; 142 | return; 143 | } 144 | } 145 | 146 | newNode.parent = parent; 147 | 148 | if (parent === null) { 149 | this.root = newNode; 150 | } else if (this.comparator(key, parent.key)) { 151 | parent.left = newNode; 152 | } else { 153 | parent.right = newNode; 154 | } 155 | 156 | this.nodeCount++; 157 | this.fixInsert(newNode); 158 | } 159 | 160 | private findNode(key: K): RBNode | null { 161 | let current: RBNode | null = this.root; 162 | while (current !== null) { 163 | if (this.isEqual(key, current.key)) { 164 | return current; 165 | } 166 | current = this.comparator(key, current.key) ? current.left : current.right; 167 | } 168 | return null; 169 | } 170 | 171 | find(key: K): V | undefined { 172 | const node: RBNode | null = this.findNode(key); 173 | return node ? node.value : undefined; 174 | } 175 | 176 | private findMinNode(node: RBNode): RBNode { 177 | let current: RBNode | null = node; 178 | while (current?.left !== null) { 179 | current = current.left; 180 | } 181 | return current; 182 | } 183 | 184 | min(): V | undefined { 185 | if (!this.root) return undefined; 186 | return this.findMinNode(this.root).value; 187 | } 188 | 189 | private findMaxNode(node: RBNode): RBNode { 190 | let current: RBNode | null = node; 191 | while (current?.right !== null) { 192 | current = current.right; 193 | } 194 | return current; 195 | } 196 | 197 | max(): V | undefined { 198 | if (!this.root) return undefined; 199 | return this.findMaxNode(this.root).value; 200 | } 201 | 202 | remove(key: K): void { 203 | const node: RBNode | null = this.findNode(key); 204 | if (node) { 205 | this.nodeCount--; 206 | this.deleteNode(node); 207 | } 208 | } 209 | 210 | private deleteNode(node: RBNode): void { 211 | let x: RBNode | null; 212 | let y: RBNode | null = node; 213 | let originalColor: Color = y.color; 214 | 215 | if (node.left === null) { 216 | x = node.right; 217 | this.transplant(node, node.right); 218 | } else if (node.right === null) { 219 | x = node.left; 220 | this.transplant(node, node.left); 221 | } else { 222 | y = this.findMinNode(node.right); 223 | originalColor = y.color; 224 | x = y.right; 225 | 226 | if (y.parent === node) { 227 | if (x) x.parent = y; 228 | } else { 229 | this.transplant(y, y.right); 230 | y.right = node.right; 231 | y.right.parent = y; 232 | } 233 | 234 | this.transplant(node, y); 235 | y.left = node.left; 236 | y.left.parent = y; 237 | y.color = node.color; 238 | } 239 | 240 | if (originalColor === Color.BLACK && x) { 241 | this.fixDelete(x); 242 | } 243 | } 244 | 245 | private transplant(u: RBNode, v: RBNode | null): void { 246 | if (u.parent === null) { 247 | this.root = v; 248 | } else if (u === u.parent.left) { 249 | u.parent.left = v; 250 | } else { 251 | u.parent.right = v; 252 | } 253 | if (v !== null) { 254 | v.parent = u.parent; 255 | } 256 | } 257 | 258 | private fixDelete(x: RBNode): void { 259 | while (x !== this.root && x.color === Color.BLACK) { 260 | if (x === x.parent!.left) { 261 | let w = x.parent!.right!; 262 | 263 | if (w.color === Color.RED) { 264 | w.color = Color.BLACK; 265 | x.parent!.color = Color.RED; 266 | this.rotateLeft(x.parent!); 267 | w = x.parent!.right!; 268 | } 269 | 270 | if ((!w.left || w.left.color === Color.BLACK) && 271 | (!w.right || w.right.color === Color.BLACK)) { 272 | w.color = Color.RED; 273 | x = x.parent!; 274 | } else { 275 | if (!w.right || w.right.color === Color.BLACK) { 276 | if (w.left) w.left.color = Color.BLACK; 277 | w.color = Color.RED; 278 | this.rotateRight(w); 279 | w = x.parent!.right!; 280 | } 281 | 282 | w.color = x.parent!.color; 283 | x.parent!.color = Color.BLACK; 284 | if (w.right) w.right.color = Color.BLACK; 285 | this.rotateLeft(x.parent!); 286 | x = this.root!; 287 | } 288 | } else { 289 | let w = x.parent!.left!; 290 | 291 | if (w.color === Color.RED) { 292 | w.color = Color.BLACK; 293 | x.parent!.color = Color.RED; 294 | this.rotateRight(x.parent!); 295 | w = x.parent!.left!; 296 | } 297 | 298 | if ((!w.right || w.right.color === Color.BLACK) && 299 | (!w.left || w.left.color === Color.BLACK)) { 300 | w.color = Color.RED; 301 | x = x.parent!; 302 | } else { 303 | if (!w.left || w.left.color === Color.BLACK) { 304 | if (w.right) w.right.color = Color.BLACK; 305 | w.color = Color.RED; 306 | this.rotateLeft(w); 307 | w = x.parent!.left!; 308 | } 309 | 310 | w.color = x.parent!.color; 311 | x.parent!.color = Color.BLACK; 312 | if (w.left) w.left.color = Color.BLACK; 313 | this.rotateRight(x.parent!); 314 | x = this.root!; 315 | } 316 | } 317 | } 318 | x.color = Color.BLACK; 319 | } 320 | 321 | isEmpty(): boolean { 322 | return this.root === null; 323 | } 324 | 325 | size(): number { 326 | return this.nodeCount; 327 | } 328 | 329 | clear(): void { 330 | this.root = null; 331 | this.nodeCount = 0; 332 | } 333 | 334 | private isEqual(a: K, b: K): boolean { 335 | // Two values are equal if neither is less than the other 336 | return !this.comparator(a, b) && !this.comparator(b, a); 337 | } 338 | 339 | private inorderTraversal(node: RBNode | null, callback: (key: K, value: V) => void): void { 340 | if (node !== null) { 341 | this.inorderTraversal(node.left, callback); 342 | callback(node.key, node.value); 343 | this.inorderTraversal(node.right, callback); 344 | } 345 | } 346 | 347 | forEach(callback: (key: K, value: V) => void): void { 348 | this.inorderTraversal(this.root, callback); 349 | } 350 | 351 | /** 352 | * Checks if two red-black trees are equal. 353 | * Returns false if comparing with null/undefined. 354 | */ 355 | equals(other: RedBlackTree): boolean { 356 | if (!other || !(other instanceof RedBlackTree)) return false; 357 | if (this.size() !== other.size()) return false; 358 | return this.areTreesEqual(this.root, other.root); 359 | } 360 | 361 | private areTreesEqual(node1: RBNode | null, node2: RBNode | null): boolean { 362 | if (node1 === null && node2 === null) return true; 363 | if (node1 === null || node2 === null) return false; 364 | if (!this.isEqual(node1.key, node2.key) || 365 | !Utils.equals(node1.value, node2.value)) return false; 366 | return this.areTreesEqual(node1.left, node2.left) && this.areTreesEqual(node1.right, node2.right); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /lib/set.ts: -------------------------------------------------------------------------------- 1 | import { BaseCollection } from './base-collection'; 2 | import { BinarySearchTree } from './binary-search-tree'; 3 | import { Comparator } from '../types'; 4 | 5 | 6 | export interface Set { 7 | insert(element: T): void; 8 | insertList(elements: T[]): void; 9 | remove(element: T): void; 10 | find(element: T): boolean; 11 | has(element: T): boolean; 12 | forEach(callback: (element: T) => void): void; 13 | } 14 | 15 | 16 | export class Set extends BaseCollection implements Set { 17 | private tree: BinarySearchTree; 18 | 19 | constructor(comparator: Comparator = (a: T, b: T) => a < b) { 20 | super(); 21 | this.tree = new BinarySearchTree(comparator); 22 | } 23 | 24 | /** 25 | * Adds a value to the set if it's not already present. 26 | */ 27 | insert(value: T): void { 28 | this.tree.insert(value); 29 | } 30 | 31 | /** 32 | * Adds multiple values to the set if they're not already present. 33 | */ 34 | insertList(values: T[]): void { 35 | for (const value of values) { 36 | this.tree.insert(value); 37 | } 38 | } 39 | 40 | /** 41 | * Checks if a value exists in the set. 42 | */ 43 | find(value: T): boolean { 44 | return this.tree.find(value); 45 | } 46 | 47 | has(value: T): boolean { 48 | return this.find(value); 49 | } 50 | 51 | /** 52 | * Removes a value from the set. 53 | */ 54 | remove(value: T): void { 55 | this.tree.remove(value); 56 | } 57 | 58 | forEach(callback: (element: T) => void): void { 59 | this.tree.forEach(callback); 60 | } 61 | 62 | /** 63 | * Removes all elements from the set. 64 | */ 65 | clear(): void { 66 | this.tree = new BinarySearchTree(); 67 | } 68 | 69 | /** 70 | * Returns true if the set contains no elements. 71 | */ 72 | isEmpty(): boolean { 73 | return this.tree.size() === 0; 74 | } 75 | 76 | /** 77 | * Returns the number of elements in the set. 78 | */ 79 | size(): number { 80 | return this.tree.size(); 81 | } 82 | 83 | /** 84 | * Checks if two sets are equal. 85 | */ 86 | equals(other: Set): boolean { 87 | if (!other || !(other instanceof Set)) return false; 88 | return this.tree.equals(other.tree); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/stack.ts: -------------------------------------------------------------------------------- 1 | import { BaseCollection } from "./base-collection"; 2 | import { LinkedList } from "./linked-list"; 3 | 4 | 5 | export interface Stack { 6 | push(element: T): void; 7 | pop(): T | undefined; 8 | top(): T | undefined; 9 | } 10 | 11 | 12 | export class Stack extends BaseCollection implements Stack { 13 | private llData: LinkedList; 14 | 15 | constructor() { 16 | super(); 17 | this.llData = new LinkedList(); 18 | } 19 | 20 | /** 21 | * Adds an element to the top of the stack. 22 | */ 23 | push(element: T): void { 24 | this.llData.pushFront(element); 25 | } 26 | 27 | /** 28 | * Removes and returns the top element from the stack, or undefined if stack is empty. 29 | */ 30 | pop(): T | undefined { 31 | return this.llData.popFront(); 32 | } 33 | 34 | /** 35 | * Returns the top element of the stack without removing it, or undefined if stack is empty. 36 | */ 37 | top(): T | undefined { 38 | return this.llData.front(); 39 | } 40 | 41 | /** 42 | * Checks if the stack is empty. Returns true if the stack is empty, false otherwise. 43 | */ 44 | isEmpty(): boolean { 45 | return this.llData.isEmpty(); 46 | } 47 | 48 | /** 49 | * Returns the number of elements in the stack. The size of the stack 50 | */ 51 | size(): number { 52 | return this.llData.size(); 53 | } 54 | 55 | /** 56 | * Removes all elements from the stack. 57 | */ 58 | clear(): void { 59 | this.llData.clear(); 60 | } 61 | 62 | /** 63 | * Checks if two stacks are equal. 64 | */ 65 | equals(other: Stack): boolean { 66 | if (!other || !(other instanceof Stack)) return false; 67 | return this.llData.equals(other.llData); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | export class Utils { 2 | static equals(key1: K, key2: K): boolean { 3 | if (key1 === key2) return true; 4 | if (key1 == null || key2 == null) return key1 === key2; 5 | 6 | // Check for custom equals method 7 | if (typeof (key1 as any).equals === 'function') { 8 | return (key1 as any).equals(key2); 9 | } 10 | 11 | // Handle NaN values 12 | if (typeof key1 === 'number' && typeof key2 === 'number') { 13 | // NaN is the only value that isn't equal to itself. 14 | return key1 === key2 || (isNaN(key1 as number) && isNaN(key2 as number)); 15 | } 16 | 17 | // Handle special object types 18 | if (key1 instanceof Date) { 19 | return key2 instanceof Date && 20 | (key1.getTime() === key2.getTime() || (isNaN(key1.getTime()) && isNaN(key2.getTime()))); 21 | } 22 | 23 | if (key1 instanceof RegExp) { 24 | return key2 instanceof RegExp && key1.toString() === key2.toString(); 25 | } 26 | 27 | // Handle plain objects 28 | if (typeof key1 === 'object' && typeof key2 === 'object') { 29 | const keys1 = Object.keys(key1); 30 | const keys2 = Object.keys(key2); 31 | return keys1.length === keys2.length && 32 | keys1.every(k => k in key2 && Utils.equals((key1 as any)[k], (key2 as any)[k])); 33 | } 34 | 35 | if (Array.isArray(key1)) { 36 | if (!Array.isArray(key2) || key1.length !== key2.length) return false; 37 | for (let i = 0; i < key1.length; i++) { 38 | if (!Utils.equals(key1[i], key2[i])) return false; 39 | } 40 | return true; 41 | } 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-ds-lib", 3 | "version": "0.4.1", 4 | "description": "A collection of TypeScript data structure implementations", 5 | "author": "Artiom Baloian ", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "README.md", 12 | "LICENSE" 13 | ], 14 | "scripts": { 15 | "build": "rm -rf dist && tsc", 16 | "prepare": "npm run build", 17 | "test": "rm -rf dist && tsc && jest", 18 | "benchmark": "ts-node benchmarks/run.ts" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/baloian/typescript-ds.git" 23 | }, 24 | "devDependencies": { 25 | "ts-node": "^10.9.2", 26 | "@types/jest": "^29.5.14", 27 | "@types/node": "^22.15.21", 28 | "ts-jest": "^29.2.5", 29 | "typescript": "^5.8.3" 30 | }, 31 | "keywords": [ 32 | "typescript", 33 | "data-structures", 34 | "algorithms", 35 | "binary-search-tree", 36 | "linked-list", 37 | "hash-table", 38 | "queue", 39 | "stack", 40 | "deque", 41 | "priority-queue", 42 | "red-black-tree", 43 | "set", 44 | "map", 45 | "matrix" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /tests/binary-search-tree.test.ts: -------------------------------------------------------------------------------- 1 | import { BinarySearchTree } from '../lib/binary-search-tree'; 2 | 3 | describe('BinarySearchTree', () => { 4 | let bst: BinarySearchTree; 5 | 6 | beforeEach(() => { 7 | bst = new BinarySearchTree(); 8 | }); 9 | 10 | describe('Basic Operations', () => { 11 | test('should create empty tree', () => { 12 | expect(bst.isEmpty()).toBe(true); 13 | expect(bst.size()).toBe(0); 14 | }); 15 | 16 | test('should insert and search values', () => { 17 | bst.insert(5); 18 | bst.insert(3); 19 | bst.insert(7); 20 | 21 | expect(bst.find(5)).toBe(true); 22 | expect(bst.find(3)).toBe(true); 23 | expect(bst.find(7)).toBe(true); 24 | expect(bst.find(1)).toBe(false); 25 | expect(bst.size()).toBe(3); 26 | expect(bst.isEmpty()).toBe(false); 27 | }); 28 | 29 | test('should clear all nodes from tree', () => { 30 | bst.insert(5); 31 | bst.insert(3); 32 | bst.insert(7); 33 | 34 | bst.clear(); 35 | expect(bst.isEmpty()).toBe(true); 36 | expect(bst.find(5)).toBe(false); 37 | expect(bst.min()).toBeUndefined(); 38 | expect(bst.max()).toBeUndefined(); 39 | expect(bst.size()).toBe(0); 40 | }); 41 | }); 42 | 43 | describe('Min/Max Operations', () => { 44 | test('should find min and max values', () => { 45 | bst.insert(5); 46 | bst.insert(3); 47 | bst.insert(7); 48 | bst.insert(1); 49 | bst.insert(9); 50 | 51 | expect(bst.min()).toBe(1); 52 | expect(bst.max()).toBe(9); 53 | }); 54 | 55 | test('should return undefined min/max for empty tree', () => { 56 | expect(bst.min()).toBeUndefined(); 57 | expect(bst.max()).toBeUndefined(); 58 | }); 59 | 60 | test('should handle min/max with unbalanced tree', () => { 61 | bst.insert(10); 62 | bst.insert(8); 63 | bst.insert(6); 64 | bst.insert(4); 65 | bst.insert(2); 66 | 67 | expect(bst.min()).toBe(2); 68 | expect(bst.max()).toBe(10); 69 | 70 | bst.remove(2); 71 | expect(bst.min()).toBe(4); 72 | 73 | bst.remove(10); 74 | expect(bst.max()).toBe(8); 75 | }); 76 | }); 77 | 78 | describe('Remove Operations', () => { 79 | test('should remove leaf nodes', () => { 80 | bst.insert(5); 81 | bst.insert(3); 82 | bst.insert(7); 83 | 84 | bst.remove(3); 85 | expect(bst.find(3)).toBe(false); 86 | expect(bst.find(5)).toBe(true); 87 | expect(bst.find(7)).toBe(true); 88 | expect(bst.size()).toBe(2); 89 | }); 90 | 91 | test('should remove nodes with one child', () => { 92 | bst.insert(5); 93 | bst.insert(3); 94 | bst.insert(2); 95 | 96 | bst.remove(3); 97 | expect(bst.find(3)).toBe(false); 98 | expect(bst.find(5)).toBe(true); 99 | expect(bst.find(2)).toBe(true); 100 | expect(bst.size()).toBe(2); 101 | }); 102 | 103 | test('should remove nodes with two children', () => { 104 | bst.insert(5); 105 | bst.insert(3); 106 | bst.insert(7); 107 | bst.insert(6); 108 | bst.insert(8); 109 | 110 | bst.remove(7); 111 | expect(bst.find(7)).toBe(false); 112 | expect(bst.find(5)).toBe(true); 113 | expect(bst.find(8)).toBe(true); 114 | expect(bst.find(6)).toBe(true); 115 | expect(bst.size()).toBe(4); 116 | }); 117 | 118 | test('should handle removing root node', () => { 119 | bst.insert(5); 120 | bst.insert(3); 121 | bst.insert(7); 122 | 123 | bst.remove(5); 124 | expect(bst.find(5)).toBe(false); 125 | expect(bst.find(3)).toBe(true); 126 | expect(bst.find(7)).toBe(true); 127 | expect(bst.size()).toBe(2); 128 | }); 129 | 130 | test('should handle removing non-existent values', () => { 131 | bst.insert(5); 132 | bst.insert(3); 133 | 134 | bst.remove(7); 135 | expect(bst.size()).toBe(2); 136 | expect(bst.find(5)).toBe(true); 137 | expect(bst.find(3)).toBe(true); 138 | }); 139 | 140 | test('should handle complex removal scenario', () => { 141 | // Create a more complex tree structure 142 | bst.insert(10); 143 | bst.insert(5); 144 | bst.insert(15); 145 | bst.insert(3); 146 | bst.insert(7); 147 | bst.insert(13); 148 | bst.insert(17); 149 | bst.insert(1); 150 | bst.insert(4); 151 | bst.insert(6); 152 | bst.insert(8); 153 | 154 | // Remove nodes in specific order to test different cases 155 | bst.remove(5); // Remove node with two children 156 | expect(bst.size()).toBe(10); 157 | 158 | const result: number[] = []; 159 | bst.forEach((value) => result.push(value)); 160 | expect(result).toEqual([1, 3, 4, 6, 7, 8, 10, 13, 15, 17]); 161 | 162 | bst.remove(15); // Remove another node with two children 163 | bst.remove(1); // Remove leaf node 164 | bst.remove(17); // Remove node with no left child 165 | bst.remove(3); // Remove node with one child 166 | 167 | const finalResult: number[] = []; 168 | bst.forEach((value) => finalResult.push(value)); 169 | expect(finalResult).toEqual([4, 6, 7, 8, 10, 13]); 170 | expect(bst.size()).toBe(6); 171 | }); 172 | }); 173 | 174 | describe('Special Cases', () => { 175 | test('should not allow duplicate values', () => { 176 | const bst = new BinarySearchTree(); 177 | bst.insert(5); 178 | bst.insert(5); // Should not insert 179 | bst.insert(5); // Should not insert 180 | 181 | expect(bst.size()).toBe(1); 182 | bst.remove(5); 183 | expect(bst.size()).toBe(0); 184 | expect(bst.find(5)).toBe(false); 185 | }); 186 | 187 | test('should work with custom comparator', () => { 188 | const reverseBst = new BinarySearchTree((a, b) => a > b); 189 | reverseBst.insert(5); 190 | reverseBst.insert(3); 191 | reverseBst.insert(7); 192 | 193 | expect(reverseBst.min()).toBe(7); 194 | expect(reverseBst.max()).toBe(3); 195 | }); 196 | 197 | test('should handle complex operations with custom comparator', () => { 198 | const customBst = new BinarySearchTree((a, b) => a.length < b.length || (a.length === b.length && a < b)); 199 | 200 | customBst.insert("cat"); 201 | customBst.insert("dog"); 202 | customBst.insert("elephant"); 203 | customBst.insert("fox"); 204 | customBst.insert("butterfly"); 205 | 206 | expect(customBst.min()).toBe("cat"); 207 | expect(customBst.max()).toBe("butterfly"); 208 | 209 | const result: string[] = []; 210 | customBst.forEach((value) => result.push(value)); 211 | expect(result).toEqual(["cat", "dog", "fox", "elephant", "butterfly"]); 212 | }); 213 | }); 214 | 215 | describe('Equals Operations', () => { 216 | test('should consider empty trees equal', () => { 217 | const bst1 = new BinarySearchTree(); 218 | const bst2 = new BinarySearchTree(); 219 | expect(bst1.equals(bst2)).toBe(true); 220 | }); 221 | 222 | test('should consider identical trees equal', () => { 223 | const bst1 = new BinarySearchTree(); 224 | const bst2 = new BinarySearchTree(); 225 | 226 | [5, 3, 7, 1, 9].forEach(val => { 227 | bst1.insert(val); 228 | bst2.insert(val); 229 | }); 230 | 231 | expect(bst1.equals(bst2)).toBe(true); 232 | }); 233 | 234 | test('should consider trees with different values unequal', () => { 235 | const bst1 = new BinarySearchTree(); 236 | const bst2 = new BinarySearchTree(); 237 | 238 | bst1.insert(5); 239 | bst1.insert(3); 240 | bst1.insert(7); 241 | 242 | bst2.insert(5); 243 | bst2.insert(3); 244 | bst2.insert(8); 245 | 246 | expect(bst1.equals(bst2)).toBe(false); 247 | }); 248 | 249 | test('should consider trees with different sizes unequal', () => { 250 | const bst1 = new BinarySearchTree(); 251 | const bst2 = new BinarySearchTree(); 252 | 253 | bst1.insert(5); 254 | bst1.insert(3); 255 | bst1.insert(7); 256 | 257 | bst2.insert(5); 258 | bst2.insert(3); 259 | 260 | expect(bst1.equals(bst2)).toBe(false); 261 | }); 262 | 263 | test('should work with custom comparator', () => { 264 | const bst1 = new BinarySearchTree((a, b) => a.length < b.length); 265 | const bst2 = new BinarySearchTree((a, b) => a.length < b.length); 266 | 267 | ["cat", "elephant", "dog"].forEach(val => { 268 | bst1.insert(val); 269 | bst2.insert(val); 270 | }); 271 | 272 | expect(bst1.equals(bst2)).toBe(true); 273 | 274 | bst2.insert("butterfly"); 275 | expect(bst1.equals(bst2)).toBe(false); 276 | }); 277 | 278 | test('should handle complex tree structures', () => { 279 | const bst1 = new BinarySearchTree(); 280 | const bst2 = new BinarySearchTree(); 281 | 282 | // Create identical complex structures 283 | [10, 5, 15, 3, 7, 13, 17, 1, 4, 6, 8].forEach(val => { 284 | bst1.insert(val); 285 | bst2.insert(val); 286 | }); 287 | 288 | expect(bst1.equals(bst2)).toBe(true); 289 | 290 | // Modify one tree 291 | bst1.remove(7); 292 | expect(bst1.equals(bst2)).toBe(false); 293 | 294 | // Make the same modification to the other tree 295 | bst2.remove(7); 296 | expect(bst1.equals(bst2)).toBe(true); 297 | }); 298 | }); 299 | 300 | describe('Traversal Operations', () => { 301 | test('should traverse tree in-order', () => { 302 | bst.insert(5); 303 | bst.insert(3); 304 | bst.insert(7); 305 | bst.insert(1); 306 | bst.insert(9); 307 | 308 | const result: number[] = []; 309 | bst.forEach((value) => result.push(value)); 310 | expect(result).toEqual([1, 3, 5, 7, 9]); 311 | }); 312 | 313 | test('should traverse tree pre-order', () => { 314 | bst.insert(5); 315 | bst.insert(3); 316 | bst.insert(7); 317 | bst.insert(1); 318 | bst.insert(9); 319 | 320 | const result: number[] = []; 321 | bst.forEach((value) => result.push(value), 'preorder'); 322 | expect(result).toEqual([5, 3, 1, 7, 9]); 323 | }); 324 | 325 | test('should traverse tree post-order', () => { 326 | bst.insert(5); 327 | bst.insert(3); 328 | bst.insert(7); 329 | bst.insert(1); 330 | bst.insert(9); 331 | 332 | const result: number[] = []; 333 | bst.forEach((value) => result.push(value), 'postorder'); 334 | expect(result).toEqual([1, 3, 9, 7, 5]); 335 | }); 336 | 337 | test('should handle forEach on empty tree', () => { 338 | const result: number[] = []; 339 | bst.forEach((value) => result.push(value)); 340 | expect(result).toEqual([]); 341 | }); 342 | 343 | test('should handle complex traversal scenarios', () => { 344 | // Create an unbalanced tree 345 | bst.insert(10); 346 | bst.insert(5); 347 | bst.insert(15); 348 | bst.insert(3); 349 | bst.insert(7); 350 | bst.insert(13); 351 | bst.insert(17); 352 | bst.insert(1); 353 | 354 | const inOrder: number[] = []; 355 | const preOrder: number[] = []; 356 | const postOrder: number[] = []; 357 | 358 | bst.forEach((value) => inOrder.push(value), 'inorder'); 359 | bst.forEach((value) => preOrder.push(value), 'preorder'); 360 | bst.forEach((value) => postOrder.push(value), 'postorder'); 361 | 362 | expect(inOrder).toEqual([1, 3, 5, 7, 10, 13, 15, 17]); 363 | expect(preOrder).toEqual([10, 5, 3, 1, 7, 15, 13, 17]); 364 | expect(postOrder).toEqual([1, 3, 7, 5, 13, 17, 15, 10]); 365 | 366 | // Remove some nodes and verify traversal still works 367 | bst.remove(5); 368 | bst.remove(15); 369 | 370 | const newInOrder: number[] = []; 371 | bst.forEach((value) => newInOrder.push(value)); 372 | expect(newInOrder).toEqual([1, 3, 7, 10, 13, 17]); 373 | }); 374 | }); 375 | }); 376 | -------------------------------------------------------------------------------- /tests/deque.test.ts: -------------------------------------------------------------------------------- 1 | import { Deque } from '../lib/deque'; 2 | 3 | describe('Deque', () => { 4 | let deque: Deque; 5 | 6 | beforeEach(() => { 7 | deque = new Deque(); 8 | }); 9 | 10 | test('should create empty deque', () => { 11 | expect(deque.isEmpty()).toBe(true); 12 | expect(deque.size()).toBe(0); 13 | }); 14 | 15 | test('should push elements to front', () => { 16 | deque.pushFront(1); 17 | deque.pushFront(2); 18 | expect(deque.size()).toBe(2); 19 | expect(deque.front()).toBe(2); 20 | expect(deque.back()).toBe(1); 21 | }); 22 | 23 | test('should push elements to back', () => { 24 | deque.pushBack(1); 25 | deque.pushBack(2); 26 | expect(deque.size()).toBe(2); 27 | expect(deque.front()).toBe(1); 28 | expect(deque.back()).toBe(2); 29 | }); 30 | 31 | test('should pop elements from front', () => { 32 | deque.pushBack(1); 33 | deque.pushBack(2); 34 | expect(deque.popFront()).toBe(1); 35 | expect(deque.size()).toBe(1); 36 | expect(deque.popFront()).toBe(2); 37 | expect(deque.isEmpty()).toBe(true); 38 | }); 39 | 40 | test('should pop elements from back', () => { 41 | deque.pushBack(1); 42 | deque.pushBack(2); 43 | expect(deque.popBack()).toBe(2); 44 | expect(deque.size()).toBe(1); 45 | expect(deque.popBack()).toBe(1); 46 | expect(deque.isEmpty()).toBe(true); 47 | }); 48 | 49 | test('should return undefined when popping empty deque', () => { 50 | expect(deque.popFront()).toBeUndefined(); 51 | expect(deque.popBack()).toBeUndefined(); 52 | }); 53 | 54 | test('should return front and back elements without removing them', () => { 55 | deque.pushBack(1); 56 | deque.pushBack(2); 57 | expect(deque.front()).toBe(1); 58 | expect(deque.back()).toBe(2); 59 | expect(deque.size()).toBe(2); 60 | }); 61 | 62 | test('should return undefined when checking front/back of empty deque', () => { 63 | expect(deque.front()).toBeUndefined(); 64 | expect(deque.back()).toBeUndefined(); 65 | }); 66 | 67 | test('should clear all elements from deque', () => { 68 | deque.pushBack(1); 69 | deque.pushBack(2); 70 | deque.pushBack(3); 71 | deque.clear(); 72 | expect(deque.isEmpty()).toBe(true); 73 | expect(deque.size()).toBe(0); 74 | }); 75 | 76 | test('should correctly report size', () => { 77 | expect(deque.size()).toBe(0); 78 | deque.pushBack(1); 79 | expect(deque.size()).toBe(1); 80 | deque.pushFront(2); 81 | expect(deque.size()).toBe(2); 82 | deque.popBack(); 83 | expect(deque.size()).toBe(1); 84 | deque.popFront(); 85 | expect(deque.size()).toBe(0); 86 | }); 87 | 88 | describe('Equals Operations', () => { 89 | test('should consider empty deques equal', () => { 90 | const deque1 = new Deque(); 91 | const deque2 = new Deque(); 92 | expect(deque1.equals(deque2)).toBe(true); 93 | }); 94 | 95 | test('should consider identical deques equal', () => { 96 | const deque1 = new Deque(); 97 | const deque2 = new Deque(); 98 | 99 | [1, 2, 3].forEach(val => { 100 | deque1.pushBack(val); 101 | deque2.pushBack(val); 102 | }); 103 | 104 | expect(deque1.equals(deque2)).toBe(true); 105 | }); 106 | 107 | test('should consider deques with different values unequal', () => { 108 | const deque1 = new Deque(); 109 | const deque2 = new Deque(); 110 | 111 | deque1.pushBack(1); 112 | deque1.pushBack(2); 113 | deque1.pushBack(3); 114 | 115 | deque2.pushBack(1); 116 | deque2.pushBack(2); 117 | deque2.pushBack(4); 118 | 119 | expect(deque1.equals(deque2)).toBe(false); 120 | }); 121 | 122 | test('should consider deques with different sizes unequal', () => { 123 | const deque1 = new Deque(); 124 | const deque2 = new Deque(); 125 | 126 | deque1.pushBack(1); 127 | deque1.pushBack(2); 128 | 129 | deque2.pushBack(1); 130 | deque2.pushBack(2); 131 | deque2.pushBack(3); 132 | 133 | expect(deque1.equals(deque2)).toBe(false); 134 | }); 135 | 136 | test('should work with complex data types', () => { 137 | const deque1 = new Deque(); 138 | const deque2 = new Deque(); 139 | 140 | ["cat", "dog", "elephant"].forEach(val => { 141 | deque1.pushBack(val); 142 | deque2.pushBack(val); 143 | }); 144 | 145 | expect(deque1.equals(deque2)).toBe(true); 146 | 147 | deque2.popBack(); 148 | deque2.pushBack("lion"); 149 | expect(deque1.equals(deque2)).toBe(false); 150 | }); 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /tests/hash-table.test.ts: -------------------------------------------------------------------------------- 1 | import { HashTable } from '../lib/hash-table'; 2 | 3 | describe('HashTable', () => { 4 | let hashTable: HashTable; 5 | 6 | beforeEach(() => { 7 | hashTable = new HashTable(); 8 | }); 9 | 10 | describe('Basic Operations', () => { 11 | test('should start empty', () => { 12 | expect(hashTable.isEmpty()).toBe(true); 13 | expect(hashTable.size()).toBe(0); 14 | }); 15 | 16 | test('should insert and retrieve values', () => { 17 | hashTable.insert('one', 1); 18 | hashTable.insert('two', 2); 19 | hashTable.insert('three', 3); 20 | 21 | expect(hashTable.get('one')).toBe(1); 22 | expect(hashTable.get('two')).toBe(2); 23 | expect(hashTable.get('three')).toBe(3); 24 | expect(hashTable.size()).toBe(3); 25 | }); 26 | 27 | test('should update existing keys', () => { 28 | hashTable.insert('key', 1); 29 | expect(hashTable.get('key')).toBe(1); 30 | 31 | hashTable.insert('key', 2); 32 | expect(hashTable.get('key')).toBe(2); 33 | expect(hashTable.size()).toBe(1); 34 | }); 35 | 36 | test('should remove elements', () => { 37 | hashTable.insert('one', 1); 38 | hashTable.insert('two', 2); 39 | 40 | expect(hashTable.remove('one')).toBe(true); 41 | expect(hashTable.get('one')).toBeUndefined(); 42 | expect(hashTable.size()).toBe(1); 43 | 44 | expect(hashTable.remove('nonexistent')).toBe(false); 45 | }); 46 | 47 | test('should clear the table', () => { 48 | hashTable.insert('one', 1); 49 | hashTable.insert('two', 2); 50 | hashTable.insert('three', 3); 51 | 52 | hashTable.clear(); 53 | expect(hashTable.isEmpty()).toBe(true); 54 | expect(hashTable.size()).toBe(0); 55 | expect(hashTable.get('one')).toBeUndefined(); 56 | }); 57 | 58 | test('should handle undefined and null values', () => { 59 | const nullTable = new HashTable(); 60 | nullTable.insert('null', null); 61 | nullTable.insert('undefined', undefined); 62 | 63 | expect(nullTable.get('null')).toBeNull(); 64 | expect(nullTable.get('undefined')).toBeUndefined(); 65 | expect(nullTable.size()).toBe(2); 66 | }); 67 | 68 | test('should handle removing from empty bucket', () => { 69 | expect(hashTable.remove('nonexistent')).toBe(false); 70 | expect(hashTable.size()).toBe(0); 71 | }); 72 | }); 73 | 74 | describe('Collision Handling', () => { 75 | test('should handle collisions', () => { 76 | // Force collisions by using a small capacity 77 | const smallTable = new HashTable(2); 78 | smallTable.insert('a', 1); 79 | smallTable.insert('b', 2); 80 | smallTable.insert('c', 3); 81 | smallTable.insert('d', 4); 82 | 83 | expect(smallTable.get('a')).toBe(1); 84 | expect(smallTable.get('b')).toBe(2); 85 | expect(smallTable.get('c')).toBe(3); 86 | expect(smallTable.get('d')).toBe(4); 87 | }); 88 | 89 | test('should handle removing elements with collisions', () => { 90 | const smallTable = new HashTable(2); 91 | smallTable.insert('a', 1); 92 | smallTable.insert('b', 2); 93 | smallTable.insert('c', 3); 94 | 95 | expect(smallTable.remove('b')).toBe(true); 96 | expect(smallTable.get('a')).toBe(1); 97 | expect(smallTable.get('b')).toBeUndefined(); 98 | expect(smallTable.get('c')).toBe(3); 99 | }); 100 | 101 | test('should handle many collisions', () => { 102 | const tinyTable = new HashTable(1); 103 | for (let i = 0; i < 100; i++) { 104 | tinyTable.insert(i, `value${i}`); 105 | } 106 | 107 | for (let i = 0; i < 100; i++) { 108 | expect(tinyTable.get(i)).toBe(`value${i}`); 109 | } 110 | expect(tinyTable.size()).toBe(100); 111 | }); 112 | }); 113 | 114 | describe('Key Type Support', () => { 115 | test('should handle different key types', () => { 116 | const mixedTable = new HashTable(); 117 | 118 | mixedTable.insert(42, 'number'); 119 | mixedTable.insert(true, 'boolean'); 120 | mixedTable.insert({ id: 1 }, 'object'); 121 | mixedTable.insert([1, 2, 3], 'array'); 122 | 123 | expect(mixedTable.get(42)).toBe('number'); 124 | expect(mixedTable.get(true)).toBe('boolean'); 125 | expect(mixedTable.get({ id: 1 })).toBe('object'); 126 | expect(mixedTable.get([1, 2, 3])).toBe('array'); 127 | }); 128 | 129 | test('should handle keys with custom hashCode and equals methods', () => { 130 | class CustomKey { 131 | constructor(private id: number) { } 132 | 133 | hashCode(): number { 134 | return this.id; 135 | } 136 | 137 | equals(other: CustomKey): boolean { 138 | return this.id === other.id; 139 | } 140 | } 141 | 142 | const customTable = new HashTable(); 143 | const key1 = new CustomKey(1); 144 | const key2 = new CustomKey(2); 145 | const key1Duplicate = new CustomKey(1); 146 | 147 | customTable.insert(key1, 'first'); 148 | customTable.insert(key2, 'second'); 149 | 150 | expect(customTable.get(key1)).toBe('first'); 151 | expect(customTable.get(key2)).toBe('second'); 152 | expect(customTable.get(key1Duplicate)).toBe('first'); 153 | 154 | expect(customTable.size()).toBe(2); 155 | }); 156 | 157 | test('should handle Date objects as keys', () => { 158 | const dateTable = new HashTable(); 159 | const date1 = new Date('2023-01-01'); 160 | const date2 = new Date('2023-01-01'); 161 | const date3 = new Date('2023-12-31'); 162 | 163 | dateTable.insert(date1, 'new year'); 164 | dateTable.insert(date3, 'year end'); 165 | 166 | expect(dateTable.get(date2)).toBe('new year'); 167 | expect(dateTable.get(date3)).toBe('year end'); 168 | }); 169 | 170 | test('should handle Symbol keys', () => { 171 | const symbolTable = new HashTable(); 172 | const sym1 = Symbol('test1'); 173 | const sym2 = Symbol('test2'); 174 | 175 | symbolTable.insert(sym1, 'value1'); 176 | symbolTable.insert(sym2, 'value2'); 177 | 178 | expect(symbolTable.get(sym1)).toBe('value1'); 179 | expect(symbolTable.get(sym2)).toBe('value2'); 180 | expect(symbolTable.get(Symbol('test1'))).toBeUndefined(); 181 | }); 182 | }); 183 | 184 | describe('Edge Cases', () => { 185 | test('should handle zero capacity', () => { 186 | const zeroTable = new HashTable(0); 187 | zeroTable.insert('test', 1); 188 | expect(zeroTable.get('test')).toBe(1); 189 | }); 190 | 191 | test('should handle negative capacity', () => { 192 | const negativeTable = new HashTable(-5); 193 | negativeTable.insert('test', 1); 194 | expect(negativeTable.get('test')).toBe(1); 195 | }); 196 | 197 | test('should handle large numbers of operations', () => { 198 | const largeTable = new HashTable(); 199 | const operations = 10000; 200 | 201 | // Insert many items 202 | for (let i = 0; i < operations; i++) { 203 | largeTable.insert(i, i * 2); 204 | } 205 | 206 | // Verify all items 207 | for (let i = 0; i < operations; i++) { 208 | expect(largeTable.get(i)).toBe(i * 2); 209 | } 210 | 211 | expect(largeTable.size()).toBe(operations); 212 | }); 213 | }); 214 | 215 | describe('forEach Method', () => { 216 | test('should iterate over all entries', () => { 217 | const table = new HashTable(32); 218 | const entries: [string, number][] = [ 219 | ['a', 1], 220 | ['b', 2], 221 | ['c', 3] 222 | ]; 223 | 224 | entries.forEach(([key, value]) => table.insert(key, value)); 225 | 226 | const visitedEntries: [string, number][] = []; 227 | table.forEach((key, value) => { 228 | visitedEntries.push([key, value]); 229 | }); 230 | 231 | expect(visitedEntries.length).toBe(entries.length); 232 | entries.forEach(([key, value]) => { 233 | expect(visitedEntries).toContainEqual([key, value]); 234 | }); 235 | }); 236 | 237 | test('should handle empty table', () => { 238 | const emptyTable = new HashTable(); 239 | const mockCallback = jest.fn(); 240 | 241 | emptyTable.forEach(mockCallback); 242 | expect(mockCallback).not.toHaveBeenCalled(); 243 | }); 244 | 245 | test('should handle table with collisions', () => { 246 | const smallTable = new HashTable(32); 247 | const entries = [ 248 | ['a', 1], 249 | ['b', 2], 250 | ['c', 3], 251 | ['d', 4] 252 | ]; 253 | 254 | entries.forEach(([key, value]) => smallTable.insert(key as string, value as number)); 255 | 256 | const visitedEntries: [string, number][] = []; 257 | smallTable.forEach((key, value) => { 258 | visitedEntries.push([key, value]); 259 | }); 260 | 261 | expect(visitedEntries.length).toBe(entries.length); 262 | entries.forEach(([key, value]) => { 263 | expect(visitedEntries).toContainEqual([key, value]); 264 | }); 265 | }); 266 | 267 | test('should provide correct this context', () => { 268 | const table = new HashTable(32); 269 | table.insert('a', 1); 270 | table.insert('b', 2); 271 | 272 | const context = { multiplier: 2 }; 273 | const results: number[] = []; 274 | 275 | table.forEach(function(this: typeof context, key: string, value: number) { 276 | results.push(value * this.multiplier); 277 | }.bind(context)); 278 | 279 | expect(results).toContain(2); // 1 * 2 280 | expect(results).toContain(4); // 2 * 2 281 | }); 282 | }); 283 | 284 | describe('equals Method', () => { 285 | test('should consider empty tables equal', () => { 286 | const table1 = new HashTable(); 287 | const table2 = new HashTable(); 288 | expect(table1.equals(table2)).toBe(true); 289 | }); 290 | 291 | test('should consider tables with same key-value pairs equal', () => { 292 | const table1 = new HashTable(); 293 | const table2 = new HashTable(); 294 | 295 | table1.insert('a', 1); 296 | table1.insert('b', 2); 297 | table1.insert('c', 3); 298 | 299 | table2.insert('a', 1); 300 | table2.insert('b', 2); 301 | table2.insert('c', 3); 302 | 303 | expect(table1.equals(table2)).toBe(true); 304 | }); 305 | 306 | test('should consider tables with different values unequal', () => { 307 | const table1 = new HashTable(); 308 | const table2 = new HashTable(); 309 | 310 | table1.insert('a', 1); 311 | table1.insert('b', 2); 312 | 313 | table2.insert('a', 1); 314 | table2.insert('b', 3); 315 | 316 | expect(table1.equals(table2)).toBe(false); 317 | }); 318 | 319 | test('should consider tables with different sizes unequal', () => { 320 | const table1 = new HashTable(); 321 | const table2 = new HashTable(); 322 | 323 | table1.insert('a', 1); 324 | table1.insert('b', 2); 325 | 326 | table2.insert('a', 1); 327 | 328 | expect(table1.equals(table2)).toBe(false); 329 | }); 330 | 331 | test('should handle comparison with null/undefined', () => { 332 | const table = new HashTable(); 333 | table.insert('a', 1); 334 | 335 | expect(table.equals(null as any)).toBe(false); 336 | expect(table.equals(undefined as any)).toBe(false); 337 | }); 338 | 339 | test('should handle tables with collisions', () => { 340 | const table1 = new HashTable(2); 341 | const table2 = new HashTable(2); 342 | 343 | table1.insert('a', 1); 344 | table1.insert('b', 2); 345 | table1.insert('c', 3); 346 | 347 | table2.insert('a', 1); 348 | table2.insert('b', 2); 349 | table2.insert('c', 3); 350 | 351 | expect(table1.equals(table2)).toBe(true); 352 | }); 353 | }); 354 | }); 355 | -------------------------------------------------------------------------------- /tests/hash-utils.test.ts: -------------------------------------------------------------------------------- 1 | import { HashUtils } from '../lib/hash-utils'; 2 | 3 | describe('HashUtils', () => { 4 | describe('hash', () => { 5 | test('should handle numbers', () => { 6 | expect(HashUtils.hash(42, 100)).toBeLessThan(100); 7 | expect(HashUtils.hash(-42, 100)).toBeLessThan(100); 8 | expect(HashUtils.hash(0, 100)).toBeLessThan(100); 9 | expect(HashUtils.hash(Number.MAX_SAFE_INTEGER, 100)).toBeLessThan(100); 10 | expect(HashUtils.hash(Number.MIN_SAFE_INTEGER, 100)).toBeLessThan(100); 11 | }); 12 | 13 | test('should handle strings', () => { 14 | expect(HashUtils.hash('test', 100)).toBeLessThan(100); 15 | expect(HashUtils.hash('', 100)).toBeLessThan(100); 16 | expect(HashUtils.hash('long string with spaces', 100)).toBeLessThan(100); 17 | expect(HashUtils.hash('🚀', 100)).toBeLessThan(100); // Unicode characters 18 | expect(HashUtils.hash('\n\t\r', 100)).toBeLessThan(100); // Special characters 19 | }); 20 | 21 | test('should handle objects with hashCode method', () => { 22 | const obj = { 23 | hashCode: () => 42 24 | }; 25 | expect(HashUtils.hash(obj, 100)).toBe(42); 26 | 27 | const objWithNegativeHash = { 28 | hashCode: () => -10 29 | }; 30 | expect(HashUtils.hash(objWithNegativeHash, 100)).toBe(-10); 31 | }); 32 | 33 | test('should handle objects without hashCode method', () => { 34 | const obj = { a: 1, b: 2 }; 35 | expect(HashUtils.hash(obj, 100)).toBeLessThan(100); 36 | 37 | const nestedObj = { a: { b: { c: 3 } } }; 38 | expect(HashUtils.hash(nestedObj, 100)).toBeLessThan(100); 39 | 40 | const objWithFunction = { fn: () => console.log('test') }; 41 | expect(HashUtils.hash(objWithFunction, 100)).toBeLessThan(100); 42 | }); 43 | 44 | test('should handle null and undefined', () => { 45 | expect(HashUtils.hash(null, 100)).toBeLessThan(100); 46 | expect(HashUtils.hash(undefined, 100)).toBeLessThan(100); 47 | }); 48 | 49 | test('should handle special types', () => { 50 | expect(HashUtils.hash(true, 100)).toBeLessThan(100); 51 | expect(HashUtils.hash(false, 100)).toBeLessThan(100); 52 | expect(HashUtils.hash(Symbol('test'), 100)).toBeLessThan(100); 53 | expect(HashUtils.hash(new Date(), 100)).toBeLessThan(100); 54 | expect(HashUtils.hash(/regex/, 100)).toBeLessThan(100); 55 | expect(HashUtils.hash(BigInt(9007199254740991), 100)).toBeLessThan(100); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /tests/heap.test.ts: -------------------------------------------------------------------------------- 1 | import { Heap } from '../lib/heap'; 2 | 3 | describe('Heap', () => { 4 | let heap: Heap; 5 | 6 | beforeEach(() => { 7 | heap = new Heap(); 8 | }); 9 | 10 | describe('initialization', () => { 11 | test('should create empty heap', () => { 12 | expect(heap.isEmpty()).toBe(true); 13 | expect(heap.size()).toBe(0); 14 | }); 15 | 16 | test('should initialize with custom comparator', () => { 17 | const maxHeap = new Heap((a, b) => a > b); 18 | expect(maxHeap.isEmpty()).toBe(true); 19 | expect(maxHeap.size()).toBe(0); 20 | }); 21 | }); 22 | 23 | describe('insertion', () => { 24 | test('should push elements and maintain min heap property', () => { 25 | heap.push(5); 26 | heap.push(3); 27 | heap.push(7); 28 | heap.push(1); 29 | 30 | expect(heap.top()).toBe(1); 31 | expect(heap.size()).toBe(4); 32 | }); 33 | 34 | test('should handle duplicate values', () => { 35 | heap.push(3); 36 | heap.push(3); 37 | heap.push(3); 38 | 39 | expect(heap.size()).toBe(3); 40 | expect(heap.pop()).toBe(3); 41 | expect(heap.pop()).toBe(3); 42 | expect(heap.pop()).toBe(3); 43 | }); 44 | 45 | test('should handle negative numbers', () => { 46 | heap.push(-5); 47 | heap.push(3); 48 | heap.push(-10); 49 | heap.push(0); 50 | 51 | expect(heap.pop()).toBe(-10); 52 | expect(heap.pop()).toBe(-5); 53 | expect(heap.pop()).toBe(0); 54 | expect(heap.pop()).toBe(3); 55 | }); 56 | }); 57 | 58 | describe('removal', () => { 59 | test('should pop elements in sorted order', () => { 60 | heap.push(5); 61 | heap.push(3); 62 | heap.push(7); 63 | heap.push(1); 64 | 65 | expect(heap.pop()).toBe(1); 66 | expect(heap.pop()).toBe(3); 67 | expect(heap.pop()).toBe(5); 68 | expect(heap.pop()).toBe(7); 69 | expect(heap.pop()).toBeUndefined(); 70 | }); 71 | 72 | test('should clear all elements', () => { 73 | heap.push(1); 74 | heap.push(2); 75 | heap.push(3); 76 | 77 | heap.clear(); 78 | expect(heap.isEmpty()).toBe(true); 79 | expect(heap.size()).toBe(0); 80 | expect(heap.top()).toBeUndefined(); 81 | }); 82 | 83 | test('should handle pop on single element heap', () => { 84 | heap.push(1); 85 | expect(heap.pop()).toBe(1); 86 | expect(heap.isEmpty()).toBe(true); 87 | }); 88 | 89 | test('should maintain heap property after multiple pops', () => { 90 | heap.push(5); 91 | heap.push(3); 92 | heap.push(7); 93 | heap.push(1); 94 | heap.push(4); 95 | 96 | heap.pop(); // removes 1 97 | heap.pop(); // removes 3 98 | heap.push(2); // adds new element 99 | 100 | expect(heap.pop()).toBe(2); 101 | expect(heap.pop()).toBe(4); 102 | expect(heap.pop()).toBe(5); 103 | expect(heap.pop()).toBe(7); 104 | }); 105 | }); 106 | 107 | describe('inspection', () => { 108 | test('should peek at top element without removing it', () => { 109 | heap.push(5); 110 | heap.push(3); 111 | 112 | expect(heap.top()).toBe(3); 113 | expect(heap.size()).toBe(2); 114 | }); 115 | 116 | test('should return undefined when peeking empty heap', () => { 117 | expect(heap.top()).toBeUndefined(); 118 | }); 119 | 120 | test('should correctly report size after operations', () => { 121 | expect(heap.size()).toBe(0); 122 | 123 | heap.push(1); 124 | expect(heap.size()).toBe(1); 125 | 126 | heap.push(2); 127 | expect(heap.size()).toBe(2); 128 | 129 | heap.pop(); 130 | expect(heap.size()).toBe(1); 131 | 132 | heap.clear(); 133 | expect(heap.size()).toBe(0); 134 | }); 135 | }); 136 | 137 | describe('custom comparator', () => { 138 | test('should work as max heap with custom comparator', () => { 139 | const maxHeap = new Heap((a, b) => a > b); 140 | 141 | maxHeap.push(5); 142 | maxHeap.push(3); 143 | maxHeap.push(7); 144 | maxHeap.push(1); 145 | 146 | expect(maxHeap.pop()).toBe(7); 147 | expect(maxHeap.pop()).toBe(5); 148 | expect(maxHeap.pop()).toBe(3); 149 | expect(maxHeap.pop()).toBe(1); 150 | }); 151 | 152 | test('should work with custom object comparator', () => { 153 | interface Person { 154 | name: string; 155 | age: number; 156 | } 157 | 158 | const personHeap = new Heap((a, b) => a.age < b.age); 159 | 160 | personHeap.push({ name: 'Alice', age: 30 }); 161 | personHeap.push({ name: 'Bob', age: 25 }); 162 | personHeap.push({ name: 'Charlie', age: 35 }); 163 | 164 | expect(personHeap.pop()?.age).toBe(25); 165 | expect(personHeap.pop()?.age).toBe(30); 166 | expect(personHeap.pop()?.age).toBe(35); 167 | }); 168 | 169 | test('should handle equal elements with custom comparator', () => { 170 | const maxHeap = new Heap((a, b) => a > b); 171 | 172 | maxHeap.push(5); 173 | maxHeap.push(5); 174 | maxHeap.push(5); 175 | 176 | expect(maxHeap.size()).toBe(3); 177 | expect(maxHeap.pop()).toBe(5); 178 | expect(maxHeap.pop()).toBe(5); 179 | expect(maxHeap.pop()).toBe(5); 180 | }); 181 | }); 182 | 183 | describe('equality', () => { 184 | test('should consider empty heaps equal', () => { 185 | const heap1 = new Heap(); 186 | const heap2 = new Heap(); 187 | expect(heap1.equals(heap2)).toBe(true); 188 | }); 189 | 190 | test('should consider heaps with same elements equal', () => { 191 | const heap1 = new Heap(); 192 | const heap2 = new Heap(); 193 | 194 | [5, 3, 7, 1].forEach(n => { 195 | heap1.push(n); 196 | heap2.push(n); 197 | }); 198 | 199 | expect(heap1.equals(heap2)).toBe(true); 200 | }); 201 | 202 | test('should consider heaps with different elements unequal', () => { 203 | const heap1 = new Heap(); 204 | const heap2 = new Heap(); 205 | 206 | heap1.push(1); 207 | heap1.push(2); 208 | heap2.push(1); 209 | heap2.push(3); 210 | 211 | expect(heap1.equals(heap2)).toBe(false); 212 | }); 213 | 214 | test('should consider heaps with different sizes unequal', () => { 215 | const heap1 = new Heap(); 216 | const heap2 = new Heap(); 217 | 218 | heap1.push(1); 219 | heap1.push(2); 220 | heap2.push(1); 221 | 222 | expect(heap1.equals(heap2)).toBe(false); 223 | }); 224 | 225 | test('should handle equality with custom comparators', () => { 226 | const heap1 = new Heap((a, b) => a > b); 227 | const heap2 = new Heap((a, b) => a > b); 228 | 229 | [5, 3, 7, 1].forEach(n => { 230 | heap1.push(n); 231 | heap2.push(n); 232 | }); 233 | 234 | expect(heap1.equals(heap2)).toBe(true); 235 | }); 236 | }); 237 | }); 238 | -------------------------------------------------------------------------------- /tests/linked-list.test.ts: -------------------------------------------------------------------------------- 1 | import { LinkedList } from '../lib/linked-list'; 2 | 3 | describe('LinkedList', () => { 4 | let list: LinkedList; 5 | 6 | beforeEach(() => { 7 | list = new LinkedList(); 8 | }); 9 | 10 | describe('Adding elements', () => { 11 | test('should add elements to the end', () => { 12 | list.pushBack(1); 13 | list.pushBack(2); 14 | list.pushBack(3); 15 | expect(list.size()).toBe(3); 16 | expect(list.get(0)).toBe(1); 17 | expect(list.get(1)).toBe(2); 18 | expect(list.get(2)).toBe(3); 19 | }); 20 | 21 | test('should add elements to the front', () => { 22 | list.pushFront(1); 23 | list.pushFront(2); 24 | list.pushFront(3); 25 | expect(list.size()).toBe(3); 26 | expect(list.get(0)).toBe(3); 27 | expect(list.get(1)).toBe(2); 28 | expect(list.get(2)).toBe(1); 29 | }); 30 | }); 31 | 32 | describe('Inserting elements', () => { 33 | test('should insert at valid positions', () => { 34 | list.pushBack(1); 35 | list.pushBack(3); 36 | expect(list.insert(2, 1)).toBe(true); 37 | expect(list.get(0)).toBe(1); 38 | expect(list.get(1)).toBe(2); 39 | expect(list.get(2)).toBe(3); 40 | }); 41 | 42 | test('should insert at beginning', () => { 43 | list.pushBack(1); 44 | list.pushBack(2); 45 | expect(list.insert(0, 0)).toBe(true); 46 | expect(list.get(0)).toBe(0); 47 | expect(list.get(1)).toBe(1); 48 | expect(list.get(2)).toBe(2); 49 | }); 50 | 51 | test('should insert at end', () => { 52 | list.pushBack(1); 53 | list.pushBack(2); 54 | expect(list.insert(3, 2)).toBe(true); 55 | expect(list.get(0)).toBe(1); 56 | expect(list.get(1)).toBe(2); 57 | expect(list.get(2)).toBe(3); 58 | }); 59 | 60 | test('should return false for invalid positions', () => { 61 | expect(list.insert(1, -1)).toBe(false); 62 | expect(list.insert(1, 1)).toBe(false); 63 | }); 64 | }); 65 | 66 | describe('Removing elements', () => { 67 | describe('removeIf', () => { 68 | test('should remove elements that satisfy the condition', () => { 69 | list.pushBack(1); 70 | list.pushBack(2); 71 | list.pushBack(3); 72 | expect(list.removeIf(x => x === 2)).toBe(true); 73 | expect(list.size()).toBe(2); 74 | expect(list.get(0)).toBe(1); 75 | expect(list.get(1)).toBe(3); 76 | }); 77 | 78 | test('should remove multiple elements that satisfy the condition', () => { 79 | list.pushBack(1); 80 | list.pushBack(2); 81 | list.pushBack(2); 82 | list.pushBack(3); 83 | expect(list.removeIf(x => x === 2)).toBe(true); 84 | expect(list.size()).toBe(3); 85 | expect(list.get(0)).toBe(1); 86 | expect(list.get(1)).toBe(2); 87 | expect(list.get(2)).toBe(3); 88 | }); 89 | 90 | test('should remove first element that satisfies condition', () => { 91 | list.pushBack(1); 92 | list.pushBack(2); 93 | list.pushBack(3); 94 | expect(list.removeIf(x => x === 1)).toBe(true); 95 | expect(list.size()).toBe(2); 96 | expect(list.get(0)).toBe(2); 97 | expect(list.get(1)).toBe(3); 98 | }); 99 | 100 | test('should remove last element that satisfies condition', () => { 101 | list.pushBack(1); 102 | list.pushBack(2); 103 | list.pushBack(3); 104 | expect(list.removeIf(x => x === 3)).toBe(true); 105 | expect(list.size()).toBe(2); 106 | expect(list.get(0)).toBe(1); 107 | expect(list.get(1)).toBe(2); 108 | }); 109 | 110 | test('should return false if no element satisfies the condition', () => { 111 | list.pushBack(1); 112 | list.pushBack(2); 113 | expect(list.removeIf(x => x === 3)).toBe(false); 114 | expect(list.size()).toBe(2); 115 | }); 116 | }); 117 | 118 | describe('removeAt', () => { 119 | test('should remove elements at valid positions', () => { 120 | list.pushBack(1); 121 | list.pushBack(2); 122 | list.pushBack(3); 123 | expect(list.removeAt(1)).toBe(2); 124 | expect(list.size()).toBe(2); 125 | expect(list.get(0)).toBe(1); 126 | expect(list.get(1)).toBe(3); 127 | }); 128 | 129 | test('should remove first element', () => { 130 | list.pushBack(1); 131 | list.pushBack(2); 132 | list.pushBack(3); 133 | expect(list.removeAt(0)).toBe(1); 134 | expect(list.size()).toBe(2); 135 | expect(list.get(0)).toBe(2); 136 | expect(list.get(1)).toBe(3); 137 | }); 138 | 139 | test('should remove last element', () => { 140 | list.pushBack(1); 141 | list.pushBack(2); 142 | list.pushBack(3); 143 | expect(list.removeAt(2)).toBe(3); 144 | expect(list.size()).toBe(2); 145 | expect(list.get(0)).toBe(1); 146 | expect(list.get(1)).toBe(2); 147 | }); 148 | 149 | test('should return undefined for invalid positions', () => { 150 | list.pushBack(1); 151 | expect(list.removeAt(-1)).toBeUndefined(); 152 | expect(list.removeAt(1)).toBeUndefined(); 153 | }); 154 | }); 155 | }); 156 | 157 | describe('Accessing elements', () => { 158 | test('should return elements at valid positions', () => { 159 | list.pushBack(1); 160 | list.pushBack(2); 161 | expect(list.get(0)).toBe(1); 162 | expect(list.get(1)).toBe(2); 163 | }); 164 | 165 | test('should return undefined for invalid positions', () => { 166 | list.pushBack(1); 167 | expect(list.get(-1)).toBeUndefined(); 168 | expect(list.get(1)).toBeUndefined(); 169 | }); 170 | }); 171 | 172 | describe('List state', () => { 173 | test('should return true for empty list', () => { 174 | expect(list.isEmpty()).toBe(true); 175 | }); 176 | 177 | test('should return false for non-empty list', () => { 178 | list.pushBack(1); 179 | expect(list.isEmpty()).toBe(false); 180 | }); 181 | 182 | test('should return correct size', () => { 183 | expect(list.size()).toBe(0); 184 | list.pushBack(1); 185 | expect(list.size()).toBe(1); 186 | list.pushBack(2); 187 | expect(list.size()).toBe(2); 188 | }); 189 | 190 | test('should remove all elements', () => { 191 | list.pushBack(1); 192 | list.pushBack(2); 193 | list.clear(); 194 | expect(list.isEmpty()).toBe(true); 195 | expect(list.size()).toBe(0); 196 | }); 197 | }); 198 | 199 | describe('Pop operations', () => { 200 | describe('popBack', () => { 201 | test('should pop elements from back', () => { 202 | list.pushBack(1); 203 | list.pushBack(2); 204 | list.pushBack(3); 205 | expect(list.popBack()).toBe(3); 206 | expect(list.size()).toBe(2); 207 | expect(list.popBack()).toBe(2); 208 | expect(list.size()).toBe(1); 209 | expect(list.popBack()).toBe(1); 210 | expect(list.size()).toBe(0); 211 | }); 212 | 213 | test('should return undefined when popping from empty list from back', () => { 214 | expect(list.popBack()).toBeUndefined(); 215 | }); 216 | }); 217 | 218 | describe('popFront', () => { 219 | test('should pop elements from front', () => { 220 | list.pushBack(1); 221 | list.pushBack(2); 222 | list.pushBack(3); 223 | expect(list.popFront()).toBe(1); 224 | expect(list.size()).toBe(2); 225 | expect(list.popFront()).toBe(2); 226 | expect(list.size()).toBe(1); 227 | expect(list.popFront()).toBe(3); 228 | expect(list.size()).toBe(0); 229 | }); 230 | 231 | test('should return undefined when popping from empty list from front', () => { 232 | expect(list.popFront()).toBeUndefined(); 233 | }); 234 | }); 235 | }); 236 | 237 | describe('Mixed operations', () => { 238 | test('should handle mixed push and pop operations', () => { 239 | list.pushBack(1); 240 | list.pushFront(2); 241 | expect(list.popBack()).toBe(1); 242 | list.pushBack(3); 243 | expect(list.popFront()).toBe(2); 244 | expect(list.size()).toBe(1); 245 | expect(list.get(0)).toBe(3); 246 | }); 247 | 248 | test('should maintain correct order after multiple operations', () => { 249 | list.pushBack(1); 250 | list.pushFront(2); 251 | list.pushBack(3); 252 | list.pushFront(4); 253 | expect(list.size()).toBe(4); 254 | expect(list.get(0)).toBe(4); 255 | expect(list.get(1)).toBe(2); 256 | expect(list.get(2)).toBe(1); 257 | expect(list.get(3)).toBe(3); 258 | }); 259 | }); 260 | 261 | describe('Conditional insertion', () => { 262 | describe('insertBefore', () => { 263 | test('should insert before element that satisfies condition', () => { 264 | list.pushBack(1); 265 | list.pushBack(3); 266 | list.pushBack(5); 267 | expect(list.insertBefore(2, x => x === 3)).toBe(true); 268 | expect(list.get(0)).toBe(1); 269 | expect(list.get(1)).toBe(2); 270 | expect(list.get(2)).toBe(3); 271 | expect(list.get(3)).toBe(5); 272 | }); 273 | 274 | test('should insert before first element when condition matches', () => { 275 | list.pushBack(1); 276 | list.pushBack(2); 277 | expect(list.insertBefore(0, x => x === 1)).toBe(true); 278 | expect(list.get(0)).toBe(0); 279 | expect(list.get(1)).toBe(1); 280 | expect(list.get(2)).toBe(2); 281 | }); 282 | 283 | test('should return false when inserting before with no match', () => { 284 | list.pushBack(1); 285 | list.pushBack(2); 286 | expect(list.insertBefore(3, x => x === 4)).toBe(false); 287 | }); 288 | }); 289 | 290 | describe('insertAfter', () => { 291 | test('should insert after element that satisfies condition', () => { 292 | list.pushBack(1); 293 | list.pushBack(3); 294 | list.pushBack(5); 295 | expect(list.insertAfter(4, x => x === 3)).toBe(true); 296 | expect(list.get(0)).toBe(1); 297 | expect(list.get(1)).toBe(3); 298 | expect(list.get(2)).toBe(4); 299 | expect(list.get(3)).toBe(5); 300 | }); 301 | 302 | test('should insert after last element when condition matches', () => { 303 | list.pushBack(1); 304 | list.pushBack(2); 305 | expect(list.insertAfter(3, x => x === 2)).toBe(true); 306 | expect(list.get(0)).toBe(1); 307 | expect(list.get(1)).toBe(2); 308 | expect(list.get(2)).toBe(3); 309 | }); 310 | 311 | test('should return false when inserting after with no match', () => { 312 | list.pushBack(1); 313 | list.pushBack(2); 314 | expect(list.insertAfter(3, x => x === 4)).toBe(false); 315 | }); 316 | }); 317 | }); 318 | 319 | describe('Accessing ends', () => { 320 | describe('front', () => { 321 | test('should return first element with front()', () => { 322 | list.pushBack(1); 323 | list.pushBack(2); 324 | list.pushBack(3); 325 | expect(list.front()).toBe(1); 326 | expect(list.size()).toBe(3); // Check size hasn't changed 327 | }); 328 | 329 | test('should return undefined when calling front() on empty list', () => { 330 | expect(list.front()).toBeUndefined(); 331 | }); 332 | }); 333 | 334 | describe('back', () => { 335 | test('should return last element with back()', () => { 336 | list.pushBack(1); 337 | list.pushBack(2); 338 | list.pushBack(3); 339 | expect(list.back()).toBe(3); 340 | expect(list.size()).toBe(3); // Check size hasn't changed 341 | }); 342 | 343 | test('should return undefined when calling back() on empty list', () => { 344 | expect(list.back()).toBeUndefined(); 345 | }); 346 | }); 347 | }); 348 | 349 | describe('Iteration', () => { 350 | test('should iterate through all elements with forEach', () => { 351 | list.pushBack(1); 352 | list.pushBack(2); 353 | list.pushBack(3); 354 | const elements: number[] = []; 355 | list.forEach(x => elements.push(x)); 356 | expect(elements).toEqual([1, 2, 3]); 357 | }); 358 | 359 | test('should not call callback for empty list', () => { 360 | const callback = jest.fn(); 361 | list.forEach(callback); 362 | expect(callback).not.toHaveBeenCalled(); 363 | }); 364 | 365 | test('should call callback with correct elements in order', () => { 366 | list.pushBack(1); 367 | list.pushBack(2); 368 | const callback = jest.fn(); 369 | list.forEach(callback); 370 | expect(callback).toHaveBeenNthCalledWith(1, 1); 371 | expect(callback).toHaveBeenNthCalledWith(2, 2); 372 | expect(callback).toHaveBeenCalledTimes(2); 373 | }); 374 | 375 | test('should allow callback to modify elements', () => { 376 | list.pushBack(1); 377 | list.pushBack(2); 378 | list.pushBack(3); 379 | const doubled: number[] = []; 380 | list.forEach(x => doubled.push(x * 2)); 381 | expect(doubled).toEqual([2, 4, 6]); 382 | }); 383 | }); 384 | 385 | describe('Equality', () => { 386 | test('should consider empty lists equal', () => { 387 | const list1 = new LinkedList(); 388 | const list2 = new LinkedList(); 389 | expect(list1.equals(list2)).toBe(true); 390 | }); 391 | 392 | test('should consider lists with same elements in same order equal', () => { 393 | const list1 = new LinkedList(); 394 | const list2 = new LinkedList(); 395 | 396 | [1, 2, 3].forEach(n => { 397 | list1.pushBack(n); 398 | list2.pushBack(n); 399 | }); 400 | 401 | expect(list1.equals(list2)).toBe(true); 402 | }); 403 | 404 | test('should consider lists with different elements unequal', () => { 405 | const list1 = new LinkedList(); 406 | const list2 = new LinkedList(); 407 | 408 | list1.pushBack(1); 409 | list1.pushBack(2); 410 | list2.pushBack(1); 411 | list2.pushBack(3); 412 | 413 | expect(list1.equals(list2)).toBe(false); 414 | }); 415 | 416 | test('should consider lists with same elements in different order unequal', () => { 417 | const list1 = new LinkedList(); 418 | const list2 = new LinkedList(); 419 | 420 | list1.pushBack(1); 421 | list1.pushBack(2); 422 | list2.pushBack(2); 423 | list2.pushBack(1); 424 | 425 | expect(list1.equals(list2)).toBe(false); 426 | }); 427 | 428 | test('should consider lists with different sizes unequal', () => { 429 | const list1 = new LinkedList(); 430 | const list2 = new LinkedList(); 431 | 432 | list1.pushBack(1); 433 | list1.pushBack(2); 434 | list2.pushBack(1); 435 | 436 | expect(list1.equals(list2)).toBe(false); 437 | }); 438 | 439 | test('should handle comparison with null/undefined', () => { 440 | const list1 = new LinkedList(); 441 | list1.pushBack(1); 442 | 443 | expect(list1.equals(null as any)).toBe(false); 444 | expect(list1.equals(undefined as any)).toBe(false); 445 | }); 446 | }); 447 | }); 448 | -------------------------------------------------------------------------------- /tests/map.test.ts: -------------------------------------------------------------------------------- 1 | import { Map } from '../lib/map'; 2 | 3 | describe('Map', () => { 4 | let map: Map; 5 | 6 | beforeEach(() => { 7 | map = new Map(); 8 | }); 9 | 10 | describe('initialization', () => { 11 | test('should start empty', () => { 12 | expect(map.isEmpty()).toBe(true); 13 | expect(map.size()).toBe(0); 14 | }); 15 | 16 | test('should initialize with custom comparator', () => { 17 | const reverseMap = new Map((a, b) => a > b); 18 | reverseMap.insert('a', 1); 19 | reverseMap.insert('b', 2); 20 | reverseMap.insert('c', 3); 21 | 22 | const keys: string[] = []; 23 | reverseMap.forEach((key) => keys.push(key)); 24 | expect(keys).toEqual(['c', 'b', 'a']); 25 | }); 26 | }); 27 | 28 | describe('insertion and lookup', () => { 29 | test('should insert and find key-value pairs correctly', () => { 30 | map.insert('one', 1); 31 | map.insert('two', 2); 32 | map.insert('three', 3); 33 | 34 | expect(map.isEmpty()).toBe(false); 35 | expect(map.size()).toBe(3); 36 | expect(map.find('one')).toBe(1); 37 | expect(map.find('two')).toBe(2); 38 | expect(map.find('three')).toBe(3); 39 | expect(map.find('four')).toBeUndefined(); 40 | }); 41 | 42 | test('should handle duplicate keys by updating values', () => { 43 | map.insert('test', 1); 44 | map.insert('test', 2); 45 | map.insert('test', 3); 46 | 47 | expect(map.size()).toBe(1); 48 | expect(map.find('test')).toBe(3); 49 | }); 50 | 51 | test('should handle inserting undefined or null values', () => { 52 | map.insert('undefined', undefined as any); 53 | map.insert('null', null as any); 54 | 55 | expect(map.find('undefined')).toBeUndefined(); 56 | expect(map.find('null')).toBeNull(); 57 | expect(map.size()).toBe(2); 58 | }); 59 | 60 | test('should handle large number of insertions', () => { 61 | const numInsertions = 1000; 62 | for (let i = 0; i < numInsertions; i++) { 63 | map.insert(`key${i}`, i); 64 | } 65 | expect(map.size()).toBe(numInsertions); 66 | expect(map.find('key500')).toBe(500); 67 | }); 68 | }); 69 | 70 | describe('deletion', () => { 71 | test('should delete key-value pairs correctly', () => { 72 | map.insert('one', 1); 73 | map.insert('two', 2); 74 | map.insert('three', 3); 75 | 76 | expect(map.size()).toBe(3); 77 | 78 | map.delete('two'); 79 | expect(map.find('two')).toBeUndefined(); 80 | expect(map.size()).toBe(2); 81 | 82 | map.delete('one'); 83 | expect(map.find('one')).toBeUndefined(); 84 | expect(map.size()).toBe(1); 85 | }); 86 | 87 | test('should handle deleting non-existent keys', () => { 88 | map.insert('one', 1); 89 | map.insert('two', 2); 90 | 91 | const initialSize = map.size(); 92 | map.delete('three'); 93 | 94 | expect(map.size()).toBe(initialSize); 95 | }); 96 | 97 | test('should clear all entries', () => { 98 | map.insert('one', 1); 99 | map.insert('two', 2); 100 | map.insert('three', 3); 101 | 102 | expect(map.isEmpty()).toBe(false); 103 | 104 | map.clear(); 105 | 106 | expect(map.isEmpty()).toBe(true); 107 | expect(map.size()).toBe(0); 108 | }); 109 | 110 | test('should handle repeated delete operations', () => { 111 | map.insert('test', 1); 112 | map.delete('test'); 113 | map.delete('test'); 114 | map.delete('test'); 115 | 116 | expect(map.size()).toBe(0); 117 | expect(map.find('test')).toBeUndefined(); 118 | }); 119 | 120 | test('should handle delete after clear', () => { 121 | map.insert('one', 1); 122 | map.clear(); 123 | map.delete('one'); 124 | 125 | expect(map.size()).toBe(0); 126 | expect(map.find('one')).toBeUndefined(); 127 | }); 128 | }); 129 | 130 | describe('iteration', () => { 131 | test('should iterate over all key-value pairs', () => { 132 | const entries: [string, number][] = [ 133 | ['one', 1], 134 | ['two', 2], 135 | ['three', 3] 136 | ]; 137 | 138 | entries.forEach(([key, value]) => map.insert(key, value)); 139 | 140 | const result: [string, number][] = []; 141 | map.forEach((key, value) => { 142 | result.push([key, value]); 143 | }); 144 | 145 | expect(result).toHaveLength(entries.length); 146 | entries.forEach(([key, value]) => { 147 | expect(result).toContainEqual([key, value]); 148 | }); 149 | }); 150 | 151 | test('should iterate in order based on keys', () => { 152 | map.insert('c', 3); 153 | map.insert('a', 1); 154 | map.insert('b', 2); 155 | 156 | const keys: string[] = []; 157 | map.forEach((key) => { 158 | keys.push(key); 159 | }); 160 | 161 | expect(keys).toEqual(['a', 'b', 'c']); 162 | }); 163 | 164 | test('should handle empty map', () => { 165 | const mockCallback = jest.fn(); 166 | map.forEach(mockCallback); 167 | expect(mockCallback).not.toHaveBeenCalled(); 168 | }); 169 | 170 | test('should handle iteration after modifications', () => { 171 | map.insert('a', 1); 172 | map.insert('b', 2); 173 | map.insert('c', 3); 174 | 175 | map.delete('b'); 176 | map.insert('d', 4); 177 | 178 | const result: string[] = []; 179 | map.forEach((key) => result.push(key)); 180 | 181 | expect(result).toEqual(['a', 'c', 'd']); 182 | }); 183 | 184 | test('should handle callback that modifies the map', () => { 185 | map.insert('a', 1); 186 | map.insert('b', 2); 187 | 188 | map.forEach((key, value) => { 189 | map.insert(key, value * 2); 190 | }); 191 | 192 | expect(map.find('a')).toBe(2); 193 | expect(map.find('b')).toBe(4); 194 | }); 195 | }); 196 | 197 | describe('equality', () => { 198 | test('should consider empty maps equal', () => { 199 | const map1 = new Map(); 200 | const map2 = new Map(); 201 | expect(map1.equals(map2)).toBe(true); 202 | }); 203 | 204 | test('should consider maps with same key-value pairs equal', () => { 205 | const map1 = new Map(); 206 | const map2 = new Map(); 207 | 208 | map1.insert('a', 1); 209 | map1.insert('b', 2); 210 | map1.insert('c', 3); 211 | 212 | map2.insert('a', 1); 213 | map2.insert('b', 2); 214 | map2.insert('c', 3); 215 | 216 | expect(map1.equals(map2)).toBe(true); 217 | }); 218 | 219 | test('should consider maps with different values for same keys unequal', () => { 220 | const map1 = new Map(); 221 | const map2 = new Map(); 222 | 223 | map1.insert('a', 1); 224 | map1.insert('b', 2); 225 | 226 | map2.insert('a', 1); 227 | map2.insert('b', 3); 228 | 229 | expect(map1.equals(map2)).toBe(false); 230 | }); 231 | 232 | test('should consider maps with different keys unequal', () => { 233 | const map1 = new Map(); 234 | const map2 = new Map(); 235 | 236 | map1.insert('a', 1); 237 | map1.insert('b', 2); 238 | 239 | map2.insert('a', 1); 240 | map2.insert('c', 2); 241 | 242 | expect(map1.equals(map2)).toBe(false); 243 | }); 244 | 245 | test('should consider maps with different sizes unequal', () => { 246 | const map1 = new Map(); 247 | const map2 = new Map(); 248 | 249 | map1.insert('a', 1); 250 | map1.insert('b', 2); 251 | 252 | map2.insert('a', 1); 253 | 254 | expect(map1.equals(map2)).toBe(false); 255 | }); 256 | 257 | test('should handle comparison with null/undefined', () => { 258 | const map1 = new Map(); 259 | map1.insert('a', 1); 260 | 261 | expect(map1.equals(null as any)).toBe(false); 262 | expect(map1.equals(undefined as any)).toBe(false); 263 | }); 264 | }); 265 | 266 | describe('edge cases', () => { 267 | test('should handle special characters in keys', () => { 268 | const specialKeys = ['!@#$', ' ', '\n\t', '']; 269 | specialKeys.forEach((key, index) => { 270 | map.insert(key, index); 271 | }); 272 | 273 | specialKeys.forEach((key, index) => { 274 | expect(map.find(key)).toBe(index); 275 | }); 276 | }); 277 | 278 | test('should handle operations after clear', () => { 279 | map.insert('a', 1); 280 | map.clear(); 281 | map.insert('b', 2); 282 | 283 | expect(map.size()).toBe(1); 284 | expect(map.find('a')).toBeUndefined(); 285 | expect(map.find('b')).toBe(2); 286 | }); 287 | }); 288 | }); 289 | -------------------------------------------------------------------------------- /tests/matrix.test.ts: -------------------------------------------------------------------------------- 1 | import { Matrix } from '../lib/matrix'; 2 | 3 | describe('Matrix', () => { 4 | let matrix: Matrix; 5 | 6 | beforeEach(() => { 7 | matrix = new Matrix(3, 3); 8 | }); 9 | 10 | describe('constructor', () => { 11 | it('should create an empty matrix of specified dimensions', () => { 12 | expect(matrix.rows()).toBe(3); 13 | expect(matrix.columns()).toBe(3); 14 | expect(matrix.isEmpty()).toBe(false); 15 | }); 16 | 17 | it('should create a matrix with zero dimensions', () => { 18 | const emptyMatrix = new Matrix(0, 0); 19 | expect(emptyMatrix.isEmpty()).toBe(true); 20 | expect(emptyMatrix.size()).toBe(0); 21 | }); 22 | }); 23 | 24 | describe('get/set operations', () => { 25 | it('should set and get values correctly', () => { 26 | matrix.set(0, 0, 1); 27 | matrix.set(1, 1, 2); 28 | matrix.set(2, 2, 3); 29 | 30 | expect(matrix.get(0, 0)).toBe(1); 31 | expect(matrix.get(1, 1)).toBe(2); 32 | expect(matrix.get(2, 2)).toBe(3); 33 | }); 34 | 35 | it('should return undefined for out of bounds access', () => { 36 | expect(matrix.get(-1, 0)).toBeUndefined(); 37 | expect(matrix.get(0, -1)).toBeUndefined(); 38 | expect(matrix.get(3, 0)).toBeUndefined(); 39 | expect(matrix.get(0, 3)).toBeUndefined(); 40 | }); 41 | 42 | it('should ignore out of bounds set operations', () => { 43 | matrix.set(-1, 0, 1); 44 | matrix.set(0, -1, 1); 45 | matrix.set(3, 0, 1); 46 | matrix.set(0, 3, 1); 47 | expect(matrix.toArray()).toEqual([ 48 | [undefined, undefined, undefined], 49 | [undefined, undefined, undefined], 50 | [undefined, undefined, undefined] 51 | ]); 52 | }); 53 | }); 54 | 55 | describe('matrix operations', () => { 56 | it('should fill matrix with a value', () => { 57 | matrix.fill(5); 58 | for (let i = 0; i < 3; i++) { 59 | for (let j = 0; j < 3; j++) { 60 | expect(matrix.get(i, j)).toBe(5); 61 | } 62 | } 63 | }); 64 | 65 | it('should clear matrix', () => { 66 | matrix.fill(5); 67 | matrix.clear(); 68 | for (let i = 0; i < 3; i++) { 69 | for (let j = 0; j < 3; j++) { 70 | expect(matrix.get(i, j)).toBeUndefined(); 71 | } 72 | } 73 | }); 74 | 75 | it('should transpose matrix correctly', () => { 76 | matrix.set(0, 1, 1); 77 | matrix.set(1, 0, 2); 78 | const transposed = matrix.transpose(); 79 | expect(transposed.get(1, 0)).toBe(1); 80 | expect(transposed.get(0, 1)).toBe(2); 81 | }); 82 | 83 | it('should add matrices correctly', () => { 84 | const other = new Matrix(3, 3); 85 | matrix.fill(1); 86 | other.fill(2); 87 | const result = matrix.add(other); 88 | for (let i = 0; i < 3; i++) { 89 | for (let j = 0; j < 3; j++) { 90 | expect(result.get(i, j)).toBe(3); 91 | } 92 | } 93 | }); 94 | 95 | it('should throw error when adding matrices of different dimensions', () => { 96 | const other = new Matrix(2, 3); 97 | matrix.fill(1); 98 | other.fill(2); 99 | expect(() => matrix.add(other)).toThrow('Matrix dimensions must match for addition'); 100 | }); 101 | 102 | it('should subtract matrices correctly', () => { 103 | const other = new Matrix(3, 3); 104 | matrix.fill(3); 105 | other.fill(1); 106 | const result = matrix.subtract(other); 107 | for (let i = 0; i < 3; i++) { 108 | for (let j = 0; j < 3; j++) { 109 | expect(result.get(i, j)).toBe(2); 110 | } 111 | } 112 | }); 113 | 114 | it('should throw error when subtracting matrices of different dimensions', () => { 115 | const other = new Matrix(2, 3); 116 | matrix.fill(1); 117 | other.fill(2); 118 | expect(() => matrix.subtract(other)).toThrow('Matrix dimensions must match for subtraction'); 119 | }); 120 | 121 | it('should multiply matrices correctly', () => { 122 | const other = new Matrix(3, 3); 123 | matrix.fill(2); 124 | other.fill(3); 125 | const result = matrix.multiply(other); 126 | for (let i = 0; i < 3; i++) { 127 | for (let j = 0; j < 3; j++) { 128 | expect(result.get(i, j)).toBe(18); // 2 * 3 * 3 (dot product) 129 | } 130 | } 131 | }); 132 | 133 | it('should throw error when multiplying incompatible matrices', () => { 134 | const other = new Matrix(2, 3); 135 | matrix.fill(1); 136 | other.fill(2); 137 | expect(() => matrix.multiply(other)).toThrow('Matrix dimensions must be compatible for multiplication'); 138 | }); 139 | 140 | it('should multiply by scalar correctly', () => { 141 | matrix.fill(2); 142 | const result = matrix.scalarMultiply(3); 143 | for (let i = 0; i < 3; i++) { 144 | for (let j = 0; j < 3; j++) { 145 | expect(result.get(i, j)).toBe(6); 146 | } 147 | } 148 | }); 149 | }); 150 | 151 | describe('utility operations', () => { 152 | it('should map values correctly', () => { 153 | matrix.fill(1); 154 | const result = matrix.map((value) => value * 2); 155 | for (let i = 0; i < 3; i++) { 156 | for (let j = 0; j < 3; j++) { 157 | expect(result.get(i, j)).toBe(2); 158 | } 159 | } 160 | }); 161 | 162 | it('should execute forEach correctly', () => { 163 | matrix.fill(1); 164 | let sum = 0; 165 | matrix.forEach((value) => { 166 | sum += value; 167 | }); 168 | expect(sum).toBe(9); 169 | }); 170 | 171 | it('should get/set rows correctly', () => { 172 | matrix.setRow(0, [1, 2, 3]); 173 | expect(matrix.getRow(0)).toEqual([1, 2, 3]); 174 | }); 175 | 176 | it('should ignore invalid row operations', () => { 177 | matrix.setRow(-1, [1, 2, 3]); 178 | matrix.setRow(3, [1, 2, 3]); 179 | matrix.setRow(0, [1, 2]); // Wrong length 180 | expect(matrix.getRow(-1)).toEqual([]); 181 | expect(matrix.getRow(3)).toEqual([]); 182 | }); 183 | 184 | it('should get/set columns correctly', () => { 185 | matrix.setColumn(0, [1, 2, 3]); 186 | expect(matrix.getColumn(0)).toEqual([1, 2, 3]); 187 | }); 188 | 189 | it('should ignore invalid column operations', () => { 190 | matrix.setColumn(-1, [1, 2, 3]); 191 | matrix.setColumn(3, [1, 2, 3]); 192 | expect(matrix.getColumn(-1)).toEqual([]); 193 | expect(matrix.getColumn(3)).toEqual([]); 194 | }); 195 | 196 | it('should convert to array correctly', () => { 197 | matrix.fill(1); 198 | const arr = matrix.toArray(); 199 | expect(arr).toEqual([ 200 | [1, 1, 1], 201 | [1, 1, 1], 202 | [1, 1, 1] 203 | ]); 204 | }); 205 | 206 | it('should clone matrix correctly', () => { 207 | matrix.fill(1); 208 | const clone = matrix.clone(); 209 | expect(clone.toArray()).toEqual(matrix.toArray()); 210 | 211 | // Verify deep copy 212 | matrix.set(0, 0, 2); 213 | expect(clone.get(0, 0)).toBe(1); 214 | }); 215 | 216 | it('should calculate size correctly', () => { 217 | expect(matrix.size()).toBe(9); 218 | const matrix2 = new Matrix(2, 4); 219 | expect(matrix2.size()).toBe(8); 220 | }); 221 | 222 | it('should check if matrix is square', () => { 223 | expect(matrix.isSquare()).toBe(true); 224 | const rectMatrix = new Matrix(2, 3); 225 | expect(rectMatrix.isSquare()).toBe(false); 226 | }); 227 | 228 | it('should check if matrix is symmetric', () => { 229 | matrix.set(0, 1, 2); 230 | matrix.set(1, 0, 2); 231 | matrix.set(0, 2, 3); 232 | matrix.set(2, 0, 3); 233 | matrix.set(1, 2, 4); 234 | matrix.set(2, 1, 4); 235 | expect(matrix.isSymmetric()).toBe(true); 236 | 237 | matrix.set(0, 1, 5); // Make asymmetric 238 | expect(matrix.isSymmetric()).toBe(false); 239 | }); 240 | 241 | it('should swap rows correctly', () => { 242 | matrix.setRow(0, [1, 2, 3]); 243 | matrix.setRow(1, [4, 5, 6]); 244 | matrix.swapRows(0, 1); 245 | expect(matrix.getRow(0)).toEqual([4, 5, 6]); 246 | expect(matrix.getRow(1)).toEqual([1, 2, 3]); 247 | }); 248 | 249 | it('should swap columns correctly', () => { 250 | matrix.setColumn(0, [1, 2, 3]); 251 | matrix.setColumn(1, [4, 5, 6]); 252 | matrix.swapColumns(0, 1); 253 | expect(matrix.getColumn(0)).toEqual([4, 5, 6]); 254 | expect(matrix.getColumn(1)).toEqual([1, 2, 3]); 255 | }); 256 | 257 | it('should extract submatrix correctly', () => { 258 | matrix.fill(1); 259 | matrix.set(0, 0, 2); 260 | matrix.set(0, 1, 3); 261 | matrix.set(1, 0, 4); 262 | matrix.set(1, 1, 5); 263 | const sub = matrix.submatrix(0, 0, 1, 1); 264 | expect(sub.rows()).toBe(2); 265 | expect(sub.columns()).toBe(2); 266 | expect(sub.get(0, 0)).toBe(2); 267 | expect(sub.get(0, 1)).toBe(3); 268 | expect(sub.get(1, 0)).toBe(4); 269 | expect(sub.get(1, 1)).toBe(5); 270 | }); 271 | 272 | it('should insert matrix correctly', () => { 273 | const small = new Matrix(2, 2); 274 | small.fill(5); 275 | matrix.fill(1); 276 | matrix.insertMatrix(small, 0, 0); 277 | expect(matrix.get(0, 0)).toBe(5); 278 | expect(matrix.get(0, 1)).toBe(5); 279 | expect(matrix.get(1, 0)).toBe(5); 280 | expect(matrix.get(1, 1)).toBe(5); 281 | expect(matrix.get(2, 2)).toBe(1); 282 | }); 283 | 284 | it('should get/set diagonal correctly', () => { 285 | matrix.setDiagonal([1, 2, 3]); 286 | expect(matrix.getDiagonal()).toEqual([1, 2, 3]); 287 | }); 288 | 289 | it('should calculate trace correctly', () => { 290 | matrix.setDiagonal([1, 2, 3]); 291 | expect(matrix.trace()).toBe(6); 292 | }); 293 | 294 | it('should check equality correctly', () => { 295 | const other = new Matrix(3, 3); 296 | matrix.fill(1); 297 | other.fill(1); 298 | expect(matrix.equals(other)).toBe(true); 299 | 300 | other.set(0, 0, 2); 301 | expect(matrix.equals(other)).toBe(false); 302 | 303 | const different = new Matrix(2, 3); 304 | expect(matrix.equals(different)).toBe(false); 305 | }); 306 | 307 | it('should resize matrix correctly', () => { 308 | matrix.fill(1); 309 | matrix.resize(2, 4); 310 | expect(matrix.rows()).toBe(2); 311 | expect(matrix.columns()).toBe(4); 312 | expect(matrix.get(0, 0)).toBe(1); 313 | expect(matrix.get(0, 3)).toBeUndefined(); 314 | }); 315 | }); 316 | }); 317 | -------------------------------------------------------------------------------- /tests/priority-queue.test.ts: -------------------------------------------------------------------------------- 1 | import { PriorityQueue } from '../lib/priority-queue'; 2 | 3 | describe('PriorityQueue', () => { 4 | let pq: PriorityQueue; 5 | let numPq: PriorityQueue; 6 | 7 | beforeEach(() => { 8 | pq = new PriorityQueue((a: string, b: string) => a < b); 9 | numPq = new PriorityQueue((a: number, b: number) => a < b); 10 | }); 11 | 12 | describe('initialization', () => { 13 | test('should create empty priority queue', () => { 14 | expect(pq.isEmpty()).toBe(true); 15 | expect(pq.size()).toBe(0); 16 | expect(numPq.isEmpty()).toBe(true); 17 | expect(numPq.size()).toBe(0); 18 | }); 19 | }); 20 | 21 | describe('push operations', () => { 22 | test('should push string elements', () => { 23 | pq.push('first'); 24 | pq.push('second'); 25 | pq.push('third'); 26 | expect(pq.size()).toBe(3); 27 | expect(pq.front()).toBe('first'); 28 | }); 29 | 30 | test('should push number elements', () => { 31 | numPq.push(3); 32 | numPq.push(1); 33 | numPq.push(2); 34 | expect(numPq.size()).toBe(3); 35 | expect(numPq.front()).toBe(1); 36 | }); 37 | 38 | test('should maintain order when pushing strings', () => { 39 | pq.push('c'); 40 | pq.push('a'); 41 | pq.push('b'); 42 | expect(pq.pop()).toBe('a'); 43 | expect(pq.pop()).toBe('b'); 44 | expect(pq.pop()).toBe('c'); 45 | }); 46 | 47 | test('should maintain order when pushing numbers', () => { 48 | numPq.push(30); 49 | numPq.push(10); 50 | numPq.push(20); 51 | expect(numPq.pop()).toBe(10); 52 | expect(numPq.pop()).toBe(20); 53 | expect(numPq.pop()).toBe(30); 54 | }); 55 | }); 56 | 57 | describe('pop operations', () => { 58 | test('should pop string elements in order', () => { 59 | pq.push('c'); 60 | pq.push('a'); 61 | pq.push('b'); 62 | expect(pq.pop()).toBe('a'); 63 | expect(pq.size()).toBe(2); 64 | expect(pq.pop()).toBe('b'); 65 | expect(pq.pop()).toBe('c'); 66 | expect(pq.isEmpty()).toBe(true); 67 | }); 68 | 69 | test('should pop number elements in order', () => { 70 | numPq.push(300); 71 | numPq.push(100); 72 | numPq.push(200); 73 | expect(numPq.pop()).toBe(100); 74 | expect(numPq.size()).toBe(2); 75 | expect(numPq.pop()).toBe(200); 76 | expect(numPq.pop()).toBe(300); 77 | expect(numPq.isEmpty()).toBe(true); 78 | }); 79 | 80 | test('should return undefined when popping empty queues', () => { 81 | expect(pq.pop()).toBeUndefined(); 82 | expect(numPq.pop()).toBeUndefined(); 83 | }); 84 | }); 85 | 86 | describe('front operations', () => { 87 | test('should return front string element without removing it', () => { 88 | pq.push('b'); 89 | pq.push('a'); 90 | expect(pq.front()).toBe('a'); 91 | expect(pq.size()).toBe(2); 92 | }); 93 | 94 | test('should return front number element without removing it', () => { 95 | numPq.push(20); 96 | numPq.push(10); 97 | expect(numPq.front()).toBe(10); 98 | expect(numPq.size()).toBe(2); 99 | }); 100 | 101 | test('should return undefined when checking front of empty queues', () => { 102 | expect(pq.front()).toBeUndefined(); 103 | expect(numPq.front()).toBeUndefined(); 104 | }); 105 | }); 106 | 107 | describe('clear operations', () => { 108 | test('should clear all string elements from queue', () => { 109 | pq.push('a'); 110 | pq.push('b'); 111 | pq.push('c'); 112 | pq.clear(); 113 | expect(pq.isEmpty()).toBe(true); 114 | expect(pq.size()).toBe(0); 115 | }); 116 | 117 | test('should clear all number elements from queue', () => { 118 | numPq.push(1); 119 | numPq.push(2); 120 | numPq.push(3); 121 | numPq.clear(); 122 | expect(numPq.isEmpty()).toBe(true); 123 | expect(numPq.size()).toBe(0); 124 | }); 125 | 126 | test('should handle pushing strings after clearing', () => { 127 | pq.push('x'); 128 | pq.push('y'); 129 | pq.clear(); 130 | pq.push('z'); 131 | expect(pq.size()).toBe(1); 132 | expect(pq.front()).toBe('z'); 133 | }); 134 | 135 | test('should handle pushing numbers after clearing', () => { 136 | numPq.push(1); 137 | numPq.push(2); 138 | numPq.clear(); 139 | numPq.push(3); 140 | expect(numPq.size()).toBe(1); 141 | expect(numPq.front()).toBe(3); 142 | }); 143 | }); 144 | 145 | describe('ordering behavior', () => { 146 | test('should handle string elements in order', () => { 147 | pq.push('a'); 148 | pq.push('b'); 149 | pq.push('c'); 150 | expect(pq.pop()).toBe('a'); 151 | expect(pq.pop()).toBe('b'); 152 | expect(pq.pop()).toBe('c'); 153 | }); 154 | 155 | test('should handle number elements in order', () => { 156 | numPq.push(10); 157 | numPq.push(20); 158 | numPq.push(30); 159 | expect(numPq.pop()).toBe(10); 160 | expect(numPq.pop()).toBe(20); 161 | expect(numPq.pop()).toBe(30); 162 | }); 163 | 164 | test('should handle mixed string insertions', () => { 165 | pq.push('c'); 166 | pq.push('a'); 167 | pq.push('b'); 168 | pq.push('d'); 169 | pq.push('e'); 170 | expect(pq.pop()).toBe('a'); 171 | expect(pq.pop()).toBe('b'); 172 | expect(pq.pop()).toBe('c'); 173 | expect(pq.pop()).toBe('d'); 174 | expect(pq.pop()).toBe('e'); 175 | }); 176 | 177 | test('should handle mixed number insertions', () => { 178 | numPq.push(30); 179 | numPq.push(10); 180 | numPq.push(20); 181 | numPq.push(40); 182 | numPq.push(50); 183 | expect(numPq.pop()).toBe(10); 184 | expect(numPq.pop()).toBe(20); 185 | expect(numPq.pop()).toBe(30); 186 | expect(numPq.pop()).toBe(40); 187 | expect(numPq.pop()).toBe(50); 188 | }); 189 | }); 190 | 191 | describe('size management', () => { 192 | test('should maintain size correctly after string operations', () => { 193 | expect(pq.size()).toBe(0); 194 | pq.push('x'); 195 | expect(pq.size()).toBe(1); 196 | pq.push('y'); 197 | expect(pq.size()).toBe(2); 198 | pq.pop(); 199 | expect(pq.size()).toBe(1); 200 | pq.clear(); 201 | expect(pq.size()).toBe(0); 202 | }); 203 | 204 | test('should maintain size correctly after number operations', () => { 205 | expect(numPq.size()).toBe(0); 206 | numPq.push(1); 207 | expect(numPq.size()).toBe(1); 208 | numPq.push(2); 209 | expect(numPq.size()).toBe(2); 210 | numPq.pop(); 211 | expect(numPq.size()).toBe(1); 212 | numPq.clear(); 213 | expect(numPq.size()).toBe(0); 214 | }); 215 | }); 216 | 217 | describe('performance', () => { 218 | test('should handle large number of string elements', () => { 219 | for (let i = 0; i < 1000; i++) { 220 | pq.push(`item${i.toString().padStart(4, '0')}`); 221 | } 222 | let count = 0; 223 | while (!pq.isEmpty()) { 224 | count++; 225 | pq.pop(); 226 | } 227 | expect(count).toBe(1000); 228 | }); 229 | 230 | test('should handle large number of number elements', () => { 231 | for (let i = 0; i < 1000; i++) { 232 | numPq.push(i); 233 | } 234 | let count = 0; 235 | while (!numPq.isEmpty()) { 236 | count++; 237 | numPq.pop(); 238 | } 239 | expect(count).toBe(1000); 240 | }); 241 | }); 242 | 243 | describe('equality', () => { 244 | test('should consider empty queues equal', () => { 245 | const pq1 = new PriorityQueue((a, b) => a < b); 246 | const pq2 = new PriorityQueue((a, b) => a < b); 247 | expect(pq1.equals(pq2)).toBe(true); 248 | }); 249 | 250 | test('should consider queues with same elements in same order equal', () => { 251 | const pq1 = new PriorityQueue((a, b) => a < b); 252 | const pq2 = new PriorityQueue((a, b) => a < b); 253 | 254 | pq1.push('a'); 255 | pq1.push('b'); 256 | pq1.push('c'); 257 | 258 | pq2.push('a'); 259 | pq2.push('b'); 260 | pq2.push('c'); 261 | 262 | expect(pq1.equals(pq2)).toBe(true); 263 | }); 264 | 265 | test('should consider queues with different elements unequal', () => { 266 | const pq1 = new PriorityQueue((a, b) => a < b); 267 | const pq2 = new PriorityQueue((a, b) => a < b); 268 | 269 | pq1.push('a'); 270 | pq1.push('b'); 271 | pq1.push('c'); 272 | 273 | pq2.push('a'); 274 | pq2.push('b'); 275 | pq2.push('d'); 276 | 277 | expect(pq1.equals(pq2)).toBe(false); 278 | }); 279 | 280 | test('should consider queues with different sizes unequal', () => { 281 | const pq1 = new PriorityQueue((a, b) => a < b); 282 | const pq2 = new PriorityQueue((a, b) => a < b); 283 | 284 | pq1.push('a'); 285 | pq1.push('b'); 286 | 287 | pq2.push('a'); 288 | pq2.push('b'); 289 | pq2.push('c'); 290 | 291 | expect(pq1.equals(pq2)).toBe(false); 292 | }); 293 | 294 | test('should handle comparison with null/undefined', () => { 295 | const pq1 = new PriorityQueue((a, b) => a < b); 296 | pq1.push('a'); 297 | 298 | expect(pq1.equals(null as any)).toBe(false); 299 | expect(pq1.equals(undefined as any)).toBe(false); 300 | }); 301 | 302 | test('should consider queues with different comparators unequal', () => { 303 | const pq1 = new PriorityQueue((a, b) => a < b); 304 | const pq2 = new PriorityQueue((a, b) => a > b); 305 | 306 | pq1.push('a'); 307 | pq1.push('b'); 308 | 309 | pq2.push('a'); 310 | pq2.push('b'); 311 | 312 | expect(pq1.equals(pq2)).toBe(false); 313 | }); 314 | }); 315 | }); 316 | -------------------------------------------------------------------------------- /tests/queue.test.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from '../lib/queue'; 2 | 3 | describe('Queue', () => { 4 | let queue: Queue; 5 | 6 | beforeEach(() => { 7 | queue = new Queue(); 8 | }); 9 | 10 | test('should create empty queue', () => { 11 | expect(queue.isEmpty()).toBe(true); 12 | expect(queue.size()).toBe(0); 13 | }); 14 | 15 | test('should push elements to queue', () => { 16 | queue.push(1); 17 | queue.push(2); 18 | expect(queue.size()).toBe(2); 19 | expect(queue.front()).toBe(1); 20 | }); 21 | 22 | test('should pop elements from queue', () => { 23 | queue.push(1); 24 | queue.push(2); 25 | expect(queue.pop()).toBe(1); 26 | expect(queue.size()).toBe(1); 27 | expect(queue.pop()).toBe(2); 28 | expect(queue.isEmpty()).toBe(true); 29 | }); 30 | 31 | test('should return undefined when popping empty queue', () => { 32 | expect(queue.pop()).toBeUndefined(); 33 | }); 34 | 35 | test('should return front element without removing it', () => { 36 | queue.push(1); 37 | queue.push(2); 38 | expect(queue.front()).toBe(1); 39 | expect(queue.size()).toBe(2); 40 | }); 41 | 42 | test('should return undefined when checking front of empty queue', () => { 43 | expect(queue.front()).toBeUndefined(); 44 | }); 45 | 46 | test('should clear all elements from queue', () => { 47 | queue.push(1); 48 | queue.push(2); 49 | queue.push(3); 50 | queue.clear(); 51 | expect(queue.isEmpty()).toBe(true); 52 | expect(queue.size()).toBe(0); 53 | }); 54 | 55 | describe('equality', () => { 56 | test('should consider empty queues equal', () => { 57 | const queue1 = new Queue(); 58 | const queue2 = new Queue(); 59 | expect(queue1.equals(queue2)).toBe(true); 60 | }); 61 | 62 | test('should consider queues with same elements in same order equal', () => { 63 | const queue1 = new Queue(); 64 | const queue2 = new Queue(); 65 | 66 | queue1.push(1); 67 | queue1.push(2); 68 | queue1.push(3); 69 | 70 | queue2.push(1); 71 | queue2.push(2); 72 | queue2.push(3); 73 | 74 | expect(queue1.equals(queue2)).toBe(true); 75 | }); 76 | 77 | test('should consider queues with different elements unequal', () => { 78 | const queue1 = new Queue(); 79 | const queue2 = new Queue(); 80 | 81 | queue1.push(1); 82 | queue1.push(2); 83 | queue1.push(3); 84 | 85 | queue2.push(1); 86 | queue2.push(2); 87 | queue2.push(4); 88 | 89 | expect(queue1.equals(queue2)).toBe(false); 90 | }); 91 | 92 | test('should consider queues with different sizes unequal', () => { 93 | const queue1 = new Queue(); 94 | const queue2 = new Queue(); 95 | 96 | queue1.push(1); 97 | queue1.push(2); 98 | queue2.push(1); 99 | 100 | expect(queue1.equals(queue2)).toBe(false); 101 | }); 102 | 103 | test('should handle comparison with null/undefined', () => { 104 | const queue1 = new Queue(); 105 | queue1.push(1); 106 | 107 | expect(queue1.equals(null as any)).toBe(false); 108 | expect(queue1.equals(undefined as any)).toBe(false); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /tests/red-black-tree.test.ts: -------------------------------------------------------------------------------- 1 | import { RedBlackTree } from '../lib/red-black-tree'; 2 | 3 | describe('RedBlackTree', () => { 4 | let tree: RedBlackTree; 5 | 6 | beforeEach(() => { 7 | tree = new RedBlackTree(); 8 | }); 9 | 10 | describe('Initialization', () => { 11 | test('should start empty', () => { 12 | expect(tree.isEmpty()).toBe(true); 13 | expect(tree.size()).toBe(0); 14 | }); 15 | 16 | test('should initialize with custom comparator', () => { 17 | const reverseTree = new RedBlackTree((a, b) => b < a); 18 | reverseTree.insert(1, 1); 19 | reverseTree.insert(2, 2); 20 | const values: number[] = []; 21 | reverseTree.forEach((_, value) => values.push(value)); 22 | expect(values).toEqual([2, 1]); 23 | }); 24 | }); 25 | 26 | describe('Insertion', () => { 27 | test('should insert values correctly', () => { 28 | tree.insert(5, 5); 29 | tree.insert(3, 3); 30 | tree.insert(7, 7); 31 | 32 | expect(tree.isEmpty()).toBe(false); 33 | expect(tree.size()).toBe(3); 34 | expect(tree.find(5)).toBe(5); 35 | expect(tree.find(3)).toBe(3); 36 | expect(tree.find(7)).toBe(7); 37 | expect(tree.find(1)).toBeUndefined(); 38 | }); 39 | 40 | test('should handle duplicate inserts', () => { 41 | tree.insert(5, 1); 42 | tree.insert(5, 2); 43 | tree.insert(5, 3); 44 | 45 | expect(tree.size()).toBe(1); 46 | expect(tree.find(5)).toBe(3); 47 | }); 48 | 49 | test('should handle large number of insertions', () => { 50 | const values = Array.from({ length: 1000 }, (_, i) => i); 51 | values.forEach(v => tree.insert(v, v)); 52 | expect(tree.size()).toBe(1000); 53 | 54 | // Verify some random values 55 | expect(tree.find(42)).toBe(42); 56 | expect(tree.find(999)).toBe(999); 57 | expect(tree.find(500)).toBe(500); 58 | }); 59 | }); 60 | 61 | describe('Removal', () => { 62 | test('should remove values correctly', () => { 63 | tree.insert(5, 5); 64 | tree.insert(3, 3); 65 | tree.insert(7, 7); 66 | tree.insert(1, 1); 67 | tree.insert(9, 9); 68 | 69 | expect(tree.size()).toBe(5); 70 | 71 | tree.remove(3); 72 | expect(tree.find(3)).toBeUndefined(); 73 | expect(tree.size()).toBe(4); 74 | 75 | tree.remove(5); 76 | expect(tree.find(5)).toBeUndefined(); 77 | expect(tree.size()).toBe(3); 78 | }); 79 | 80 | test('should handle removing non-existent values', () => { 81 | tree.insert(5, 5); 82 | tree.insert(3, 3); 83 | 84 | const initialSize = tree.size(); 85 | tree.remove(7); 86 | 87 | expect(tree.size()).toBe(initialSize); 88 | }); 89 | 90 | test('should handle removing root node', () => { 91 | tree.insert(5, 5); 92 | tree.insert(3, 3); 93 | tree.insert(7, 7); 94 | 95 | tree.remove(5); 96 | expect(tree.find(5)).toBeUndefined(); 97 | expect(tree.size()).toBe(2); 98 | expect(tree.find(3)).toBe(3); 99 | expect(tree.find(7)).toBe(7); 100 | }); 101 | 102 | test('should handle removing all nodes sequentially', () => { 103 | [5, 3, 7, 1, 9].forEach(v => tree.insert(v, v)); 104 | 105 | [5, 3, 7, 1, 9].forEach(v => { 106 | tree.remove(v); 107 | expect(tree.find(v)).toBeUndefined(); 108 | }); 109 | 110 | expect(tree.isEmpty()).toBe(true); 111 | }); 112 | }); 113 | 114 | describe('Clear operation', () => { 115 | test('should clear all nodes', () => { 116 | tree.insert(5, 5); 117 | tree.insert(3, 3); 118 | tree.insert(7, 7); 119 | 120 | expect(tree.isEmpty()).toBe(false); 121 | 122 | tree.clear(); 123 | 124 | expect(tree.isEmpty()).toBe(true); 125 | expect(tree.size()).toBe(0); 126 | }); 127 | 128 | test('should handle clear on empty tree', () => { 129 | tree.clear(); 130 | expect(tree.isEmpty()).toBe(true); 131 | expect(tree.size()).toBe(0); 132 | }); 133 | }); 134 | 135 | describe('Size management', () => { 136 | test('should maintain correct size after operations', () => { 137 | expect(tree.size()).toBe(0); 138 | 139 | tree.insert(1, 1); 140 | expect(tree.size()).toBe(1); 141 | 142 | tree.insert(2, 2); 143 | tree.insert(3, 3); 144 | expect(tree.size()).toBe(3); 145 | 146 | tree.remove(2); 147 | expect(tree.size()).toBe(2); 148 | 149 | tree.clear(); 150 | expect(tree.size()).toBe(0); 151 | }); 152 | 153 | test('should handle rapid insertions and deletions', () => { 154 | for (let i = 0; i < 100; i++) { 155 | tree.insert(i, i); 156 | } 157 | expect(tree.size()).toBe(100); 158 | 159 | for (let i = 0; i < 50; i++) { 160 | tree.remove(i); 161 | } 162 | expect(tree.size()).toBe(50); 163 | 164 | for (let i = 0; i < 25; i++) { 165 | tree.insert(i, i); 166 | } 167 | expect(tree.size()).toBe(75); 168 | }); 169 | }); 170 | 171 | describe('Iteration', () => { 172 | test('should iterate through values in order using forEach', () => { 173 | tree.insert(5, 50); 174 | tree.insert(3, 30); 175 | tree.insert(7, 70); 176 | tree.insert(1, 10); 177 | tree.insert(9, 90); 178 | 179 | const keys: number[] = []; 180 | const values: number[] = []; 181 | 182 | tree.forEach((key, value) => { 183 | keys.push(key); 184 | values.push(value); 185 | }); 186 | 187 | expect(keys).toEqual([1, 3, 5, 7, 9]); 188 | expect(values).toEqual([10, 30, 50, 70, 90]); 189 | }); 190 | 191 | test('should handle forEach on empty tree', () => { 192 | const values: number[] = []; 193 | tree.forEach((_, value) => values.push(value)); 194 | expect(values).toEqual([]); 195 | }); 196 | 197 | test('should maintain order after multiple operations', () => { 198 | const nums = [5, 3, 7, 1, 9, 2, 8, 4, 6]; 199 | nums.forEach(n => tree.insert(n, n * 10)); 200 | 201 | const values: number[] = []; 202 | tree.forEach((_, value) => values.push(value)); 203 | expect(values).toEqual([10, 20, 30, 40, 50, 60, 70, 80, 90]); 204 | }); 205 | }); 206 | 207 | describe('Equality', () => { 208 | test('should consider empty trees equal', () => { 209 | const tree1 = new RedBlackTree(); 210 | const tree2 = new RedBlackTree(); 211 | expect(tree1.equals(tree2)).toBe(true); 212 | }); 213 | 214 | test('should consider trees with same key-value pairs equal', () => { 215 | const tree1 = new RedBlackTree(); 216 | const tree2 = new RedBlackTree(); 217 | 218 | [5, 3, 7, 1, 9].forEach(v => { 219 | tree1.insert(v, v * 10); 220 | tree2.insert(v, v * 10); 221 | }); 222 | 223 | expect(tree1.equals(tree2)).toBe(true); 224 | }); 225 | 226 | test('should consider trees with different values unequal', () => { 227 | const tree1 = new RedBlackTree(); 228 | const tree2 = new RedBlackTree(); 229 | 230 | [5, 3, 7].forEach(v => tree1.insert(v, v * 10)); 231 | [5, 3, 7].forEach(v => tree2.insert(v, v * 20)); 232 | 233 | expect(tree1.equals(tree2)).toBe(false); 234 | }); 235 | 236 | test('should consider trees with different keys unequal', () => { 237 | const tree1 = new RedBlackTree(); 238 | const tree2 = new RedBlackTree(); 239 | 240 | [5, 3, 7].forEach(v => tree1.insert(v, v)); 241 | [5, 3, 8].forEach(v => tree2.insert(v, v)); 242 | 243 | expect(tree1.equals(tree2)).toBe(false); 244 | }); 245 | 246 | test('should consider trees with different sizes unequal', () => { 247 | const tree1 = new RedBlackTree(); 248 | const tree2 = new RedBlackTree(); 249 | 250 | [5, 3, 7].forEach(v => tree1.insert(v, v)); 251 | [5, 3].forEach(v => tree2.insert(v, v)); 252 | 253 | expect(tree1.equals(tree2)).toBe(false); 254 | }); 255 | }); 256 | }); 257 | -------------------------------------------------------------------------------- /tests/set.test.ts: -------------------------------------------------------------------------------- 1 | import { Set } from '../lib/set'; 2 | 3 | describe('Set', () => { 4 | let set: Set; 5 | 6 | beforeEach(() => { 7 | set = new Set(); 8 | }); 9 | 10 | describe('Initialization', () => { 11 | test('should start empty', () => { 12 | expect(set.isEmpty()).toBe(true); 13 | expect(set.size()).toBe(0); 14 | }); 15 | 16 | test('should initialize with custom comparator', () => { 17 | const customSet = new Set((a, b) => a > b); 18 | expect(customSet.isEmpty()).toBe(true); 19 | expect(customSet.size()).toBe(0); 20 | }); 21 | }); 22 | 23 | describe('Single Element Operations', () => { 24 | test('should insert elements correctly', () => { 25 | set.insert(1); 26 | expect(set.size()).toBe(1); 27 | expect(set.find(1)).toBe(true); 28 | 29 | // Duplicate insertion should not increase size 30 | set.insert(1); 31 | expect(set.size()).toBe(1); 32 | expect(set.find(1)).toBe(true); 33 | 34 | set.insert(2); 35 | expect(set.size()).toBe(2); 36 | expect(set.find(2)).toBe(true); 37 | 38 | // Test inserting many elements 39 | for (let i = 3; i <= 10; i++) { 40 | set.insert(i); 41 | expect(set.find(i)).toBe(true); 42 | } 43 | expect(set.size()).toBe(10); 44 | 45 | // Verify all elements are still findable 46 | for (let i = 1; i <= 10; i++) { 47 | expect(set.find(i)).toBe(true); 48 | } 49 | }); 50 | 51 | test('should remove elements correctly', () => { 52 | set.insert(1); 53 | set.insert(2); 54 | set.insert(3); 55 | 56 | expect(set.remove(2)); 57 | expect(set.size()).toBe(2); 58 | expect(set.find(2)).toBe(false); 59 | expect(set.find(1)).toBe(true); 60 | expect(set.find(3)).toBe(true); 61 | 62 | // Removing non-existent element should return false 63 | expect(set.remove(4)); 64 | expect(set.size()).toBe(2); 65 | 66 | // Remove remaining elements 67 | expect(set.remove(1)); 68 | expect(set.remove(3)); 69 | expect(set.isEmpty()).toBe(true); 70 | 71 | // Try removing from empty set 72 | expect(set.remove(1)); 73 | }); 74 | 75 | test('should find elements correctly', () => { 76 | // Empty set should not find any elements 77 | expect(set.find(1)).toBe(false); 78 | 79 | // Test finding elements after insertion 80 | set.insert(1); 81 | set.insert(2); 82 | set.insert(3); 83 | expect(set.find(1)).toBe(true); 84 | expect(set.find(2)).toBe(true); 85 | expect(set.find(3)).toBe(true); 86 | expect(set.find(4)).toBe(false); 87 | 88 | // Test finding after removal 89 | set.remove(2); 90 | expect(set.find(2)).toBe(false); 91 | 92 | // Test finding after clear 93 | set.clear(); 94 | expect(set.find(1)).toBe(false); 95 | expect(set.find(2)).toBe(false); 96 | expect(set.find(3)).toBe(false); 97 | }); 98 | 99 | test('should handle has() alias correctly', () => { 100 | set.insert(1); 101 | expect(set.has(1)).toBe(true); 102 | expect(set.has(2)).toBe(false); 103 | 104 | set.remove(1); 105 | expect(set.has(1)).toBe(false); 106 | }); 107 | }); 108 | 109 | describe('Bulk Operations', () => { 110 | test('should clear all elements', () => { 111 | set.insert(1); 112 | set.insert(2); 113 | set.insert(3); 114 | 115 | set.clear(); 116 | expect(set.isEmpty()).toBe(true); 117 | expect(set.size()).toBe(0); 118 | expect(set.find(1)).toBe(false); 119 | 120 | // Clear empty set 121 | set.clear(); 122 | expect(set.isEmpty()).toBe(true); 123 | }); 124 | 125 | test('should insert multiple elements with insertList', () => { 126 | const elements = [1, 2, 3, 4, 5]; 127 | set.insertList(elements); 128 | 129 | expect(set.size()).toBe(elements.length); 130 | elements.forEach(el => { 131 | expect(set.find(el)).toBe(true); 132 | }); 133 | 134 | // Insert another list 135 | const moreElements = [6, 7, 8]; 136 | set.insertList(moreElements); 137 | expect(set.size()).toBe(elements.length + moreElements.length); 138 | }); 139 | 140 | test('should handle duplicate elements in insertList', () => { 141 | const elements = [1, 1, 2, 2, 3]; 142 | set.insertList(elements); 143 | 144 | expect(set.size()).toBe(3); // Only unique elements should be inserted 145 | expect(set.find(1)).toBe(true); 146 | expect(set.find(2)).toBe(true); 147 | expect(set.find(3)).toBe(true); 148 | 149 | // Add more duplicates 150 | set.insertList([1, 2, 3, 3, 3]); 151 | expect(set.size()).toBe(3); 152 | }); 153 | 154 | test('should handle empty array in insertList', () => { 155 | set.insertList([]); 156 | expect(set.isEmpty()).toBe(true); 157 | expect(set.size()).toBe(0); 158 | 159 | // Add elements then empty array 160 | set.insert(1); 161 | set.insertList([]); 162 | expect(set.size()).toBe(1); 163 | }); 164 | }); 165 | 166 | describe('Iteration', () => { 167 | test('should iterate over elements with forEach', () => { 168 | const elements = [1, 2, 3, 4, 5]; 169 | elements.forEach(el => set.insert(el)); 170 | 171 | const result: number[] = []; 172 | set.forEach(value => result.push(value)); 173 | 174 | expect(result.length).toBe(elements.length); 175 | expect(result.sort()).toEqual(elements.sort()); 176 | 177 | // Test with callback that modifies external state 178 | let sum = 0; 179 | set.forEach(value => sum += value); 180 | expect(sum).toBe(15); 181 | }); 182 | 183 | test('should handle forEach on empty set', () => { 184 | const result: number[] = []; 185 | set.forEach(value => result.push(value)); 186 | expect(result).toEqual([]); 187 | 188 | // Test with callback that throws 189 | expect(() => { 190 | set.forEach(() => { throw new Error('Should not be called'); }); 191 | }).not.toThrow(); 192 | }); 193 | }); 194 | 195 | describe('Type Support', () => { 196 | test('should work with different data types', () => { 197 | const stringSet = new Set(); 198 | stringSet.insert('hello'); 199 | stringSet.insert('world'); 200 | 201 | expect(stringSet.size()).toBe(2); 202 | expect(stringSet.find('hello')).toBe(true); 203 | expect(stringSet.find('nonexistent')).toBe(false); 204 | 205 | // Test with empty string 206 | stringSet.insert(''); 207 | expect(stringSet.find('')).toBe(true); 208 | 209 | // Test with special characters 210 | stringSet.insert('🚀'); 211 | stringSet.insert('\n\t'); 212 | expect(stringSet.find('🚀')).toBe(true); 213 | expect(stringSet.find('\n\t')).toBe(true); 214 | }); 215 | 216 | test('should work with custom comparator', () => { 217 | const reverseSet = new Set((a, b) => a > b); 218 | reverseSet.insert(1); 219 | reverseSet.insert(2); 220 | reverseSet.insert(3); 221 | 222 | expect(reverseSet.size()).toBe(3); 223 | expect(reverseSet.find(2)).toBe(true); 224 | 225 | const result: number[] = []; 226 | reverseSet.forEach(value => result.push(value)); 227 | expect(result).toEqual([3, 2, 1]); // Should be in reverse order 228 | }); 229 | }); 230 | 231 | describe('Equality', () => { 232 | test('should consider empty sets equal', () => { 233 | const set1 = new Set(); 234 | const set2 = new Set(); 235 | expect(set1.equals(set2)).toBe(true); 236 | }); 237 | 238 | test('should consider sets with same elements equal', () => { 239 | const set1 = new Set(); 240 | const set2 = new Set(); 241 | 242 | [1, 2, 3, 4, 5].forEach(n => { 243 | set1.insert(n); 244 | set2.insert(n); 245 | }); 246 | 247 | expect(set1.equals(set2)).toBe(true); 248 | }); 249 | 250 | test('should consider sets with different elements unequal', () => { 251 | const set1 = new Set(); 252 | const set2 = new Set(); 253 | 254 | [1, 2, 3].forEach(n => set1.insert(n)); 255 | [1, 2, 4].forEach(n => set2.insert(n)); 256 | 257 | expect(set1.equals(set2)).toBe(false); 258 | }); 259 | 260 | test('should consider sets with different sizes unequal', () => { 261 | const set1 = new Set(); 262 | const set2 = new Set(); 263 | 264 | [1, 2, 3].forEach(n => set1.insert(n)); 265 | [1, 2].forEach(n => set2.insert(n)); 266 | 267 | expect(set1.equals(set2)).toBe(false); 268 | }); 269 | 270 | test('should handle equality with custom comparators', () => { 271 | const set1 = new Set((a, b) => a > b); 272 | const set2 = new Set((a, b) => a > b); 273 | 274 | [1, 2, 3].forEach(n => { 275 | set1.insert(n); 276 | set2.insert(n); 277 | }); 278 | 279 | expect(set1.equals(set2)).toBe(true); 280 | }); 281 | }); 282 | }); 283 | -------------------------------------------------------------------------------- /tests/stack.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from '../lib/stack'; 2 | 3 | describe('Stack', () => { 4 | let stack: Stack; 5 | 6 | beforeEach(() => { 7 | stack = new Stack(); 8 | }); 9 | 10 | describe('initialization', () => { 11 | test('should create empty stack', () => { 12 | expect(stack.isEmpty()).toBe(true); 13 | expect(stack.size()).toBe(0); 14 | expect(stack.top()).toBeUndefined(); 15 | }); 16 | }); 17 | 18 | describe('push operations', () => { 19 | test('should push elements to stack', () => { 20 | stack.push(1); 21 | stack.push(2); 22 | expect(stack.size()).toBe(2); 23 | expect(stack.top()).toBe(2); 24 | }); 25 | 26 | test('should push multiple elements in correct order', () => { 27 | const elements = [1, 2, 3, 4, 5]; 28 | elements.forEach(elem => stack.push(elem)); 29 | 30 | for (let i = elements.length - 1; i >= 0; i--) { 31 | expect(stack.pop()).toBe(elements[i]); 32 | } 33 | }); 34 | 35 | test('should handle pushing after clearing', () => { 36 | stack.push(1); 37 | stack.clear(); 38 | stack.push(2); 39 | expect(stack.top()).toBe(2); 40 | expect(stack.size()).toBe(1); 41 | }); 42 | }); 43 | 44 | describe('pop operations', () => { 45 | test('should pop elements from stack', () => { 46 | stack.push(1); 47 | stack.push(2); 48 | expect(stack.pop()).toBe(2); 49 | expect(stack.size()).toBe(1); 50 | expect(stack.pop()).toBe(1); 51 | expect(stack.isEmpty()).toBe(true); 52 | }); 53 | 54 | test('should return undefined when popping empty stack', () => { 55 | expect(stack.pop()).toBeUndefined(); 56 | }); 57 | 58 | test('should handle multiple pop operations on empty stack', () => { 59 | expect(stack.pop()).toBeUndefined(); 60 | expect(stack.pop()).toBeUndefined(); 61 | expect(stack.isEmpty()).toBe(true); 62 | }); 63 | 64 | test('should handle push after pop operations', () => { 65 | stack.push(1); 66 | stack.pop(); 67 | stack.push(2); 68 | expect(stack.top()).toBe(2); 69 | expect(stack.size()).toBe(1); 70 | }); 71 | }); 72 | 73 | describe('top operations', () => { 74 | test('should return top element without removing it', () => { 75 | stack.push(1); 76 | stack.push(2); 77 | expect(stack.top()).toBe(2); 78 | expect(stack.size()).toBe(2); 79 | }); 80 | 81 | test('should return undefined when checking top of empty stack', () => { 82 | expect(stack.top()).toBeUndefined(); 83 | }); 84 | 85 | test('should consistently return same top element', () => { 86 | stack.push(42); 87 | expect(stack.top()).toBe(42); 88 | expect(stack.top()).toBe(42); 89 | expect(stack.size()).toBe(1); 90 | }); 91 | }); 92 | 93 | describe('clear operations', () => { 94 | test('should clear all elements from stack', () => { 95 | stack.push(1); 96 | stack.push(2); 97 | stack.push(3); 98 | stack.clear(); 99 | expect(stack.isEmpty()).toBe(true); 100 | expect(stack.size()).toBe(0); 101 | }); 102 | 103 | test('should handle clear on empty stack', () => { 104 | stack.clear(); 105 | expect(stack.isEmpty()).toBe(true); 106 | expect(stack.size()).toBe(0); 107 | }); 108 | 109 | test('should handle multiple clear operations', () => { 110 | stack.push(1); 111 | stack.clear(); 112 | stack.clear(); 113 | expect(stack.isEmpty()).toBe(true); 114 | expect(stack.size()).toBe(0); 115 | }); 116 | }); 117 | 118 | describe('size operations', () => { 119 | test('should correctly report size', () => { 120 | expect(stack.size()).toBe(0); 121 | stack.push(1); 122 | expect(stack.size()).toBe(1); 123 | stack.push(2); 124 | expect(stack.size()).toBe(2); 125 | stack.pop(); 126 | expect(stack.size()).toBe(1); 127 | }); 128 | 129 | test('should handle size after various operations', () => { 130 | stack.push(1); 131 | stack.push(2); 132 | stack.pop(); 133 | stack.push(3); 134 | stack.push(4); 135 | stack.pop(); 136 | expect(stack.size()).toBe(2); 137 | }); 138 | }); 139 | 140 | describe('edge cases', () => { 141 | test('should handle alternating push/pop operations', () => { 142 | stack.push(1); 143 | expect(stack.pop()).toBe(1); 144 | stack.push(2); 145 | expect(stack.pop()).toBe(2); 146 | expect(stack.isEmpty()).toBe(true); 147 | }); 148 | 149 | test('should handle large number of operations', () => { 150 | const iterations = 1000; 151 | for (let i = 0; i < iterations; i++) { 152 | stack.push(i); 153 | } 154 | expect(stack.size()).toBe(iterations); 155 | for (let i = iterations - 1; i >= 0; i--) { 156 | expect(stack.pop()).toBe(i); 157 | } 158 | expect(stack.isEmpty()).toBe(true); 159 | }); 160 | }); 161 | 162 | describe('equality', () => { 163 | test('should consider empty stacks equal', () => { 164 | const stack1 = new Stack(); 165 | const stack2 = new Stack(); 166 | expect(stack1.equals(stack2)).toBe(true); 167 | }); 168 | 169 | test('should consider stacks with same elements in same order equal', () => { 170 | const stack1 = new Stack(); 171 | const stack2 = new Stack(); 172 | 173 | [1, 2, 3].forEach(v => { 174 | stack1.push(v); 175 | stack2.push(v); 176 | }); 177 | 178 | expect(stack1.equals(stack2)).toBe(true); 179 | }); 180 | 181 | test('should consider stacks with different sizes unequal', () => { 182 | const stack1 = new Stack(); 183 | const stack2 = new Stack(); 184 | 185 | stack1.push(1); 186 | stack1.push(2); 187 | stack2.push(1); 188 | 189 | expect(stack1.equals(stack2)).toBe(false); 190 | }); 191 | 192 | test('should consider stacks with same size but different elements unequal', () => { 193 | const stack1 = new Stack(); 194 | const stack2 = new Stack(); 195 | 196 | stack1.push(1); 197 | stack1.push(2); 198 | stack2.push(1); 199 | stack2.push(3); 200 | 201 | expect(stack1.equals(stack2)).toBe(false); 202 | }); 203 | 204 | test('should consider stacks with same elements in different order unequal', () => { 205 | const stack1 = new Stack(); 206 | const stack2 = new Stack(); 207 | 208 | stack1.push(1); 209 | stack1.push(2); 210 | stack2.push(2); 211 | stack2.push(1); 212 | 213 | expect(stack1.equals(stack2)).toBe(false); 214 | }); 215 | }); 216 | }); 217 | -------------------------------------------------------------------------------- /tests/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { HashUtils } from '../lib/hash-utils'; 2 | import { Utils } from '../lib/utils'; 3 | 4 | describe('Utils', () => { 5 | describe('equals', () => { 6 | test('should handle primitive types', () => { 7 | expect(Utils.equals(42, 42)).toBe(true); 8 | expect(Utils.equals(42, 43)).toBe(false); 9 | expect(Utils.equals('test', 'test')).toBe(true); 10 | expect(Utils.equals('test', 'other')).toBe(false); 11 | expect(Utils.equals(true, true)).toBe(true); 12 | expect(Utils.equals(true, false)).toBe(false); 13 | expect(Utils.equals(0, -0)).toBe(true); 14 | expect(Utils.equals(NaN, NaN)).toBe(true); 15 | }); 16 | 17 | test('should handle objects with equals method', () => { 18 | const obj1 = { 19 | equals: (other: any) => other.value === 42, 20 | value: 42 21 | }; 22 | const obj2 = { value: 42 }; 23 | const obj3 = { value: 43 }; 24 | expect(Utils.equals(obj1, obj2)).toBe(true); 25 | expect(Utils.equals(obj1, obj3)).toBe(false); 26 | }); 27 | 28 | test('should handle dates', () => { 29 | const date1 = new Date('2023-01-01'); 30 | const date2 = new Date('2023-01-01'); 31 | const date3 = new Date('2023-12-31'); 32 | expect(Utils.equals(date1, date2)).toBe(true); 33 | expect(Utils.equals(date1, date3)).toBe(false); 34 | expect(Utils.equals(new Date('Invalid Date'), new Date('Invalid Date'))).toBe(true); 35 | }); 36 | 37 | test('should handle arrays', () => { 38 | expect(Utils.equals([1, 2, 3], [1, 2, 3])).toBe(true); 39 | expect(Utils.equals([1, 2, 3], [1, 2, 4])).toBe(false); 40 | expect(Utils.equals([1, 2], [1, 2, 3])).toBe(false); 41 | expect(Utils.equals([], [])).toBe(true); 42 | expect(Utils.equals([[1, 2], [3, 4]], [[1, 2], [3, 4]])).toBe(true); 43 | expect(Utils.equals([null, undefined], [null, undefined])).toBe(true); 44 | }); 45 | 46 | test('should handle nested objects', () => { 47 | const obj1 = { a: 1, b: { c: 2 } }; 48 | const obj2 = { a: 1, b: { c: 2 } }; 49 | const obj3 = { a: 1, b: { c: 3 } }; 50 | expect(Utils.equals(obj1, obj2)).toBe(true); 51 | expect(Utils.equals(obj1, obj3)).toBe(false); 52 | expect(Utils.equals({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })).toBe(true); 53 | expect(Utils.equals({}, {})).toBe(true); 54 | }); 55 | 56 | test('should handle null and undefined', () => { 57 | expect(Utils.equals(null, null)).toBe(true); 58 | expect(Utils.equals(undefined, undefined)).toBe(true); 59 | expect(Utils.equals(null, undefined)).toBe(false); 60 | expect(Utils.equals(null, 'test')).toBe(false); 61 | expect(Utils.equals(undefined, 0)).toBe(false); 62 | expect(Utils.equals(null, false)).toBe(false); 63 | }); 64 | 65 | test('should handle regular expressions', () => { 66 | expect(Utils.equals(/test/, /test/)).toBe(true); 67 | expect(Utils.equals(/test/i, /test/i)).toBe(true); 68 | expect(Utils.equals(/test/, /other/)).toBe(false); 69 | expect(Utils.equals(/test/i, /test/g)).toBe(false); 70 | expect(Utils.equals(/[a-z]+/gim, /[a-z]+/gim)).toBe(true); 71 | expect(Utils.equals(/\d+/, /\d+/)).toBe(true); 72 | }); 73 | 74 | test('should handle special cases', () => { 75 | expect(Utils.equals(Symbol('test'), Symbol('test'))).toBe(false); 76 | expect(Utils.equals(BigInt(42), BigInt(42))).toBe(true); 77 | expect(Utils.equals(() => { }, () => { })).toBe(false); 78 | expect(Utils.equals(new Set([1, 2]), new Set([1, 2]))).toBe(true); 79 | expect(Utils.equals(new Map([[1, 'a']]), new Map([[1, 'a']]))).toBe(true); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "declaration": true, 6 | "allowJs": true, 7 | "strict": true, 8 | "noImplicitAny": false, 9 | "moduleResolution": "NodeNext", 10 | "allowSyntheticDefaultImports": true, 11 | "sourceMap": true, 12 | "outDir": "dist", 13 | "lib": ["ES2022"] 14 | }, 15 | "exclude": [ 16 | "benchmarks", 17 | "dist", 18 | "node_modules", 19 | "doc", 20 | "tests", 21 | "*.test.ts" 22 | ] 23 | } -------------------------------------------------------------------------------- /types/index.ts: -------------------------------------------------------------------------------- 1 | export type Comparator = (a: T, b: T) => boolean; --------------------------------------------------------------------------------