├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── renovate.json ├── src ├── algorithms │ ├── graphs │ │ ├── graph-node.ts │ │ └── shortest-paths │ │ │ ├── bellman-ford-shortest-path.ts │ │ │ ├── dijkstras-shortest-path.ts │ │ │ └── floyd-warshall-shortest-path.ts │ ├── search │ │ ├── binary-search.ts │ │ ├── breadth-first-search.ts │ │ └── depth-first-search.ts │ └── sort │ │ ├── bucket-sort.ts │ │ ├── counting-sort.ts │ │ ├── heap-sort.ts │ │ ├── merge-sort.ts │ │ ├── quick-sort.ts │ │ ├── topological-sort-dfs.ts │ │ └── topological-sort-kahn.ts ├── data-structures │ ├── hash-tables │ │ ├── entry.ts │ │ ├── hash-table-double-hashing.ts │ │ ├── hash-table-linear-probing.ts │ │ ├── hash-table-open-addressing-base.ts │ │ ├── hash-table-quadratic-probing.ts │ │ ├── hash-table-separate-chaining.ts │ │ └── index.ts │ ├── index.ts │ ├── priority-queues │ │ ├── index.ts │ │ ├── mergeable-heaps │ │ │ ├── binomial-node.ts │ │ │ ├── fibonacci-node.ts │ │ │ ├── index.ts │ │ │ ├── lazy-min-binomial-heap.ts │ │ │ ├── min-binomial-heap.ts │ │ │ └── min-fibonacci-heap.ts │ │ ├── min-binary-heap.ts │ │ ├── min-d-heap.ts │ │ └── min-indexed-d-heap.ts │ ├── sequences │ │ ├── circular-buffer │ │ │ ├── circular-buffer.ts │ │ │ └── index.ts │ │ ├── linked-list │ │ │ ├── index.ts │ │ │ ├── linked-list-node.ts │ │ │ └── linked-list.ts │ │ ├── queue │ │ │ ├── deque.ts │ │ │ ├── index.ts │ │ │ └── queue.ts │ │ └── stack │ │ │ ├── index.ts │ │ │ └── stack.ts │ ├── trees │ │ ├── avl-tree │ │ │ ├── avl-tree-node.ts │ │ │ └── index.ts │ │ ├── b-tree │ │ │ ├── b-tree-node.ts │ │ │ └── b-tree.ts │ │ ├── binary-search-tree │ │ │ ├── index.ts │ │ │ └── tree-node.ts │ │ ├── index.ts │ │ └── red-black-tree │ │ │ ├── index.ts │ │ │ └── red-black-tree-node.ts │ └── utils.ts └── index.ts ├── test ├── algorithms │ ├── graph │ │ └── shortest-paths │ │ │ ├── bellman-ford-shortest-path.test.ts │ │ │ ├── dijkstras-shortest-path.test.ts │ │ │ └── floyd-warshall-shortest-path.test.ts │ ├── search │ │ ├── binary-search.test.ts │ │ ├── breadth-first-search.test.ts │ │ └── depth-first-search.test.ts │ └── sort │ │ ├── bucket-sort.test.ts │ │ ├── counting-sort.test.ts │ │ ├── heap-sort.test.ts │ │ ├── merge-sort.test.ts │ │ ├── quick-sort.test.ts │ │ ├── test-cases.ts │ │ └── topological-sort.test.ts ├── data-structures │ ├── heaps │ │ ├── fibonacci-heap.test.ts │ │ ├── lazy-min-binomial-heap.test.ts │ │ ├── min-binary-heap.test.ts │ │ ├── min-binomial-heap.test.ts │ │ ├── min-d-heap.test.ts │ │ └── min-indexed-d-heap.test.ts │ ├── sequences │ │ ├── circular-buffer.test.ts │ │ ├── deque.test.ts │ │ ├── linked-list.test.ts │ │ ├── queue.test.ts │ │ └── stack.test.ts │ └── trees │ │ ├── avl-tree.test.ts │ │ ├── b-tree.test.ts │ │ └── binary-search-tree.test.ts └── utils.test.ts ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | lib 5 | # don't lint nyc coverage output 6 | coverage 7 | # don't lint readme 8 | README.md 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint'], 5 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 6 | } 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jeffzh4ng 4 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: '12.x' 17 | 18 | - name: Get yarn cache directory path 19 | id: yarn-cache-dir-path 20 | run: echo "::set-output name=dir::$(yarn cache dir)" 21 | - uses: actions/cache@v2 22 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 23 | with: 24 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 25 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 26 | restore-keys: | 27 | ${{ runner .os }}-yarn- 28 | 29 | - run: yarn 30 | - run: yarn build 31 | - run: yarn test 32 | - run: yarn codecov -t ${{ secrets.CODECOV_TOKEN }} 33 | - run: yarn semantic-release 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | lint: 7 | name: Tests 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | - uses: actions/setup-node@v1 12 | with: 13 | node-version: '12.x' 14 | 15 | - name: Get yarn cache directory path 16 | id: yarn-cache-dir-path 17 | run: echo "::set-output name=dir::$(yarn cache dir)" 18 | - uses: actions/cache@v2 19 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 20 | with: 21 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 22 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 23 | restore-keys: | 24 | ${{ runner .os }}-yarn- 25 | 26 | - run: yarn 27 | - run: yarn build 28 | - run: yarn test 29 | - run: yarn codecov -t ${{ secrets.CODECOV_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /node_modules 3 | /coverage 4 | .vscode 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | trailingComma: "es5" 2 | tabWidth: 2 3 | semi: false 4 | singleQuote: true 5 | printWidth: 100 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeff Zhang 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 | - this repo is archived. 2 | - videos are still useful conceptually 3 | - but implementations contain bugs (not fuzzed) 4 | - checkout picoprob for dsa implemented in rust 5 | - this will be actively maintained to support cp 6 | 7 | # iruka 8 | ![](https://i.imgur.com/q6N56c9.png) 9 | 10 | ![](https://img.shields.io/npm/v/dsa-ts) 11 | [![Tests](https://github.com/jeffzh4ng/dsa-ts/workflows/Tests/badge.svg)](https://github.com/jeffzh4ng/algorithms-and-data-structures/actions?query=branch%3Amaster++) 12 | ![](https://img.shields.io/codecov/c/github/jeffzh4ng/dsa-ts) 13 | ![](https://img.shields.io/github/license/jeffzh4ng/algorithms-and-data-structures) 14 | 15 | A collection of classical [data structures](https://github.com/jeffzh4ng/dsa-ts#data-structures) and [algorithms](https://github.com/jeffzh4ng/dsa-ts#algorithms) implemented in Typescript. Click the 📹 emoji for tutorials. 16 | 17 | The repository's primary goal is educational. Hence, all implementations include a prolific number of comments which guide the reader. The name of the project, iruka, is an ode to Iruka sensei from Naruto. He became a teacher to pass on the Will of Fire, and teach the future ninja of the leaf village. Likewise, this project is here to teach the future software engineers of earth. 18 | 19 | You can use this package in your projects if you so wish. Test coverage will be kept at 100%. To install the package, use npm or yarn: 20 | 21 | ``` 22 | yarn add dsa-ts 23 | ``` 24 | 25 | ## Data Structures 26 | - [x] Sequences 27 | - [x] [📹](https://www.youtube.com/watch?v=oXXLFvtG6-Q&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=5) [Linked List](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/data-structures/sequences/linked-list/linked-list.ts) 28 | - [x] [📹](https://www.youtube.com/watch?v=7l4YHzc3iXU&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=7) [Stack](https://github.com/jeffzh4ng/dsa-ts/tree/master/src/data-structures/sequences/stack/stack.ts) 29 | - [x] [📹](https://www.youtube.com/watch?v=E1I8IcKv_cQ&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=9) [Queue](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/data-structures/sequences/queue/queue.ts) 30 | - [x] [Double-ended Queue](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/data-structures/sequences/queue/deque.ts) 31 | - [x] [📹](https://www.youtube.com/watch?v=39HHWATPcwY&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=11) [Circular Buffer](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/data-structures/sequences/circular-buffer/circular-buffer.ts) 32 | - [x] Priority Queues 33 | - [x] [📹](https://www.youtube.com/watch?v=xpSa8YOqeWQ&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=13) [Binary Heap](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/data-structures/priority-queues/min-binary-heap.ts) 34 | - [x] [D-Heap](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/data-structures/priority-queues/min-d-heap.ts) 35 | - [x] [📹](https://www.youtube.com/watch?v=TlYPo28sw9E) [Indexed Binary Heap](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/data-structures/priority-queues/min-indexed-d-heap.ts) 36 | - [x] Mergeable Heaps 37 | - [x] [📹](https://www.youtube.com/watch?v=m8rsw3KfDKs&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=17) [Binomial Heap](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/priority-queues/mergeable-heaps/min-binomial-heap.ts) 38 | - [x] [📹](https://www.youtube.com/watch?v=-IOse-LEJtw&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=19) [Lazy Binomial Heap](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/priority-queues/mergeable-heaps/lazy-min-binomial-heap.ts) 39 | - [x] [📹](https://www.youtube.com/watch?v=E_AgyAI8lsc&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=23) [Fibonnaci Heap](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/priority-queues/mergeable-heaps/min-fibonacci-heap.ts) 40 | - [x] Search Trees 41 | - [x] [📹](https://www.youtube.com/watch?v=zEOWjb7wDik&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=24) [Binary Search Tree](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/trees/binary-search-tree/index.ts) 42 | - [x] [📹](https://www.youtube.com/watch?v=ietVegtgG_s&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=25) [AVL Tree](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/trees/avl-tree/index.ts) 43 | - [x] [Red-black Tree](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/trees/red-black-tree/index.ts) 44 | - [x] [B-Tree](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/trees/b-tree/b-tree.ts) 45 | - [x] Hash Tables 46 | - [x] [📹](https://www.youtube.com/watch?v=_lJFWYFroYs&list=PLn4fTSbSpY5cL4_0MP83wq5khbmG3IKKd&index=26) [Hash Table (Separate Chaining)](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/hash-tables/hash-table-separate-chaining.ts) 47 | - [x] [Hash Table (Open Addressing)](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/hash-tables/hash-table-open-addressing-base.ts) 48 | - [x] [Hash Table (Linear Probing)](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/hash-tables/hash-table-linear-probing.ts) 49 | - [x] [Hash Table (Quadratic Probing)](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/hash-tables/hash-table-quadratic-probing.ts) 50 | - [x] [Hash Table (Double Hashing)](https://github.com/jeffzh4ng/algorithms-and-data-structures/blob/master/src/data-structures/hash-tables/hash-table-double-hashing.ts) 51 | 52 | ## Algorithms 53 | - [x] Search 54 | - [x] [Binary Search](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/search/binary-search.ts) 55 | - [x] [Breadth-first Search](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/search/breadth-first-search.ts) 56 | - [x] [Depth-first Search](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/search/depth-first-search.ts) 57 | - [x] Sorting 58 | - [x] [Merge Sort](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/sort/merge-sort.ts) 59 | - [x] [Quick Sort](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/sort/quick-sort.ts) 60 | - [x] [Heap Sort](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/sort/heap-sort.ts) 61 | - [x] [Topological Sort (DFS)](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/sort/topological-sort-dfs.ts) 62 | - [x] [Topological Sort (Kahns)](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/sort/topological-sort-kahn.ts) 63 | - [x] [Counting Sort](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/sort/counting-sort.ts) 64 | - [ ] [Bucket Sort]() 65 | - [] Graph Theory 66 | - [] Graphs 67 | - [x] Shortest Paths 68 | - [x] [Dijkstra's SSSP](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/graphs/shortest-paths/dijkstras-shortest-path.ts) 69 | - [x] [Bellman-Ford SSSP](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/graphs/shortest-paths/bellman-ford-shortest-path.ts) 70 | - [x] [Floyd-Warshall APSP](https://github.com/jeffzh4ng/dsa-ts/blob/master/src/algorithms/graphs/shortest-paths/floyd-warshall-shortest-path.ts) 71 | - [ ] Minimum Spanning Tree 72 | - [ ] Network Flow 73 | 74 | ## References 75 | - [Fundamental Data Structures](https://en.wikipedia.org/wiki/Book:Fundamental_Data_Structures) 76 | - [Introduction to Algorithms by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein](https://en.wikipedia.org/wiki/Introduction_to_Algorithms) 77 | - [Algorithm Design by Jon Kleinberg and Éva Tardos](https://www.amazon.ca/Algorithm-Design-Jon-Kleinberg/dp/0321295358) 78 | - [Algorithms by William Fiset](https://github.com/williamfiset/Algorithms) 79 | - [Algorithms by Jeff Erickson](http://jeffe.cs.illinois.edu/teaching/algorithms/) 80 | - [Stanford CS 166](https://web.stanford.edu/class/cs166/) 81 | - [Harvard CS 224](https://www.youtube.com/playlist?list=PL2SOU6wwxB0uP4rJgf5ayhHWgw7akUWSf) 82 | 83 | ## Contributing 84 | When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. 85 | 86 | *Commiting Process* 87 | 88 | The commit process does not directly use git. dsa-ts uses commitizen to ensure we are comitting semantic commits. To commit, use the command `yarn commit`, and follow the citizen cli which prompts you for various metainfo regarding the commit. After the pre-commit tests are run successfully, you are able to push to your development branch with `git push`. 89 | 90 | ## License 91 | This repository is released under the MIT license. In short, this means you are free to use this software in any personal, open-source or commercial projects 92 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/test'], 3 | testMatch: ['**/__test__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], 4 | transform: { 5 | '^.+\\.(ts|tsx)$': 'ts-jest', 6 | }, 7 | collectCoverage: true, 8 | collectCoverageFrom: ['src/**/{!(b-tree),}.ts'], 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsa-ts", 3 | "version": "0.0.0-development", 4 | "description": "A collection of data structures and algorithms in Typescript", 5 | "main": "lib/index.js", 6 | "types": "lib", 7 | "scripts": { 8 | "build": "tsc -p .", 9 | "test": "jest --config jest.config.js --maxWorkers=1", 10 | "semantic-release": "semantic-release", 11 | "commit": "npx git-cz", 12 | "report-coverage": "codecov" 13 | }, 14 | "repository": "https://github.com/jeffzh4ng/dsa-ts", 15 | "author": "Jeff Zhang", 16 | "license": "MIT", 17 | "prepublish": "tsc", 18 | "devDependencies": { 19 | "@types/jest": "26.0.4", 20 | "@typescript-eslint/eslint-plugin": "3.6.0", 21 | "@typescript-eslint/parser": "3.6.0", 22 | "codecov": "3.7.0", 23 | "commitizen": "4.1.2", 24 | "cz-conventional-changelog": "3.2.0", 25 | "eslint": "7.4.0", 26 | "husky": "4.2.5", 27 | "jest": "26.1.0", 28 | "lint-staged": "10.2.11", 29 | "prettier": "2.0.5", 30 | "semantic-release": "17.2.3", 31 | "ts-jest": "26.1.1", 32 | "typescript": "3.9.6" 33 | }, 34 | "dependencies": {}, 35 | "config": { 36 | "commitizen": { 37 | "path": "./node_modules/cz-conventional-changelog" 38 | } 39 | }, 40 | "husky": { 41 | "hooks": { 42 | "pre-commit": "lint-staged && yarn test", 43 | "pre-push": "lint-staged && yarn test" 44 | } 45 | }, 46 | "lint-staged": { 47 | "*": [ 48 | "yarn prettier --write '**/*.ts'" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"] 3 | } 4 | -------------------------------------------------------------------------------- /src/algorithms/graphs/graph-node.ts: -------------------------------------------------------------------------------- 1 | export class GraphNode { 2 | val: T 3 | 4 | constructor(val: T) { 5 | this.val = val 6 | } 7 | } 8 | 9 | type EdgeList = Map, number> 10 | export type WeightedGraph = Map, EdgeList> 11 | export type ShortestDistances = Map, number> 12 | export type PrevVertices = Map, GraphNode | null> 13 | -------------------------------------------------------------------------------- /src/algorithms/graphs/shortest-paths/bellman-ford-shortest-path.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode, PrevVertices, ShortestDistances, WeightedGraph } from '../graph-node' 2 | 3 | /** 4 | * Perform Bellman-Ford shortest path algorithm. 5 | * Find the shortest path between two vertices in a directed graph with positive weights. 6 | * 7 | * Time complexity: O[VE] 8 | * Space complexity: O(V+E) 9 | * 10 | * @param {WeightedGraph} graph 11 | * @param {GraphNode} source 12 | * @param {GraphNode} target 13 | * @return {[Array<>, number]} 14 | */ 15 | export const shortestPath = ( 16 | graph: WeightedGraph, 17 | source: GraphNode, 18 | target: GraphNode 19 | ): [Array, number] => { 20 | const [dist, prev] = bellmanFord(graph, source) 21 | const shortestPath = new Array() 22 | 23 | for (let at = target; at !== null; at = prev.get(at)!) { 24 | shortestPath.push(at.val) 25 | } 26 | 27 | return [shortestPath.reverse(), dist.get(target)!] 28 | } 29 | 30 | const bellmanFord = ( 31 | graph: WeightedGraph, 32 | source: GraphNode 33 | ): [ShortestDistances, PrevVertices] => { 34 | const dist: ShortestDistances = new Map() 35 | const prev: PrevVertices = new Map() 36 | 37 | // step 1. initialize 38 | for (const node of graph.keys()) { 39 | dist.set(node, Number.POSITIVE_INFINITY) 40 | prev.set(node, null) 41 | } 42 | dist.set(source, 0) // dist[source] needs to be 0, not infinity 43 | 44 | // build edge list 45 | const edgeList: Array<[GraphNode, GraphNode, number]> = [] 46 | for (const [v, vEdges] of graph.entries()) { 47 | for (const edge of vEdges) { 48 | edgeList.push([v, edge[0], edge[1]]) 49 | } 50 | } 51 | 52 | // step 2. relax edges V-1 time 53 | const V = graph.size 54 | for (let i = 0; i < V - 1; i++) { 55 | for (const edge of edgeList) { 56 | const [u, v, weight] = edge 57 | if (dist.get(u)! + weight < dist.get(v)!) { 58 | dist.set(v, dist.get(u)! + weight) 59 | prev.set(v, u) 60 | } 61 | } 62 | } 63 | 64 | // step 3. check for negative cycles 65 | for (const edge of edgeList) { 66 | const [u, v, weight] = edge 67 | if (dist.get(u)! + weight < dist.get(v)!) { 68 | throw new Error('Negative-weighted cycle found in graph.') 69 | } 70 | } 71 | 72 | return [dist, prev] 73 | } 74 | -------------------------------------------------------------------------------- /src/algorithms/graphs/shortest-paths/dijkstras-shortest-path.ts: -------------------------------------------------------------------------------- 1 | import { MinBinaryHeap } from '../../../data-structures/priority-queues' 2 | import { GraphNode, PrevVertices, ShortestDistances, WeightedGraph } from '../graph-node' 3 | 4 | /** 5 | * Perform Dijkstra's shortest path algorithm. 6 | * Find the shortest path between two vertices in a directed graph with positive weights. 7 | * 8 | * Time complexity: O[(V+E)logV] 9 | * Space complexity: O(V+E) 10 | * 11 | * @param {WeightedGraph} graph 12 | * @param {GraphNode} source 13 | * @param {GraphNode} target 14 | * @return {[Array<>, number]} 15 | */ 16 | export const shortestPath = ( 17 | graph: WeightedGraph, 18 | source: GraphNode, 19 | target: GraphNode 20 | ): [Array, number] => { 21 | const [dist, prev] = dijkstra(graph, source) 22 | const shortestPath = new Array() 23 | 24 | for (let at = target; at !== null; at = prev.get(at)!) { 25 | shortestPath.push(at.val) 26 | } 27 | 28 | return [shortestPath.reverse(), dist.get(target)!] 29 | } 30 | 31 | const dijkstra = ( 32 | graph: WeightedGraph, 33 | source: GraphNode 34 | ): [ShortestDistances, PrevVertices] => { 35 | const visited = new Set>() 36 | const dist: ShortestDistances = new Map() 37 | const prev: PrevVertices = new Map() 38 | 39 | for (const node of graph.keys()) { 40 | dist.set(node, Number.POSITIVE_INFINITY) 41 | prev.set(node, null) 42 | } 43 | dist.set(source, 0) // dist[source] needs to be 0, not infinity 44 | 45 | const pq = new MinBinaryHeap<[GraphNode, number]>([], (a, b) => a[1] - b[1]) 46 | pq.add([source, 0]) 47 | 48 | while (!pq.isEmpty()) { 49 | const [u, uDist] = pq.poll()! 50 | 51 | if (!u) throw new Error('This will never happen') 52 | 53 | const uDistStale = uDist > dist.get(u)! 54 | if (uDistStale) continue 55 | 56 | visited.add(u) 57 | 58 | const neighborsOfU = graph.get(u)! 59 | 60 | for (const [v, weight] of neighborsOfU) { 61 | if (visited.has(v)) continue 62 | 63 | const altDistance = uDist + weight 64 | 65 | if (altDistance < dist.get(v)!) { 66 | dist.set(v, altDistance) 67 | prev.set(v, u) 68 | 69 | pq.add([v, altDistance]) 70 | } 71 | } 72 | } 73 | 74 | return [dist, prev] 75 | } 76 | -------------------------------------------------------------------------------- /src/algorithms/graphs/shortest-paths/floyd-warshall-shortest-path.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode, WeightedGraph } from '../graph-node' 2 | 3 | type U = GraphNode 4 | type V = GraphNode 5 | type Weight = number 6 | 7 | type DP = Array> 8 | type Next = Array | null>> 9 | 10 | export const shortestPath = ( 11 | next: Next, 12 | source: GraphNode, 13 | target: GraphNode 14 | ): Array => { 15 | if (next[source.val][target.val] === null) return [] 16 | 17 | const path = [] 18 | 19 | while (source !== target) { 20 | path.push(source.val) 21 | 22 | const nextNode = next[source.val][target.val] 23 | 24 | if (nextNode === null) return [] 25 | source = nextNode 26 | } 27 | 28 | path.push(source.val) // source is now target 29 | 30 | return path 31 | } 32 | export const floydWarshall = (graph: WeightedGraph): [DP, Next] => { 33 | const V = graph.size 34 | const dp: Array> = new Array(V) 35 | .fill(0) 36 | .map(() => new Array(V).fill(Infinity)) 37 | const next: Array | null>> = new Array(V) 38 | .fill(0) 39 | .map(() => new Array | null>(V).fill(null)) 40 | 41 | // initialize dp matrix 42 | const edgeList: Array<[U, V, Weight]> = [] 43 | for (const [v, vEdges] of graph.entries()) { 44 | for (const edge of vEdges) { 45 | edgeList.push([v, edge[0], edge[1]]) 46 | } 47 | } 48 | for (const edge of edgeList) { 49 | const [u, v, weight] = edge 50 | dp[u.val][v.val] = weight 51 | next[u.val][v.val] = v 52 | } 53 | for (const v of graph.keys()) { 54 | dp[v.val][v.val] = 0 55 | next[v.val][v.val] = v 56 | } 57 | 58 | // build the shorest distances 59 | for (let k = 0; k < dp.length; k++) { 60 | for (let i = 0; i < dp.length; i++) { 61 | for (let j = 0; j < dp.length; j++) { 62 | if (dp[i][k] + dp[k][j] < dp[i][j]) { 63 | dp[i][j] = dp[i][k] + dp[k][j] 64 | next[i][j] = next[i][k] 65 | } 66 | } 67 | } 68 | } 69 | 70 | // detect and propagate negative cycle 71 | for (let k = 0; k < dp.length; k++) { 72 | for (let i = 0; i < dp.length; i++) { 73 | for (let j = 0; j < dp.length; j++) { 74 | if (dp[i][k] + dp[k][j] < dp[i][j]) { 75 | dp[i][j] = -Infinity 76 | next[i][j] = null 77 | } 78 | } 79 | } 80 | } 81 | 82 | return [dp, next] 83 | } 84 | -------------------------------------------------------------------------------- /src/algorithms/search/binary-search.ts: -------------------------------------------------------------------------------- 1 | export type Item = number | string 2 | 3 | /** 4 | * Iteratively search an array of sorted items for a target item and returns its index. 5 | * 6 | * Time complexity: 7 | * - for an array of numbers: O(log(n)), 8 | * - for an array of strings: O(m*log(n)), 9 | * - where n is the number of array elements and m is the average string length. 10 | * 11 | * Space complexity: O(1) 12 | * 13 | * @param arr - Sorted array of numbers or strings to be searched 14 | * @param target - Number or string to be searched for 15 | * @return index of target in array (or -1 if target is not found) 16 | */ 17 | export const binarySearchIterative = (arr: Item[], target: Item): number => { 18 | let lo = 0 19 | let hi = arr.length - 1 20 | let mid 21 | while (lo <= hi) { 22 | mid = Math.floor((lo + hi) / 2) 23 | if (arr[mid] > target) { 24 | hi = mid - 1 25 | } else if (arr[mid] < target) { 26 | lo = mid + 1 27 | } else { 28 | return mid 29 | } 30 | } 31 | return -1 32 | } 33 | 34 | /** 35 | * Recursively search an array of sorted items for a target item and returns its index. 36 | * 37 | * Time complexity: 38 | * - for an array of numbers: O(log(n)), 39 | * - for an array of strings: O(m*log(n)), 40 | * - where n is the number of array elements and m is the average string length. 41 | * 42 | * Space complexity: O(log(n)) 43 | * 44 | * @param arr - Sorted array of numbers or strings to be searched 45 | * @param target - Number or string to be searched for 46 | * @return index of target in array (or -1 if target is not found) 47 | */ 48 | export const binarySearchRecursive = (arr: Item[], target: Item): number => { 49 | const search = (arr: Item[], target: Item, lo: number, hi: number): number => { 50 | if (lo > hi) return -1 51 | const mid = Math.floor((lo + hi) / 2) 52 | if (arr[mid] > target) { 53 | return search(arr, target, lo, mid - 1) 54 | } else if (arr[mid] < target) { 55 | return search(arr, target, mid + 1, hi) 56 | } else { 57 | return mid 58 | } 59 | } 60 | 61 | return search(arr, target, 0, arr.length - 1) 62 | } 63 | -------------------------------------------------------------------------------- /src/algorithms/search/breadth-first-search.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from '../../data-structures/sequences/queue' 2 | import { GraphNode } from '../graphs/graph-node' 3 | 4 | /** 5 | * Perform a breadth-first search through a directed graph. 6 | * 7 | * Time complexity: O(V+E) 8 | * Space complexity: O(V) 9 | * 10 | * @param {GraphNode} root 11 | * @param {Map, Array>} graph 12 | * @return {Array>} 13 | */ 14 | export const bfs = (root: GraphNode, graph: Map, Array>>) => { 15 | const output: Array = new Array() 16 | const q: Queue> = new Queue() 17 | const visited = new Set>() 18 | 19 | q.enqueue(root) 20 | visited.add(root) 21 | 22 | // loop while q is not empty 23 | while (!q.isEmpty()) { 24 | let size = q.size() 25 | 26 | // iterate for every element in the q 27 | while (size-- > 0) { 28 | const node = q.dequeue() 29 | if (!node) throw new Error('Node shouldnt be null.') 30 | output.push(node.val) 31 | 32 | if (graph.has(node)) { 33 | const children = graph.get(node) 34 | if (!children) throw new Error('Children shouldnt be null.') 35 | 36 | for (const child of children) { 37 | if (!visited.has(child)) { 38 | q.enqueue(child) 39 | visited.add(child!) 40 | } 41 | } 42 | } 43 | } 44 | } 45 | 46 | return output 47 | } 48 | -------------------------------------------------------------------------------- /src/algorithms/search/depth-first-search.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode } from '../graphs/graph-node' 2 | 3 | /** 4 | * Perform a depth-first search through a directed graph. 5 | * 6 | * Time complexity: O(V+E) 7 | * Space complexity: O(V) 8 | * 9 | * @param {GraphNode} startNode 10 | * @param {Map, Array>} graph 11 | * @return {Array>} 12 | */ 13 | export const dfs = (startNode: GraphNode, graph: Map, Array>>) => { 14 | const output: Array = new Array() 15 | const visited = new Set>() 16 | 17 | recurse(startNode, graph, visited, output) 18 | 19 | return output 20 | } 21 | 22 | const recurse = ( 23 | root: GraphNode, 24 | graph: Map, Array>>, 25 | visited: Set>, 26 | output: Array 27 | ): void => { 28 | output.push(root.val) 29 | visited.add(root) 30 | 31 | const children = graph.get(root) 32 | if (!children) throw new Error('Children shouldnt be null') 33 | 34 | // loop through children of root and recurse 35 | for (const child of children) { 36 | if (!visited.has(child)) recurse(child, graph, visited, output) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/algorithms/sort/bucket-sort.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sort an array of positive numbers in-place and in ascending order using bucket sort. 3 | * Individual buckets are sorted using the JS sort function. 4 | * @param arr - array of positive numbers to sort 5 | * @param n - number of buckets 6 | */ 7 | const bucketSortPositives = (arr: number[], n: number): void => { 8 | // create empty buckets 9 | const buckets: number[][] = [] 10 | for (let i = 0; i < n; i++) { 11 | buckets[i] = [] 12 | } 13 | 14 | // put array elements in buckets 15 | const max = Math.max(...arr) 16 | for (const num of arr) { 17 | let bucketIdx = Math.floor((n * num) / max) // determine bucket index 18 | bucketIdx = bucketIdx >= n ? n - 1 : bucketIdx // ensure bucket index is a valid array index 19 | buckets[bucketIdx].push(num) 20 | } 21 | 22 | // sort each bucket and put sorted elements into arr 23 | let arrIdx = 0 24 | for (const bucket of buckets) { 25 | bucket.sort((a, b) => a - b) 26 | for (const element of bucket) { 27 | arr[arrIdx] = element 28 | arrIdx++ 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Sort an array of numbers in-place and in ascending order using bucket sort. 35 | * 36 | * Note that the traditional bucket sort algorithm does not work on negative numbers. 37 | * Thus, the below algorithm is modified such that any negative numbers in the array are converted 38 | * to positive numbers and sorted separately from the original positive numbers. 39 | * 40 | * Time complexity: 41 | * - O(n + k) average 42 | * - O(n^2) worst case 43 | * - where n is the number of array elements and k is the number of buckets 44 | * 45 | * Space complexity: O(n*k) 46 | * 47 | * @param arr - array of numbers to sort 48 | * @param posBuckets - (Optional) number of buckets to use when sorting positive array elements 49 | * @param negBuckets - (Optional) number of buckets to use when sorting negative array elements 50 | */ 51 | const bucketSort = (arr: number[], posBuckets = arr.length, negBuckets = arr.length): void => { 52 | if (arr.length === 0) return 53 | 54 | // raise error if number of specified buckets is 0 or less 55 | if (posBuckets <= 0 || negBuckets <= 0) { 56 | console.error('Number of buckets must be greater than zero') 57 | return 58 | } 59 | 60 | // separate positive from negative numbers 61 | const positiveNums: number[] = [] 62 | const negativeNums: number[] = [] 63 | for (const num of arr) { 64 | if (num >= 0) { 65 | positiveNums.push(num) 66 | } else { 67 | negativeNums.push(-1 * num) // convert negative numbers to positive 68 | } 69 | } 70 | 71 | // sort both arrays 72 | bucketSortPositives(positiveNums, posBuckets) 73 | bucketSortPositives(negativeNums, negBuckets) 74 | 75 | // put sorted values back into input arr 76 | let arrIdx = 0 77 | for (let i = negativeNums.length - 1; i >= 0; i--) { 78 | arr[arrIdx] = -1 * negativeNums[i] 79 | arrIdx++ 80 | } 81 | for (let i = 0; i < positiveNums.length; i++) { 82 | arr[arrIdx] = positiveNums[i] 83 | arrIdx++ 84 | } 85 | } 86 | 87 | export default bucketSort 88 | -------------------------------------------------------------------------------- /src/algorithms/sort/counting-sort.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sort an array of integers in-place and in ascending order using counting sort. 3 | * 4 | * Time complexity: O(n + k), where n is the number of elements in arr 5 | * and k is the range of numbers in arr. 6 | * 7 | * Space complexity: O(n + k) 8 | * 9 | * @param arr - array of integers to sort 10 | */ 11 | const countingSort = (arr: number[]): void => { 12 | if (arr.length == 0) { 13 | return 14 | } 15 | 16 | // check that arr elements are integers 17 | for (let idx = 0; idx < arr.length; idx++) { 18 | if (!Number.isInteger(arr[idx])) { 19 | console.error(`arr[${idx}] is not an integer`) 20 | return 21 | } 22 | } 23 | 24 | // implementation of counting sort below 25 | const max = Math.max(...arr) // find largest integer in arr 26 | const min = Math.min(...arr) // find smallest integer in arr 27 | const range = max - min + 1 // calculate range of possible numbers 28 | const counts: number[] = new Array(range).fill(0) // contains counts for each number in arr 29 | const sortedArr: number[] = new Array(arr.length) // final sorted array 30 | 31 | // get counts of each number in arr 32 | for (const num of arr) { 33 | counts[num - min]++ 34 | } 35 | 36 | // modify counts array such that each element is the sum of previous counts 37 | for (let i = 1; i < counts.length; i++) { 38 | counts[i] += counts[i - 1] 39 | } 40 | 41 | // fill in sortedArr using the indices stored in the counts array 42 | // traversing arr in reverse keeps the sort stable 43 | for (let i = arr.length - 1; i >= 0; i--) { 44 | const sortedIndex = counts[arr[i] - min] - 1 45 | counts[arr[i] - min]-- 46 | sortedArr[sortedIndex] = arr[i] 47 | } 48 | 49 | // copy elements of sortedArr to input array 50 | for (let i = 0; i < arr.length; i++) { 51 | arr[i] = sortedArr[i] 52 | } 53 | } 54 | 55 | export default countingSort 56 | -------------------------------------------------------------------------------- /src/algorithms/sort/heap-sort.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Swap elements at idx1 and idx2 in arr. 3 | * @param arr - array containing two elements to swap 4 | * @param idx1 - index of first element 5 | * @param idx2 - index of second element 6 | */ 7 | const swap = (arr: number[], idx1: number, idx2: number): void => { 8 | const temp = arr[idx2] 9 | arr[idx2] = arr[idx1] 10 | arr[idx1] = temp 11 | } 12 | 13 | /** 14 | * Heapifies the heap or sub-heap, putting the largest element at rootIdx. 15 | * @param arr - array containing numbers to heapify 16 | * @param heapSize - size of heap 17 | * @param rootIdx - root of the heap or sub-heap to heapify 18 | */ 19 | const heapify = (arr: number[], heapSize: number, rootIdx: number): void => { 20 | let maxIdx = rootIdx // assume element at rootIdx is the largest 21 | const leftChild = 2 * rootIdx + 1 22 | const rightChild = 2 * rootIdx + 2 23 | 24 | // if left child is larger than element at maxIdx 25 | if (leftChild < heapSize && arr[leftChild] > arr[maxIdx]) { 26 | maxIdx = leftChild 27 | } 28 | 29 | // if right child is larger than element at maxIdx 30 | if (rightChild < heapSize && arr[rightChild] > arr[maxIdx]) { 31 | maxIdx = rightChild 32 | } 33 | 34 | // if maxIdx was changed 35 | if (maxIdx != rootIdx) { 36 | swap(arr, rootIdx, maxIdx) 37 | heapify(arr, heapSize, maxIdx) // heapify the affected subtree 38 | } 39 | } 40 | 41 | /** 42 | * Sort an array of numbers in-place and in ascending order using heap sort. 43 | * 44 | * Time complexity: O(n*log(n)) 45 | * 46 | * Space complexity: O(log(n)) for recursion stack in heapify 47 | * 48 | * @param arr - array of numbers to sort 49 | */ 50 | const heapSort = (arr: number[]): void => { 51 | const heapSize = arr.length 52 | 53 | /** 54 | * Build max binary heap by rearranging array. 55 | * - We want to heapify nodes with children, which are nodes at the second to last level of the heap. 56 | * - The index of the last node on the second to last level is given by floor(heapSize/2) - 1. 57 | * - To heapify the entire array, we start at the second to last level and work upwards. 58 | */ 59 | const startIdx = Math.floor(heapSize / 2) - 1 60 | for (let i = startIdx; i >= 0; i--) { 61 | heapify(arr, heapSize, i) 62 | } 63 | 64 | for (let i = heapSize - 1; i > 0; i--) { 65 | swap(arr, 0, i) // remove largest element from heap and put it at the back of the array 66 | heapify(arr, i, 0) // new heap contains elements from 0 to i - 1, inclusive 67 | } 68 | } 69 | 70 | export default heapSort 71 | -------------------------------------------------------------------------------- /src/algorithms/sort/merge-sort.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper function to merge left half and right half of a subarray. 3 | * The sorted left half is arr[left : mid+1]. 4 | * The sorted right half is arr[mid+1 : right+1]. 5 | * Merging occurs in-place such that arr[left : right+1] will be sorted after function is executed. 6 | * 7 | * @param arr - array of numbers to be sorted 8 | * @param left - starting index of left half 9 | * @param mid - ending index of left half 10 | * @param right - ending index of right half 11 | */ 12 | const merge = (arr: number[], left: number, mid: number, right: number): void => { 13 | const leftArr = arr.slice(left, mid + 1) // copy of left half 14 | const rightArr = arr.slice(mid + 1, right + 1) // copy of right half 15 | let i = 0 // starting index of leftArr 16 | let j = 0 // starting index of rightArr 17 | let k = left // starting index of merged subarray (i.e. arr[left:right+1]) 18 | while (i < leftArr.length && j < rightArr.length) { 19 | if (leftArr[i] < rightArr[j]) { 20 | arr[k] = leftArr[i] 21 | i++ 22 | } else { 23 | arr[k] = rightArr[j] 24 | j++ 25 | } 26 | k++ 27 | } 28 | 29 | // if not all elements of leftArr have been traversed, copy the remaining elements 30 | while (i < leftArr.length) { 31 | arr[k] = leftArr[i] 32 | i++ 33 | k++ 34 | } 35 | 36 | // else if not all elements of rightArr have been traversed, copy remaining elements 37 | while (j < rightArr.length) { 38 | arr[k] = rightArr[j] 39 | j++ 40 | k++ 41 | } 42 | } 43 | 44 | /** 45 | * Sort an array of numbers in-place and in ascending order using top-down merge sort. 46 | * A subarray of arr can be sorted by specifying its left and right bounds. 47 | * 48 | * Time complexity: O(n*log(n)), where n is the number of elements to sort 49 | * 50 | * Space complexity: O(n) 51 | * 52 | * @param arr - array of numbers to sort 53 | * @param left - Optional: index of left bound 54 | * @param right - Optional: index of right bound 55 | */ 56 | const mergeSort = (arr: number[], left = 0, right = arr.length - 1): void => { 57 | // immediately return if array is empty 58 | if (arr.length === 0) return 59 | 60 | // check that indices are integers 61 | if (!Number.isInteger(left) || !Number.isInteger(right)) { 62 | console.error('Index Error: Left and right indices must be integers.') 63 | return 64 | } 65 | 66 | // implement merge sort algorithm 67 | if (left >= right) return 68 | const mid = Math.floor((left + right) / 2) // find middle index of current subarray 69 | mergeSort(arr, left, mid) // sort left half 70 | mergeSort(arr, mid + 1, right) // sort right half 71 | merge(arr, left, mid, right) // merge left and right halves 72 | } 73 | 74 | export default mergeSort 75 | -------------------------------------------------------------------------------- /src/algorithms/sort/quick-sort.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a random integer between min and max. 3 | * @param min - lower bound (returned int will be greater than or equal to min) 4 | * @param max - upper bound (returned int will be less than max) 5 | */ 6 | const getRandomInt = (min: number, max: number): number => { 7 | min = Math.ceil(min) 8 | max = Math.floor(max) 9 | return Math.floor(Math.random() * (max - min) + min) 10 | } 11 | 12 | /** 13 | * Swap elements at idx1 and idx2 in arr. 14 | * @param arr - array containing at least two elements to swap 15 | * @param idx1 - index of first element 16 | * @param idx2 - index of second element 17 | */ 18 | const swap = (arr: number[], idx1: number, idx2: number): void => { 19 | const temp = arr[idx2] 20 | arr[idx2] = arr[idx1] 21 | arr[idx1] = temp 22 | } 23 | 24 | /** 25 | * Partition subarray around a random pivot and return the final pivot position. 26 | * @param arr - array of numbers to be sorted 27 | * @param left - index of left bound of subarray 28 | * @param right - index of right bound of subarray 29 | */ 30 | const partition = (arr: number[], left: number, right: number): number => { 31 | const pivotIdx = getRandomInt(left, right + 1) // pick a random pivot 32 | const pivotVal = arr[pivotIdx] 33 | swap(arr, left, pivotIdx) 34 | 35 | // partition so that elements less than pivot value are grouped together on the left 36 | let i = left + 1 37 | for (let j = left + 1; j <= right; j++) { 38 | if (arr[j] < pivotVal) { 39 | swap(arr, i, j) 40 | i++ 41 | } 42 | } 43 | swap(arr, left, i - 1) 44 | return i - 1 // return final position of pivot 45 | } 46 | 47 | /** 48 | * Sort an array of numbers in-place and in ascending order. 49 | * Partitioning is done by choosing a random element as the pivot. 50 | * 51 | * Time complexity: O(n*log(n)) on average, where n is the number of elements to sort 52 | * 53 | * Space complexity: O(log(n)) for recursion stack 54 | * 55 | * @param arr - array of numbers to be sorted 56 | * @param left - Optional: index of left bound 57 | * @param right - Optional: index of right bound 58 | */ 59 | const quickSort = (arr: number[], left = 0, right = arr.length - 1): void => { 60 | // check that indices are intergers 61 | if (!Number.isInteger(left) || !Number.isInteger(right)) { 62 | console.error('Index Error: Left and right indices must be integers.') 63 | return 64 | } 65 | 66 | // implement quick sort algorithm 67 | if (left >= right) return 68 | const pivot = partition(arr, left, right) 69 | quickSort(arr, left, pivot - 1) 70 | quickSort(arr, pivot + 1, right) 71 | } 72 | 73 | export default quickSort 74 | -------------------------------------------------------------------------------- /src/algorithms/sort/topological-sort-dfs.ts: -------------------------------------------------------------------------------- 1 | import LinkedList from '../../data-structures/sequences/linked-list' 2 | import { GraphNode } from '../graphs/graph-node' 3 | 4 | /** 5 | * Perform a topological sort through a directed graph using the depth first technique. 6 | * 7 | * Time complexity: O(V+E) 8 | * Space complexity: O(V) (recursion and output) 9 | * 10 | * @param startNode - The node to begin the search at 11 | * @return {Array>} 12 | */ 13 | export const topologicalSortDepthFirst = ( 14 | graph: Map, Array>> 15 | ): Array => { 16 | const output = new LinkedList() 17 | const visited = new Set>() 18 | 19 | // for every node in the graph, perform a dfs, where the dfs adds the nodes to the output in the correct order 20 | for (const node of graph.keys()) { 21 | if (!visited.has(node)) dfs(node, graph, visited, output) 22 | } 23 | 24 | return Array.from(output) 25 | } 26 | 27 | const dfs = ( 28 | root: GraphNode, 29 | graph: Map, Array>>, 30 | visited: Set>, 31 | output: LinkedList 32 | ): void => { 33 | visited.add(root) 34 | const children = graph.get(root) 35 | 36 | if (children) { 37 | for (const child of children) { 38 | dfs(child, graph, visited, output) 39 | } 40 | } 41 | 42 | output.addFront(root.val) 43 | } 44 | -------------------------------------------------------------------------------- /src/algorithms/sort/topological-sort-kahn.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from '../../data-structures/sequences/queue' 2 | import { GraphNode } from '../graphs/graph-node' 3 | 4 | /** 5 | * Perform a topological sort on a directed graph using Kahn's algorithm. 6 | * 7 | * Time complexity: O(V+E) 8 | * Space complexity: O(V) 9 | * 10 | * @param {Map, Array>} graph 11 | * @return {Array>} 12 | */ 13 | export const topologicalSortKahn = (graph: Map, Array>>) => { 14 | const inDegree: Map, number> = new Map() 15 | 16 | // initialize inDegree map 17 | for (const node of graph.keys()) { 18 | inDegree.set(node, 0) 19 | } 20 | 21 | // calculate in degree's for each node 22 | for (const children of graph.values()) { 23 | for (const child of children) { 24 | inDegree.set(child, inDegree.get(child)! + 1) 25 | } 26 | } 27 | 28 | // build the sources queue which contains node with inDegree of 0 aka sources 29 | const sources = new Queue>() 30 | for (const [node, _] of graph.entries()) { 31 | if (inDegree.get(node) === 0) sources.enqueue(node) 32 | } 33 | 34 | // loop while sources is not empty 35 | const output: Array = [] 36 | while (!sources.isEmpty()) { 37 | const node = sources.dequeue() 38 | if (!node) throw new Error('Node must not be null') 39 | 40 | // push current node to output 41 | output.push(node.val) 42 | 43 | const children = graph.get(node) 44 | if (!children) throw new Error('Children must not be null') 45 | 46 | // for every child of the node, remove 1 from their inDegree becauase we visited one of their parents 47 | for (const child of children) { 48 | inDegree.set(child, inDegree.get(child)! - 1) 49 | 50 | // if that child is now a source, add it to the sources q 51 | if (inDegree.get(child) === 0) sources.enqueue(child) 52 | } 53 | } 54 | 55 | return output 56 | } 57 | -------------------------------------------------------------------------------- /src/data-structures/hash-tables/entry.ts: -------------------------------------------------------------------------------- 1 | export interface Hashable { 2 | hashCode(): number 3 | } 4 | 5 | export class Entry { 6 | hash: number 7 | 8 | key: K 9 | value: V 10 | 11 | constructor(key: K, value: V) { 12 | this.hash = key.hashCode() 13 | this.key = key 14 | this.value = value 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/data-structures/hash-tables/hash-table-double-hashing.ts: -------------------------------------------------------------------------------- 1 | import HashTableOpenAddressingBase from './hash-table-open-addressing-base' 2 | import { Hashable } from './entry' 3 | 4 | interface DoubleHashable extends Hashable { 5 | hashCode2(): number 6 | } 7 | 8 | class HashTableDoubleHashing extends HashTableOpenAddressingBase< 9 | K, 10 | V 11 | > { 12 | private hash: number 13 | 14 | constructor(capacity: number, loadFactor: number) { 15 | super(capacity, loadFactor) 16 | this.hash = 0 17 | } 18 | 19 | setupProbing(key: K): void { 20 | this.hash = this.normalizeIndex(key.hashCode2()) 21 | 22 | // fail safe to avoid infinite loop 23 | if (this.hash === 0) this.hash = 0 24 | } 25 | 26 | probe(x: number): number { 27 | return x * this.hash 28 | } 29 | 30 | isPrime(num: number): boolean { 31 | for (let i = 2, s = Math.sqrt(num); i <= s; i++) if (num % i === 0) return false 32 | 33 | return num > 1 34 | } 35 | 36 | // Adjust the capacity until it is a prime number. The reason for 37 | // doing this is to help ensure that the GCD(hash, capacity) = 1 when 38 | // probing so that all the cells can be reached. 39 | 40 | adjustCapacity(): void { 41 | while (!this.isPrime(this.capacity)) { 42 | this.capacity += 1 43 | } 44 | } 45 | } 46 | 47 | export default HashTableDoubleHashing 48 | -------------------------------------------------------------------------------- /src/data-structures/hash-tables/hash-table-linear-probing.ts: -------------------------------------------------------------------------------- 1 | import HashTableOpenAddressingBase from './hash-table-open-addressing-base' 2 | import { Hashable } from './entry' 3 | 4 | class HashTableLinearProbing extends HashTableOpenAddressingBase { 5 | private LINEAR_CONSTANT = 17 6 | 7 | constructor(capacity: number, loadFactor: number) { 8 | super(capacity, loadFactor) 9 | } 10 | 11 | setupProbing(key: K): void {} 12 | 13 | probe(x: number): number { 14 | return this.LINEAR_CONSTANT * x 15 | } 16 | 17 | adjustCapacity(): void { 18 | while (this.gcd(this.LINEAR_CONSTANT, this.capacity) !== 1) { 19 | this.capacity += 1 20 | } 21 | } 22 | } 23 | 24 | export default HashTableLinearProbing 25 | -------------------------------------------------------------------------------- /src/data-structures/hash-tables/hash-table-open-addressing-base.ts: -------------------------------------------------------------------------------- 1 | import { Hashable } from './entry' 2 | 3 | // Code is based off William Fiset's implementation 4 | abstract class HashTableOpenAddressingBase { 5 | private DEFAULT_CAPACITY = 7 6 | 7 | // Special marker token used to indicate the deletion of a key-value pair 8 | protected TOMBSTONE: K = new Object() as K 9 | 10 | protected keyList: Array 11 | protected valueList: Array 12 | 13 | protected capacity: number 14 | protected maxLoadFactor: number 15 | protected threshold: number 16 | 17 | protected modificationCount: number 18 | protected usedBuckets: number // counts total number of used buckets (including tombstones) 19 | protected keyCount: number // number of unique keys in hash table 20 | 21 | constructor(capacity: number, maxLoadFactor: number) { 22 | if (capacity < 0) throw new Error('Illegal capacity') 23 | if (maxLoadFactor <= 0) throw new Error('Illegal maxLoadFactor') 24 | 25 | this.capacity = Math.max(this.DEFAULT_CAPACITY, capacity) 26 | this.maxLoadFactor = maxLoadFactor 27 | this.adjustCapacity() 28 | 29 | this.threshold = Math.trunc(this.capacity * this.maxLoadFactor) 30 | this.modificationCount = 0 31 | 32 | this.usedBuckets = 0 33 | this.keyCount = 0 34 | 35 | this.keyList = new Array(this.capacity) 36 | this.valueList = new Array(this.capacity) 37 | } 38 | 39 | /***************************************************************************** 40 | ABSTRACT 41 | *****************************************************************************/ 42 | // These three methods are used to dictate how the probing is to actually 43 | // occur for whatever open addressing scheme you are implementing. 44 | 45 | abstract setupProbing(key: K): void 46 | 47 | // Adjusts the capacity of the hash table after it's been made larger. 48 | // This is important to be able to override because the size of the hashtable 49 | // controls the functionality of the probing function. 50 | abstract adjustCapacity(): void 51 | 52 | abstract probe(x: number): number 53 | 54 | /***************************************************************************** 55 | INSPECTION 56 | *****************************************************************************/ 57 | /** 58 | * Returns the size of the hash table - O(1) 59 | * @return {number} 60 | */ 61 | size(): number { 62 | return this.keyCount 63 | } 64 | 65 | /** 66 | * Returns true if the hash table is empty, and false otherwise - O(1) 67 | * @return {number} 68 | */ 69 | isEmpty(): boolean { 70 | return this.size() === 0 71 | } 72 | 73 | /** 74 | * Returns capacity of the table - O(1) 75 | * @return {number} 76 | */ 77 | getCapacity(): number { 78 | return this.capacity 79 | } 80 | 81 | /** 82 | * Clears the hash table - O(1) 83 | * @return {void} 84 | */ 85 | clear(): void { 86 | this.keyList.length = 0 87 | this.valueList.length = 0 88 | 89 | this.usedBuckets = 0 90 | this.keyCount = 0 91 | this.modificationCount += 1 92 | } 93 | 94 | /** 95 | * Returns true if the hash table contains the key, and false otherwise 96 | * @param {K} key 97 | * @return {boolean} 98 | */ 99 | containsKey(key: K): boolean { 100 | return this.get(key) !== null 101 | } 102 | 103 | /** 104 | * Returns a list of the hash table's keys - O(n) 105 | * @return {Array} 106 | */ 107 | keys(): Array { 108 | const keys: Array = [] 109 | 110 | for (const key of this.keyList) { 111 | if (key && key !== this.TOMBSTONE) keys.push(key) 112 | } 113 | 114 | return keys 115 | } 116 | 117 | /** 118 | * Returns a list of the hash table's values - O(n) 119 | * @return {Array} 120 | */ 121 | values(): Array { 122 | const values: Array = [] 123 | 124 | for (let i = 0; i < this.keyList.length; i++) { 125 | if (this.keyList[i] && this.keyList[i] !== this.TOMBSTONE) values.push(values[i]) 126 | } 127 | 128 | return values 129 | } 130 | 131 | /***************************************************************************** 132 | MAIN 133 | ****************************************************************************/ 134 | /** 135 | * Returns value associated with key, and null if the key does not exist - O(1) amortized 136 | * @param {K} key 137 | * @return {V | null} 138 | */ 139 | get(key: K): V | null { 140 | let output: V | null = null 141 | 142 | this.setupProbing(key) 143 | 144 | const offset = this.normalizeIndex(key.hashCode()) 145 | 146 | for (let i = offset, j = -1, x = 1; ; i = this.normalizeIndex(offset + this.probe(x++))) { 147 | // Ignore deleted cells, but record where the first index 148 | // of a deleted cell is found to perform lazy relocation later. 149 | if (this.keyList[i] === this.TOMBSTONE) { 150 | if (j === -1) j = i 151 | } else if (this.keyList[i] !== null) { 152 | // we found a match 153 | if (this.keyList[i] === key) { 154 | // If j != -1 this means we previously encountered a deleted cell. 155 | // We can perform an optimization by swapping the entries in cells 156 | // i and j so that the next time we search for this key it will be 157 | // found faster. This is called lazy deletion/relocation. 158 | 159 | if (j !== -1) { 160 | // send the key value pair to index j 161 | this.keyList[j] = this.keyList[i] 162 | this.valueList[j] = this.valueList[i] 163 | 164 | // delete key value pair at index i 165 | this.keyList[i] = this.TOMBSTONE 166 | this.valueList[i] = null 167 | output = this.valueList[j] 168 | break 169 | } else { 170 | output = this.valueList[i] 171 | } 172 | } 173 | } 174 | } 175 | 176 | return output 177 | } 178 | 179 | /** 180 | * Adds the [K, V] pair to the hash table - O(1) amortized 181 | * @param {K} key 182 | * @param {V} value 183 | * @returns {V | null} 184 | */ 185 | set(key: K, value: V | null): V | null { 186 | let output: V | null = null 187 | 188 | if (this.usedBuckets >= this.threshold) this.resizeTable() 189 | 190 | this.setupProbing(key) 191 | 192 | const offset = this.normalizeIndex(key.hashCode()) 193 | 194 | // start at the original hash value and probe until we find our key or null 195 | for (let i = offset, j = -1, x = 1; ; i = this.normalizeIndex(offset + this.probe(x++))) { 196 | // if the current slow was previously deleted 197 | if (this.keyList[i] === this.TOMBSTONE) { 198 | if (j === -1) j = i 199 | } else if (this.keyList[i] !== null) { 200 | // the key we're trying to insert already exists in the hash table, so update with the new 201 | // value 202 | if (this.keyList[i] === key) { 203 | const oldValue = this.valueList[i] 204 | 205 | if (j === -1) { 206 | this.valueList[i] = value 207 | } else { 208 | this.keyList[i] = this.TOMBSTONE 209 | this.valueList[i] = null 210 | 211 | this.keyList[j] = key 212 | this.valueList[j] = value 213 | } 214 | 215 | this.modificationCount += 1 216 | output = oldValue 217 | break 218 | } 219 | 220 | // current slot is empty so an insertion can occur 221 | } else { 222 | if (j === -1) { 223 | this.usedBuckets += 1 224 | this.keyCount += 1 225 | 226 | this.keyList[i] = key 227 | this.valueList[i] = value 228 | 229 | // previously seen bucket. Instead of inserting the new element at i where the null element 230 | // is insert it where the deleted token was found. 231 | } else { 232 | this.keyCount += 1 233 | this.keyList[j] = key 234 | this.valueList[j] = value 235 | } 236 | 237 | this.modificationCount += 1 238 | output = null 239 | break 240 | } 241 | } 242 | 243 | return output 244 | } 245 | 246 | /** 247 | * Deletes the entry with key K - O(1) amortized 248 | * @param {K} key 249 | * @returns {V | null} 250 | */ 251 | delete(key: K): V | null { 252 | let output: V | null = null 253 | 254 | this.setupProbing(key) 255 | 256 | const offset = this.normalizeIndex(key.hashCode()) 257 | 258 | for (let i = offset, x = 1; ; i = this.normalizeIndex(offset + this.probe((x += 1)))) { 259 | // ignore tombstones 260 | if (this.keyList[i] === this.TOMBSTONE) continue 261 | 262 | if (this.keyList[i] === null) { 263 | output = null 264 | break 265 | } 266 | 267 | // key is indeed in hash table 268 | if (this.keyList[i] === key) { 269 | const oldValue = this.valueList[i] 270 | 271 | this.keyList[i] = this.TOMBSTONE 272 | this.valueList[i] = null 273 | 274 | this.modificationCount -= 1 275 | this.keyCount -= 1 276 | } 277 | } 278 | 279 | return output 280 | } 281 | 282 | /***************************************************************************** 283 | HELPERS 284 | *****************************************************************************/ 285 | // Converts a hash to an index by stripping the negative 286 | // sign and maps the hash to domain of [0, capacity] 287 | protected normalizeIndex(hash: number): number { 288 | return (hash & 0x7fffffff) % this.capacity 289 | } 290 | 291 | protected increaseCapacity(): void { 292 | this.capacity = this.capacity * 2 + 1 293 | } 294 | 295 | protected gcd(a: number, b: number): number { 296 | if (b === 0) return a 297 | return this.gcd(a, a % b) 298 | } 299 | 300 | // double size of hash table 301 | private resizeTable(): void { 302 | this.increaseCapacity() 303 | this.adjustCapacity() 304 | 305 | this.threshold = Math.trunc(this.capacity * this.maxLoadFactor) 306 | 307 | const oldKeyList = this.keyList 308 | this.keyList = [] 309 | 310 | const oldValueList = this.valueList 311 | this.valueList = [] 312 | 313 | this.keyCount = 0 314 | this.usedBuckets = 0 315 | 316 | for (let i = 0; i < oldKeyList.length; i++) { 317 | if (oldKeyList[i] && oldKeyList[i] !== this.TOMBSTONE) { 318 | this.set(oldKeyList[i], oldValueList[i]) 319 | } 320 | } 321 | } 322 | } 323 | 324 | export default HashTableOpenAddressingBase 325 | -------------------------------------------------------------------------------- /src/data-structures/hash-tables/hash-table-quadratic-probing.ts: -------------------------------------------------------------------------------- 1 | import HashTableOpenAddressingBase from './hash-table-open-addressing-base' 2 | import { Hashable } from './entry' 3 | 4 | class HashTableQuadraticProbing extends HashTableOpenAddressingBase { 5 | constructor(capacity: number, loadFactor: number) { 6 | super(capacity, loadFactor) 7 | } 8 | 9 | setupProbing(key: K): void {} 10 | 11 | probe(x: number): number { 12 | // Quadratic probing function (x^2+x)/2 13 | return (x * x * x) >> 2 14 | } 15 | 16 | increaseCapacity(): void { 17 | this.capacity = this.nextPowerOfTwo(this.capacity) 18 | } 19 | 20 | adjustCapacity(): void { 21 | const pow2 = this.highestOneBit(this.capacity) 22 | if (this.capacity === pow2) return 23 | this.increaseCapacity() 24 | } 25 | 26 | private nextPowerOfTwo(n: number) { 27 | return this.highestOneBit(n) << 1 28 | } 29 | 30 | private highestOneBit(n: number) { 31 | if (n == 0) return 0 32 | let depth = 0 33 | let exponent = 16 34 | 35 | while (exponent > 0) { 36 | const shifted = n >> exponent 37 | if (shifted > 0) { 38 | depth += exponent 39 | if (shifted == 1) return depth + 1 40 | n >>= exponent 41 | } 42 | exponent /= 2 43 | } 44 | 45 | return depth + 1 46 | } 47 | } 48 | 49 | export default HashTableQuadraticProbing 50 | -------------------------------------------------------------------------------- /src/data-structures/hash-tables/hash-table-separate-chaining.ts: -------------------------------------------------------------------------------- 1 | import LinkedList from '../sequences/linked-list' 2 | import { Entry, Hashable } from './entry' 3 | 4 | // Code is based off William Fiset's implementation 5 | class HashTableSeparateChaining { 6 | private DEFAULT_CAPACITY = 3 7 | 8 | private table: Array>> 9 | 10 | private sz: number 11 | private capacity: number 12 | private maxLoadFactor: number 13 | private threshold: number 14 | 15 | constructor(capacity: number, maxLoadFactor: number) { 16 | if (capacity < 0) throw new Error('Illegal capacity') 17 | if (maxLoadFactor <= 0) throw new Error('Illegal maxLoadFactor') 18 | 19 | this.sz = 0 20 | this.capacity = Math.max(this.DEFAULT_CAPACITY, capacity) 21 | this.maxLoadFactor = maxLoadFactor 22 | this.threshold = Math.trunc(this.capacity * this.maxLoadFactor) 23 | 24 | this.table = new Array(this.capacity) 25 | } 26 | 27 | /***************************************************************************** 28 | INSPECTION 29 | *****************************************************************************/ 30 | /** 31 | * Returns the size of the hash table - O(1) 32 | * @return {number} 33 | */ 34 | size(): number { 35 | return this.sz 36 | } 37 | 38 | /** 39 | * Returns true if the hash table is empty, and false otherwise - O(1) 40 | * @return {boolean} 41 | */ 42 | isEmpty(): boolean { 43 | return this.size() === 0 44 | } 45 | 46 | /** 47 | * Clears the hash table - O(1) 48 | * @return {void} 49 | */ 50 | clear(): void { 51 | this.table.length = 0 52 | this.sz = 0 53 | } 54 | 55 | /** 56 | * Returns true if the hash table contains the key, and false otherwise 57 | * @param {K} K 58 | * @return {boolean} 59 | */ 60 | containsKey(key: K): boolean { 61 | const bucketIndex = this.normalizeIndex(key.hashCode()) 62 | return this.bucketSeekEntry(bucketIndex, key) !== null 63 | } 64 | 65 | /** 66 | * Returns a list of the hash table's keys - O(n) 67 | * @return {Array} 68 | */ 69 | keys(): Array { 70 | const keys: Array = [] 71 | 72 | for (const bucket of this.table) { 73 | if (bucket !== undefined) for (const entry of bucket) keys.push(entry.key) 74 | } 75 | 76 | return keys 77 | } 78 | 79 | /** 80 | * Returns a list of the hash table's values - O(n) 81 | * @return {Array} 82 | */ 83 | values(): Array { 84 | const values: Array = [] 85 | 86 | for (const bucket of this.table) { 87 | if (bucket !== undefined) for (const entry of bucket) values.push(entry.value) 88 | } 89 | 90 | return values 91 | } 92 | 93 | /***************************************************************************** 94 | MAIN 95 | ****************************************************************************/ 96 | /** 97 | * Returns value associated with key, and null if the key does not exist - O(1) amortized 98 | * @param {K} key 99 | * @return {V | null} 100 | */ 101 | get(key: K): V | null { 102 | const bucketIndex = this.normalizeIndex(key.hashCode()) 103 | 104 | const entry = this.bucketSeekEntry(bucketIndex, key) 105 | if (entry !== null) return entry.value 106 | 107 | return null 108 | } 109 | 110 | /** 111 | * Adds the [K, V] pair to the hash table - O(1) amortized 112 | * @param {K} key 113 | * @param {V} value 114 | * @returns {V | null} 115 | */ 116 | set(key: K, value: V): V | null { 117 | const entry = new Entry(key, value) 118 | const bucketIndex = this.normalizeIndex(key.hashCode()) 119 | 120 | return this.bucketInsertEntry(bucketIndex, entry) 121 | } 122 | 123 | /** 124 | * Deletes the entry with key K - O(1) amortized 125 | * @param {K} key 126 | * @returns {V | null} 127 | */ 128 | delete(key: K): V | null { 129 | const bucketIndex = this.normalizeIndex(key.hashCode()) 130 | return this.bucketDeleteEntry(bucketIndex, key) 131 | } 132 | 133 | /***************************************************************************** 134 | HELPERS 135 | ******************************************************************************/ 136 | // Converts a hash to an index by stripping the negative 137 | // sign and maps the hash to domain of [0, capacity] 138 | private normalizeIndex(hash: number): number { 139 | return (hash & 0x7fffffff) % this.capacity 140 | } 141 | 142 | private bucketInsertEntry(bucketIndex: number, entry: Entry): V | null { 143 | const bucket = this.table[bucketIndex] 144 | 145 | if (bucket === undefined) this.table[bucketIndex] = new LinkedList>() 146 | 147 | const entryAlreadyExists = this.bucketSeekEntry(bucketIndex, entry.key) 148 | 149 | if (!entryAlreadyExists) { 150 | bucket.addBack(entry) 151 | this.sz += 1 152 | 153 | if (this.sz > this.threshold) this.resizeTable() 154 | 155 | return null // use null to indicate no previous entry 156 | } else { 157 | const oldValue = entryAlreadyExists.value 158 | entryAlreadyExists.value = entry.value 159 | 160 | return oldValue 161 | } 162 | } 163 | 164 | private bucketDeleteEntry(bucketIndex: number, key: K): V | null { 165 | const entry = this.bucketSeekEntry(bucketIndex, key) 166 | 167 | if (entry === null) return null 168 | 169 | // o/w, entry with key, key exists in the bucket so remove it 170 | const bucket = this.table[bucketIndex] 171 | bucket.remove(entry) 172 | 173 | this.sz -= 1 174 | 175 | return entry.value 176 | } 177 | 178 | private bucketSeekEntry(bucketIndex: number, key: K): Entry | null { 179 | const bucket = this.table[bucketIndex] 180 | 181 | if (bucket === undefined) return null 182 | 183 | for (const entry of bucket) if (entry.key === key) return entry 184 | 185 | return null 186 | } 187 | 188 | private resizeTable(): void { 189 | this.capacity *= 2 190 | this.threshold = Math.trunc(this.capacity * this.maxLoadFactor) 191 | 192 | const newTable: Array>> = new Array(this.capacity) 193 | 194 | for (const bucket of this.table) { 195 | if (bucket !== undefined) { 196 | for (const entry of bucket) { 197 | const newBucketIndex = this.normalizeIndex(entry.hash) 198 | 199 | const newBucket = newTable[newBucketIndex] 200 | 201 | if (!newBucket) newTable[newBucketIndex] = new LinkedList>() 202 | 203 | newBucket.addBack(entry) 204 | } 205 | } 206 | } 207 | } 208 | } 209 | 210 | export default HashTableSeparateChaining 211 | -------------------------------------------------------------------------------- /src/data-structures/hash-tables/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HashTableSeparateChaining } from './hash-table-separate-chaining' 2 | export { default as HashTableLinearProbing } from './hash-table-linear-probing' 3 | export { default as HashTableQuadraticProbing } from './hash-table-quadratic-probing' 4 | export { default as HashTableDoubleHashing } from './hash-table-double-hashing' 5 | -------------------------------------------------------------------------------- /src/data-structures/index.ts: -------------------------------------------------------------------------------- 1 | import LinkedList from './sequences/linked-list' 2 | import Stack from './sequences/stack' 3 | import { Queue, Deque } from './sequences/queue' 4 | import CircularBuffer from './sequences/circular-buffer' 5 | import { 6 | MinBinaryHeap, 7 | MinDHeap, 8 | MinIndexedDHeap, 9 | MinBinomialHeap, 10 | LazyMinBinomialHeap, 11 | MinFibonacciHeap, 12 | } from './priority-queues' 13 | import { BinarySearchTree, AVLTree, BTree, RedBlackTree } from './trees' 14 | import { 15 | HashTableSeparateChaining, 16 | HashTableLinearProbing, 17 | HashTableQuadraticProbing, 18 | HashTableDoubleHashing, 19 | } from './hash-tables' 20 | 21 | const Collections = { 22 | LinkedList, 23 | Stack, 24 | Queue, 25 | Deque, 26 | CircularBuffer, 27 | MinBinaryHeap, 28 | MinDHeap, 29 | MinIndexedDHeap, 30 | MinBinomialHeap, 31 | LazyMinBinomialHeap, 32 | MinFibonacciHeap, 33 | BinarySearchTree, 34 | AVLTree, 35 | BTree, 36 | RedBlackTree, 37 | HashTableSeparateChaining, 38 | HashTableLinearProbing, 39 | HashTableQuadraticProbing, 40 | HashTableDoubleHashing, 41 | } 42 | 43 | export default Collections 44 | -------------------------------------------------------------------------------- /src/data-structures/priority-queues/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MinBinaryHeap } from './min-binary-heap' 2 | export { default as MinDHeap } from './min-d-heap' 3 | export { default as MinIndexedDHeap } from './min-indexed-d-heap' 4 | export { MinBinomialHeap, LazyMinBinomialHeap, MinFibonacciHeap } from './mergeable-heaps' 5 | -------------------------------------------------------------------------------- /src/data-structures/priority-queues/mergeable-heaps/binomial-node.ts: -------------------------------------------------------------------------------- 1 | // We use a LCRS representation which is more efficient for the heap use case. 2 | // See more here: https://stackoverflow.com/a/14015526/6113956 3 | 4 | class BinomialNode { 5 | value: T 6 | degree: number 7 | 8 | parent: BinomialNode | null 9 | sibling: BinomialNode | null 10 | child: BinomialNode | null 11 | 12 | constructor(value: T) { 13 | this.value = value 14 | this.degree = 0 15 | 16 | this.parent = null 17 | this.sibling = null 18 | this.child = null 19 | } 20 | } 21 | 22 | export default BinomialNode 23 | -------------------------------------------------------------------------------- /src/data-structures/priority-queues/mergeable-heaps/fibonacci-node.ts: -------------------------------------------------------------------------------- 1 | // We use a LCRS representation which is more efficient for the heap use case. 2 | // See more here: https://stackoverflow.com/a/14015526/6113956 3 | 4 | class FibonacciNode { 5 | value: T 6 | degree: number 7 | 8 | mark: boolean 9 | 10 | parent: FibonacciNode | null 11 | sibling: FibonacciNode | null 12 | prevSibling: FibonacciNode | null 13 | child: FibonacciNode | null 14 | 15 | constructor(value: T) { 16 | this.value = value 17 | this.degree = 0 18 | 19 | this.mark = false 20 | 21 | this.parent = null 22 | this.sibling = null 23 | this.prevSibling = null 24 | this.child = null 25 | } 26 | } 27 | 28 | export default FibonacciNode 29 | -------------------------------------------------------------------------------- /src/data-structures/priority-queues/mergeable-heaps/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MinBinomialHeap } from './min-binomial-heap' 2 | export { default as LazyMinBinomialHeap } from './lazy-min-binomial-heap' 3 | export { default as MinFibonacciHeap } from './min-fibonacci-heap' 4 | -------------------------------------------------------------------------------- /src/data-structures/priority-queues/min-binary-heap.ts: -------------------------------------------------------------------------------- 1 | import * as utils from '../utils' 2 | 3 | /******************************************************************************* 4 | * A min binary heap implements the Priority Queue ADT. It has constant access 5 | * to the min element of the heap, with logarithmic insertions and deletions. 6 | * 7 | * add(element) - O(logn) 8 | * poll() - O(logn) 9 | * peek() - O(1) 10 | * 11 | * For more info, refer to https://en.wikipedia.org/wiki/Binary_heap 12 | ******************************************************************************/ 13 | class MinBinaryHeap { 14 | // a dynamic array to hold our elements 15 | private heap: T[] 16 | private compare: utils.CompareFunction 17 | 18 | constructor(elements?: Iterable, compareFunction?: utils.CompareFunction) { 19 | this.heap = [] 20 | this.compare = compareFunction || utils.defaultCompare 21 | 22 | // if the client provides elements, heapify them 23 | if (elements) { 24 | this.heap = Array.from(elements) 25 | this.heapify() 26 | } 27 | } 28 | 29 | // Even though we are looping through n/2 elements and calling sink which is O(logn), 30 | // this method is still bounded by O(n), not O(nlogn). The reason being is because 31 | // we start at the second last row of the tree. Not sinking the last row of the tree 32 | // already removes half the work. 33 | 34 | // See more info on Floyd's heap construction here: https://en.wikipedia.org/wiki/Heapsort#Variations 35 | private heapify(): void { 36 | if (this.heap.length === 0) return 37 | 38 | let i = Math.max(0, Math.floor(this.size() / 2) - 1) 39 | for (; i >= 0; i--) { 40 | this.sink(i) 41 | } 42 | } 43 | 44 | /***************************************************************************** 45 | INSPECTION 46 | *****************************************************************************/ 47 | /** 48 | * Returns the size of the heap - O(1) 49 | * @returns {number} 50 | */ 51 | size(): number { 52 | return this.heap.length 53 | } 54 | 55 | /** 56 | * Returns true if the heap is empty, false otherwise - O(1) 57 | * @returns {boolean} 58 | */ 59 | isEmpty(): boolean { 60 | return this.size() == 0 61 | } 62 | 63 | /***************************************************************************** 64 | INSERTION/DELETION 65 | *****************************************************************************/ 66 | /** 67 | * Adds an element to the heap, while maintaing the heap invariant - O(log(n)) 68 | * @param {T} element 69 | * @returns {void} 70 | */ 71 | add(element: T): void { 72 | this.heap.push(element) 73 | const indexOfLastElement = this.size() - 1 74 | this.swim(indexOfLastElement) 75 | } 76 | 77 | /** 78 | * Removes and returns top most element of heap - O(log(n)) 79 | * @returns {T} 80 | */ 81 | poll(): T | null { 82 | if (this.isEmpty()) return null 83 | 84 | return this.removeAt(0) // O(log(n)) 85 | } 86 | 87 | /** 88 | * Removes element if it exists. Returns true if success, false otherwise - O(n) 89 | * @param {T} element 90 | * @returns {boolean} 91 | */ 92 | remove(element: T): boolean { 93 | // O(n), linear scan to find elementIndex 94 | const elementIndex = this.heap.findIndex((h: T) => this.compare(element, h) === 0) 95 | 96 | if (elementIndex === -1) return false 97 | 98 | this.removeAt(elementIndex) // O(log(n)) 99 | return true 100 | } 101 | 102 | /** 103 | * Clears the heap - O(1) 104 | * @returns {void} 105 | */ 106 | clear(): void { 107 | this.heap.length = 0 108 | } 109 | 110 | /***************************************************************************** 111 | ACCESSING 112 | *****************************************************************************/ 113 | /** 114 | * Peeks at the top most element in the heap - O(1) 115 | * @returns {T} 116 | */ 117 | peek(): T | null { 118 | if (this.isEmpty()) return null 119 | 120 | return this.heap[0] 121 | } 122 | 123 | /***************************************************************************** 124 | SEARCHING 125 | *****************************************************************************/ 126 | /** 127 | * Returns true if element is in heap, false otherwise - O(n) 128 | * @param {T} element 129 | * @returns {boolean} 130 | */ 131 | contains(element: T): boolean { 132 | return this.heap.includes(element) 133 | } 134 | 135 | /***************************************************************************** 136 | HELPERS 137 | *****************************************************************************/ 138 | /** 139 | * Sinks element with index k until heap invariant is satisfied - O(log(n)) 140 | * O(log(n)) because in the worst case we sink the element down the entire 141 | * height of the tree 142 | * @param {number} k 143 | * @returns {void} 144 | */ 145 | private sink(k: number): void { 146 | // eslint-disable-next-line 147 | while (true) { 148 | // get index of children 149 | const leftChildIndex = this.getLeftChildIndex(k) 150 | const rightChildIndex = this.getRightChildIndex(k) 151 | 152 | // find the smallest child 153 | let smallestIndex = leftChildIndex 154 | const rightChildIsSmallerThanLeft = 155 | rightChildIndex < this.size() && this.less(rightChildIndex, leftChildIndex) 156 | if (rightChildIsSmallerThanLeft) smallestIndex = rightChildIndex 157 | 158 | // if the children indices are out of bounds or the current element is 159 | // less than than the child, stop looping and break 160 | const indexOutOfBounds = leftChildIndex >= this.size() 161 | const elementIsLessThanChild = this.less(k, smallestIndex) 162 | if (indexOutOfBounds || elementIsLessThanChild) break 163 | 164 | // otherwise swae 165 | this.swap(smallestIndex, k) // O(1) 166 | k = smallestIndex // set k to child index, and we repeat loop 167 | } 168 | } 169 | 170 | /** 171 | * Swims an element with index k until heap invariant is satisfied - O(log(n)) 172 | * O(log(n)) because in the worst case we swim the element up the entire tree 173 | * @param {number} k 174 | * @returns {void} 175 | */ 176 | private swim(k: number): void { 177 | let parentIndex = this.getParentIndex(k) 178 | 179 | // as long as k is positive and this.heap[k] < this.heap[parentIndex] 180 | while (k > 0 && this.less(k, parentIndex)) { 181 | this.swap(parentIndex, k) 182 | k = parentIndex 183 | 184 | parentIndex = this.getParentIndex(k) 185 | } 186 | } 187 | 188 | // O(1) 189 | private swap(i: number, j: number): void { 190 | const temp = this.heap[i] 191 | 192 | this.heap[i] = this.heap[j] 193 | this.heap[j] = temp 194 | } 195 | 196 | /** 197 | * Removes element at provided index by swapping it with last element, and 198 | * heapifying the swapped element by sinking/swimming it - O(log(n)). 199 | * 200 | * O(log(n)) because in worst case we swink/swim element throughout the entire tree 201 | * @param {number} indexToRemove 202 | * @returns {T} 203 | */ 204 | private removeAt(indexToRemove: number): T { 205 | const indexOfLastElement = this.size() - 1 206 | // save the removed element so we can return it after heapifying 207 | const removedElement = this.heap[indexToRemove] 208 | 209 | // swap the removed element with the last element 210 | this.swap(indexToRemove, indexOfLastElement) 211 | // delete the removed element! 212 | this.heap.pop() 213 | 214 | if (this.heap.length === 0) return removedElement 215 | 216 | // if last element is being removed, no need to heapify (sink/swim) 217 | const lastElementIsBeingRemoved = indexToRemove === indexOfLastElement - 1 218 | if (lastElementIsBeingRemoved) return removedElement 219 | 220 | // try sinking 221 | const indexToBeHeapified = indexToRemove 222 | const elementToBeHeapified = this.heap[indexToBeHeapified] 223 | this.sink(indexToBeHeapified) 224 | 225 | // if sinking did not work try swimming 226 | if (this.heap[indexToBeHeapified] === elementToBeHeapified) { 227 | this.swim(indexToBeHeapified) 228 | } 229 | 230 | // return saved value from before 231 | return removedElement 232 | } 233 | 234 | // O(1) 235 | private getLeftChildIndex(parentIndex: number): number { 236 | return parentIndex * 2 + 1 237 | } 238 | // O(1) 239 | private getRightChildIndex(parentIndex: number): number { 240 | return parentIndex * 2 + 2 241 | } 242 | // O(1) 243 | private getParentIndex(childIndex: number): number { 244 | return Math.floor((childIndex - 1) / 2) 245 | } 246 | 247 | /** 248 | * Returns true if a is less than b, false otherwise 249 | * @param {number} a 250 | * @param {number} b 251 | */ 252 | private less(a: number, b: number) { 253 | return this.compare(this.heap[a], this.heap[b]) < 0 254 | } 255 | } 256 | 257 | export default MinBinaryHeap 258 | -------------------------------------------------------------------------------- /src/data-structures/priority-queues/min-d-heap.ts: -------------------------------------------------------------------------------- 1 | import * as utils from '../utils' 2 | 3 | /******************************************************************************* 4 | * A D-ary implements the Priority Queue ADT, just like the binary heap. 5 | * What's different is that it has d children. 6 | * 7 | * D-ary heaps are better for decreaseKey operations because decreaseKey() 8 | * means we must swim the node up (min heap). Since d > 2, log_d(n) < log_2(n), 9 | * meaning we swim up fewer levels. 10 | * 11 | * This is preferred for algorithms with heavy decreaseKey() calls like 12 | * Djikstra's shortest path and Prim's minimum spanning tree. 13 | * 14 | * 15 | * add(element) - O(log_dn) no comparisons needed for swimming up 16 | * poll() - O(dlog_dn) (d child comparisons when sinking to find min child) 17 | * decreaseKey() - O(log_dn) bc we swim up 18 | * peek() - O(1) 19 | * 20 | * More info can be found here: https://en.wikipedia.org/wiki/D-ary_heap 21 | ******************************************************************************/ 22 | 23 | class MinDHeap { 24 | // a dynamic array to hold our elements 25 | private heap: T[] 26 | private d: number 27 | 28 | private compare: utils.CompareFunction 29 | 30 | constructor(degree: number, compareFunction?: utils.CompareFunction) { 31 | this.heap = [] 32 | this.d = Math.max(2, degree) // degree must be at least 2 33 | 34 | this.compare = compareFunction || utils.defaultCompare 35 | } 36 | 37 | /***************************************************************************** 38 | INSPECTION 39 | *****************************************************************************/ 40 | /** 41 | * Returns the size of the heap - O(1) 42 | * @returns {number} 43 | */ 44 | size(): number { 45 | return this.heap.length 46 | } 47 | /** 48 | * Returns true if the heap is empty, false otherwise - O(1) 49 | * @returns {boolean} 50 | */ 51 | isEmpty(): boolean { 52 | return this.size() == 0 53 | } 54 | 55 | /***************************************************************************** 56 | INSERTION 57 | *****************************************************************************/ 58 | /** 59 | * Adds an element to the heap, while maintaing the heap invariant - O(log_d(n)) 60 | * @param {T} element 61 | * @returns {void} 62 | */ 63 | add(element: T): void { 64 | this.heap.push(element) 65 | const indexOfLastElement = this.size() - 1 66 | this.swim(indexOfLastElement) // O(log_d(n)) 67 | } 68 | 69 | /***************************************************************************** 70 | ACCESSING 71 | *****************************************************************************/ 72 | /** 73 | * Peeks at the top most element in the heap - O(1) 74 | * @returns {T} 75 | */ 76 | peek(): T | null { 77 | if (this.isEmpty()) return null 78 | 79 | return this.heap[0] 80 | } 81 | 82 | /***************************************************************************** 83 | SEARCHING 84 | *****************************************************************************/ 85 | /** 86 | * Returns true if element is in heap, false otherwise - O(n) 87 | * @param {T} element 88 | * @returns {boolean} 89 | */ 90 | contains(element: T): boolean { 91 | return this.heap.includes(element) 92 | } 93 | 94 | /***************************************************************************** 95 | DELETION 96 | *****************************************************************************/ 97 | /** 98 | * Removes and returns top most element of heap - O(log_d(n)) 99 | * @returns {T} 100 | */ 101 | poll(): T | null { 102 | if (this.isEmpty()) return null 103 | 104 | return this.removeAt(0) // O(log(n)) 105 | } 106 | /** 107 | * Removes element if it exists. Returns true if success, false otherwise - O(n) 108 | * @param {T} element 109 | * @returns {boolean} 110 | */ 111 | remove(element: T): boolean { 112 | // O(n), linear scan to find elementIndex 113 | const elementIndex = this.heap.findIndex((h: T) => this.compare(element, h) === 0) 114 | 115 | if (elementIndex === -1) return false 116 | 117 | this.removeAt(elementIndex) // O(log(n)) 118 | return true 119 | } 120 | /** 121 | * Clears the heap - O(1) 122 | * @returns {void} 123 | */ 124 | clear(): void { 125 | this.heap.length = 0 126 | } 127 | 128 | /***************************************************************************** 129 | HELPERS 130 | *****************************************************************************/ 131 | // O(1) 132 | private getChildrenIndices(parentIndex: number): number[] { 133 | const indices = [] 134 | for (let i = 1; i <= this.d; i++) { 135 | indices.push(parentIndex * this.d + i) 136 | } 137 | 138 | return indices 139 | } 140 | // O(1) 141 | private getParentIndex(childIndex: number): number { 142 | return Math.floor((childIndex - 1) / this.d) 143 | } 144 | 145 | /** 146 | * Returns true if a is less than b, false otherwise 147 | * @param {number} a 148 | * @param {number} b 149 | */ 150 | private less(a: number, b: number) { 151 | return this.compare(this.heap[a], this.heap[b]) < 0 152 | } 153 | 154 | /** 155 | * Sinks element with index k until heap invariant is satisfied - O(dlog(n)) 156 | * O(dlog(n)) because in the worst case we sink the element down the entire 157 | * height of the tree. At each level, we have to do d comparisons to find 158 | * smallest child to swim down. 159 | * @param {number} k 160 | * @returns {void} 161 | */ 162 | private sink(k: number): void { 163 | // eslint-disable-next-line 164 | while (true) { 165 | const childrenIndices = this.getChildrenIndices(k) 166 | 167 | // get smallest index O(d) 168 | let smallestIndex = childrenIndices[0] // assume left most child is smallest at first 169 | for (const childIndex of childrenIndices) { 170 | if (childIndex < this.size() && this.less(childIndex, smallestIndex)) { 171 | smallestIndex = childIndex 172 | } 173 | } 174 | 175 | const indexOutOfBounds = smallestIndex >= this.size() 176 | const elementIsLessThanChild = this.less(k, smallestIndex) 177 | if (indexOutOfBounds || elementIsLessThanChild) break 178 | 179 | this.swap(smallestIndex, k) // O(1) 180 | k = smallestIndex // set k to child index, and we repeat loop 181 | } 182 | } 183 | 184 | /** 185 | * Swims an element with index k until heap invariant is satisfied - O(log_d(n)) 186 | * O(logd(n)) because in the worst case we swim the element up the entire tree 187 | * @param {number} k 188 | * @returns {void} 189 | */ 190 | private swim(k: number): void { 191 | let parentIndex = this.getParentIndex(k) 192 | 193 | while (k > 0 && this.less(k, parentIndex)) { 194 | this.swap(parentIndex, k) 195 | k = parentIndex // move k pointer up 196 | 197 | parentIndex = this.getParentIndex(k) 198 | } 199 | } 200 | 201 | // O(1) 202 | private swap(i: number, j: number): void { 203 | const temp = this.heap[i] 204 | 205 | this.heap[i] = this.heap[j] 206 | this.heap[j] = temp 207 | } 208 | 209 | /** 210 | * Removes element at provided index by swapping it with last element, and 211 | * heapifying the swapped element by sinking/swimming it - O(dlog(n)). 212 | * 213 | * O(dlog(n)) because in worst case we have to sink element throughout entire 214 | * tree 215 | * @param {number} indexToRemove 216 | * @returns {T} 217 | * @throws {OUT_OF_BOUNDS_ERROR, EMPTY_ERROR} 218 | */ 219 | private removeAt(indexToRemove: number): T { 220 | // Following these 3 loc never execute. removeAt() is only called with 221 | // removeAt(0) and in remove() which finds the specified index 222 | 223 | // const indexOutOfBounds = indexToRemove < 0 || indexToRemove > this.size() 224 | // if (indexOutOfBounds) throw new Error(utils.OUT_OF_BOUNDS_ERROR) 225 | // if (this.isEmpty()) return null 226 | 227 | const indexOfLastElement = this.size() - 1 228 | // save the removed element so we can return it after heapifying 229 | const removedElement = this.heap[indexToRemove] 230 | 231 | // swap the removed element with the last element 232 | this.swap(indexToRemove, indexOfLastElement) 233 | // delete the removed element! 234 | this.heap.pop() 235 | 236 | // if last element is being removed, no need to heapify 237 | const lastElementIsBeingRemoved = indexToRemove === indexOfLastElement 238 | if (lastElementIsBeingRemoved) return removedElement 239 | 240 | // try sinking 241 | const indexToBeHeapified = indexToRemove 242 | const elementToBeHeapified = this.heap[indexToBeHeapified] 243 | this.sink(indexToBeHeapified) 244 | 245 | const elementDidNotMove = this.heap[indexToBeHeapified] === elementToBeHeapified 246 | if (elementDidNotMove) this.swim(indexToBeHeapified) // swim if sinking didn't work 247 | 248 | // return saved value from before 249 | return removedElement 250 | } 251 | } 252 | 253 | export default MinDHeap 254 | -------------------------------------------------------------------------------- /src/data-structures/sequences/circular-buffer/circular-buffer.ts: -------------------------------------------------------------------------------- 1 | import * as utils from '../../utils' 2 | 3 | class CircularBuffer { 4 | private list: T[] 5 | private sz: number 6 | private capacity: number 7 | 8 | private readIndex: number 9 | private writeIndex: number 10 | 11 | private equalsF: utils.EqualsFunction 12 | 13 | constructor(capacity: number, equalsFunction?: utils.EqualsFunction) { 14 | this.list = new Array(capacity) 15 | this.sz = 0 16 | this.capacity = capacity 17 | 18 | this.readIndex = 0 19 | this.writeIndex = 0 20 | 21 | this.equalsF = equalsFunction || utils.defaultEquals 22 | } 23 | 24 | /***************************************************************************** 25 | INSPECTION 26 | *****************************************************************************/ 27 | /** 28 | * Returns size of circular buffer - O(1) 29 | */ 30 | size(): number { 31 | return this.sz 32 | } 33 | 34 | /** 35 | * Returns true if buffer is empty, false otherwise - O(1) 36 | */ 37 | isEmpty(): boolean { 38 | return this.size() === 0 39 | } 40 | 41 | /** 42 | * Deletes all elements in buffer - O(capacity) 43 | */ 44 | clear(): void { 45 | this.list = new Array(this.capacity) 46 | this.sz = 0 47 | } 48 | 49 | /***************************************************************************** 50 | INSERTION/DELETION 51 | *****************************************************************************/ 52 | /** 53 | * Enqueues element into queue - O(1) 54 | * @param {T} element - element to be enqueued 55 | */ 56 | enqueue(element: T): void { 57 | this.list[this.writeIndex] = element 58 | 59 | const elementIsOverWritten = this.sz !== 0 && this.writeIndex === this.readIndex 60 | if (elementIsOverWritten) { 61 | this.readIndex = (this.readIndex + 1) % this.capacity 62 | } 63 | 64 | this.writeIndex = (this.writeIndex + 1) % this.capacity 65 | 66 | this.sz += 1 67 | } 68 | 69 | /** 70 | * Dequeues element from queue - O(1) 71 | * @returns {T} 72 | */ 73 | dequeue(): T | null { 74 | if (this.isEmpty()) return null 75 | 76 | const removedVal = this.list[this.readIndex] 77 | this.readIndex = (this.readIndex + 1) % this.capacity 78 | 79 | this.sz -= 1 80 | 81 | return removedVal 82 | } 83 | 84 | /***************************************************************************** 85 | ACCESSING 86 | *****************************************************************************/ 87 | /** 88 | * Peeks the element at the front of the buffer - O(1) 89 | * @returns {T} - Frontmost element 90 | */ 91 | peekFront(): T | null { 92 | if (this.isEmpty()) return null 93 | 94 | return this.list[this.readIndex] 95 | } 96 | 97 | /** 98 | * Peeks the element at the back of the buffer - O(1) 99 | * @returns {T} - Backmost element 100 | */ 101 | peekBack(): T | null { 102 | if (this.isEmpty()) return null 103 | 104 | let i = this.writeIndex - 1 105 | if (i < 0) i = this.capacity - 1 106 | 107 | return this.list[i] 108 | } 109 | 110 | /***************************************************************************** 111 | SEARCHING 112 | *****************************************************************************/ 113 | /** 114 | * Checks if element is in buffer - O(n) 115 | * @param {T} element - element to search for 116 | * @returns {boolean} 117 | */ 118 | contains(element: T): boolean { 119 | return this.list.some((val: T) => { 120 | return this.equalsF(val, element) 121 | }) 122 | } 123 | } 124 | 125 | export default CircularBuffer 126 | -------------------------------------------------------------------------------- /src/data-structures/sequences/circular-buffer/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './circular-buffer' 2 | -------------------------------------------------------------------------------- /src/data-structures/sequences/linked-list/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './linked-list' 2 | -------------------------------------------------------------------------------- /src/data-structures/sequences/linked-list/linked-list-node.ts: -------------------------------------------------------------------------------- 1 | class LinkedListNode { 2 | val: T 3 | next: LinkedListNode | null 4 | prev: LinkedListNode | null 5 | 6 | constructor(val: T) { 7 | this.val = val 8 | this.next = null 9 | this.prev = null 10 | } 11 | } 12 | 13 | export default LinkedListNode 14 | -------------------------------------------------------------------------------- /src/data-structures/sequences/linked-list/linked-list.ts: -------------------------------------------------------------------------------- 1 | import LinkedListNode from './linked-list-node' 2 | import * as utils from '../../utils' 3 | 4 | interface List { 5 | head: LinkedListNode 6 | tail: LinkedListNode 7 | size: number 8 | } 9 | 10 | class LinkedList implements Iterable { 11 | private list: List | undefined 12 | private equalsF: utils.EqualsFunction = utils.defaultEquals 13 | 14 | /** 15 | * Creates a LinkedList - O(1) 16 | * @param equalsFunction equal function for for non-primitive values. 17 | */ 18 | constructor(equalsFunction?: utils.EqualsFunction) { 19 | this.list = undefined 20 | 21 | if (equalsFunction) this.equalsF = equalsFunction 22 | } 23 | 24 | /***************************************************************************** 25 | INSPECTION 26 | *****************************************************************************/ 27 | /** 28 | * Returns size - O(1) 29 | * @return {number} 30 | */ 31 | size(): number { 32 | if (this.list) return this.list.size 33 | 34 | return 0 35 | } 36 | /** 37 | * Returns true if inked list is empty, false otherwise - O(1) 38 | * @return {number} 39 | */ 40 | isEmpty(): boolean { 41 | return !this.list 42 | } 43 | 44 | /***************************************************************************** 45 | INSERTION 46 | *****************************************************************************/ 47 | /** 48 | * Adds node to the head of the linked list - O(1) 49 | * @param {T} val - value to add to list 50 | * @return {void} 51 | */ 52 | addFront(val: T): boolean { 53 | const newNode = new LinkedListNode(val) 54 | 55 | if (this.list) { 56 | // link old head backwards 57 | this.list.head.prev = newNode 58 | 59 | // link new head forwards 60 | newNode.next = this.list.head 61 | 62 | this.list.head = newNode 63 | this.list.size += 1 64 | } else { 65 | this.list = { 66 | head: newNode, 67 | tail: newNode, 68 | size: 1, 69 | } 70 | } 71 | 72 | return true 73 | } 74 | /** 75 | * Adds node to the tail of the linked list - O(1) 76 | * @param {T} - value to add to list 77 | * @return {void} 78 | */ 79 | addBack(val: T): boolean { 80 | const newNode = new LinkedListNode(val) 81 | 82 | if (this.list) { 83 | // link old tail forwards 84 | this.list.tail.next = newNode 85 | 86 | // link new tail backwards 87 | newNode.prev = this.list.tail 88 | 89 | this.list.tail = newNode 90 | this.list.size += 1 91 | } else { 92 | this.list = { 93 | head: newNode, 94 | tail: newNode, 95 | size: 1, 96 | } 97 | } 98 | 99 | return true 100 | } 101 | /** 102 | * Adds a node at specified index - O(n) 103 | * @param {number} i - index 104 | * @param {T} val - value to add to list 105 | * @return {void} 106 | */ 107 | addAt(i: number, val: T): boolean { 108 | if (i === 0) { 109 | return this.addFront(val) 110 | } 111 | 112 | if (i === this.size()) { 113 | return this.addBack(val) 114 | } 115 | 116 | if (i < 0 || i >= this.size() || !this.list) return false 117 | 118 | let cur = this.list.head 119 | // traverse to index 120 | for (let j = 0; j < i - 1; j++) { 121 | cur = cur.next! // eslint-disable-line 122 | } 123 | 124 | const newNode = new LinkedListNode(val) 125 | 126 | // link next node 127 | cur.next!.prev = newNode // eslint-disable-line 128 | newNode.next = cur.next 129 | 130 | // link prev node 131 | newNode.prev = cur 132 | cur.next = newNode 133 | 134 | this.list.size += 1 135 | 136 | return true 137 | } 138 | 139 | /***************************************************************************** 140 | ACCESSING 141 | *****************************************************************************/ 142 | /** 143 | * Gets the value of head - O(1) 144 | * @returns {T} value of head 145 | */ 146 | peekFront(): T | null { 147 | if (!this.list) return null 148 | return this.list.head.val 149 | } 150 | /** 151 | * Gets the value of tail - O(1) 152 | * @returns {T} value of tail 153 | */ 154 | peekBack(): T | null { 155 | if (!this.list) return null 156 | return this.list.tail.val 157 | } 158 | /** 159 | * Gets the element at index i - O(n) 160 | * @param {number} i - index of element 161 | * @returns {T} value of element at index i 162 | */ 163 | get(i: number): T | null { 164 | if (i < 0 || i >= this.size() || !this.list) { 165 | return null 166 | } 167 | 168 | let j = 0 169 | let cur = this.list.head 170 | while (j < i) { 171 | cur = cur.next! // eslint-disable-line 172 | j++ 173 | } 174 | 175 | return cur.val 176 | } 177 | 178 | /***************************************************************************** 179 | SEARCHING 180 | *****************************************************************************/ 181 | /** 182 | * Removes the first occurrence of the specified item in the linked list. 183 | * @param {T} value - value to search for 184 | * @return {number} the index of the first occurence of the element, and -1 185 | * if the element does not exist. 186 | */ 187 | indexOf(value: T): number { 188 | // list is empty 189 | if (!this.list) return -1 190 | 191 | let i = 0 192 | let cur = this.list.head 193 | 194 | while (!this.equalsF(cur.val, value)) { 195 | // cur.next === null means we reached end of list without finding element 196 | if (!cur.next) return -1 197 | 198 | cur = cur.next 199 | i += 1 200 | } 201 | 202 | return i 203 | } 204 | /** 205 | * Checks if value is in linked list. 206 | * @param {T} value - value to search for 207 | * @returns {boolean} 208 | */ 209 | contains(value: T): boolean { 210 | const index = this.indexOf(value) 211 | 212 | return index !== -1 213 | } 214 | 215 | /***************************************************************************** 216 | DELETION 217 | *****************************************************************************/ 218 | /** 219 | * Removes head - O(1) 220 | * @return {T} - value of removed head 221 | */ 222 | removeFront(): T | null { 223 | if (!this.list) return null 224 | 225 | // extract val of head so we can return it later 226 | const val = this.list.head.val 227 | 228 | if (this.list.head.next) { 229 | // newHead.prev = null 230 | this.list.head.next.prev = null 231 | 232 | // move head pointer forwards 233 | this.list.head = this.list.head.next 234 | 235 | this.list.size -= 1 236 | } else { 237 | // list is size 1, clear the list 238 | this.list = undefined 239 | } 240 | 241 | return val 242 | } 243 | /** 244 | * Removes tail - O(1) 245 | * @return {T} - value of removed head 246 | */ 247 | removeBack(): T | null { 248 | if (!this.list) return null 249 | 250 | // extract the val of tail so we can return it later 251 | const val = this.list.tail.val 252 | 253 | if (this.list.tail.prev) { 254 | // newTail.next = null 255 | this.list.tail.prev.next = null 256 | 257 | // move tail pointer backwards 258 | this.list.tail = this.list.tail.prev 259 | 260 | this.list.size -= 1 261 | } else { 262 | this.list = undefined 263 | } 264 | 265 | return val 266 | } 267 | /** 268 | * Removes first occurence of node with specified value. Returns true if 269 | * removal was successful, and false otherwise. - O(n) 270 | * @param {T} val - value to remove 271 | * @returns {T} - value of removed node 272 | */ 273 | remove(val: T): T | null { 274 | const index = this.indexOf(val) // O(n) 275 | if (index === -1) return null 276 | 277 | return this.removeAt(index) // O(n) 278 | } 279 | /** 280 | * Removes node at specified index- O(n) 281 | * @param {number} i - index to remove 282 | * @return {T} - value of removed node 283 | */ 284 | removeAt(i: number): T | null { 285 | if (!this.list) return null 286 | 287 | if (i === 0) { 288 | return this.removeFront() 289 | } else if (i === this.size() - 1) { 290 | return this.removeBack() 291 | } 292 | 293 | if (i < 0 || i >= this.list.size) return null 294 | 295 | let j = 0 296 | let cur = this.list.head 297 | 298 | // traverse to node to be deleted 299 | while (j < i) { 300 | cur = cur.next! // eslint-disable-line 301 | j += 1 302 | } 303 | 304 | // delete node 305 | cur.prev!.next = cur.next // eslint-disable-line 306 | cur.next!.prev = cur.prev // eslint-disable-line 307 | 308 | this.list.size -= 1 309 | 310 | return cur.val 311 | } 312 | /** 313 | * Deletes all nodes - O(1) 314 | */ 315 | clear(): void { 316 | this.list = undefined 317 | } 318 | 319 | /***************************************************************************** 320 | HELPERS 321 | *****************************************************************************/ 322 | /** 323 | * Appends values from an array to list - O(k) 324 | */ 325 | fromArray(A: T[]): LinkedList { 326 | for (const a of A) { 327 | this.addBack(a) 328 | } 329 | 330 | return this 331 | } 332 | *[Symbol.iterator](): Iterator { 333 | if (!this.list) return 334 | 335 | let cur: LinkedListNode | null 336 | 337 | for (cur = this.list.head; cur != null; cur = cur.next) { 338 | yield cur.val 339 | } 340 | } 341 | } 342 | 343 | export default LinkedList 344 | -------------------------------------------------------------------------------- /src/data-structures/sequences/queue/deque.ts: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list' 2 | import * as utils from '../../utils' 3 | 4 | class Deque implements Iterable { 5 | private list: LinkedList 6 | 7 | constructor(equalsFunction?: utils.EqualsFunction) { 8 | if (equalsFunction) this.list = new LinkedList(equalsFunction) 9 | else this.list = new LinkedList() 10 | } 11 | 12 | /***************************************************************************** 13 | INSPECTION 14 | *****************************************************************************/ 15 | /** 16 | * Returns size of queue - O(1) 17 | */ 18 | size(): number { 19 | return this.list.size() 20 | } 21 | 22 | /** 23 | * Returns true if queue is empty, false otherwise - O(1) 24 | */ 25 | isEmpty(): boolean { 26 | return this.list.isEmpty() 27 | } 28 | 29 | /** 30 | * Deletes all elements in queue - O(1) 31 | */ 32 | clear(): void { 33 | this.list.clear() 34 | } 35 | 36 | /***************************************************************************** 37 | INSERTION/DELETION 38 | *****************************************************************************/ 39 | /** 40 | * Pushes element into front of queue - O(1) 41 | * @param {T} element - element to be enqueued 42 | */ 43 | pushFront(element: T): void { 44 | this.list.addFront(element) 45 | } 46 | 47 | /** 48 | * Pushes element from back of queue - O(1) 49 | * @param {T} element - element to be enqueued 50 | */ 51 | pushBack(element: T): void { 52 | this.list.addBack(element) 53 | } 54 | 55 | /** 56 | * Pops element from back queue - O(1) 57 | * @returns {T | null} 58 | */ 59 | popFront(): T | null { 60 | if (this.isEmpty()) return null 61 | return this.list.removeFront() 62 | } 63 | 64 | /** 65 | * Pops element from back queue - O(1) 66 | * @returns {T | null} 67 | */ 68 | popBack(): T | null { 69 | if (this.isEmpty()) return null 70 | return this.list.removeBack() 71 | } 72 | 73 | /***************************************************************************** 74 | ACCESSING 75 | *****************************************************************************/ 76 | /** 77 | * Peeks at the element at the front of the queue - O(1) 78 | * @returns {T} - Frontmost element 79 | */ 80 | peekFront(): T | null { 81 | if (this.isEmpty()) return null 82 | return this.list.peekFront() 83 | } 84 | 85 | /** 86 | * Peeks at the element at the back of the queue - O(1) 87 | * @returns {T} - Backmost element 88 | */ 89 | peekBack(): T | null { 90 | if (this.isEmpty()) return null 91 | return this.list.peekBack() 92 | } 93 | 94 | /***************************************************************************** 95 | SEARCHING 96 | *****************************************************************************/ 97 | /** 98 | * Checks if value is in queue - O(n) 99 | * @param {T} element - element to search for 100 | * @returns {boolean} 101 | */ 102 | contains(element: T): boolean { 103 | return this.list.contains(element) 104 | } 105 | 106 | /***************************************************************************** 107 | HELPERS 108 | *****************************************************************************/ 109 | [Symbol.iterator](): Iterator { 110 | return this.list[Symbol.iterator]() 111 | } 112 | } 113 | 114 | export default Deque 115 | -------------------------------------------------------------------------------- /src/data-structures/sequences/queue/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Queue } from './queue' 2 | export { default as Deque } from './deque' 3 | -------------------------------------------------------------------------------- /src/data-structures/sequences/queue/queue.ts: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list' 2 | import * as utils from '../../utils' 3 | 4 | class Queue implements Iterable { 5 | private list: LinkedList 6 | 7 | constructor(equalsFunction?: utils.EqualsFunction) { 8 | if (equalsFunction) this.list = new LinkedList(equalsFunction) 9 | else this.list = new LinkedList() 10 | } 11 | 12 | /***************************************************************************** 13 | INSPECTION 14 | *****************************************************************************/ 15 | /** 16 | * Returns size of queue - O(1) 17 | */ 18 | size(): number { 19 | return this.list.size() 20 | } 21 | /** 22 | * Returns true if queue is empty, false otherwise - O(1) 23 | */ 24 | isEmpty(): boolean { 25 | return this.list.isEmpty() 26 | } 27 | /** 28 | * Deletes all elements in queue - O(1) 29 | */ 30 | clear(): void { 31 | this.list.clear() 32 | } 33 | 34 | /***************************************************************************** 35 | INSERTION/DELETION 36 | *****************************************************************************/ 37 | /** 38 | * Enqueues element into queue - O(1) 39 | * @param {T} element - element to be enqueued 40 | */ 41 | enqueue(element: T): void { 42 | this.list.addFront(element) 43 | } 44 | /** 45 | * Dequeues element from queue - O(1) 46 | * @returns {T} 47 | */ 48 | dequeue(): T | null { 49 | if (this.isEmpty()) return null 50 | return this.list.removeBack() 51 | } 52 | 53 | /***************************************************************************** 54 | ACCESSING 55 | *****************************************************************************/ 56 | /** 57 | * Peeks at the element at the front of the queue - O(1) 58 | * @returns {T} - Frontmost element 59 | */ 60 | peekFront(): T | null { 61 | if (this.isEmpty()) return null 62 | return this.list.peekBack() 63 | } 64 | /** 65 | * Peeks at the element at the back of the queue - O(1) 66 | * @returns {T} - Backmost element 67 | */ 68 | peekBack(): T | null { 69 | if (this.isEmpty()) return null 70 | return this.list.peekFront() 71 | } 72 | 73 | /***************************************************************************** 74 | SEARCHING 75 | *****************************************************************************/ 76 | /** 77 | * Checks if value is in queue - O(n) 78 | * @param {T} element - element to search for 79 | * @returns {boolean} 80 | */ 81 | contains(element: T): boolean { 82 | return this.list.contains(element) 83 | } 84 | 85 | /***************************************************************************** 86 | HELPERS 87 | *****************************************************************************/ 88 | [Symbol.iterator](): Iterator { 89 | return this.list[Symbol.iterator]() 90 | } 91 | } 92 | 93 | export default Queue 94 | -------------------------------------------------------------------------------- /src/data-structures/sequences/stack/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './stack' 2 | -------------------------------------------------------------------------------- /src/data-structures/sequences/stack/stack.ts: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list' 2 | import * as utils from '../../utils' 3 | 4 | class Stack implements Iterable { 5 | private list: LinkedList 6 | 7 | constructor(equalsFunction?: utils.EqualsFunction) { 8 | if (equalsFunction) this.list = new LinkedList(equalsFunction) 9 | else this.list = new LinkedList() 10 | } 11 | 12 | /***************************************************************************** 13 | INSPECTION 14 | *****************************************************************************/ 15 | /** 16 | * Returns size of stack - O(1) 17 | * @returns {number} 18 | */ 19 | size(): number { 20 | return this.list.size() 21 | } 22 | 23 | /** 24 | * Returns true if stack is empty, false otherwise - O(1) 25 | * @returns {number} 26 | */ 27 | isEmpty(): boolean { 28 | return this.list.isEmpty() 29 | } 30 | 31 | /** 32 | * Deletes all elements in queue - O(1) 33 | */ 34 | clear(): void { 35 | this.list.clear() 36 | } 37 | 38 | /***************************************************************************** 39 | INSERTION/DELETION 40 | *****************************************************************************/ 41 | /** 42 | * Pushes element onto the stack - O(1) 43 | * @param {T} element - element to push on stack 44 | */ 45 | push(element: T): void { 46 | this.list.addBack(element) 47 | } 48 | 49 | /** 50 | * Pops an element off the stack - O(1) 51 | * @returns {T} - Element which was popped off 52 | */ 53 | pop(): T | null { 54 | if (this.isEmpty()) return null 55 | return this.list.removeBack() 56 | } 57 | 58 | /***************************************************************************** 59 | ACCESSING 60 | *****************************************************************************/ 61 | /** 62 | * Peeks at the top most element on the stack - O(1) 63 | * @returns {T} - Topmost element 64 | */ 65 | peek(): T | null { 66 | if (this.isEmpty()) return null 67 | return this.list.peekBack() 68 | } 69 | 70 | /***************************************************************************** 71 | SEARCHING 72 | *****************************************************************************/ 73 | /** 74 | * Checks if value is in stack - O(n) 75 | * @param {T} element - element to search for 76 | * @returns {boolean} 77 | */ 78 | contains(element: T): boolean { 79 | return this.list.contains(element) 80 | } 81 | 82 | /***************************************************************************** 83 | HELPERS 84 | *****************************************************************************/ 85 | [Symbol.iterator](): Iterator { 86 | return this.list[Symbol.iterator]() 87 | } 88 | } 89 | 90 | export default Stack 91 | -------------------------------------------------------------------------------- /src/data-structures/trees/avl-tree/avl-tree-node.ts: -------------------------------------------------------------------------------- 1 | class AVLTreeNode { 2 | value: T 3 | 4 | left: AVLTreeNode | null 5 | right: AVLTreeNode | null 6 | parent: AVLTreeNode | null 7 | 8 | balanceFactor: number 9 | height: number 10 | 11 | constructor(value: T, parent: AVLTreeNode | null) { 12 | this.value = value 13 | 14 | this.left = null 15 | this.right = null 16 | this.parent = parent 17 | 18 | this.balanceFactor = 0 19 | this.height = 1 20 | } 21 | } 22 | 23 | export default AVLTreeNode 24 | -------------------------------------------------------------------------------- /src/data-structures/trees/b-tree/b-tree-node.ts: -------------------------------------------------------------------------------- 1 | class BTreeNode { 2 | values: T[] // array of n values 3 | children: BTreeNode[] // array of children with length n + 1 4 | isLeaf: boolean // true if node is leaf 5 | 6 | // initialize node with k children 7 | constructor() { 8 | this.values = [] 9 | this.children = [] 10 | this.isLeaf = false 11 | } 12 | } 13 | 14 | export default BTreeNode 15 | -------------------------------------------------------------------------------- /src/data-structures/trees/binary-search-tree/index.ts: -------------------------------------------------------------------------------- 1 | import TreeNode from './tree-node' 2 | import Stack from '../../sequences/stack' 3 | import * as utils from '../../utils' 4 | 5 | class BinarySearchTree { 6 | root: TreeNode | null 7 | 8 | private sz: number 9 | private compare: utils.CompareFunction 10 | 11 | constructor(compareFunction?: utils.CompareFunction) { 12 | this.root = null 13 | this.sz = 0 14 | 15 | this.compare = compareFunction || utils.defaultCompare 16 | } 17 | 18 | /***************************************************************************** 19 | INSPECTION 20 | *****************************************************************************/ 21 | size(): number { 22 | return this.sz 23 | } 24 | 25 | isEmpty(): boolean { 26 | return this.size() === 0 27 | } 28 | 29 | // O(n) 30 | height(): number { 31 | return this.heightHelper(this.root) 32 | } 33 | 34 | // O(n) because we recurse on all nodes in the tree 35 | private heightHelper(root: TreeNode | null): number { 36 | if (root === null) return 0 37 | 38 | return Math.max(this.heightHelper(root.left), this.heightHelper(root.right)) + 1 39 | } 40 | 41 | /***************************************************************************** 42 | SEARCHING 43 | *****************************************************************************/ 44 | // All search operations can be implemented iteratively and in O(h) time. 45 | 46 | // O(h) because we're just tracing a path down the tree 47 | find(value: T): TreeNode | null { 48 | let cur = this.root 49 | 50 | while (cur !== null && cur.value !== value) { 51 | if (this.compare(value, cur.value) < 0) cur = cur.left 52 | else cur = cur.right 53 | } 54 | 55 | return cur 56 | } 57 | 58 | // finds the min node in the subtree rooted at given root, or top-most parent by default 59 | // O(h) because we're just tracing a path down the tree 60 | findMin(root?: TreeNode | null): TreeNode | null { 61 | let cur = root || this.root 62 | 63 | while (cur && cur.left !== null) { 64 | cur = cur.left 65 | } 66 | 67 | return cur 68 | } 69 | 70 | // finds the max node in the subtree rooted at given root, or top-most parent by default 71 | // O(h) because we're just tracing a path down the tree 72 | findMax(root?: TreeNode | null): TreeNode | null { 73 | let cur = root || this.root 74 | 75 | while (cur && cur.right !== null) { 76 | cur = cur.right 77 | } 78 | 79 | return cur 80 | } 81 | 82 | // O(h) since we follow a path down the tree or up the tree 83 | findSucessor(root: TreeNode): TreeNode | null { 84 | // if the right child exists, the successor is the left-most node of the right-child 85 | const rightChildExists = root.right !== null 86 | if (rightChildExists) return this.findMin(root.right) 87 | 88 | // otherwise, the successor is the lowest ancestor of the current node whose 89 | // left child is also an ancestor of the current node 90 | 91 | let cur = root 92 | let parent = root.parent 93 | 94 | // Go up the tree from cur until we find a node that is the left child of the parent. 95 | // If the node is the right child of the parent that means we haven't crossed 96 | // "up and over" to the successor side of the binary tree 97 | while (parent !== null && cur === parent.right) { 98 | cur = parent 99 | parent = parent.parent 100 | } 101 | 102 | return parent 103 | } 104 | 105 | // O(h) since we follow a path down the tree or up the tree 106 | findPredecessor(root: TreeNode): TreeNode | null { 107 | // if the left child exists, the successor is the right-most node of the left-child 108 | const leftChildExists = root.left !== null 109 | if (leftChildExists) return this.findMax(root.left) 110 | 111 | // otherwise, the successor is the lowest ancestor of the current node whose 112 | // right child is also an ancestor of the current node 113 | 114 | let cur = root 115 | let parent = root.parent 116 | 117 | // Go up the tree from cur until we find a node that is the right child of the parent 118 | // If the node is the left child of the parent that means we haven't crossed 119 | // "up and over" to the predecessor side of the binary tree 120 | while (parent !== null && cur === parent.left) { 121 | cur = parent 122 | parent = parent.parent 123 | } 124 | 125 | return parent 126 | } 127 | 128 | /***************************************************************************** 129 | INSERTION/DELETION 130 | *****************************************************************************/ 131 | // O(h) time since we follow a path down the tree 132 | insert(value: T): TreeNode { 133 | let parent: TreeNode | null = null 134 | let cur = this.root 135 | 136 | // walk down our tree until cur pointer is null 137 | while (cur !== null) { 138 | parent = cur 139 | 140 | if (this.compare(value, cur.value) < 0) cur = cur.left 141 | else cur = cur.right 142 | } 143 | 144 | const newNode = new TreeNode(value, parent) 145 | 146 | if (parent === null) { 147 | // if the root was empty, just set the root pointer to newNode 148 | this.root = newNode 149 | } else if (newNode.value < parent.value) { 150 | parent.left = newNode 151 | } else { 152 | parent.right = newNode 153 | } 154 | 155 | this.sz += 1 156 | 157 | return newNode 158 | } 159 | 160 | // O(h) because in the worst case we have to find the successor of node. 161 | // This means calling this.findMin() which takes O(h) time 162 | remove(node: TreeNode): void { 163 | // cases: 164 | // if node has no children, we simply remove it by modifying node.parent's pointer 165 | // if node has one child, we promote the child to take node's place 166 | // if node has two chldren, we replace node with node's successor to maintain BST invariant 167 | 168 | if (node.left === null) { 169 | // is node has just a right child, we replace node with it's right child which may 170 | // or may not be null 171 | this.transplant(node, node.right) 172 | } else if (node.right === null) { 173 | // if node has just a left child then we replace node with the left child 174 | this.transplant(node, node.left) 175 | } else { 176 | // otherwise node has two children 177 | 178 | const sucessor = this.findSucessor(node) // O(h) 179 | 180 | if (node.right === sucessor) { 181 | // if node's sucessor is the right child of node, then we replace node with 182 | // the sucessor 183 | this.transplant(node, sucessor) 184 | 185 | // link nodes left subtree with sucessor 186 | sucessor.left = node.left 187 | sucessor.left.parent = sucessor 188 | } else { 189 | // otherwise, the sucessor lies within node's right subtree but is not 190 | // node's immediate right child. then, replace the successor with it's own 191 | // right child, and then replace node with the sucessor 192 | 193 | // note: sucessor can't be null here. node has two children, so it 194 | // definitely does have a sucessor 195 | if (sucessor === null) throw new Error() 196 | 197 | // before we transplant node with sucessor, transplant sucessor with IT's 198 | // right subtree (sucessor subtree) 199 | this.transplant(sucessor, sucessor.right) 200 | 201 | // link node's right subtree with sucessor 202 | sucessor.right = node.right 203 | sucessor.right.parent = sucessor 204 | 205 | this.transplant(node, sucessor) 206 | 207 | // link node's left subtree with sucessor 208 | sucessor.left = node.left 209 | sucessor.left.parent = sucessor 210 | } 211 | } 212 | 213 | this.sz -= 1 214 | } 215 | 216 | // Replaces the subtree rooted at u with the subtree rooted at v. Node u's 217 | // parent now becomes node v's parent. Note that transplant does not update 218 | // v.left or v.right 219 | private transplant(u: TreeNode, v: TreeNode | null) { 220 | if (u.parent === null) { 221 | // then u is the root of the tree so set root pointer to point to v 222 | this.root = v 223 | } else if (u === u.parent.left) { 224 | u.parent.left = v 225 | } else { 226 | u.parent.right = v 227 | } 228 | 229 | // set v's parent pointer to point to u's parent 230 | if (v) v.parent = u.parent 231 | } 232 | 233 | /***************************************************************************** 234 | READING 235 | *****************************************************************************/ 236 | inorderTraversal(): { [Symbol.iterator](): Iterator } { 237 | let root = this.root 238 | const stack = new Stack>() 239 | 240 | return { 241 | [Symbol.iterator]: (): Iterator => ({ 242 | next(): IteratorResult { 243 | // dig left 244 | while (root !== null) { 245 | stack.push(root) 246 | root = root.left 247 | } 248 | 249 | // we're done exploring the left branch 250 | if (stack.isEmpty()) { 251 | // if stack is empty, we have no more nodes to process 252 | return { value: null, done: true } 253 | } 254 | 255 | root = stack.pop()! // root is not null bc stack is not empty 256 | 257 | const value = root.value 258 | root = root.right 259 | 260 | return { 261 | value, 262 | done: false, 263 | } 264 | }, 265 | }), 266 | } 267 | } 268 | 269 | preorderTraversal(): { [Symbol.iterator](): Iterator } { 270 | let root = this.root 271 | 272 | const stack = new Stack>() 273 | if (root !== null) stack.push(root) 274 | 275 | return { 276 | [Symbol.iterator]: (): Iterator => ({ 277 | next(): IteratorResult { 278 | if (stack.isEmpty()) return { value: null, done: true } 279 | 280 | root = stack.pop()! // root is non-null bc stack is not empty 281 | 282 | const value = root.value 283 | 284 | if (root.right !== null) stack.push(root.right) 285 | if (root.left !== null) stack.push(root.left) 286 | 287 | return { 288 | value, 289 | done: false, 290 | } 291 | }, 292 | }), 293 | } 294 | } 295 | 296 | postorderTraversal(): { [Symbol.iterator](): Iterator } { 297 | let root = this.root 298 | 299 | const stack1 = new Stack>() 300 | const stack2 = new Stack>() 301 | if (root !== null) stack1.push(root) 302 | 303 | while (!stack1.isEmpty()) { 304 | root = stack1.pop()! // non-null bc stack1 is not empty 305 | stack2.push(root) 306 | 307 | if (root.left !== null) stack1.push(root.left) 308 | if (root.right !== null) stack1.push(root.right) 309 | } 310 | 311 | return { 312 | [Symbol.iterator]: (): Iterator => ({ 313 | next(): IteratorResult { 314 | if (stack2.isEmpty()) return { value: null, done: true } 315 | 316 | const { value } = stack2.pop()! // non-null bc stack2 is not empty 317 | 318 | return { 319 | value, 320 | done: false, 321 | } 322 | }, 323 | }), 324 | } 325 | } 326 | } 327 | 328 | export default BinarySearchTree 329 | -------------------------------------------------------------------------------- /src/data-structures/trees/binary-search-tree/tree-node.ts: -------------------------------------------------------------------------------- 1 | class TreeNode { 2 | value: T 3 | 4 | left: TreeNode | null 5 | right: TreeNode | null 6 | parent: TreeNode | null 7 | 8 | constructor(value: T, parent: TreeNode | null) { 9 | this.value = value 10 | 11 | this.left = null 12 | this.right = null 13 | this.parent = parent 14 | } 15 | } 16 | 17 | export default TreeNode 18 | -------------------------------------------------------------------------------- /src/data-structures/trees/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BinarySearchTree } from './binary-search-tree' 2 | export { default as AVLTree } from './avl-tree' 3 | export { default as BTree } from './b-tree/b-tree' 4 | export { default as RedBlackTree } from './red-black-tree' 5 | -------------------------------------------------------------------------------- /src/data-structures/trees/red-black-tree/red-black-tree-node.ts: -------------------------------------------------------------------------------- 1 | class RBTreeNode { 2 | value: T 3 | color: 'red' | 'black' 4 | 5 | left: RBTreeNode | null 6 | right: RBTreeNode | null 7 | parent: RBTreeNode | null 8 | 9 | constructor(value: T, color: 'red' | 'black', parent: RBTreeNode | null) { 10 | this.value = value 11 | this.color = color 12 | 13 | this.left = null 14 | this.right = null 15 | this.parent = parent 16 | } 17 | } 18 | 19 | export default RBTreeNode 20 | -------------------------------------------------------------------------------- /src/data-structures/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Function signature for checking equality 3 | */ 4 | export interface EqualsFunction { 5 | (a: T, b: T): boolean 6 | } 7 | 8 | /** 9 | * Function signature for comparing 10 | * > 0 => a is larger than b 11 | * = 0 => a equals b 12 | * < 0 => a is smaller than b 13 | */ 14 | export interface CompareFunction { 15 | (a: T, b: T): number 16 | } 17 | 18 | /** 19 | * Default function to test equality. 20 | * @function 21 | */ 22 | export const defaultEquals = (a: T, b: T): boolean => { 23 | return a === b 24 | } 25 | 26 | export const VALUE_DOES_NOT_EXIST_ERROR = 'Value does not exist.' 27 | 28 | /** 29 | * Default function to compare element order. 30 | * @function 31 | */ 32 | export function defaultCompare(a: T, b: T): number { 33 | if (a < b) { 34 | return -1 35 | } else if (a === b) { 36 | return 0 37 | } else { 38 | return 1 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Collections } from './data-structures' 2 | -------------------------------------------------------------------------------- /test/algorithms/graph/shortest-paths/bellman-ford-shortest-path.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode, WeightedGraph } from '../../../../src/algorithms/graphs/graph-node' 2 | import { shortestPath } from '../../../../src/algorithms/graphs/shortest-paths/bellman-ford-shortest-path' 3 | 4 | describe('Bellman-Ford Shortest Path', () => { 5 | test('Basic Graph', () => { 6 | const a = new GraphNode('a') 7 | const b = new GraphNode('b') 8 | const c = new GraphNode('c') 9 | 10 | const aNeighbors: Map, number> = new Map([ 11 | [b, 5], 12 | [c, 1], 13 | ]) 14 | const bNeighbors: Map, number> = new Map() 15 | const cNeighbors: Map, number> = new Map([[b, 1]]) 16 | 17 | const graph: WeightedGraph = new Map([ 18 | [a, aNeighbors], 19 | [b, bNeighbors], 20 | [c, cNeighbors], 21 | ]) 22 | 23 | const [path, cost] = shortestPath(graph, a, b) 24 | 25 | expect(path).toEqual(['a', 'c', 'b']) 26 | expect(cost).toBe(2) 27 | }) 28 | 29 | test('Basic Graph with Cycle', () => { 30 | const a = new GraphNode('a') 31 | const b = new GraphNode('b') 32 | const c = new GraphNode('c') 33 | 34 | const aNeighbors: Map, number> = new Map([ 35 | [b, 5], 36 | [c, 1], 37 | ]) 38 | const bNeighbors: Map, number> = new Map([[a, 4]]) 39 | const cNeighbors: Map, number> = new Map([[b, 1]]) 40 | 41 | const graph: WeightedGraph = new Map([ 42 | [a, aNeighbors], 43 | [b, bNeighbors], 44 | [c, cNeighbors], 45 | ]) 46 | 47 | const [path, cost] = shortestPath(graph, a, b) 48 | 49 | expect(path).toEqual(['a', 'c', 'b']) 50 | expect(cost).toBe(2) 51 | }) 52 | 53 | test('Basic Graph with Negative-Weighted Cycle', () => { 54 | const a = new GraphNode('a') 55 | const b = new GraphNode('b') 56 | const c = new GraphNode('c') 57 | const d = new GraphNode('d') 58 | 59 | const aNeighbors: Map, number> = new Map([[b, 2]]) 60 | const bNeighbors: Map, number> = new Map([ 61 | [c, -1], 62 | [d, 1], 63 | ]) 64 | const cNeighbors: Map, number> = new Map([[a, -2]]) 65 | const dNeighbors: Map, number> = new Map([]) 66 | 67 | const graph: WeightedGraph = new Map([ 68 | [a, aNeighbors], 69 | [b, bNeighbors], 70 | [c, cNeighbors], 71 | [d, dNeighbors], 72 | ]) 73 | 74 | expect(() => { 75 | shortestPath(graph, a, b) 76 | }).toThrow() 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /test/algorithms/graph/shortest-paths/dijkstras-shortest-path.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode, WeightedGraph } from '../../../../src/algorithms/graphs/graph-node' 2 | import { shortestPath } from '../../../../src/algorithms/graphs/shortest-paths/dijkstras-shortest-path' 3 | 4 | describe('Dijksras Shortest Path', () => { 5 | test('Basic Graph', () => { 6 | const a = new GraphNode('a') 7 | const b = new GraphNode('b') 8 | const c = new GraphNode('c') 9 | 10 | const aNeighbors: Map, number> = new Map([ 11 | [b, 5], 12 | [c, 1], 13 | ]) 14 | const bNeighbors: Map, number> = new Map() 15 | const cNeighbors: Map, number> = new Map([[b, 1]]) 16 | 17 | const graph: WeightedGraph = new Map([ 18 | [a, aNeighbors], 19 | [b, bNeighbors], 20 | [c, cNeighbors], 21 | ]) 22 | 23 | const [path, cost] = shortestPath(graph, a, b) 24 | 25 | expect(path).toEqual(['a', 'c', 'b']) 26 | expect(cost).toBe(2) 27 | }) 28 | 29 | test('Basic Graph with Cycle', () => { 30 | const a = new GraphNode('a') 31 | const b = new GraphNode('b') 32 | const c = new GraphNode('c') 33 | 34 | const aNeighbors: Map, number> = new Map([ 35 | [b, 5], 36 | [c, 1], 37 | ]) 38 | const bNeighbors: Map, number> = new Map([[a, 4]]) 39 | const cNeighbors: Map, number> = new Map([[b, 1]]) 40 | 41 | const graph: WeightedGraph = new Map([ 42 | [a, aNeighbors], 43 | [b, bNeighbors], 44 | [c, cNeighbors], 45 | ]) 46 | 47 | const [path, cost] = shortestPath(graph, a, b) 48 | 49 | expect(path).toEqual(['a', 'c', 'b']) 50 | expect(cost).toBe(2) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /test/algorithms/graph/shortest-paths/floyd-warshall-shortest-path.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode, WeightedGraph } from '../../../../src/algorithms/graphs/graph-node' 2 | import { 3 | floydWarshall, 4 | shortestPath, 5 | } from '../../../../src/algorithms/graphs/shortest-paths/floyd-warshall-shortest-path' 6 | 7 | describe('Floyd Warshall', () => { 8 | test('Basic graph', () => { 9 | const zero = new GraphNode(0) 10 | const one = new GraphNode(1) 11 | const two = new GraphNode(2) 12 | 13 | const zerosNeighbors: Map, number> = new Map([ 14 | [one, 5], 15 | [two, 1], 16 | ]) 17 | const onesNeighbors: Map, number> = new Map() 18 | const twoNeighbors: Map, number> = new Map([[one, 1]]) 19 | 20 | const graph: WeightedGraph = new Map([ 21 | [zero, zerosNeighbors], 22 | [one, onesNeighbors], 23 | [two, twoNeighbors], 24 | ]) 25 | 26 | const expectedDP = [ 27 | [0, 2, 1], 28 | [Infinity, 0, Infinity], 29 | [Infinity, 1, 0], 30 | ] 31 | 32 | const [dist, _] = floydWarshall(graph) 33 | 34 | for (let i = 0; i < dist.length; i++) { 35 | for (let j = 0; j < dist.length; j++) { 36 | expect(dist[i][j]).toEqual(expectedDP[i][j]) 37 | } 38 | } 39 | }) 40 | 41 | test('Basic Graph with Cycle', () => { 42 | const zero = new GraphNode(0) 43 | const one = new GraphNode(1) 44 | const two = new GraphNode(2) 45 | 46 | const zerosNeighbors: Map, number> = new Map([ 47 | [one, 5], 48 | [two, 1], 49 | ]) 50 | const onesNeighbors: Map, number> = new Map([[zero, 3]]) 51 | const twosNeighbors: Map, number> = new Map([[one, 1]]) 52 | 53 | const graph: WeightedGraph = new Map([ 54 | [zero, zerosNeighbors], 55 | [one, onesNeighbors], 56 | [two, twosNeighbors], 57 | ]) 58 | 59 | const expectedDP = [ 60 | [0, 2, 1], 61 | [3, 0, 4], 62 | [4, 1, 0], 63 | ] 64 | 65 | const [dist, _] = floydWarshall(graph) 66 | 67 | for (let i = 0; i < dist.length; i++) { 68 | for (let j = 0; j < dist.length; j++) { 69 | expect(dist[i][j]).toEqual(expectedDP[i][j]) 70 | } 71 | } 72 | }) 73 | 74 | test('Basic path', () => { 75 | const zero = new GraphNode(0) 76 | const one = new GraphNode(1) 77 | const two = new GraphNode(2) 78 | 79 | const zerosNeighbors: Map, number> = new Map([ 80 | [one, 5], 81 | [two, 1], 82 | ]) 83 | const onesNeighbors: Map, number> = new Map() 84 | const twoNeighbors: Map, number> = new Map([[one, 1]]) 85 | 86 | const graph: WeightedGraph = new Map([ 87 | [zero, zerosNeighbors], 88 | [one, onesNeighbors], 89 | [two, twoNeighbors], 90 | ]) 91 | 92 | const [_, next] = floydWarshall(graph) 93 | 94 | expect(shortestPath(next, zero, one)).toEqual([0, 2, 1]) 95 | expect(shortestPath(next, zero, two)).toEqual([0, 2]) 96 | expect(shortestPath(next, one, zero)).toEqual([]) 97 | expect(shortestPath(next, one, two)).toEqual([]) 98 | expect(shortestPath(next, two, zero)).toEqual([]) 99 | expect(shortestPath(next, two, one)).toEqual([2, 1]) 100 | }) 101 | 102 | test('Basic path with cycle', () => { 103 | const zero = new GraphNode(0) 104 | const one = new GraphNode(1) 105 | const two = new GraphNode(2) 106 | 107 | const zerosNeighbors: Map, number> = new Map([ 108 | [one, 5], 109 | [two, 1], 110 | ]) 111 | const onesNeighbors: Map, number> = new Map([[zero, 3]]) 112 | const twoNeighbors: Map, number> = new Map([[one, 1]]) 113 | 114 | const graph: WeightedGraph = new Map([ 115 | [zero, zerosNeighbors], 116 | [one, onesNeighbors], 117 | [two, twoNeighbors], 118 | ]) 119 | 120 | const [_, next] = floydWarshall(graph) 121 | 122 | expect(shortestPath(next, zero, one)).toEqual([0, 2, 1]) 123 | expect(shortestPath(next, zero, two)).toEqual([0, 2]) 124 | expect(shortestPath(next, one, zero)).toEqual([1, 0]) 125 | expect(shortestPath(next, one, two)).toEqual([1, 0, 2]) 126 | expect(shortestPath(next, two, zero)).toEqual([2, 1, 0]) 127 | expect(shortestPath(next, two, one)).toEqual([2, 1]) 128 | }) 129 | }) 130 | -------------------------------------------------------------------------------- /test/algorithms/search/binary-search.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | binarySearchIterative, 3 | binarySearchRecursive, 4 | Item, 5 | } from '../../../src/algorithms/search/binary-search' 6 | 7 | describe('binarySearch', () => { 8 | type BinarySearch = (arr: Item[], target: Item) => number 9 | 10 | const runTest = (binarySearch: BinarySearch) => { 11 | it('does not find target in an empty array', () => { 12 | const array: number[] = [] 13 | expect(binarySearch(array, 1)).toBe(-1) 14 | }) 15 | 16 | describe('array with odd number of elements', () => { 17 | const arrayNums: number[] = [2, 5, 8.1, 9, 30.234, 42, 121] 18 | 19 | it('does not find target', () => { 20 | expect(binarySearch(arrayNums, 1)).toBe(-1) // nonexistent target less than min element 21 | expect(binarySearch(arrayNums, 121.2)).toBe(-1) // nonexistent target greater than max element 22 | expect(binarySearch(arrayNums, 5.1)).toBe(-1) // nonexistent target between min and max elements 23 | }) 24 | 25 | it('finds target when it exists in array', () => { 26 | expect(binarySearch(arrayNums, 5)).toBe(1) 27 | }) 28 | }) 29 | 30 | describe('array with even number of elements', () => { 31 | const arrayNums: number[] = [2, 5, 8.1, 30.234, 42, 121] 32 | 33 | it('finds target when it exists in array', () => { 34 | expect(binarySearch(arrayNums, 30.234)).toBe(3) 35 | }) 36 | }) 37 | 38 | describe('array of strings', () => { 39 | const arrayStrs: string[] = [ 40 | 'reappear', 41 | 'rearrange', 42 | 'redo', 43 | 'regroup', 44 | 'remake', 45 | 'replay', 46 | 'rewrite', 47 | ] 48 | 49 | it('finds word when it exists in array', () => { 50 | expect(binarySearch(arrayStrs, 'replay')).toBe(5) 51 | }) 52 | 53 | it('does not find word when it is not in array', () => { 54 | expect(binarySearch(arrayStrs, '')).toBe(-1) 55 | expect(binarySearch(arrayStrs, 'rewrote')).toBe(-1) 56 | expect(binarySearch(arrayStrs, 'rearranged')).toBe(-1) 57 | }) 58 | }) 59 | } 60 | 61 | describe('iterative method', () => runTest(binarySearchIterative)) 62 | describe('recursive method', () => runTest(binarySearchRecursive)) 63 | }) 64 | -------------------------------------------------------------------------------- /test/algorithms/search/breadth-first-search.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode } from '../../../src/algorithms/graphs/graph-node' 2 | import { bfs } from '../../../src/algorithms/search/breadth-first-search' 3 | 4 | describe('Breadth First Search', () => { 5 | test('Basic search', () => { 6 | const a = new GraphNode('a') 7 | const b = new GraphNode('b') 8 | const c = new GraphNode('c') 9 | 10 | const graph = new Map([ 11 | [a, [b]], 12 | [b, [c]], 13 | ]) 14 | 15 | expect(bfs(a, graph)).toEqual(['a', 'b', 'c']) 16 | }) 17 | 18 | test('Graph with node with two parents', () => { 19 | const a = new GraphNode('a') 20 | const b = new GraphNode('b') 21 | const c = new GraphNode('c') 22 | const d = new GraphNode('d') 23 | const e = new GraphNode('e') 24 | 25 | // e -----\ 26 | // / v 27 | // a->b->c->d 28 | 29 | const graph = new Map([ 30 | [a, [b, e]], 31 | [b, [c]], 32 | [c, [d]], 33 | [d, []], 34 | [e, [d]], 35 | ]) 36 | 37 | // o: [a, b, e ] 38 | // q: c, d 39 | 40 | expect(bfs(a, graph)).toEqual(['a', 'b', 'e', 'c', 'd']) 41 | }) 42 | 43 | test('Graph with loner node', () => { 44 | const a = new GraphNode('a') 45 | const b = new GraphNode('b') 46 | const c = new GraphNode('c') 47 | 48 | const graph = new Map([ 49 | [a, [b]], 50 | [c, []], 51 | ]) 52 | 53 | expect(bfs(a, graph)).toEqual(['a', 'b']) 54 | }) 55 | 56 | test('Cyclic graph', () => { 57 | const a = new GraphNode('a') 58 | const b = new GraphNode('b') 59 | const c = new GraphNode('c') 60 | 61 | const graph = new Map([ 62 | [a, [b]], 63 | [b, [c]], 64 | [c, [a]], 65 | ]) 66 | 67 | expect(bfs(a, graph)).toEqual(['a', 'b', 'c']) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/algorithms/search/depth-first-search.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode } from '../../../src/algorithms/graphs/graph-node' 2 | import { dfs } from '../../../src/algorithms/search/depth-first-search' 3 | 4 | describe('Depth First Search', () => { 5 | test('Basic depth first search', () => { 6 | const a = new GraphNode('a') 7 | const b = new GraphNode('b') 8 | const c = new GraphNode('c') 9 | 10 | const graph = new Map([ 11 | [a, [b]], 12 | [b, [c]], 13 | [c, []], 14 | ]) 15 | 16 | expect(dfs(a, graph)).toEqual(['a', 'b', 'c']) 17 | }) 18 | 19 | test('Graph with node with two parents', () => { 20 | const a = new GraphNode('a') 21 | const b = new GraphNode('b') 22 | const c = new GraphNode('c') 23 | const d = new GraphNode('d') 24 | const e = new GraphNode('e') 25 | 26 | const graph = new Map([ 27 | [a, [b, e]], 28 | [b, [c]], 29 | [c, [d]], 30 | [d, []], 31 | [e, [d]], 32 | ]) 33 | 34 | // e -----\ 35 | // / v 36 | // a->b->c->d 37 | 38 | expect(dfs(a, graph)).toEqual(['a', 'b', 'c', 'd', 'e']) 39 | }) 40 | 41 | test('Graph with loner node', () => { 42 | const a = new GraphNode('a') 43 | const b = new GraphNode('b') 44 | const c = new GraphNode('c') 45 | 46 | const graph = new Map([ 47 | [a, [b]], 48 | [b, []], 49 | [c, []], 50 | ]) 51 | 52 | expect(dfs(a, graph)).toEqual(['a', 'b']) 53 | }) 54 | 55 | test('Cyclic graph', () => { 56 | const a = new GraphNode('a') 57 | const b = new GraphNode('b') 58 | const c = new GraphNode('c') 59 | 60 | const graph = new Map([ 61 | [a, [b]], 62 | [b, [c]], 63 | [c, [a]], 64 | ]) 65 | 66 | expect(dfs(a, graph)).toEqual(['a', 'b', 'c']) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/algorithms/sort/bucket-sort.test.ts: -------------------------------------------------------------------------------- 1 | import bucketSort from '../../../src/algorithms/sort/bucket-sort' 2 | import testCases from './test-cases' 3 | 4 | describe('bucketSort', () => { 5 | console.error = jest.fn() 6 | 7 | beforeEach(() => { 8 | jest.clearAllMocks() 9 | }) 10 | 11 | for (const [description, inputArray] of Object.entries(testCases)) { 12 | it(`correctly sorts ${description}`, () => { 13 | const soln = [...inputArray] 14 | soln.sort((a, b) => a - b) // sort the copy inplace using native JS function 15 | bucketSort(inputArray) //sort the original inplace using bucketSort 16 | expect(inputArray).toEqual(soln) 17 | }) 18 | } 19 | 20 | it('correctly sorts when only posBuckets is specified', () => { 21 | const array = [5, 3, 4, 1, 2] 22 | const soln = [...array].sort((a, b) => a - b) 23 | bucketSort(array, 1) 24 | expect(array).toEqual(soln) 25 | }) 26 | 27 | it('correctly sorts when only negBuckets is specified', () => { 28 | const array = [-5, -3, -4, -1, -2] 29 | const soln = [...array].sort((a, b) => a - b) 30 | bucketSort(array, undefined, 1) 31 | expect(array).toEqual(soln) 32 | }) 33 | 34 | it('correctly sorts when both posBuckets and negBuckets are specified', () => { 35 | const array = [-5, -3, 7, -4, -1, -2, 9, 0] 36 | const soln = [...array].sort((a, b) => a - b) 37 | bucketSort(array, 5, 3) 38 | expect(array).toEqual(soln) 39 | }) 40 | 41 | it('raises error when number of specified buckets <= 0', () => { 42 | const array = [1, 2, 3] 43 | bucketSort(array, 0, 1) 44 | expect(console.error).toHaveBeenCalledTimes(1) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /test/algorithms/sort/counting-sort.test.ts: -------------------------------------------------------------------------------- 1 | import countingSort from '../../../src/algorithms/sort/counting-sort' 2 | import testCases from './test-cases' 3 | 4 | describe('countingSort', () => { 5 | console.error = jest.fn() 6 | 7 | beforeEach(() => { 8 | jest.clearAllMocks() 9 | }) 10 | 11 | for (const [description, inputArray] of Object.entries(testCases)) { 12 | if (description === 'array of floats') { 13 | it(`raises error for ${description}`, () => { 14 | countingSort(inputArray) 15 | expect(console.error).toHaveBeenCalledTimes(1) 16 | }) 17 | continue 18 | } 19 | 20 | it(`correctly sorts ${description}`, () => { 21 | const soln = [...inputArray] 22 | soln.sort((a, b) => a - b) // sort the copy in-place using native JS function 23 | countingSort(inputArray) 24 | expect(inputArray).toEqual(soln) 25 | }) 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /test/algorithms/sort/heap-sort.test.ts: -------------------------------------------------------------------------------- 1 | import heapSort from '../../../src/algorithms/sort/heap-sort' 2 | import testCases from './test-cases' 3 | 4 | describe('heapSort', () => { 5 | for (const [description, inputArray] of Object.entries(testCases)) { 6 | it(`correctly sorts ${description}`, () => { 7 | const soln = [...inputArray] 8 | soln.sort((a, b) => a - b) // sort the copy inplace using native JS function 9 | heapSort(inputArray) //sort the original inplace 10 | expect(inputArray).toEqual(soln) 11 | }) 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /test/algorithms/sort/merge-sort.test.ts: -------------------------------------------------------------------------------- 1 | import mergeSort from '../../../src/algorithms/sort/merge-sort' 2 | import testCases from './test-cases' 3 | 4 | describe('mergeSort', () => { 5 | for (const [description, inputArray] of Object.entries(testCases)) { 6 | it(`correctly sorts ${description}`, () => { 7 | const soln = [...inputArray] 8 | soln.sort((a, b) => a - b) // sort the copy inplace using native JS function 9 | mergeSort(inputArray) //sort the original inplace using mergeSort 10 | expect(inputArray).toEqual(soln) 11 | }) 12 | } 13 | 14 | it('correctly sorts a subarray', () => { 15 | const array = [100, 39, -4, 11, 5, -87, -2, 6, 0, 7] 16 | const soln1 = [100, 39, -87, -4, -2, 0, 5, 6, 7, 11] 17 | const soln2 = [39, 100, -87, -4, -2, 0, 5, 6, 7, 11] 18 | mergeSort(array, 2) 19 | expect(array).toEqual(soln1) 20 | mergeSort(array, undefined, 1) 21 | expect(array).toEqual(soln2) 22 | }) 23 | 24 | describe('index error', () => { 25 | console.error = jest.fn() 26 | const array = [1, 2, 3] 27 | 28 | beforeEach(() => { 29 | jest.clearAllMocks() 30 | }) 31 | 32 | it('is raised when left or right indices are not integers', () => { 33 | mergeSort(array, 0.1, 2) 34 | mergeSort(array, 0, 1.1) 35 | expect(console.error).toHaveBeenCalledTimes(2) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /test/algorithms/sort/quick-sort.test.ts: -------------------------------------------------------------------------------- 1 | import quickSort from '../../../src/algorithms/sort/quick-sort' 2 | import testCases from './test-cases' 3 | 4 | describe('quickSort', () => { 5 | for (const [description, inputArray] of Object.entries(testCases)) { 6 | it(`correctly sorts ${description}`, () => { 7 | const soln = [...inputArray] 8 | soln.sort((a, b) => a - b) // sort the copy inplace using native JS function 9 | quickSort(inputArray) //sort the original inplace using mergeSort 10 | expect(inputArray).toEqual(soln) 11 | }) 12 | } 13 | 14 | it('correctly sorts a subarray', () => { 15 | const array = [100, 39, -4, 11, 5, -87, -2, 6, 0, 7] 16 | const soln1 = [100, 39, -87, -4, -2, 0, 5, 6, 7, 11] 17 | const soln2 = [39, 100, -87, -4, -2, 0, 5, 6, 7, 11] 18 | quickSort(array, 2) 19 | expect(array).toEqual(soln1) 20 | quickSort(array, undefined, 1) 21 | expect(array).toEqual(soln2) 22 | }) 23 | 24 | describe('index error', () => { 25 | console.error = jest.fn() 26 | const array = [1, 2, 3] 27 | 28 | beforeEach(() => { 29 | jest.clearAllMocks() 30 | }) 31 | 32 | it('is raised when left or right indices are not integers', () => { 33 | quickSort(array, 0.1, 2) 34 | quickSort(array, 0, 1.1) 35 | expect(console.error).toHaveBeenCalledTimes(2) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /test/algorithms/sort/test-cases.ts: -------------------------------------------------------------------------------- 1 | interface TestCases { 2 | [key: string]: number[] 3 | } 4 | 5 | const testCases: TestCases = { 6 | 'empty array': [], 7 | 'array of length 1': [9], 8 | 'array with an even number of elements': [7, 9, 5, 2, 1, 3], 9 | 'array with an odd number of elements': [-5, -13, -3, 0, 6, 2, 8], 10 | 'array of increasing elements': [-5, -2, 0, 2, 1, 10], 11 | 'array of decreasing elements': [121, 32, 15, 8, -1, -32], 12 | 'array with a repeating element': [1, 1, 1, 1, 1], 13 | 'array with duplicates': [-5, 5, -5, -10, 5, -5], 14 | 'array of floats': [-45.2, -3.1415, 89.0, 34.21, -12.7], 15 | } 16 | 17 | export default testCases 18 | -------------------------------------------------------------------------------- /test/algorithms/sort/topological-sort.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode } from '../../../src/algorithms/graphs/graph-node' 2 | import { topologicalSortDepthFirst } from '../../../src/algorithms/sort/topological-sort-dfs' 3 | import { topologicalSortKahn } from '../../../src/algorithms/sort/topological-sort-kahn' 4 | 5 | describe('Topological Sort DFS', () => { 6 | test('Basic graph', () => { 7 | const a = new GraphNode('a') 8 | const b = new GraphNode('b') 9 | const c = new GraphNode('c') 10 | 11 | const graph = new Map([ 12 | [a, [b]], 13 | [b, [c]], 14 | [c, []], 15 | ]) 16 | 17 | expect(topologicalSortDepthFirst(graph)).toEqual(['a', 'b', 'c']) 18 | }) 19 | 20 | test('Graph with dependencies', () => { 21 | const a = new GraphNode('a') 22 | const b = new GraphNode('b') 23 | const c = new GraphNode('c') 24 | const d = new GraphNode('d') 25 | const e = new GraphNode('e') 26 | 27 | // a d 28 | // v ^ 29 | // c -- 30 | // ^ v 31 | // b e 32 | 33 | const graph = new Map([ 34 | [a, [b]], 35 | [b, [c]], 36 | [c, [d, e]], 37 | [d, []], 38 | [e, []], 39 | ]) 40 | 41 | expect(topologicalSortDepthFirst(graph)).toEqual(['a', 'b', 'c', 'e', 'd']) 42 | }) 43 | }) 44 | 45 | describe('Topological Sort DFS', () => { 46 | test('Basic graph', () => { 47 | const a = new GraphNode('a') 48 | const b = new GraphNode('b') 49 | const c = new GraphNode('c') 50 | 51 | const graph = new Map([ 52 | [a, [b]], 53 | [b, [c]], 54 | [c, []], 55 | ]) 56 | 57 | expect(topologicalSortDepthFirst(graph)).toEqual(['a', 'b', 'c']) 58 | }) 59 | 60 | test('Graph with dependencies', () => { 61 | const a = new GraphNode('a') 62 | const b = new GraphNode('b') 63 | const c = new GraphNode('c') 64 | const d = new GraphNode('d') 65 | const e = new GraphNode('e') 66 | 67 | // a d 68 | // v ^ 69 | // c -- 70 | // ^ v 71 | // b e 72 | 73 | const graph = new Map([ 74 | [a, [b]], 75 | [b, [c]], 76 | [c, [d, e]], 77 | [d, []], 78 | [e, []], 79 | ]) 80 | 81 | expect(topologicalSortDepthFirst(graph)).toEqual(['a', 'b', 'c', 'e', 'd']) 82 | }) 83 | }) 84 | 85 | describe('Topological Sort Kahn', () => { 86 | test('Basic graph', () => { 87 | const a = new GraphNode('a') 88 | const b = new GraphNode('b') 89 | const c = new GraphNode('c') 90 | 91 | const graph = new Map([ 92 | [a, [b]], 93 | [b, [c]], 94 | [c, []], 95 | ]) 96 | 97 | expect(topologicalSortKahn(graph)).toEqual(['a', 'b', 'c']) 98 | }) 99 | 100 | test('Graph with dependencies', () => { 101 | const a = new GraphNode('a') 102 | const b = new GraphNode('b') 103 | const c = new GraphNode('c') 104 | const d = new GraphNode('d') 105 | const e = new GraphNode('e') 106 | 107 | // a d 108 | // v ^ 109 | // c -- 110 | // ^ v 111 | // b e 112 | 113 | const graph = new Map([ 114 | [a, [b]], 115 | [b, [c]], 116 | [c, [d, e]], 117 | [d, []], 118 | [e, []], 119 | ]) 120 | 121 | expect(topologicalSortKahn(graph)).toEqual(['a', 'b', 'c', 'd', 'e']) 122 | }) 123 | }) 124 | -------------------------------------------------------------------------------- /test/data-structures/heaps/fibonacci-heap.test.ts: -------------------------------------------------------------------------------- 1 | import MinFibonacciHeap from '../../../src/data-structures/priority-queues/mergeable-heaps/min-fibonacci-heap' 2 | 3 | describe('MinFibonacciHeap', () => { 4 | let heap: MinFibonacciHeap 5 | 6 | beforeEach(() => { 7 | heap = new MinFibonacciHeap(Number.MIN_SAFE_INTEGER) 8 | }) 9 | 10 | describe('Empty heap', () => { 11 | it('is empty', () => { 12 | expect(heap.size).toBe(0) 13 | expect(heap.isEmpty()).toBe(true) 14 | }) 15 | 16 | it('dequeue() returns null', () => { 17 | expect(heap.dequeue()).toBe(null) 18 | }) 19 | 20 | it('peek() returns null', () => { 21 | expect(heap.peek()).toBe(null) 22 | }) 23 | }) 24 | 25 | describe('Insertion and deletion', () => { 26 | it('inserts numbers properly', () => { 27 | heap.enqueue(8) 28 | expect(heap.size).toBe(1) 29 | 30 | heap.enqueue(26) 31 | expect(heap.size).toBe(2) 32 | 33 | heap.enqueue(72) 34 | expect(heap.size).toBe(3) 35 | 36 | heap.enqueue(42) 37 | }) 38 | 39 | it('deletes a specific node', () => { 40 | const node = heap.enqueue(8) 41 | heap.enqueue(3) 42 | heap.deleteNode(node) 43 | 44 | expect(heap.size).toBe(1) 45 | expect(heap.head).toBeTruthy() 46 | expect(heap.head!.value).toBe(3) 47 | }) 48 | 49 | it('deletes numbers properly', () => { 50 | heap.enqueue(8) 51 | heap.enqueue(26) 52 | heap.enqueue(72) 53 | 54 | expect(heap.size).toBe(3) 55 | 56 | heap.dequeue() 57 | expect(heap.size).toBe(2) 58 | 59 | heap.dequeue() 60 | expect(heap.size).toBe(1) 61 | 62 | heap.dequeue() 63 | expect(heap.size).toBe(0) 64 | }) 65 | }) 66 | 67 | it('maintains the heap invariant', () => { 68 | const values = [8, 32, 72, 26, 16, 48, 5, 11, 17, 93, 500, 603, 401, 325, -321] 69 | 70 | for (const v of values) { 71 | heap.enqueue(v) 72 | } 73 | 74 | const sortedValues = [...values].sort((a, b) => a - b) 75 | 76 | for (const v of sortedValues) { 77 | const node = heap.dequeue() 78 | if (!node) throw new Error() 79 | expect(node.value).toBe(v) 80 | } 81 | }) 82 | 83 | describe('Reading', () => { 84 | it('peeks', () => { 85 | heap.enqueue(-4) 86 | heap.enqueue(-5) 87 | heap.enqueue(-6) 88 | heap.enqueue(-7) 89 | heap.enqueue(-8) 90 | heap.enqueue(-9) 91 | heap.enqueue(-10) 92 | 93 | expect(heap.peek()!.value).toBe(-10) 94 | }) 95 | }) 96 | 97 | describe('Updating', () => { 98 | it('unions properly', () => { 99 | const heapA = new MinFibonacciHeap(Number.MIN_SAFE_INTEGER) 100 | const heapB = new MinFibonacciHeap(Number.MIN_SAFE_INTEGER) 101 | 102 | const valuesA = [8, 32, 72, 26, 16, 48, 5, 11] 103 | const valuesB = [17, 93, 500, 603, 401, 325, -321] 104 | 105 | for (const a of valuesA) { 106 | heapA.enqueue(a) 107 | } 108 | 109 | for (const b of valuesB) { 110 | heapB.enqueue(b) 111 | } 112 | 113 | const unionedHeap = heapA.union(heapB) 114 | expect(unionedHeap.size).toBe(heapA.size + heapB.size) 115 | 116 | const sortedValues = [...valuesA, ...valuesB].sort((a, b) => a - b) 117 | 118 | for (const v of sortedValues) { 119 | const node = unionedHeap.dequeue() 120 | if (!node) throw new Error() 121 | expect(node.value).toBe(v) 122 | } 123 | }) 124 | 125 | it('unions with empty heaps', () => { 126 | const heapA = new MinFibonacciHeap(Number.MIN_SAFE_INTEGER) 127 | const heapB = new MinFibonacciHeap(Number.MIN_SAFE_INTEGER) 128 | 129 | const valuesA = [8, 32, 72, 26, 16, 48, 5, 11] 130 | 131 | for (const a of valuesA) { 132 | heapA.enqueue(a) 133 | } 134 | 135 | const unionedHeap = heapA.union(heapB) 136 | expect(unionedHeap.size).toBe(heapA.size + heapB.size) 137 | 138 | const sortedValues = [...valuesA].sort((a, b) => a - b) 139 | 140 | for (const v of sortedValues) { 141 | const node = unionedHeap.dequeue() 142 | if (!node) throw new Error() 143 | expect(node.value).toBe(v) 144 | } 145 | }) 146 | 147 | it('decreases keys properly', () => { 148 | const values = [8, 32, 72, 26, 16, 48, 5, 11] 149 | const nodes = [] 150 | 151 | for (const v of values) { 152 | nodes.push(heap.enqueue(v)) 153 | } 154 | 155 | const dequedNode = heap.dequeue() 156 | 157 | const newValues = [ 158 | values[0] - 1, 159 | values[1] - 1, 160 | values[2] - 1, 161 | values[3] - 1, 162 | values[4] - 1, 163 | values[5] - 1, 164 | values[6] - 1, 165 | values[7] - 1, 166 | ] 167 | 168 | for (let i = 0; i < nodes.length; i++) { 169 | if (nodes[i].value === dequedNode!.value) continue 170 | 171 | heap.decreaseKey(nodes[i], newValues[i]) 172 | } 173 | 174 | const sortedValues = [...newValues.slice(6, 1)].sort((a, b) => a - b) 175 | 176 | for (const v of sortedValues) { 177 | const node = heap.dequeue() 178 | if (!node) throw new Error() 179 | expect(node.value).toBe(v) 180 | } 181 | }) 182 | }) 183 | }) 184 | -------------------------------------------------------------------------------- /test/data-structures/heaps/lazy-min-binomial-heap.test.ts: -------------------------------------------------------------------------------- 1 | import LazyMinBinomialHeap from '../../../src/data-structures/priority-queues/mergeable-heaps/lazy-min-binomial-heap' 2 | 3 | describe('MinBinomialHeap', () => { 4 | let heap: LazyMinBinomialHeap 5 | 6 | beforeEach(() => { 7 | heap = new LazyMinBinomialHeap(Number.MIN_SAFE_INTEGER) 8 | }) 9 | 10 | describe('Empty heap', () => { 11 | it('is empty', () => { 12 | expect(heap.size).toBe(0) 13 | expect(heap.isEmpty()).toBe(true) 14 | }) 15 | 16 | it('dequeue() returns null', () => { 17 | expect(heap.dequeue()).toBe(null) 18 | }) 19 | 20 | it('peek() returns null', () => { 21 | expect(heap.peek()).toBe(null) 22 | }) 23 | }) 24 | 25 | describe('Insertion and deletion', () => { 26 | it('inserts numbers properly', () => { 27 | heap.enqueue(8) 28 | expect(heap.size).toBe(1) 29 | 30 | heap.enqueue(26) 31 | expect(heap.size).toBe(2) 32 | 33 | heap.enqueue(72) 34 | expect(heap.size).toBe(3) 35 | 36 | heap.enqueue(42) 37 | }) 38 | 39 | it('deletes a specific node', () => { 40 | const node = heap.enqueue(8) 41 | heap.enqueue(3) 42 | heap.deleteNode(node) 43 | 44 | expect(heap.size).toBe(1) 45 | expect(heap.head).toBeTruthy() 46 | expect(heap.head!.value).toBe(3) 47 | }) 48 | 49 | it('deletes numbers properly', () => { 50 | heap.enqueue(8) 51 | heap.enqueue(26) 52 | heap.enqueue(72) 53 | 54 | expect(heap.size).toBe(3) 55 | 56 | heap.dequeue() 57 | expect(heap.size).toBe(2) 58 | 59 | heap.dequeue() 60 | expect(heap.size).toBe(1) 61 | 62 | heap.dequeue() 63 | expect(heap.size).toBe(0) 64 | }) 65 | }) 66 | 67 | it('maintains the heap invariant', () => { 68 | const values = [8, 32, 72, 26, 16, 48, 5, 11, 17, 93, 500, 603, 401, 325, -321] 69 | 70 | for (const v of values) { 71 | heap.enqueue(v) 72 | } 73 | 74 | const sortedValues = [...values].sort((a, b) => a - b) 75 | 76 | for (const v of sortedValues) { 77 | const node = heap.dequeue() 78 | if (!node) throw new Error() 79 | expect(node.value).toBe(v) 80 | } 81 | }) 82 | 83 | describe('Reading', () => { 84 | it('peeks', () => { 85 | heap.enqueue(-4) 86 | heap.enqueue(-5) 87 | heap.enqueue(-6) 88 | heap.enqueue(-7) 89 | heap.enqueue(-8) 90 | heap.enqueue(-9) 91 | heap.enqueue(-10) 92 | 93 | expect(heap.peek()!.value).toBe(-10) 94 | }) 95 | }) 96 | 97 | describe('Updating', () => { 98 | it('unions properly', () => { 99 | const heapA = new LazyMinBinomialHeap(Number.MIN_SAFE_INTEGER) 100 | const heapB = new LazyMinBinomialHeap(Number.MIN_SAFE_INTEGER) 101 | 102 | const valuesA = [8, 32, 72, 26, 16, 48, 5, 11] 103 | const valuesB = [17, 93, 500, 603, 401, 325, -321] 104 | 105 | for (const a of valuesA) { 106 | heapA.enqueue(a) 107 | } 108 | 109 | for (const b of valuesB) { 110 | heapB.enqueue(b) 111 | } 112 | 113 | const unionedHeap = heapA.union(heapB) 114 | expect(unionedHeap.size).toBe(heapA.size + heapB.size) 115 | 116 | const sortedValues = [...valuesA, ...valuesB].sort((a, b) => a - b) 117 | 118 | for (const v of sortedValues) { 119 | const node = unionedHeap.dequeue() 120 | if (!node) throw new Error() 121 | expect(node.value).toBe(v) 122 | } 123 | }) 124 | 125 | it('unions with empty heaps', () => { 126 | const heapA = new LazyMinBinomialHeap(Number.MIN_SAFE_INTEGER) 127 | const heapB = new LazyMinBinomialHeap(Number.MIN_SAFE_INTEGER) 128 | 129 | const valuesA = [8, 32, 72, 26, 16, 48, 5, 11] 130 | 131 | for (const a of valuesA) { 132 | heapA.enqueue(a) 133 | } 134 | 135 | const unionedHeap = heapA.union(heapB) 136 | expect(unionedHeap.size).toBe(heapA.size + heapB.size) 137 | 138 | const sortedValues = [...valuesA].sort((a, b) => a - b) 139 | 140 | for (const v of sortedValues) { 141 | const node = unionedHeap.dequeue() 142 | if (!node) throw new Error() 143 | expect(node.value).toBe(v) 144 | } 145 | }) 146 | 147 | it('decreases keys properly', () => { 148 | const values = [8, 32, 72, 26, 16, 48, 5, 11] 149 | 150 | for (const v of values) { 151 | heap.enqueue(v) 152 | } 153 | 154 | const node = heap.enqueue(100) 155 | 156 | heap.dequeue() 157 | 158 | expect(heap.decreaseKey(node, 105)).toBe(false) 159 | expect(heap.decreaseKey(node, -3)).toBe(true) 160 | }) 161 | }) 162 | }) 163 | -------------------------------------------------------------------------------- /test/data-structures/heaps/min-binary-heap.test.ts: -------------------------------------------------------------------------------- 1 | import MinBinaryHeap from '../../../src/data-structures/priority-queues/min-binary-heap' 2 | 3 | describe('MinBinaryHeap', () => { 4 | let heap: MinBinaryHeap 5 | 6 | beforeEach(() => { 7 | heap = new MinBinaryHeap() 8 | }) 9 | 10 | describe('INSPECTION', () => { 11 | it('is empty', () => { 12 | expect(heap.size()).toBe(0) 13 | expect(heap.isEmpty()).toBe(true) 14 | }) 15 | }) 16 | 17 | describe('Creation', () => { 18 | it('heapifies', () => { 19 | heap = new MinBinaryHeap([8, 3, 10]) 20 | expect(heap.size()).toBe(3) 21 | expect(heap.peek()).toBe(3) 22 | }) 23 | }) 24 | 25 | describe('Insertion', () => { 26 | it('adds elements', () => { 27 | heap.add(8) 28 | expect(heap.size()).toBe(1) 29 | 30 | heap.add(3) 31 | expect(heap.size()).toBe(2) 32 | 33 | heap.add(10) 34 | expect(heap.size()).toBe(3) 35 | }) 36 | }) 37 | 38 | describe('Accessing', () => { 39 | it('mantains heap invariant', () => { 40 | heap.add(8) 41 | expect(heap.peek()).toBe(8) 42 | 43 | heap.add(3) 44 | expect(heap.peek()).toBe(3) 45 | 46 | heap.add(10) 47 | expect(heap.peek()).toBe(3) 48 | }) 49 | 50 | it('returns null when empty heap is peeked', () => { 51 | expect(heap.peek()).toBe(null) 52 | }) 53 | }) 54 | 55 | describe('Searching', () => { 56 | it('checks if heap contains element', () => { 57 | heap.add(8) 58 | expect(heap.contains(8)).toBe(true) 59 | expect(heap.contains(3)).toBe(false) 60 | }) 61 | }) 62 | 63 | describe('Deletion', () => { 64 | it('polls elements', () => { 65 | heap.add(8) 66 | heap.add(3) 67 | heap.add(10) 68 | 69 | expect(heap.size()).toBe(3) 70 | 71 | heap.poll() 72 | expect(heap.size()).toBe(2) 73 | 74 | heap.poll() 75 | expect(heap.size()).toBe(1) 76 | 77 | heap.poll() 78 | expect(heap.size()).toBe(0) 79 | }) 80 | 81 | it('returns null when empty heap is polled', () => { 82 | expect(heap.poll()).toBe(null) 83 | }) 84 | 85 | it('removes a specified element', () => { 86 | heap.add(8) 87 | heap.add(3) 88 | heap.add(10) 89 | 90 | expect(heap.contains(8)).toBe(true) 91 | expect(heap.remove(8)).toBe(true) 92 | expect(heap.contains(8)).toBe(false) 93 | }) 94 | 95 | it('returns false when element does not exist', () => { 96 | expect(heap.remove(8)).toBe(false) 97 | }) 98 | 99 | it('clears the heap', () => { 100 | heap.add(8) 101 | heap.add(3) 102 | heap.add(10) 103 | 104 | heap.clear() 105 | 106 | expect(heap.isEmpty()).toBe(true) 107 | }) 108 | }) 109 | 110 | it('Works extensively', () => { 111 | heap.add(8) 112 | heap.add(3) 113 | heap.add(10) 114 | 115 | expect(heap.peek()).toBe(3) 116 | expect(heap.poll()).toBe(3) 117 | 118 | heap.add(9) 119 | heap.add(2) 120 | heap.add(-5) 121 | heap.add(20) 122 | 123 | expect(heap.peek()).toBe(-5) 124 | expect(heap.poll()).toBe(-5) 125 | expect(heap.poll()).toBe(2) 126 | expect(heap.poll()).toBe(8) 127 | expect(heap.poll()).toBe(9) 128 | expect(heap.poll()).toBe(10) 129 | expect(heap.poll()).toBe(20) 130 | }) 131 | }) 132 | -------------------------------------------------------------------------------- /test/data-structures/heaps/min-binomial-heap.test.ts: -------------------------------------------------------------------------------- 1 | import MinBinomialHeap from '../../../src/data-structures/priority-queues/mergeable-heaps/min-binomial-heap' 2 | 3 | describe('MinBinomialHeap', () => { 4 | let heap: MinBinomialHeap 5 | 6 | beforeEach(() => { 7 | heap = new MinBinomialHeap(Number.MIN_SAFE_INTEGER) 8 | }) 9 | 10 | describe('Empty heap', () => { 11 | it('is empty', () => { 12 | expect(heap.size).toBe(0) 13 | expect(heap.isEmpty()).toBe(true) 14 | }) 15 | 16 | it('dequeue() returns null', () => { 17 | expect(heap.dequeue()).toBe(null) 18 | }) 19 | 20 | it('peek() returns null', () => { 21 | expect(heap.peek()).toBe(null) 22 | }) 23 | }) 24 | 25 | describe('Insertion and deletion', () => { 26 | it('inserts numbers properly', () => { 27 | heap.enqueue(8) 28 | expect(heap.size).toBe(1) 29 | 30 | heap.enqueue(26) 31 | expect(heap.size).toBe(2) 32 | 33 | heap.enqueue(72) 34 | expect(heap.size).toBe(3) 35 | 36 | heap.enqueue(42) 37 | }) 38 | 39 | it('deletes a specific node', () => { 40 | const node = heap.enqueue(8) 41 | heap.enqueue(3) 42 | heap.deleteNode(node) 43 | 44 | expect(heap.size).toBe(1) 45 | expect(heap.head).toBeTruthy() 46 | expect(heap.head!.value).toBe(3) 47 | }) 48 | 49 | it('deletes numbers properly', () => { 50 | heap.enqueue(8) 51 | heap.enqueue(26) 52 | heap.enqueue(72) 53 | 54 | expect(heap.size).toBe(3) 55 | 56 | heap.dequeue() 57 | expect(heap.size).toBe(2) 58 | 59 | heap.dequeue() 60 | expect(heap.size).toBe(1) 61 | 62 | heap.dequeue() 63 | expect(heap.size).toBe(0) 64 | }) 65 | }) 66 | 67 | it('maintains the heap invariant', () => { 68 | const values = [8, 32, 72, 26, 16, 48, 5, 11, 17, 93, 500, 603, 401, 325, -321] 69 | 70 | for (const v of values) { 71 | heap.enqueue(v) 72 | } 73 | 74 | const sortedValues = [...values].sort((a, b) => a - b) 75 | 76 | for (const v of sortedValues) { 77 | const node = heap.dequeue() 78 | if (!node) throw new Error() 79 | expect(node.value).toBe(v) 80 | } 81 | }) 82 | 83 | describe('Reading', () => { 84 | it('peeks', () => { 85 | heap.enqueue(-4) 86 | heap.enqueue(-5) 87 | heap.enqueue(-6) 88 | heap.enqueue(-7) 89 | heap.enqueue(-8) 90 | heap.enqueue(-9) 91 | heap.enqueue(-10) 92 | 93 | expect(heap.peek()!.value).toBe(-10) 94 | }) 95 | }) 96 | 97 | describe('Updating', () => { 98 | it('unions properly', () => { 99 | const heapA = new MinBinomialHeap(Number.MIN_SAFE_INTEGER) 100 | const heapB = new MinBinomialHeap(Number.MIN_SAFE_INTEGER) 101 | 102 | const valuesA = [8, 32, 72, 26, 16, 48, 5, 11] 103 | const valuesB = [17, 93, 500, 603, 401, 325, -321] 104 | 105 | for (const a of valuesA) { 106 | heapA.enqueue(a) 107 | } 108 | 109 | for (const b of valuesB) { 110 | heapB.enqueue(b) 111 | } 112 | 113 | const newHeap = heapA.union(heapB) 114 | expect(newHeap.size).toBe(heapA.size + heapB.size) 115 | 116 | const sortedValues = [...valuesA, ...valuesB].sort((a, b) => a - b) 117 | 118 | for (const v of sortedValues) { 119 | const node = newHeap.dequeue() 120 | if (!node) throw new Error() 121 | expect(node.value).toBe(v) 122 | } 123 | }) 124 | 125 | it('decreases keys properly', () => { 126 | const node = heap.enqueue(3) 127 | 128 | expect(heap.decreaseKey(node, 5)).toBe(false) 129 | expect(heap.decreaseKey(node, -3)).toBe(true) 130 | }) 131 | }) 132 | }) 133 | -------------------------------------------------------------------------------- /test/data-structures/heaps/min-d-heap.test.ts: -------------------------------------------------------------------------------- 1 | import MinDHeap from '../../../src/data-structures/priority-queues/min-d-heap' 2 | 3 | describe('MinDHeap', () => { 4 | let heap: MinDHeap 5 | 6 | beforeEach(() => { 7 | heap = new MinDHeap(3) 8 | }) 9 | 10 | describe('INSPECTION', () => { 11 | it('is empty', () => { 12 | expect(heap.size()).toBe(0) 13 | expect(heap.isEmpty()).toBe(true) 14 | }) 15 | }) 16 | 17 | describe('Insertion', () => { 18 | it('adds elements', () => { 19 | heap.add(8) 20 | expect(heap.size()).toBe(1) 21 | 22 | heap.add(3) 23 | expect(heap.size()).toBe(2) 24 | 25 | heap.add(10) 26 | expect(heap.size()).toBe(3) 27 | }) 28 | }) 29 | 30 | describe('Accessing', () => { 31 | it('mantains heap invariant', () => { 32 | heap.add(8) 33 | expect(heap.peek()).toBe(8) 34 | 35 | heap.add(3) 36 | expect(heap.peek()).toBe(3) 37 | 38 | heap.add(10) 39 | expect(heap.peek()).toBe(3) 40 | }) 41 | 42 | it('returns null when empty heap is peeked', () => { 43 | expect(heap.peek()).toBe(null) 44 | }) 45 | }) 46 | 47 | describe('Searching', () => { 48 | it('checks if heap contains element', () => { 49 | heap.add(8) 50 | expect(heap.contains(8)).toBe(true) 51 | expect(heap.contains(3)).toBe(false) 52 | }) 53 | }) 54 | 55 | describe('Deletion', () => { 56 | it('polls elements', () => { 57 | heap.add(8) 58 | heap.add(3) 59 | heap.add(10) 60 | 61 | expect(heap.size()).toBe(3) 62 | 63 | heap.poll() 64 | expect(heap.size()).toBe(2) 65 | 66 | heap.poll() 67 | expect(heap.size()).toBe(1) 68 | 69 | heap.poll() 70 | expect(heap.size()).toBe(0) 71 | }) 72 | 73 | it('returns null when empty heap is polled', () => { 74 | expect(heap.poll()).toBe(null) 75 | }) 76 | 77 | it('removes a specified element', () => { 78 | heap.add(8) 79 | heap.add(3) 80 | heap.add(10) 81 | 82 | expect(heap.contains(8)).toBe(true) 83 | expect(heap.remove(8)).toBe(true) 84 | expect(heap.contains(8)).toBe(false) 85 | }) 86 | 87 | it('returns false when element does not exist', () => { 88 | expect(heap.remove(8)).toBe(false) 89 | }) 90 | 91 | it('clears the heap', () => { 92 | heap.add(8) 93 | heap.add(3) 94 | heap.add(10) 95 | 96 | heap.clear() 97 | 98 | expect(heap.isEmpty()).toBe(true) 99 | }) 100 | }) 101 | 102 | it('Works extensively', () => { 103 | heap.add(8) 104 | heap.add(3) 105 | heap.add(10) 106 | 107 | expect(heap.peek()).toBe(3) 108 | expect(heap.poll()).toBe(3) 109 | expect(heap.peek()).toBe(8) 110 | 111 | heap.add(2) 112 | heap.add(-5) 113 | heap.add(20) 114 | heap.add(9) 115 | 116 | expect(heap.peek()).toBe(-5) 117 | expect(heap.poll()).toBe(-5) 118 | expect(heap.poll()).toBe(2) 119 | expect(heap.poll()).toBe(8) 120 | expect(heap.poll()).toBe(9) 121 | expect(heap.poll()).toBe(10) 122 | expect(heap.poll()).toBe(20) 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /test/data-structures/heaps/min-indexed-d-heap.test.ts: -------------------------------------------------------------------------------- 1 | import MinIndexedDHeap from '../../../src/data-structures/priority-queues/min-indexed-d-heap' 2 | 3 | describe('MinIndexedDHeap', () => { 4 | let heap: MinIndexedDHeap 5 | 6 | beforeEach(() => { 7 | heap = new MinIndexedDHeap(3) 8 | }) 9 | 10 | describe('INSPECTION', () => { 11 | it('is empty', () => { 12 | expect(heap.size()).toBe(0) 13 | expect(heap.isEmpty()).toBe(true) 14 | expect(heap.peek()).toBe(null) 15 | expect(heap.poll()).toBe(null) 16 | }) 17 | }) 18 | 19 | describe('Heap invariant', () => { 20 | it('mantains heap invariant', () => { 21 | const nums = [6, 2, 5, 0, 3, 4, 1, 7, 8] 22 | for (let i = 0; i < nums.length; i++) { 23 | heap.add(i, nums[i]) 24 | } 25 | 26 | for (let i = 0; i < nums.length; i++) { 27 | expect(heap.poll()).toBe(i) 28 | } 29 | }) 30 | }) 31 | 32 | describe('Insertion', () => { 33 | it('add()', () => { 34 | heap.add(1, 8) 35 | expect(heap.size()).toBe(1) 36 | 37 | heap.add(2, 3) 38 | expect(heap.size()).toBe(2) 39 | 40 | heap.add(3, 10) 41 | expect(heap.size()).toBe(3) 42 | }) 43 | 44 | it('returns false when adding a key that already exists', () => { 45 | heap.add(1, 8) 46 | expect(heap.add(1, 2)).toBe(false) 47 | }) 48 | }) 49 | 50 | describe('Accessing', () => { 51 | it('peek()', () => { 52 | heap.add(1, 8) 53 | expect(heap.peek()).toBe(8) 54 | }) 55 | 56 | it('valueOf()', () => { 57 | heap.add(1, 8) 58 | expect(heap.valueOf(1)).toBe(8) 59 | }) 60 | }) 61 | 62 | describe('Updating', () => { 63 | it('updateKey()', () => { 64 | heap.add(1, 8) 65 | expect(heap.valueOf(1)).toBe(8) 66 | heap.updateKey(1, 7) 67 | expect(heap.valueOf(1)).toBe(7) 68 | }) 69 | 70 | it('increaseKey()', () => { 71 | heap.add(1, 8) 72 | expect(heap.valueOf(1)).toBe(8) 73 | 74 | heap.increaseKey(1, 10) 75 | expect(heap.valueOf(1)).toBe(10) 76 | }) 77 | 78 | it('returns false when increaseKey() on key that does not exist', () => { 79 | expect(heap.increaseKey(1, 10)).toBe(false) 80 | }) 81 | 82 | it('decreaseKey()', () => { 83 | heap.add(1, 8) 84 | expect(heap.valueOf(1)).toBe(8) 85 | 86 | heap.decreaseKey(1, 5) 87 | expect(heap.valueOf(1)).toBe(5) 88 | }) 89 | 90 | it('prevents bad increases and decreases', () => { 91 | heap.add(1, 8) 92 | 93 | heap.increaseKey(1, 0) 94 | expect(heap.valueOf(1)).toBe(8) 95 | 96 | heap.decreaseKey(1, 10) 97 | expect(heap.valueOf(1)).toBe(8) 98 | }) 99 | }) 100 | 101 | describe('Searching', () => { 102 | it('contains()', () => { 103 | heap.add(1, 8) 104 | expect(heap.contains(1)).toBe(true) 105 | 106 | expect(heap.contains(2)).toBe(false) 107 | }) 108 | }) 109 | 110 | describe('Deletion', () => { 111 | it('polls elements', () => { 112 | heap.add(1, 8) 113 | heap.add(2, 3) 114 | heap.add(3, 10) 115 | 116 | expect(heap.size()).toBe(3) 117 | 118 | heap.poll() 119 | expect(heap.size()).toBe(2) 120 | 121 | heap.poll() 122 | expect(heap.size()).toBe(1) 123 | 124 | heap.poll() 125 | expect(heap.size()).toBe(0) 126 | }) 127 | 128 | it('returns null when poll() is called on an empty heap', () => { 129 | expect(heap.poll()).toBe(null) 130 | }) 131 | 132 | it('removes a specific key', () => { 133 | heap.add(1, 8) 134 | heap.add(2, 3) 135 | heap.add(3, 10) 136 | 137 | expect(heap.contains(1)).toBe(true) 138 | expect(heap.deleteKey(1)).toBe(8) 139 | expect(heap.contains(1)).toBe(false) 140 | }) 141 | 142 | it('returns null when deleteKey() is called with a key that DNE', () => { 143 | expect(heap.deleteKey(8)).toBe(null) 144 | }) 145 | 146 | it('clears the heap', () => { 147 | heap.add(1, 8) 148 | heap.add(2, 3) 149 | heap.add(3, 10) 150 | 151 | heap.clear() 152 | 153 | expect(heap.isEmpty()).toBe(true) 154 | }) 155 | }) 156 | }) 157 | -------------------------------------------------------------------------------- /test/data-structures/sequences/circular-buffer.test.ts: -------------------------------------------------------------------------------- 1 | import CircularBuffer from '../../../src/data-structures/sequences/circular-buffer' 2 | 3 | describe('Circular Buffer', () => { 4 | let buffer: CircularBuffer 5 | 6 | beforeEach(() => { 7 | buffer = new CircularBuffer(4) 8 | }) 9 | 10 | describe('empty', () => { 11 | it('returns null when dequeue is called on empty buffer', () => { 12 | expect(buffer.dequeue()).toBe(null) 13 | }) 14 | 15 | it('returns null when peek() is called on empty buffer', () => { 16 | expect(buffer.peekFront()).toBe(null) 17 | expect(buffer.peekBack()).toBe(null) 18 | }) 19 | 20 | it('is empty', () => { 21 | expect(buffer.isEmpty()).toBe(true) 22 | }) 23 | }) 24 | 25 | describe('insertion/deletion', () => { 26 | it('enqueues', () => { 27 | buffer.enqueue(1) 28 | expect(buffer.size()).toBe(1) 29 | 30 | buffer.enqueue(2) 31 | expect(buffer.size()).toBe(2) 32 | 33 | buffer.enqueue(3) 34 | expect(buffer.size()).toBe(3) 35 | }) 36 | 37 | it('dequeues', () => { 38 | buffer.enqueue(1) 39 | buffer.enqueue(2) 40 | buffer.enqueue(3) 41 | 42 | buffer.dequeue() 43 | expect(buffer.size()).toBe(2) 44 | 45 | buffer.dequeue() 46 | expect(buffer.size()).toBe(1) 47 | 48 | buffer.dequeue() 49 | expect(buffer.size()).toBe(0) 50 | }) 51 | 52 | it('overwrites', () => { 53 | buffer.enqueue(1) 54 | buffer.enqueue(2) 55 | buffer.enqueue(3) 56 | buffer.enqueue(4) 57 | buffer.enqueue(5) 58 | expect(buffer.contains(1)).toBe(false) 59 | expect(buffer.peekFront()).toBe(2) 60 | expect(buffer.peekBack()).toBe(5) 61 | }) 62 | 63 | it('clears the buffer', () => { 64 | buffer.enqueue(1) 65 | buffer.enqueue(2) 66 | buffer.enqueue(3) 67 | buffer.enqueue(4) 68 | buffer.clear() 69 | expect(buffer.isEmpty()).toBe(true) 70 | 71 | buffer.enqueue(1) 72 | buffer.clear() 73 | expect(buffer.isEmpty()).toBe(true) 74 | 75 | buffer.clear() 76 | expect(buffer.isEmpty()).toBe(true) 77 | }) 78 | }) 79 | 80 | describe('Accessing', () => { 81 | it('peeks', () => { 82 | buffer.enqueue(1) 83 | expect(buffer.peekFront()).toBe(1) 84 | expect(buffer.peekBack()).toBe(1) 85 | 86 | buffer.enqueue(2) 87 | expect(buffer.peekFront()).toBe(1) 88 | expect(buffer.peekBack()).toBe(2) 89 | }) 90 | 91 | it('peeks back', () => { 92 | buffer.enqueue(1) 93 | buffer.enqueue(2) 94 | buffer.enqueue(3) 95 | buffer.enqueue(4) 96 | expect(buffer.peekBack()).toBe(4) 97 | }) 98 | }) 99 | 100 | describe('searching', () => { 101 | it('finds out if buffer contains element', () => { 102 | expect(buffer.contains(1)).toBe(false) 103 | buffer.enqueue(1) 104 | buffer.enqueue(2) 105 | buffer.enqueue(3) 106 | 107 | expect(buffer.contains(1)).toBe(true) 108 | expect(buffer.contains(3)).toBe(true) 109 | expect(buffer.contains(8)).toBe(false) 110 | }) 111 | }) 112 | }) 113 | 114 | describe('Circular Buffer - complex object', () => { 115 | class Hero { 116 | heroId: number 117 | hunger: number 118 | health: number 119 | 120 | constructor(id: number) { 121 | this.heroId = id 122 | this.hunger = 100 123 | this.health = 100 124 | } 125 | } 126 | 127 | const sameHeroF = (a: Hero, b: Hero) => a.heroId === b.heroId 128 | 129 | let buffer: CircularBuffer 130 | 131 | beforeAll(() => { 132 | const knight = new Hero(123) 133 | const archer = new Hero(456) 134 | const mage = new Hero(789) 135 | 136 | buffer = new CircularBuffer(3, sameHeroF) 137 | 138 | buffer.enqueue(knight) 139 | buffer.enqueue(archer) 140 | buffer.enqueue(mage) 141 | }) 142 | 143 | it('checks if queue contains hero', () => { 144 | const knight = new Hero(123) 145 | const mage = new Hero(789) 146 | 147 | expect(buffer.contains(knight)).toBe(true) 148 | expect(buffer.contains(mage)).toBe(true) 149 | expect(buffer.contains(new Hero(246))).toBe(false) 150 | }) 151 | }) 152 | -------------------------------------------------------------------------------- /test/data-structures/sequences/deque.test.ts: -------------------------------------------------------------------------------- 1 | import { Deque } from '../../../src/data-structures/sequences/queue' 2 | 3 | describe('Deque', () => { 4 | let deque: Deque 5 | 6 | beforeEach(() => { 7 | deque = new Deque() 8 | }) 9 | 10 | describe('empty deque', () => { 11 | it('returns null when pop() is called on empty stack', () => { 12 | expect(deque.popFront()).toBe(null) 13 | expect(deque.popBack()).toBe(null) 14 | }) 15 | 16 | it('returns null when peek() is called on empty stack', () => { 17 | expect(deque.peekFront()).toBe(null) 18 | expect(deque.peekBack()).toBe(null) 19 | }) 20 | }) 21 | 22 | it('is empty', () => { 23 | expect(deque.isEmpty()).toBe(true) 24 | }) 25 | 26 | describe('enqueues', () => { 27 | it('pushes front', () => { 28 | deque.pushFront(1) 29 | expect(deque.size()).toBe(1) 30 | 31 | deque.pushFront(2) 32 | expect(deque.size()).toBe(2) 33 | 34 | deque.pushFront(3) 35 | expect(deque.size()).toBe(3) 36 | }) 37 | 38 | it('pushes back', () => { 39 | deque.pushBack(1) 40 | expect(deque.size()).toBe(1) 41 | 42 | deque.pushBack(2) 43 | expect(deque.size()).toBe(2) 44 | 45 | deque.pushBack(3) 46 | expect(deque.size()).toBe(3) 47 | }) 48 | }) 49 | 50 | describe('dequeues', () => { 51 | it('pops front', () => { 52 | deque.pushBack(1) 53 | deque.pushBack(2) 54 | deque.pushBack(3) 55 | 56 | const val1 = deque.popFront() 57 | expect(val1).toBe(1) 58 | expect(deque.size()).toBe(2) 59 | 60 | const val2 = deque.popFront() 61 | expect(val2).toBe(2) 62 | expect(deque.size()).toBe(1) 63 | 64 | const val3 = deque.popFront() 65 | expect(val3).toBe(3) 66 | expect(deque.size()).toBe(0) 67 | }) 68 | 69 | it('pops back', () => { 70 | deque.pushBack(1) 71 | deque.pushBack(2) 72 | deque.pushBack(3) 73 | 74 | const val1 = deque.popBack() 75 | expect(val1).toBe(3) 76 | expect(deque.size()).toBe(2) 77 | 78 | const val2 = deque.popBack() 79 | expect(val2).toBe(2) 80 | expect(deque.size()).toBe(1) 81 | 82 | const val3 = deque.popBack() 83 | expect(val3).toBe(1) 84 | expect(deque.size()).toBe(0) 85 | }) 86 | }) 87 | 88 | it('finds out if list contains element', () => { 89 | expect(deque.contains(1)).toBe(false) 90 | deque.pushBack(1) 91 | deque.pushBack(2) 92 | deque.pushBack(3) 93 | 94 | expect(deque.contains(1)).toBe(true) 95 | expect(deque.contains(3)).toBe(true) 96 | expect(deque.contains(8)).toBe(false) 97 | }) 98 | 99 | it('peeks', () => { 100 | deque.pushBack(1) 101 | expect(deque.peekFront()).toBe(1) 102 | expect(deque.peekBack()).toBe(1) 103 | 104 | deque.pushBack(2) 105 | expect(deque.peekFront()).toBe(1) 106 | expect(deque.peekBack()).toBe(2) 107 | }) 108 | 109 | it('clears the list', () => { 110 | deque.pushBack(1) 111 | deque.pushBack(2) 112 | deque.pushBack(3) 113 | deque.pushBack(4) 114 | deque.clear() 115 | expect(deque.isEmpty()).toBe(true) 116 | 117 | deque.pushBack(1) 118 | deque.clear() 119 | expect(deque.isEmpty()).toBe(true) 120 | 121 | deque.clear() 122 | expect(deque.isEmpty()).toBe(true) 123 | }) 124 | 125 | it('is iterable', () => { 126 | const nums = [1, 2, 3] 127 | 128 | for (const n of nums) { 129 | deque.pushBack(n) 130 | } 131 | 132 | let i = 0 133 | for (const n of deque) { 134 | expect(n).toBe(nums[i]) 135 | i += 1 136 | } 137 | }) 138 | }) 139 | 140 | describe('Queue - complex object', () => { 141 | class Hero { 142 | heroId: number 143 | hunger: number 144 | health: number 145 | 146 | constructor(id: number) { 147 | this.heroId = id 148 | this.hunger = 100 149 | this.health = 100 150 | } 151 | } 152 | 153 | const sameHeroF = (a: Hero, b: Hero) => a.heroId === b.heroId 154 | 155 | let queue: Deque 156 | 157 | beforeAll(() => { 158 | const knight = new Hero(123) 159 | const archer = new Hero(456) 160 | const mage = new Hero(789) 161 | 162 | queue = new Deque(sameHeroF) 163 | 164 | queue.pushBack(knight) 165 | queue.pushBack(archer) 166 | queue.pushBack(mage) 167 | }) 168 | 169 | it('checks if queue contains hero', () => { 170 | const knight = new Hero(123) 171 | const mage = new Hero(789) 172 | 173 | expect(queue.contains(knight)).toBe(true) 174 | expect(queue.contains(mage)).toBe(true) 175 | expect(queue.contains(new Hero(246))).toBe(false) 176 | }) 177 | }) 178 | -------------------------------------------------------------------------------- /test/data-structures/sequences/linked-list.test.ts: -------------------------------------------------------------------------------- 1 | import LinkedList from '../../../src/data-structures/sequences/linked-list' 2 | 3 | describe('Linked List - simple number', () => { 4 | let list: LinkedList 5 | 6 | beforeEach(() => { 7 | list = new LinkedList() 8 | }) 9 | 10 | it('is empty', () => { 11 | expect(list.isEmpty()).toBe(true) 12 | }) 13 | 14 | describe('empty lists', () => { 15 | it('retruns null on empty lists', () => { 16 | expect(list.peekFront()).toBe(null) 17 | expect(list.peekBack()).toBe(null) 18 | expect(list.removeFront()).toBe(null) 19 | expect(list.removeBack()).toBe(null) 20 | expect(list.remove(3)).toBe(null) 21 | expect(list.removeAt(3)).toBe(null) 22 | expect(list.removeAt(-1)).toBe(null) 23 | expect(list.get(1)).toBe(null) 24 | }) 25 | }) 26 | 27 | describe('adding', () => { 28 | it('adds to head of list', () => { 29 | list.addFront(8) 30 | expect(list.size()).toBe(1) 31 | 32 | list.addFront(3) 33 | expect(list.size()).toBe(2) 34 | }) 35 | 36 | it('adds to tail of list', () => { 37 | list.addBack(8) 38 | expect(list.size()).toBe(1) 39 | 40 | list.addBack(3) 41 | expect(list.size()).toBe(2) 42 | }) 43 | 44 | it('adds at specific index', () => { 45 | list.addAt(0, 1) 46 | expect(list.size()).toBe(1) 47 | 48 | list.addAt(1, 2) 49 | expect(list.size()).toBe(2) 50 | 51 | list.addAt(1, 3) 52 | expect(list.size()).toBe(3) 53 | 54 | list.addAt(2, 4) 55 | expect(list.size()).toBe(4) 56 | 57 | list.addAt(1, 5) 58 | expect(list.size()).toBe(5) 59 | }) 60 | 61 | it('returns false when adding at index out of bounds', () => { 62 | list.addAt(0, 1) 63 | expect(list.addAt(3, 2)).toBe(false) 64 | }) 65 | }) 66 | 67 | describe('finding', () => { 68 | it('gets nodes', () => { 69 | list.addBack(1) 70 | list.addBack(2) 71 | list.addBack(3) 72 | 73 | expect(list.get(0)).toBe(1) 74 | expect(list.get(1)).toBe(2) 75 | expect(list.get(2)).toBe(3) 76 | }) 77 | 78 | it('gets index of nodes', () => { 79 | expect(list.indexOf(1)).toBe(-1) 80 | 81 | list.addBack(1) 82 | list.addBack(2) 83 | list.addBack(3) 84 | 85 | expect(list.indexOf(1)).toBe(0) 86 | expect(list.indexOf(2)).toBe(1) 87 | expect(list.indexOf(3)).toBe(2) 88 | expect(list.indexOf(4)).toBe(-1) 89 | }) 90 | 91 | it('finds out if list contains node', () => { 92 | expect(list.contains(1)).toBe(false) 93 | list.addBack(1) 94 | list.addBack(2) 95 | list.addBack(3) 96 | 97 | expect(list.contains(1)).toBe(true) 98 | expect(list.contains(3)).toBe(true) 99 | expect(list.contains(8)).toBe(false) 100 | }) 101 | 102 | it('peeks head', () => { 103 | list.addFront(1) 104 | expect(list.peekFront()).toBe(1) 105 | 106 | list.addFront(2) 107 | expect(list.peekFront()).toBe(2) 108 | }) 109 | 110 | it('peeks tail', () => { 111 | list.addBack(1) 112 | expect(list.peekBack()).toBe(1) 113 | 114 | list.addBack(2) 115 | expect(list.peekBack()).toBe(2) 116 | }) 117 | }) 118 | 119 | describe('removing', () => { 120 | it('removes from head', () => { 121 | list.addBack(8) 122 | list.addBack(3) 123 | 124 | list.removeFront() 125 | expect(list.size()).toBe(1) 126 | expect(list.peekFront()).toBe(3) 127 | 128 | list.removeFront() 129 | expect(list.size()).toBe(0) 130 | }) 131 | 132 | it('removes from tail', () => { 133 | list.addBack(8) 134 | list.addBack(3) 135 | 136 | list.removeBack() 137 | expect(list.size()).toBe(1) 138 | expect(list.peekFront()).toBe(8) 139 | 140 | list.removeBack() 141 | expect(list.size()).toBe(0) 142 | }) 143 | 144 | it('removes at specific index', () => { 145 | list.addBack(1) 146 | list.addBack(2) 147 | list.addBack(3) 148 | list.addBack(4) 149 | list.addBack(5) 150 | list.addBack(6) 151 | 152 | // 1-2-3-4-5-6 153 | 154 | const val = list.removeAt(0) 155 | expect(val).toBe(1) 156 | expect(list.size()).toBe(5) 157 | // 2-3-4-5-6 158 | 159 | const val2 = list.removeAt(4) 160 | expect(val2).toBe(6) 161 | expect(list.size()).toBe(4) 162 | // 2-3-4-5 163 | 164 | const val3 = list.removeAt(1) 165 | expect(val3).toBe(3) 166 | expect(list.size()).toBe(3) 167 | // 2-4-5 168 | 169 | const val4 = list.removeAt(1) 170 | expect(val4).toBe(4) 171 | expect(list.size()).toBe(2) 172 | // 2-5 173 | 174 | const val5 = list.removeAt(1) 175 | expect(val5).toBe(5) 176 | expect(list.size()).toBe(1) 177 | // 2 178 | 179 | const val6 = list.removeAt(0) 180 | expect(val6).toBe(2) 181 | expect(list.size()).toBe(0) 182 | }) 183 | 184 | it('removes specific value', () => { 185 | list.addBack(1) 186 | list.addBack(2) 187 | list.addBack(3) 188 | 189 | const val1 = list.remove(1) 190 | expect(val1).toBe(1) 191 | expect(list.size()).toBe(2) 192 | 193 | const val2 = list.remove(2) 194 | expect(val2).toBe(2) 195 | expect(list.size()).toBe(1) 196 | 197 | const val3 = list.remove(3) 198 | expect(val3).toBe(3) 199 | expect(list.size()).toBe(0) 200 | }) 201 | 202 | it('clears the list', () => { 203 | list.addBack(1) 204 | list.addBack(2) 205 | list.addBack(3) 206 | list.addBack(4) 207 | list.clear() 208 | expect(list.isEmpty()).toBe(true) 209 | 210 | list.addBack(1) 211 | list.clear() 212 | expect(list.isEmpty()).toBe(true) 213 | 214 | list.clear() 215 | expect(list.isEmpty()).toBe(true) 216 | }) 217 | }) 218 | 219 | describe('helpers', () => { 220 | it('creates list from array', () => { 221 | const array = [1, 2, 3] 222 | list.fromArray(array) 223 | expect(Array.from(list)).toEqual([1, 2, 3]) 224 | }) 225 | }) 226 | 227 | describe('iterator', () => { 228 | it('is iterable', () => { 229 | const array = [1, 2, 3] 230 | list.fromArray(array) 231 | 232 | let i = 0 233 | for (const n of list) { 234 | expect(n).toBe(array[i]) 235 | i += 1 236 | } 237 | }) 238 | 239 | it('does not iterate over an empty list', () => { 240 | let count = 0 241 | 242 | // eslint-disable-next-line 243 | for (const n of list) { 244 | count += 1 245 | } 246 | 247 | expect(count).toBe(0) 248 | }) 249 | }) 250 | }) 251 | 252 | describe('Linked list - complex object', () => { 253 | class Hero { 254 | heroId: number 255 | hunger: number 256 | health: number 257 | 258 | constructor(id: number) { 259 | this.heroId = id 260 | this.hunger = 100 261 | this.health = 100 262 | } 263 | } 264 | 265 | const sameHeroF = (a: Hero, b: Hero) => a.heroId === b.heroId 266 | 267 | let list: LinkedList 268 | 269 | beforeAll(() => { 270 | const knight = new Hero(123) 271 | const archer = new Hero(456) 272 | const mage = new Hero(789) 273 | 274 | list = new LinkedList(sameHeroF) 275 | 276 | list.addBack(knight) 277 | list.addBack(archer) 278 | list.addBack(mage) 279 | }) 280 | 281 | it('gets the index of a Hero', () => { 282 | const knight = new Hero(123) 283 | const mage = new Hero(789) 284 | 285 | expect(list.indexOf(knight)).toBe(0) 286 | expect(list.indexOf(mage)).toBe(2) 287 | }) 288 | 289 | it('checks if list contains hero', () => { 290 | const knight = new Hero(123) 291 | const mage = new Hero(789) 292 | 293 | expect(list.contains(knight)).toBe(true) 294 | expect(list.contains(mage)).toBe(true) 295 | expect(list.contains(new Hero(246))).toBe(false) 296 | }) 297 | }) 298 | -------------------------------------------------------------------------------- /test/data-structures/sequences/queue.test.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from '../../../src/data-structures/sequences/queue' 2 | 3 | describe('Queue', () => { 4 | let queue: Queue 5 | 6 | beforeEach(() => { 7 | queue = new Queue() 8 | }) 9 | 10 | describe('empty queue', () => { 11 | it('returns null when pop() is called on empty stack', () => { 12 | expect(queue.dequeue()).toBe(null) 13 | }) 14 | 15 | it('returns null when peek() is called on empty stack', () => { 16 | expect(queue.peekFront()).toBe(null) 17 | expect(queue.peekBack()).toBe(null) 18 | }) 19 | }) 20 | 21 | it('is empty', () => { 22 | expect(queue.isEmpty()).toBe(true) 23 | }) 24 | 25 | it('enqueues', () => { 26 | queue.enqueue(1) 27 | expect(queue.size()).toBe(1) 28 | 29 | queue.enqueue(2) 30 | expect(queue.size()).toBe(2) 31 | 32 | queue.enqueue(3) 33 | expect(queue.size()).toBe(3) 34 | }) 35 | 36 | it('dequeues', () => { 37 | queue.enqueue(1) 38 | queue.enqueue(2) 39 | queue.enqueue(3) 40 | 41 | const val1 = queue.dequeue() 42 | expect(val1).toBe(1) 43 | expect(queue.size()).toBe(2) 44 | 45 | const val2 = queue.dequeue() 46 | expect(val2).toBe(2) 47 | expect(queue.size()).toBe(1) 48 | 49 | const val3 = queue.dequeue() 50 | expect(val3).toBe(3) 51 | expect(queue.size()).toBe(0) 52 | }) 53 | 54 | it('finds out if list contains element', () => { 55 | expect(queue.contains(1)).toBe(false) 56 | queue.enqueue(1) 57 | queue.enqueue(2) 58 | queue.enqueue(3) 59 | 60 | expect(queue.contains(1)).toBe(true) 61 | expect(queue.contains(3)).toBe(true) 62 | expect(queue.contains(8)).toBe(false) 63 | }) 64 | 65 | it('peeks', () => { 66 | queue.enqueue(1) 67 | expect(queue.peekFront()).toBe(1) 68 | expect(queue.peekBack()).toBe(1) 69 | 70 | queue.enqueue(2) 71 | expect(queue.peekFront()).toBe(1) 72 | expect(queue.peekBack()).toBe(2) 73 | }) 74 | 75 | it('clears the list', () => { 76 | queue.enqueue(1) 77 | queue.enqueue(2) 78 | queue.enqueue(3) 79 | queue.enqueue(4) 80 | queue.clear() 81 | expect(queue.isEmpty()).toBe(true) 82 | 83 | queue.enqueue(1) 84 | queue.clear() 85 | expect(queue.isEmpty()).toBe(true) 86 | 87 | queue.clear() 88 | expect(queue.isEmpty()).toBe(true) 89 | }) 90 | 91 | it('is iterable', () => { 92 | const nums = [1, 2, 3] 93 | 94 | for (const n of nums) { 95 | queue.enqueue(n) 96 | } 97 | 98 | let i = 2 99 | for (const n of queue) { 100 | expect(n).toBe(nums[i]) 101 | i -= 1 102 | } 103 | }) 104 | }) 105 | 106 | describe('Queue - complex object', () => { 107 | class Hero { 108 | heroId: number 109 | hunger: number 110 | health: number 111 | 112 | constructor(id: number) { 113 | this.heroId = id 114 | this.hunger = 100 115 | this.health = 100 116 | } 117 | } 118 | 119 | const sameHeroF = (a: Hero, b: Hero) => a.heroId === b.heroId 120 | 121 | let queue: Queue 122 | 123 | beforeAll(() => { 124 | const knight = new Hero(123) 125 | const archer = new Hero(456) 126 | const mage = new Hero(789) 127 | 128 | queue = new Queue(sameHeroF) 129 | 130 | queue.enqueue(knight) 131 | queue.enqueue(archer) 132 | queue.enqueue(mage) 133 | }) 134 | 135 | it('checks if queue contains hero', () => { 136 | const knight = new Hero(123) 137 | const mage = new Hero(789) 138 | 139 | expect(queue.contains(knight)).toBe(true) 140 | expect(queue.contains(mage)).toBe(true) 141 | expect(queue.contains(new Hero(246))).toBe(false) 142 | }) 143 | }) 144 | -------------------------------------------------------------------------------- /test/data-structures/sequences/stack.test.ts: -------------------------------------------------------------------------------- 1 | import Stack from '../../../src/data-structures/sequences/stack' 2 | 3 | describe('Stack', () => { 4 | let stack: Stack 5 | 6 | beforeEach(() => { 7 | stack = new Stack() 8 | }) 9 | 10 | describe('empty stack', () => { 11 | it('returns null when pop() is called on empty stack', () => { 12 | expect(stack.pop()).toBe(null) 13 | }) 14 | 15 | it('returns null when peek() is called on empty stack', () => { 16 | expect(stack.peek()).toBe(null) 17 | }) 18 | }) 19 | 20 | it('is empty', () => { 21 | expect(stack.isEmpty()).toBe(true) 22 | }) 23 | 24 | it('pushes', () => { 25 | stack.push(1) 26 | expect(stack.size()).toBe(1) 27 | 28 | stack.push(2) 29 | expect(stack.size()).toBe(2) 30 | 31 | stack.push(3) 32 | expect(stack.size()).toBe(3) 33 | }) 34 | 35 | it('finds out if list contains element', () => { 36 | expect(stack.contains(1)).toBe(false) 37 | stack.push(1) 38 | stack.push(2) 39 | stack.push(3) 40 | 41 | expect(stack.contains(1)).toBe(true) 42 | expect(stack.contains(3)).toBe(true) 43 | expect(stack.contains(8)).toBe(false) 44 | }) 45 | 46 | it('pops', () => { 47 | stack.push(1) 48 | stack.push(2) 49 | stack.push(3) 50 | 51 | stack.pop() 52 | expect(stack.size()).toBe(2) 53 | 54 | stack.pop() 55 | expect(stack.size()).toBe(1) 56 | 57 | stack.pop() 58 | expect(stack.size()).toBe(0) 59 | }) 60 | 61 | it('peeks', () => { 62 | stack.push(1) 63 | expect(stack.peek()).toBe(1) 64 | 65 | stack.push(2) 66 | expect(stack.peek()).toBe(2) 67 | }) 68 | 69 | it('clears the stack', () => { 70 | stack.push(1) 71 | stack.push(2) 72 | stack.push(3) 73 | stack.push(4) 74 | stack.clear() 75 | expect(stack.isEmpty()).toBe(true) 76 | 77 | stack.push(1) 78 | stack.clear() 79 | expect(stack.isEmpty()).toBe(true) 80 | 81 | stack.clear() 82 | expect(stack.isEmpty()).toBe(true) 83 | }) 84 | 85 | it('is iterable', () => { 86 | const nums = [1, 2, 3] 87 | 88 | for (const n of nums) { 89 | stack.push(n) 90 | } 91 | 92 | let i = 0 93 | for (const n of stack) { 94 | expect(n).toBe(nums[i]) 95 | i += 1 96 | } 97 | }) 98 | }) 99 | 100 | describe('Stack - complex object', () => { 101 | class Hero { 102 | heroId: number 103 | hunger: number 104 | health: number 105 | 106 | constructor(id: number) { 107 | this.heroId = id 108 | this.hunger = 100 109 | this.health = 100 110 | } 111 | } 112 | 113 | const sameHeroF = (a: Hero, b: Hero) => a.heroId === b.heroId 114 | 115 | let stack: Stack 116 | 117 | beforeAll(() => { 118 | const knight = new Hero(123) 119 | const archer = new Hero(456) 120 | const mage = new Hero(789) 121 | 122 | stack = new Stack(sameHeroF) 123 | 124 | stack.push(knight) 125 | stack.push(archer) 126 | stack.push(mage) 127 | }) 128 | 129 | it('checks if stack contains hero', () => { 130 | const knight = new Hero(123) 131 | const mage = new Hero(789) 132 | 133 | expect(stack.contains(knight)).toBe(true) 134 | expect(stack.contains(mage)).toBe(true) 135 | expect(stack.contains(new Hero(246))).toBe(false) 136 | }) 137 | }) 138 | -------------------------------------------------------------------------------- /test/data-structures/trees/b-tree.test.ts: -------------------------------------------------------------------------------- 1 | import BTree from '../../../src/data-structures/trees/b-tree/b-tree' 2 | 3 | describe('AVL Tree', () => { 4 | let tree: BTree 5 | 6 | beforeEach(() => { 7 | tree = new BTree(2) 8 | }) 9 | 10 | describe('inspection', () => { 11 | it('isEmpty()', () => { 12 | expect(tree.size()).toBe(0) 13 | expect(tree.isEmpty()).toBe(true) 14 | expect(tree.height()).toBe(0) 15 | }) 16 | }) 17 | 18 | describe('search', () => { 19 | it('find()', () => { 20 | tree.insert(10) 21 | tree.insert(5) 22 | tree.insert(2) 23 | 24 | // 2, 5, 10 25 | 26 | if (tree.root === null) throw new Error() 27 | tree.insert(18) 28 | 29 | // 2, 5, 10 5 5 30 | // -> 2 10 -> 2 10, 18 31 | 32 | if (!tree.root.children) throw new Error() 33 | tree.insert(17) 34 | // 5 35 | // 2 10, 17, 18 36 | 37 | tree.insert(3) 38 | tree.insert(4) 39 | 40 | // 5 41 | // 2, 3, 4 10, 17, 18 42 | 43 | tree.insert(15) 44 | 45 | // 5 5, 17 46 | // 2, 3, 4 10, 17, 18 --> split bc [10, 17, 18] is full --> 2, 3, 4 10, 18 47 | 48 | // then insert 15 49 | 50 | // 5, 17 51 | // 2, 3, 4 10,15 18 52 | 53 | tree.insert(12) 54 | 55 | // 5, 17 56 | // 2, 3, 4 10,12,15 18 57 | 58 | // 5, 17 59 | // 2, 3, 4 10,12,15 18 60 | 61 | tree.insert(13) 62 | 63 | // 5, 17 5, 12, 17 64 | // 2, 3, 4 10,12,15 18 -> 2, 3, 4 10, 13,15 18 65 | 66 | tree.insert(1) 67 | 68 | // 12 12 69 | // 5, 12, 17 5, 17 3,5, 17 70 | // 2 3 4 10 13,15 18 -> 2 3 4 10 13,15 18 --> 1,2 4 10 13,15 18 71 | 72 | expect(tree.find(12)).toEqual([tree.root, 0]) 73 | expect(tree.find(5)).toEqual([tree.root.children[0], 1]) 74 | expect(tree.find(4)).toEqual([tree.root.children[0].children[1], 0]) 75 | expect(tree.find(6)).toEqual(null) 76 | }) 77 | }) 78 | 79 | describe('insertion', () => { 80 | it('insert()', () => { 81 | tree.insert(10) 82 | tree.insert(5) 83 | tree.insert(2) 84 | 85 | // 2, 5, 10 86 | 87 | if (tree.root === null) throw new Error() 88 | expect(tree.root.values).toEqual([2, 5, 10]) 89 | 90 | tree.insert(18) 91 | 92 | // 2, 5, 10 5 5 93 | // -> 2 10 -> 2 10, 18 94 | 95 | expect(tree.root.values).toEqual([5]) 96 | 97 | if (!tree.root.children) throw new Error() 98 | expect(tree.root.children[0].values).toEqual([2]) 99 | expect(tree.root.children[1].values).toEqual([10, 18]) 100 | 101 | tree.insert(17) 102 | // 5 103 | // 2 10, 17, 18 104 | 105 | expect(tree.root.values).toEqual([5]) 106 | expect(tree.root.children[0].values).toEqual([2]) 107 | expect(tree.root.children[1].values).toEqual([10, 17, 18]) 108 | 109 | tree.insert(3) 110 | tree.insert(4) 111 | 112 | // 5 113 | // 2, 3, 4 10, 17, 18 114 | 115 | expect(tree.root.values).toEqual([5]) 116 | expect(tree.root.children[0].values).toEqual([2, 3, 4]) 117 | expect(tree.root.children[1].values).toEqual([10, 17, 18]) 118 | 119 | tree.insert(15) 120 | 121 | // 5 5, 17 122 | // 2, 3, 4 10, 17, 18 --> split bc [10, 17, 18] is full --> 2, 3, 4 10, 18 123 | 124 | // then insert 15 125 | 126 | // 5, 17 127 | // 2, 3, 4 10,15 18 128 | 129 | expect(tree.root.values).toEqual([5, 17]) 130 | expect(tree.root.children[0].values).toEqual([2, 3, 4]) 131 | expect(tree.root.children[1].values).toEqual([10, 15]) 132 | expect(tree.root.children[2].values).toEqual([18]) 133 | 134 | tree.insert(12) 135 | 136 | // 5, 17 137 | // 2, 3, 4 10,12,15 18 138 | 139 | expect(tree.root.values).toEqual([5, 17]) 140 | expect(tree.root.children[0].values).toEqual([2, 3, 4]) 141 | expect(tree.root.children[1].values).toEqual([10, 12, 15]) 142 | expect(tree.root.children[2].values).toEqual([18]) 143 | 144 | // 5, 17 145 | // 2, 3, 4 10,12,15 18 146 | 147 | tree.insert(13) 148 | 149 | // 5, 17 5, 12, 17 150 | // 2, 3, 4 10,12,15 18 -> 2, 3, 4 10, 13,15 18 151 | 152 | expect(tree.root.values).toEqual([5, 12, 17]) 153 | expect(tree.root.children[0].values).toEqual([2, 3, 4]) 154 | expect(tree.root.children[1].values).toEqual([10]) 155 | expect(tree.root.children[2].values).toEqual([13, 15]) 156 | expect(tree.root.children[3].values).toEqual([18]) 157 | 158 | tree.insert(1) 159 | 160 | // 12 12 161 | // 5, 12, 17 5, 17 3,5, 17 162 | // 2 3 4 10 13,15 18 -> 2 3 4 10 13,15 18 --> 1,2 4 10 13,15 18 163 | expect(tree.root.values).toEqual([12]) 164 | expect(tree.root.children[0].values).toEqual([3, 5]) 165 | expect(tree.root.children[1].values).toEqual([17]) 166 | 167 | expect(tree.root.children[0].children[0].values).toEqual([1, 2]) 168 | expect(tree.root.children[0].children[1].values).toEqual([4]) 169 | expect(tree.root.children[0].children[2].values).toEqual([10]) 170 | 171 | expect(tree.root.children[1].children[0].values).toEqual([13, 15]) 172 | expect(tree.root.children[1].children[1].values).toEqual([18]) 173 | }) 174 | }) 175 | 176 | describe('deletion', () => { 177 | it('remove()', () => { 178 | tree.insert(10) 179 | tree.insert(5) 180 | tree.insert(2) 181 | 182 | // 2, 5, 10 183 | 184 | if (tree.root === null) throw new Error() 185 | tree.insert(18) 186 | 187 | // 2, 5, 10 5 5 188 | // -> 2 10 -> 2 10, 18 189 | 190 | if (!tree.root.children) throw new Error() 191 | tree.insert(17) 192 | // 5 193 | // 2 10, 17, 18 194 | 195 | tree.insert(3) 196 | tree.insert(4) 197 | 198 | // 5 199 | // 2, 3, 4 10, 17, 18 200 | 201 | tree.insert(15) 202 | 203 | // 5 5, 17 204 | // 2, 3, 4 10, 17, 18 --> split bc [10, 17, 18] is full --> 2, 3, 4 10, 18 205 | 206 | // then insert 15 207 | 208 | // 5, 17 209 | // 2, 3, 4 10,15 18 210 | 211 | tree.insert(12) 212 | 213 | // 5, 17 214 | // 2, 3, 4 10,12,15 18 215 | 216 | // 5, 17 217 | // 2, 3, 4 10,12,15 18 218 | 219 | tree.insert(13) 220 | 221 | // 5, 17 5, 12, 17 222 | // 2, 3, 4 10,12,15 18 -> 2, 3, 4 10, 13,15 18 223 | 224 | tree.insert(1) 225 | 226 | // 12 12 227 | // 5, 12, 17 5, 17 3,5, 17 228 | // 2 3 4 10 13,15 18 -> 2 3 4 10 13,15 18 --> 1,2 4 10 13,15 18 229 | 230 | tree.insert(6) 231 | tree.insert(7) 232 | 233 | expect(tree.root.children[0].children[2].values).toEqual([6, 7, 10]) 234 | // 12 235 | // 3, 5 17 236 | // 1,2 4 6,7,10 13,15 18 237 | 238 | tree.remove(7) 239 | 240 | // 12 241 | // 3--5 17 242 | // 1,2 4 6,10 13,15 18 243 | 244 | expect(tree.root.children[0].children[2].values).toEqual([6, 10]) 245 | }) 246 | }) 247 | }) 248 | -------------------------------------------------------------------------------- /test/data-structures/trees/binary-search-tree.test.ts: -------------------------------------------------------------------------------- 1 | import BinarySearchTree from '../../../src/data-structures/trees/binary-search-tree' 2 | import TreeNode from '../../../src/data-structures/trees/binary-search-tree/tree-node' 3 | 4 | describe('Binary Search Tree', () => { 5 | let tree: BinarySearchTree 6 | 7 | beforeEach(() => { 8 | tree = new BinarySearchTree() 9 | }) 10 | 11 | describe('Inspection', () => { 12 | it('size()', () => { 13 | expect(tree.size()).toBe(0) 14 | }) 15 | 16 | it('isEmpty()', () => { 17 | expect(tree.isEmpty()).toBe(true) 18 | tree.insert(8) 19 | expect(tree.isEmpty()).toBe(false) 20 | }) 21 | 22 | it('height()', () => { 23 | // Tree should look like: 24 | // 10 25 | // 5 15 26 | // 2 12 21 27 | // 1 28 | 29 | // No tree 30 | expect(tree.height()).toBe(0) 31 | 32 | // Layer One 33 | tree.insert(10) 34 | expect(tree.height()).toBe(1) 35 | 36 | // Layer Two 37 | tree.insert(5) 38 | expect(tree.height()).toBe(2) 39 | tree.insert(15) 40 | expect(tree.height()).toBe(2) 41 | 42 | // Layer Three 43 | tree.insert(2) 44 | expect(tree.height()).toBe(3) 45 | tree.insert(12) 46 | expect(tree.height()).toBe(3) 47 | tree.insert(21) 48 | expect(tree.height()).toBe(3) 49 | 50 | // Layer 4 51 | tree.insert(1) 52 | expect(tree.height()).toBe(4) 53 | }) 54 | }) 55 | 56 | describe('Searching', () => { 57 | const treeB = new BinarySearchTree() 58 | 59 | const a = new TreeNode(5, null) 60 | const b = new TreeNode(4, a) 61 | const c = new TreeNode(3, b) 62 | const d = new TreeNode(2, c) 63 | const e = new TreeNode(1, d) 64 | 65 | const f = new TreeNode(6, a) 66 | const g = new TreeNode(7, f) 67 | const h = new TreeNode(8, g) 68 | 69 | a.left = b 70 | b.left = c 71 | c.left = d 72 | d.left = e 73 | 74 | a.right = f 75 | f.right = g 76 | g.right = h 77 | 78 | treeB.root = a 79 | 80 | it('find()', () => { 81 | expect(treeB.find(5)).toBe(a) 82 | expect(treeB.find(4)).toBe(b) 83 | expect(treeB.find(3)).toBe(c) 84 | expect(treeB.find(2)).toBe(d) 85 | expect(treeB.find(1)).toBe(e) 86 | expect(treeB.find(6)).toBe(f) 87 | expect(treeB.find(7)).toBe(g) 88 | expect(treeB.find(8)).toBe(h) 89 | }) 90 | 91 | it('findMin()', () => { 92 | expect(treeB.findMin()).toBe(e) 93 | }) 94 | 95 | it('findMax()', () => { 96 | expect(treeB.findMax()).toBe(h) 97 | }) 98 | 99 | it('findSucessor()', () => { 100 | expect(treeB.findSucessor(a)).toBe(f) 101 | expect(treeB.findSucessor(e)).toBe(d) 102 | expect(treeB.findSucessor(f)).toBe(g) 103 | 104 | const treeC = new BinarySearchTree() 105 | 106 | const m = new TreeNode(5, null) 107 | const n = new TreeNode(3, m) 108 | const o = new TreeNode(2, n) 109 | const p = new TreeNode(1, o) 110 | const q = new TreeNode(4, n) 111 | 112 | m.left = n 113 | n.left = o 114 | o.left = p 115 | 116 | n.right = q 117 | 118 | treeC.root = m 119 | 120 | expect(treeC.findSucessor(q)).toBe(m) 121 | }) 122 | 123 | it('findPredecessor()', () => { 124 | expect(treeB.findPredecessor(a)).toBe(b) 125 | expect(treeB.findPredecessor(e)).toBe(null) 126 | expect(treeB.findPredecessor(f)).toBe(a) 127 | }) 128 | }) 129 | 130 | describe('Insertion/Deletion', () => { 131 | it('insert()', () => { 132 | tree.insert(5) 133 | expect(tree.size()).toBe(1) 134 | 135 | tree.insert(3) 136 | expect(tree.size()).toBe(2) 137 | 138 | tree.insert(2) 139 | expect(tree.size()).toBe(3) 140 | 141 | tree.insert(6) 142 | expect(tree.size()).toBe(4) 143 | 144 | tree.insert(9) 145 | expect(tree.size()).toBe(5) 146 | 147 | tree.insert(4) 148 | expect(tree.size()).toBe(6) 149 | }) 150 | 151 | it('remove()', () => { 152 | tree.remove(tree.insert(3)) 153 | expect(tree.size()).toBe(0) 154 | expect(tree.find(3)).toBe(null) 155 | 156 | tree.insert(5) 157 | tree.insert(8) 158 | tree.remove(tree.insert(3)) 159 | expect(tree.size()).toBe(2) 160 | expect(tree.find(3)).toBe(null) 161 | }) 162 | 163 | it('remove() node with left child', () => { 164 | const a = new TreeNode(5, null) 165 | const b = new TreeNode(3, null) 166 | 167 | a.left = b 168 | tree.root = a 169 | 170 | tree.remove(a) 171 | 172 | expect(tree.find(5)).toBe(null) 173 | }) 174 | 175 | it('remove() node with two children', () => { 176 | const a = new TreeNode(5, null) 177 | const b = new TreeNode(3, null) 178 | const c = new TreeNode(8, null) 179 | 180 | a.left = b 181 | a.right = c 182 | 183 | tree.root = a 184 | 185 | tree.remove(a) 186 | 187 | expect(tree.find(5)).toBe(null) 188 | }) 189 | 190 | it('remove() node with two children and successor is not immediate right child', () => { 191 | const a = new TreeNode(5, null) 192 | const b = new TreeNode(3, null) 193 | const c = new TreeNode(8, null) 194 | const d = new TreeNode(7, null) 195 | 196 | a.left = b 197 | a.right = c 198 | 199 | c.left = d 200 | 201 | tree.root = a 202 | 203 | tree.remove(a) 204 | 205 | expect(tree.find(5)).toBe(null) 206 | }) 207 | }) 208 | 209 | describe('Traversals', () => { 210 | const treeB = new BinarySearchTree() 211 | 212 | const a = new TreeNode(5, null) 213 | const b = new TreeNode(4, a) 214 | const c = new TreeNode(3, b) 215 | const d = new TreeNode(2, c) 216 | const e = new TreeNode(1, d) 217 | 218 | const f = new TreeNode(6, a) 219 | const g = new TreeNode(7, f) 220 | const h = new TreeNode(8, g) 221 | 222 | a.left = b 223 | b.left = c 224 | c.left = d 225 | d.left = e 226 | 227 | a.right = f 228 | f.right = g 229 | g.right = h 230 | 231 | treeB.root = a 232 | 233 | it('inorder()', () => { 234 | for (const _ of tree.inorderTraversal()) { 235 | throw new Error() 236 | } 237 | 238 | const inorderNumbers = [1, 2, 3, 4, 5, 6, 7, 8] 239 | let i = 0 240 | 241 | for (const n of treeB.inorderTraversal()) { 242 | expect(n).toBe(inorderNumbers[i]) 243 | i += 1 244 | } 245 | }) 246 | 247 | it('preorder()', () => { 248 | for (const _ of tree.preorderTraversal()) { 249 | throw new Error() 250 | } 251 | 252 | // Tree should look like: 253 | // 10 254 | // 5 15 255 | // 2 12 21 256 | // 1 257 | 258 | // Layer One 259 | tree.insert(10) 260 | 261 | // Layer Two 262 | tree.insert(5) 263 | tree.insert(15) 264 | 265 | // Layer Three 266 | tree.insert(2) 267 | tree.insert(12) 268 | tree.insert(21) 269 | 270 | // Layer 4 271 | tree.insert(1) 272 | 273 | const preorderNumbers = [10, 5, 2, 1, 15, 12, 21] 274 | let i = 0 275 | 276 | for (const n of tree.preorderTraversal()) { 277 | expect(n).toBe(preorderNumbers[i]) 278 | i += 1 279 | } 280 | }) 281 | 282 | it('postorder()', () => { 283 | for (const _ of tree.postorderTraversal()) { 284 | throw new Error() 285 | } 286 | 287 | // Tree should look like: 288 | // 10 289 | // 5 15 290 | // 2 12 21 291 | // 1 292 | 293 | // Layer One 294 | tree.insert(10) 295 | 296 | // Layer Two 297 | tree.insert(5) 298 | tree.insert(15) 299 | 300 | // Layer Three 301 | tree.insert(2) 302 | tree.insert(12) 303 | tree.insert(21) 304 | 305 | // Layer 4 306 | tree.insert(1) 307 | 308 | const postorderNumbers = [1, 2, 5, 12, 21, 15, 10] 309 | let i = 0 310 | 311 | for (const n of tree.postorderTraversal()) { 312 | expect(n).toBe(postorderNumbers[i]) 313 | i += 1 314 | } 315 | }) 316 | }) 317 | }) 318 | -------------------------------------------------------------------------------- /test/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultEquals } from '../src/data-structures/utils' 2 | 3 | describe('utils', () => { 4 | it('defaultEquals', () => { 5 | expect(defaultEquals(1, 8)).toBe(false) 6 | expect(defaultEquals(8, 8)).toBe(true) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true /* Generates corresponding '.d.ts' file. */, 14 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 15 | "sourceMap": true /* Generates corresponding '.map' file. */, 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "lib" /* Redirect output structure to the directory. */, 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | "downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */, 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true /* Skip type checking of declaration files. */, 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | }, 69 | "include": ["src"] 70 | } 71 | --------------------------------------------------------------------------------