├── .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 | ```
--------------------------------------------------------------------------------