├── .gitignore ├── README.md └── libraries ├── graphs ├── cyclic │ └── cyclic_graph.js └── trees │ ├── avl_tree.js │ └── binary_tree.js ├── helpers ├── arrays │ └── arrays_functions.js ├── counting │ └── counting.js └── randoms │ ├── random_boolean.js │ └── random_number.js └── searching ├── binary_search.js └── binary_search.md /.gitignore: -------------------------------------------------------------------------------- 1 | index.js 2 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # __Problem Solving JavaScript Library__ 2 | # Why? 3 | When I started learning JavaScript I was motivated to start solving problems with it, but it wasn't so easy as I had experience with C++ and I was struggling in each line compared to writing 100s of lines of C++ in a text file. 4 | 5 | After almost 7 months of working with JavaScript, I decided to try solving problems using it but now I won't struggle. 6 | 7 |
8 | 9 | # Why solve problems with JavaScript? 10 | I came across a usecase at work where I wanted to implement a depth-first traversal on a graph and I was shocked that I didn't know what are the conventions of creating a graph in JavaScript. 11 | 12 | As I'm learning JavaScript and I work with it on a daily basis, I decided to take my knowledge to the next levels and challenge myself again to solve problems with it as I believe that solving problems gives new use cases that let you be confident with the language you're using. 13 | 14 |
15 | 16 | # Repository Role 17 | This repository's main purpose is to provide easy access to common algorithms that don't need to be implemented everytime, it is not made to **SKIP** the algorithm it is made to have the algorithm as a reference, if you need to use it and you're not sure you can implement it again without help then you should consider trying to build it yourself before looking up and using the algorithm provided. 18 | 19 | Also writing the algorithm and challenging it in different problems can get corner cases that weren't covered in previous problems that could open improvements to the reference algorithms. 20 | 21 |
22 | 23 | # How to contribute 24 | Contributing is easy, create a branch and create a PR: 25 | 26 | ## Code structure 27 | ``` 28 | topic (In plural) or Verb (with ing)-> subtopic -> file 29 | ``` 30 | The file should exports the **needed** functions not all functions, we are not exporting everything we want to use the algorithm as end users. 31 | 32 | ## Naming conventions 33 | Use snake_case in files naming, and provide description for each function usage and importance in a readme file with every leaf file (if not there, create one) [Example](libraries/searching/binary_search.md). 34 | 35 | ### Titles 36 | Pr title (and commit message) in following format: 37 | ``` 38 | feat(topic): message 39 | ``` 40 | or 41 | ``` 42 | fix(topic): message 43 | ``` 44 | or 45 | ``` 46 | doc(topic/subtopic): message 47 | ``` 48 | ### Steps 49 | To make a pull request on a GitHub repository that you are not a collaborator in, you will need to follow these steps: 50 | 51 | 1. Fork the repository: Click on the "Fork" button located at the top right corner of the repository's page. This will create a copy of the repository in your own GitHub account. 52 | 53 | 2. Clone the forked repository: Go to your forked repository and click on the "Code" button. Copy the HTTPS or SSH link and use it to clone the repository to your local machine. 54 | 55 | 3. Create a new branch: Switch to a new branch on your local machine using git checkout -b new-branch-name. 56 | 57 | 4. Make changes and commit them: Make the changes that you want to contribute to the project and commit them using git add and git commit. 58 | 59 | 5. Push the changes to your forked repository: Push the changes to the new branch you just created using git push origin new-branch-name. 60 | 61 | 6. Create a pull request: Go to the original repository and click on the "Pull requests" tab. Click on the "New pull request" button and select your forked repository and the new branch you created. Fill in the details of your pull request, including a title and description of your changes. 62 | 63 | 7. Submit the pull request: Click on the "Create pull request" button to submit your pull request to the repository. The repository owner or collaborators can review your changes and decide whether to merge them into the main codebase. 64 | 65 | 66 |
67 | 68 | # Why not TypeScript? 69 | No big reason for that, might convert existing algorithms to typescript soon, might not. 70 | 71 | 72 | Pros | Cons 73 | :---: | :---: 74 | Better interface for user | User will not look at the algorithm to understand types and returns 75 | 76 | Still adding up to pros and cons, if pros gave strong reasons, let's move to TypeScript (I personally love it). 77 | 78 | 79 | -------------------------------------------------------------------------------- /libraries/graphs/cyclic/cyclic_graph.js: -------------------------------------------------------------------------------- 1 | const hasCycle = function(head) { 2 | if (!head || !head.next) return null 3 | let slowPtr = head, fastPtr = head 4 | while(fastPtr && fastPtr.next){ 5 | slowPtr = slowPtr.next 6 | fastPtr = fastPtr.next.next 7 | if (fastPtr == slowPtr){ 8 | return fastPtr 9 | } 10 | } 11 | return false 12 | }; 13 | 14 | const detectCycleStart = function(head){ 15 | let intersection = hasCycle(head) 16 | if(!intersection) return null 17 | let slowPtr = head 18 | while(slowPtr !== intersection){ 19 | slowPtr = slowPtr.next 20 | intersection = intersection.next 21 | } 22 | return intersection 23 | } 24 | 25 | module.exports = { 26 | hasCycle, 27 | detectCycleStart 28 | } -------------------------------------------------------------------------------- /libraries/graphs/trees/avl_tree.js: -------------------------------------------------------------------------------- 1 | // Reference: https://learnersbucket.com/tutorials/data-structures/avl-tree-in-javascript/ 2 | 3 | // Create node 4 | const AVLTreeNode = function (item) { 5 | this.item = item; 6 | this.height = 1; 7 | this.left = null; 8 | this.right = null; 9 | } 10 | 11 | //AVL Tree 12 | const AVLTree = function () { 13 | let root = null; 14 | 15 | //return height of the node 16 | this.height = (N) => { 17 | if (N === null) { 18 | return 0; 19 | } 20 | 21 | return N.height; 22 | } 23 | 24 | //right rotate 25 | this.rightRotate = (y) => { 26 | let x = y.left; 27 | let T2 = x.right; 28 | x.right = y; 29 | y.left = T2; 30 | y.height = Math.max(this.height(y.left), this.height(y.right)) + 1; 31 | x.height = Math.max(this.height(x.left), this.height(x.right)) + 1; 32 | return x; 33 | } 34 | 35 | //left rotate 36 | this.leftRotate = (x) => { 37 | let y = x.right; 38 | let T2 = y.left; 39 | y.left = x; 40 | x.right = T2; 41 | x.height = Math.max(this.height(x.left), this.height(x.right)) + 1; 42 | y.height = Math.max(this.height(y.left), this.height(y.right)) + 1; 43 | return y; 44 | } 45 | 46 | // get balance factor of a node 47 | this.getBalanceFactor = (N) => { 48 | if (N == null) { 49 | return 0; 50 | } 51 | 52 | return this.height(N.left) - this.height(N.right); 53 | } 54 | 55 | 56 | // helper function to insert a node 57 | const insertNodeHelper = (node, item) => { 58 | 59 | // find the position and insert the node 60 | if (node === null) { 61 | return (new AVLTreeNode(item)); 62 | } 63 | 64 | if (item < node.item) { 65 | node.left = insertNodeHelper(node.left, item); 66 | } else if (item > node.item) { 67 | node.right = insertNodeHelper(node.right, item); 68 | } else { 69 | return node; 70 | } 71 | 72 | // update the balance factor of each node 73 | // and, balance the tree 74 | node.height = 1 + Math.max(this.height(node.left), this.height(node.right)); 75 | 76 | let balanceFactor = this.getBalanceFactor(node); 77 | 78 | if (balanceFactor > 1) { 79 | if (item < node.left.item) { 80 | return this.rightRotate(node); 81 | } else if (item > node.left.item) { 82 | node.left = this.leftRotate(node.left); 83 | return this.rightRotate(node); 84 | } 85 | } 86 | 87 | if (balanceFactor < -1) { 88 | if (item > node.right.item) { 89 | return this.leftRotate(node); 90 | } else if (item < node.right.item) { 91 | node.right = this.rightRotate(node.right); 92 | return this.leftRotate(node); 93 | } 94 | } 95 | 96 | return node; 97 | } 98 | 99 | // insert a node 100 | this.insertNode = (item) => { 101 | // console.log(root); 102 | root = insertNodeHelper(root, item); 103 | } 104 | 105 | //get node with minimum value 106 | this.nodeWithMimumValue = (node) => { 107 | let current = node; 108 | while (current.left !== null) { 109 | current = current.left; 110 | } 111 | return current; 112 | } 113 | 114 | // delete helper 115 | const deleteNodeHelper = (root, item) => { 116 | 117 | // find the node to be deleted and remove it 118 | if (root == null) { 119 | return root; 120 | } 121 | if (item < root.item) { 122 | root.left = deleteNodeHelper(root.left, item); 123 | } else if (item > root.item) { 124 | root.right = deleteNodeHelper(root.right, item); 125 | } else { 126 | if ((root.left === null) || (root.right === null)) { 127 | let temp = null; 128 | if (temp == root.left) { 129 | temp = root.right; 130 | } else { 131 | temp = root.left; 132 | } 133 | 134 | if (temp == null) { 135 | temp = root; 136 | root = null; 137 | } else { 138 | root = temp; 139 | } 140 | } else { 141 | let temp = this.nodeWithMimumValue(root.right); 142 | root.item = temp.item; 143 | root.right = deleteNodeHelper(root.right, temp.item); 144 | } 145 | } 146 | if (root == null) { 147 | return root; 148 | } 149 | 150 | // Update the balance factor of each node and balance the tree 151 | root.height = Math.max(this.height(root.left), this.height(root.right)) + 1; 152 | 153 | let balanceFactor = this.getBalanceFactor(root); 154 | if (balanceFactor > 1) { 155 | if (this.getBalanceFactor(root.left) >= 0) { 156 | return this.rightRotate(root); 157 | } else { 158 | root.left = this.leftRotate(root.left); 159 | return this.rightRotate(root); 160 | } 161 | } 162 | if (balanceFactor < -1) { 163 | if (this.getBalanceFactor(root.right) <= 0) { 164 | return this.leftRotate(root); 165 | } else { 166 | root.right = this.rightRotate(root.right); 167 | return this.leftRotate(root); 168 | } 169 | } 170 | return root; 171 | } 172 | 173 | //delete a node 174 | this.deleteNode = (item) => { 175 | root = deleteNodeHelper(root, item); 176 | } 177 | 178 | // print the tree in pre - order 179 | this.preOrder = () => { 180 | preOrderHelper(root); 181 | } 182 | 183 | const preOrderHelper = (node) => { 184 | if (node) { 185 | console.log(node.item); 186 | preOrderHelper(node.left); 187 | preOrderHelper(node.right); 188 | } 189 | } 190 | 191 | this.getRoot = () => { 192 | return root 193 | } 194 | 195 | } 196 | 197 | module.exports = { 198 | AVLTree, 199 | AVLTreeNode 200 | } -------------------------------------------------------------------------------- /libraries/graphs/trees/binary_tree.js: -------------------------------------------------------------------------------- 1 | 2 | // This function changes each node in the tree according to the function sent 3 | const mutateNodes = function(node, mutationFunction) { 4 | if (!node) return 5 | mutationFunction(node) 6 | if (node.left) node.left = makeNodesWithVal(node.left, mutationFunction) 7 | if (node.right) node.right = makeNodesWithVal(node.right, mutationFunction) 8 | return node 9 | } 10 | 11 | 12 | const maxDepth = function(node) { 13 | if (root === null) { 14 | return 0; 15 | } 16 | return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; 17 | } 18 | 19 | module.exports = { 20 | mutateNodes, 21 | maxDepth 22 | } -------------------------------------------------------------------------------- /libraries/helpers/arrays/arrays_functions.js: -------------------------------------------------------------------------------- 1 | 2 | const copyArray = function (array) { 3 | return [...array] 4 | } 5 | 6 | const replacePartOfArrayWithAnotherArray = function ({ array, newArray, firstIndexOfNewArray, lastIndexOfNewArray, firstIndexOfOldArray, lastIndexOfOldArray }) { 7 | // Last index is not included 8 | if (!firstIndexOfNewArray) firstIndexOfNewArray = 0 9 | if (!lastIndexOfNewArray) lastIndexOfNewArray = newArray.length 10 | 11 | // Create the two halves of the array surrounding the updated array 12 | const firstPart = array.slice(0, firstIndexOfOldArray) 13 | const lastPart = array.slice(lastIndexOfOldArray + 1, array.length) 14 | 15 | // Concatenate the three arrays together 16 | const tempArray = [].concat(firstPart, newArray.slice(firstIndexOfNewArray, lastIndexOfNewArray), lastPart) 17 | 18 | return tempArray 19 | } 20 | 21 | /* 22 | @param arrays: array of arrays that will be concatenated 23 | */ 24 | const concatArrays = function (arrays) { 25 | 26 | // [].concat.apply([], [array1, array2, ...]) 27 | return [].concat.apply([], arrays) 28 | } 29 | 30 | 31 | module.exports = { 32 | copyArray, 33 | replacePartOfArrayWithAnotherArray, 34 | concatArrays, 35 | } -------------------------------------------------------------------------------- /libraries/helpers/counting/counting.js: -------------------------------------------------------------------------------- 1 | 2 | // Count from A to B 3 | const countFromAToB = function (a, b) { 4 | return (n - m + 1) * (n + m) / 2 5 | } 6 | 7 | // Count from 1 to N 8 | const countFromOneToN = function (n) { 9 | return countFromAToB(1,n) 10 | } 11 | 12 | module.exports = { 13 | countFromAToB, 14 | countFromOneToN 15 | } -------------------------------------------------------------------------------- /libraries/helpers/randoms/random_boolean.js: -------------------------------------------------------------------------------- 1 | 2 | const randomBoolean = function () { 3 | (Boolean) (Math.random() < 0.5) 4 | } 5 | 6 | module.exports = { 7 | randomBoolean 8 | } -------------------------------------------------------------------------------- /libraries/helpers/randoms/random_number.js: -------------------------------------------------------------------------------- 1 | function getRandomArbitrary(min, max) { 2 | return Math.random() * (max - min) + min; 3 | } 4 | 5 | function getRandomInt(min, max) { 6 | min = Math.ceil(min); 7 | max = Math.floor(max); 8 | return Math.floor(Math.random() * (max - min + 1)) + min; 9 | } 10 | 11 | module.exports = { 12 | getRandomArbitrary, 13 | getRandomInt, 14 | } -------------------------------------------------------------------------------- /libraries/searching/binary_search.js: -------------------------------------------------------------------------------- 1 | const calculateMid = function(left, right){ 2 | return left + Math.floor((right - left)/2) 3 | } 4 | 5 | const firstTrueBS = function(size, validationFunction, params){ 6 | let left = 0, right = size-1, mid = 0 7 | let soln = mid 8 | while(left <= right){ 9 | mid = calculateMid(left, right) 10 | const calculation = validationFunction(params, mid) 11 | if(calculation){ 12 | right = mid - 1 13 | soln = mid 14 | } else { 15 | left = mid + 1 16 | } 17 | } 18 | return soln 19 | } 20 | 21 | const LastTrueBS = function(size, validationFunction, params){ 22 | let left = 0, right = size-1, mid = 0 23 | let soln = mid 24 | while(left <= right){ 25 | mid = calculateMid(left, right) 26 | const calculation = validationFunction(params, mid) 27 | if(calculation){ 28 | right = mid - 1 29 | } else { 30 | soln = mid 31 | left = mid + 1 32 | } 33 | } 34 | return soln 35 | } 36 | 37 | module.exports = { 38 | firstTrueBS, 39 | LastTrueBS 40 | } -------------------------------------------------------------------------------- /libraries/searching/binary_search.md: -------------------------------------------------------------------------------- 1 | ## Implemented functions 2 | 3 | ### **First true** 4 | Assuming we have a problem that can be summed up to having many solutions (in integers) these solutions are as following: 5 | 6 | 0 | 1 | 2 | 3 | 4 | 5 | 6 7 | :---: | :---: | :---: | :---: | :---: | :---: | :---: 8 | False | False | True | True | True | True | True | True 9 | 10 | The minimum solution here is 2 since 2 or any larger number will provide a valid soltuion, these type of questions are very common as they are always searching for the minimum solution, this algorithm is called **first true** as it searches for first true in O(log(Size) * validation function complexity) 11 | 12 | In many cases the validation function complexity is O(N) which sums up to having the solution in O( N Log (Size)) for simplicity lets call it O(N Log(N)) which is good enough in many cases 13 | 14 | The biggest benefit of this algorithm is that validation functions in most cases are very simple to be implemented which makes the problem easy over all (validation function + first true function) 15 | 16 | Example: 17 | ``` 18 | /* 19 | Validation function parameters should be as following: 20 | params: Object that has whatever parameters the function needs 21 | k: the solution that is generated from FirstTrueBS function and is passed to validation function 22 | 23 | Node that: FirstTrueBS passes the params object to the validation function 24 | */ 25 | 26 | const calculateMid = function(left, right){ 27 | return left + Math.floor((right - left)/2) 28 | } 29 | 30 | const firstTrueBS = function(size, validationFunction, params){ 31 | let left = 0, right = size-1, mid = 0 32 | let soln = mid 33 | while(left <= right){ 34 | mid = calculateMid(left, right) 35 | const calculation = validationFunction(params, mid) 36 | if(calculation){ 37 | right = mid - 1 38 | soln = mid 39 | } else { 40 | left = mid + 1 41 | } 42 | } 43 | return soln 44 | } 45 | 46 | const validationFunction = function (params, k) { 47 | const { time, totalTrips } = params; 48 | let doneTrips = 0 49 | for (const tripTime of time) { 50 | doneTrips += Math.floor(k / tripTime) 51 | if (doneTrips >= totalTrips) return true 52 | } 53 | return doneTrips >= totalTrips 54 | } 55 | 56 | var minimumTime = function (time, totalTrips) { 57 | // F F F F S S S S 58 | // First true 59 | return firstTrueBS(10e16, validationFunction, { time, totalTrips }) 60 | }; 61 | ``` --------------------------------------------------------------------------------