├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README.zh-CN.md ├── README.zh-TW.md ├── assets └── big-o-graph.png ├── jest.config.js ├── package-lock.json ├── package.json └── src ├── algorithms ├── graph │ ├── articulation-points │ │ ├── README.md │ │ ├── __test__ │ │ │ └── articulationPoints.test.js │ │ └── articulationPoints.js │ ├── bellman-ford │ │ ├── README.md │ │ ├── __test__ │ │ │ └── bellmanFord.test.js │ │ └── bellmanFord.js │ ├── breadth-first-search │ │ ├── README.md │ │ ├── __test__ │ │ │ └── breadthFirstSearch.test.js │ │ └── breadthFirstSearch.js │ ├── bridges │ │ ├── README.md │ │ ├── __test__ │ │ │ └── graphBridges.test.js │ │ └── graphBridges.js │ ├── depth-first-search │ │ ├── README.md │ │ ├── __test__ │ │ │ └── depthFirstSearch.test.js │ │ └── depthFirstSearch.js │ ├── detect-cycle │ │ ├── README.md │ │ ├── __test__ │ │ │ ├── detectDirectedCycle.test.js │ │ │ ├── detectUndirectedCycle.test.js │ │ │ └── detectUndirectedCycleUsingDisjointSet.test.js │ │ ├── detectDirectedCycle.js │ │ ├── detectUndirectedCycle.js │ │ └── detectUndirectedCycleUsingDisjointSet.js │ ├── dijkstra │ │ ├── README.md │ │ ├── __test__ │ │ │ └── dijkstra.test.js │ │ └── dijkstra.js │ ├── eulerian-path │ │ ├── README.md │ │ ├── __test__ │ │ │ └── eulerianPath.test.js │ │ └── eulerianPath.js │ ├── hamiltonian-cycle │ │ ├── README.md │ │ ├── __test__ │ │ │ └── hamiltonianCycle.test.js │ │ └── hamiltonianCycle.js │ ├── kruskal │ │ ├── README.md │ │ ├── __test__ │ │ │ └── kruskal.test.js │ │ └── kruskal.js │ ├── prim │ │ ├── README.md │ │ ├── __test__ │ │ │ └── prim.test.js │ │ └── prim.js │ ├── strongly-connected-components │ │ ├── README.md │ │ ├── __test__ │ │ │ └── stronglyConnectedComponents.test.js │ │ └── stronglyConnectedComponents.js │ ├── topological-sorting │ │ ├── README.md │ │ ├── __test__ │ │ │ └── topologicalSort.test.js │ │ └── topologicalSort.js │ └── travelling-salesman │ │ ├── README.md │ │ ├── __test__ │ │ └── bfTravellingSalesman.test.js │ │ └── bfTravellingSalesman.js ├── math │ ├── euclidean-algorithm │ │ ├── README.md │ │ ├── __test__ │ │ │ └── euclieanAlgorithm.test.js │ │ └── euclideanAlgorithm.js │ ├── factorial │ │ ├── README.md │ │ ├── __test__ │ │ │ └── factorial.test.js │ │ └── factorial.js │ ├── fibonacci │ │ ├── README.md │ │ ├── __test__ │ │ │ └── fibonacci.test.js │ │ └── fibonacci.js │ ├── integer-partition │ │ ├── README.md │ │ ├── __test__ │ │ │ └── integerPartition.test.js │ │ └── integerPartition.js │ ├── least-common-multiple │ │ ├── README.md │ │ ├── __test__ │ │ │ └── leastCommonMultiple.test.js │ │ └── leastCommonMultiple.js │ └── primality-test │ │ ├── README.md │ │ ├── __test__ │ │ └── trialDivision.test.js │ │ └── trialDivision.js ├── search │ ├── binary-search │ │ ├── README.md │ │ ├── __test__ │ │ │ └── binarySearch.test.js │ │ └── binarySearch.js │ └── linear-search │ │ ├── README.md │ │ ├── __test__ │ │ └── linearSearch.test.js │ │ └── linearSearch.js ├── sets │ ├── cartesian-product │ │ ├── README.md │ │ ├── __test__ │ │ │ └── cartesianProduct.test.js │ │ └── cartesianProduct.js │ ├── combinations │ │ ├── README.md │ │ ├── __test__ │ │ │ ├── combineWithRepetitions.test.js │ │ │ └── combineWithoutRepetitions.test.js │ │ ├── combineWithRepetitions.js │ │ └── combineWithoutRepetitions.js │ ├── fisher-yates │ │ ├── README.md │ │ ├── __test__ │ │ │ └── fisherYates.test.js │ │ └── fisherYates.js │ ├── knapsack-problem │ │ ├── Knapsack.js │ │ ├── KnapsackItem.js │ │ ├── README.md │ │ └── __test__ │ │ │ ├── Knapsack.test.js │ │ │ └── KnapsackItem.test.js │ ├── longest-common-subsequnce │ │ ├── README.md │ │ ├── __test__ │ │ │ └── longestCommonSubsequnce.test.js │ │ └── longestCommonSubsequnce.js │ ├── longest-increasing-subsequence │ │ ├── README.md │ │ ├── __test__ │ │ │ └── dpLongestIncreasingSubsequence.test.js │ │ └── dpLongestIncreasingSubsequence.js │ ├── maximum-subarray │ │ ├── README.md │ │ ├── __test__ │ │ │ ├── bfMaximumSubarray.test.js │ │ │ └── dpMaximumSubarray.test.js │ │ ├── bfMaximumSubarray.js │ │ └── dpMaximumSubarray.js │ ├── permutations │ │ ├── README.md │ │ ├── __test__ │ │ │ ├── permutateWithRepetitions.test.js │ │ │ └── permutateWithoutRepetitions.test.js │ │ ├── permutateWithRepetitions.js │ │ └── permutateWithoutRepetitions.js │ ├── power-set │ │ ├── README.md │ │ ├── __test__ │ │ │ └── powerSet.test.js │ │ └── powerSet.js │ └── shortest-common-supersequence │ │ ├── README.md │ │ ├── __test__ │ │ └── shortestCommonSupersequence.test.js │ │ └── shortestCommonSupersequence.js ├── sorting │ ├── Sort.js │ ├── SortTester.js │ ├── __test__ │ │ └── Sort.test.js │ ├── bubble-sort │ │ ├── BubbleSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── BubbleSort.test.js │ ├── heap-sort │ │ ├── HeapSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── HeapSort.test.js │ ├── insertion-sort │ │ ├── InsertionSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── InsertionSort.test.js │ ├── merge-sort │ │ ├── MergeSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── MergeSort.test.js │ ├── quick-sort │ │ ├── QuickSort.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── QuickSort.test.js │ ├── selection-sort │ │ ├── README.md │ │ ├── SelectionSort.js │ │ └── __test__ │ │ │ └── SelectionSort.test.js │ └── shell-sort │ │ ├── README.md │ │ ├── ShellSort.js │ │ └── __test__ │ │ └── ShellSort.test.js ├── string │ ├── hamming-distance │ │ ├── README.md │ │ ├── __test__ │ │ │ └── hammingDistance.test.js │ │ └── hammingDistance.js │ ├── knuth-morris-pratt │ │ ├── README.md │ │ ├── __test__ │ │ │ └── knuthMorrisPratt.test.js │ │ └── knuthMorrisPratt.js │ ├── levenshtein-distance │ │ ├── README.md │ │ ├── __test__ │ │ │ └── levenshteinDistance.test.js │ │ └── levenshteinDistance.js │ ├── longest-common-substring │ │ ├── README.md │ │ ├── __test__ │ │ │ └── longestCommonSubstring.test.js │ │ └── longestCommonSubstring.js │ └── rabin-karp │ │ ├── README.md │ │ ├── __test__ │ │ └── rabinKarp.test.js │ │ └── rabinKarp.js ├── tree │ ├── breadth-first-search │ │ ├── README.md │ │ ├── __test__ │ │ │ └── breadthFirstSearch.test.js │ │ └── breadthFirstSearch.js │ └── depth-first-search │ │ ├── README.md │ │ ├── __test__ │ │ └── depthFirstSearch.test.js │ │ └── depthFirstSearch.js └── uncategorized │ ├── hanoi-tower │ ├── README.md │ ├── __test__ │ │ └── hanoiTower.test.js │ └── hanoiTower.js │ ├── knight-tour │ ├── README.md │ ├── __test__ │ │ └── knightTour.test.js │ └── knightTour.js │ └── n-queens │ ├── QueenPosition.js │ ├── README.md │ ├── __test__ │ ├── QueensPosition.test.js │ └── nQueens.test.js │ └── nQueens.js ├── data-structures ├── disjoint-set │ ├── DisjointSet.js │ ├── DisjointSetItem.js │ ├── README.md │ └── __test__ │ │ ├── DisjointSet.test.js │ │ └── DisjointSetItem.test.js ├── graph │ ├── Graph.js │ ├── GraphEdge.js │ ├── GraphVertex.js │ ├── README.md │ └── __test__ │ │ ├── Graph.test.js │ │ ├── GraphEdge.test.js │ │ └── GraphVertex.test.js ├── hash-table │ ├── HashTable.js │ ├── README.md │ └── __test__ │ │ └── HashTable.test.js ├── heap │ ├── MinHeap.js │ ├── README.md │ └── __test__ │ │ └── MinHeap.test.js ├── linked-list │ ├── LinkedList.js │ ├── LinkedListNode.js │ ├── README.md │ └── __test__ │ │ ├── LinkedList.test.js │ │ └── LinkedListNode.test.js ├── priority-queue │ ├── PriorityQueue.js │ ├── README.md │ └── __test__ │ │ └── PriorityQueue.test.js ├── queue │ ├── Queue.js │ ├── README.md │ └── __test__ │ │ └── Queue.test.js ├── stack │ ├── README.md │ ├── Stack.js │ └── __test__ │ │ └── Stack.test.js ├── tree │ ├── BinaryTreeNode.js │ ├── README.md │ ├── __test__ │ │ └── BinaryTreeNode.test.js │ ├── avl-tree │ │ ├── AvlTree.js │ │ ├── README.md │ │ └── __test__ │ │ │ └── AvlTRee.test.js │ └── binary-search-tree │ │ ├── BinarySearchTree.js │ │ ├── BinarySearchTreeNode.js │ │ ├── README.md │ │ └── __test__ │ │ ├── BinarySearchTree.test.js │ │ └── BinarySearchTreeNode.test.js └── trie │ ├── README.md │ ├── Trie.js │ ├── TrieNode.js │ └── __test__ │ ├── Trie.test.js │ └── TrieNode.test.js ├── playground ├── README.md ├── __test__ │ └── playground.test.js └── playground.js └── utils └── comparator ├── Comparator.js └── __test__ └── Comparator.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "airbnb", 4 | "plugins": ["jest"], 5 | "env": { 6 | "jest/globals": true 7 | }, 8 | "rules": { 9 | "no-bitwise": "off", 10 | "no-lonely-if": "off", 11 | "class-methods-use-this": "off", 12 | "arrow-body-style": "off", 13 | "no-loop-func": "off" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | coverage 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: node_js 4 | node_js: 5 | - node 6 | script: 7 | - npm run ci 8 | - npm run codecov 9 | notifications: 10 | email: false 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | - As much as possible, try to follow the existing format of markdown and code. 4 | - Don't forget to run `npm run lint` and `npm test` before submitting pull requests. 5 | - Make sure that **100%** of your code is covered by tests. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Oleksii Trekhleb 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 | -------------------------------------------------------------------------------- /assets/big-o-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopinav/javascript-algorithms/2e3860f357608327a3c11752d2057ed5e3261c87/assets/big-o-graph.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: false, 3 | collectCoverage: false, 4 | coverageDirectory: './coverage/', 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-algorithms-and-data-structures", 3 | "version": "0.0.3", 4 | "description": "Algorithms and data-structures implemented on JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "eslint ./src/*", 8 | "test": "jest", 9 | "ci": "npm run lint && npm run test -- --coverage", 10 | "codecov": "codecov" 11 | }, 12 | "pre-commit": [ 13 | "lint", 14 | "test" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/trekhleb/javascript-algorithms.git" 19 | }, 20 | "keywords": [ 21 | "computer-science", 22 | "cs", 23 | "algorithms", 24 | "data-structures", 25 | "javascript", 26 | "algorithm", 27 | "javascript-algorithms", 28 | "sorting-algorithms", 29 | "graph", 30 | "tree" 31 | ], 32 | "author": "Oleksii Trekhleb (https://www.linkedin.com/in/trekhleb/)", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/trekhleb/javascript-algorithms/issues" 36 | }, 37 | "homepage": "https://github.com/trekhleb/javascript-algorithms#readme", 38 | "devDependencies": { 39 | "@types/jest": "^22.2.3", 40 | "babel-cli": "^6.26.0", 41 | "babel-preset-env": "^1.7.0", 42 | "codecov": "^3.0.2", 43 | "eslint": "^4.19.1", 44 | "eslint-config-airbnb": "^16.1.0", 45 | "eslint-plugin-import": "^2.12.0", 46 | "eslint-plugin-jest": "^21.15.2", 47 | "eslint-plugin-jsx-a11y": "^6.0.3", 48 | "eslint-plugin-react": "^7.8.2", 49 | "jest": "^22.4.4", 50 | "pre-commit": "^1.2.2" 51 | }, 52 | "dependencies": {} 53 | } 54 | -------------------------------------------------------------------------------- /src/algorithms/graph/articulation-points/README.md: -------------------------------------------------------------------------------- 1 | # Articulation Points (or Cut Vertices) 2 | 3 | A vertex in an undirected connected graph is an articulation point 4 | (or cut vertex) if removing it (and edges through it) disconnects 5 | the graph. Articulation points represent vulnerabilities in a 6 | connected network – single points whose failure would split the 7 | network into 2 or more disconnected components. They are useful for 8 | designing reliable networks. 9 | 10 | For a disconnected undirected graph, an articulation point is a 11 | vertex removing which increases number of connected components. 12 | 13 | ![Articulation Points](https://www.geeksforgeeks.org/wp-content/uploads/ArticulationPoints.png) 14 | 15 | ![Articulation Points](https://www.geeksforgeeks.org/wp-content/uploads/ArticulationPoints1.png) 16 | 17 | ![Articulation Points](https://www.geeksforgeeks.org/wp-content/uploads/ArticulationPoints21.png) 18 | 19 | ## References 20 | 21 | - [GeeksForGeeks](https://www.geeksforgeeks.org/articulation-points-or-cut-vertices-in-a-graph/) 22 | - [YouTube](https://www.youtube.com/watch?v=2kREIkF9UAs&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 23 | -------------------------------------------------------------------------------- /src/algorithms/graph/bellman-ford/README.md: -------------------------------------------------------------------------------- 1 | # Bellman–Ford Algorithm 2 | 3 | The Bellman–Ford algorithm is an algorithm that computes shortest 4 | paths from a single source vertex to all of the other vertices 5 | in a weighted digraph. It is slower than Dijkstra's algorithm 6 | for the same problem, but more versatile, as it is capable of 7 | handling graphs in which some of the edge weights are negative 8 | numbers. 9 | 10 | ![Bellman-Ford](https://upload.wikimedia.org/wikipedia/commons/2/2e/Shortest_path_Dijkstra_vs_BellmanFord.gif) 11 | 12 | ## Complexity 13 | 14 | Worst-case performance `O(|V||E|)` 15 | Best-case performance `O(|E|)` 16 | Worst-case space complexity `O(|V|)` 17 | 18 | ## References 19 | 20 | - [Wikipedia](https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm) 21 | - [On YouTube by Michael Sambol](https://www.youtube.com/watch?v=obWXjtg0L64&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 22 | -------------------------------------------------------------------------------- /src/algorithms/graph/bellman-ford/bellmanFord.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {Graph} graph 3 | * @param {GraphVertex} startVertex 4 | * @return {{distances, previousVertices}} 5 | */ 6 | export default function bellmanFord(graph, startVertex) { 7 | const distances = {}; 8 | const previousVertices = {}; 9 | 10 | // Init all distances with infinity assuming that currently we can't reach 11 | // any of the vertices except start one. 12 | distances[startVertex.getKey()] = 0; 13 | graph.getAllVertices().forEach((vertex) => { 14 | previousVertices[vertex.getKey()] = null; 15 | if (vertex.getKey() !== startVertex.getKey()) { 16 | distances[vertex.getKey()] = Infinity; 17 | } 18 | }); 19 | 20 | // We need (|V| - 1) iterations. 21 | for (let iteration = 0; iteration < (graph.getAllVertices().length - 1); iteration += 1) { 22 | // During each iteration go through all vertices. 23 | Object.keys(distances).forEach((vertexKey) => { 24 | const vertex = graph.getVertexByKey(vertexKey); 25 | 26 | // Go through all vertex edges. 27 | graph.getNeighbors(vertex).forEach((neighbor) => { 28 | const edge = graph.findEdge(vertex, neighbor); 29 | // Find out if the distance to the neighbor is shorter in this iteration 30 | // then in previous one. 31 | const distanceToVertex = distances[vertex.getKey()]; 32 | const distanceToNeighbor = distanceToVertex + edge.weight; 33 | if (distanceToNeighbor < distances[neighbor.getKey()]) { 34 | distances[neighbor.getKey()] = distanceToNeighbor; 35 | previousVertices[neighbor.getKey()] = vertex; 36 | } 37 | }); 38 | }); 39 | } 40 | 41 | return { 42 | distances, 43 | previousVertices, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/algorithms/graph/breadth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Breadth-First Search (BFS) 2 | 3 | Breadth-first search (BFS) is an algorithm for traversing 4 | or searching tree or graph data structures. It starts at 5 | the tree root (or some arbitrary node of a graph, sometimes 6 | referred to as a 'search key') and explores the neighbor 7 | nodes first, before moving to the next level neighbors. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/5/5d/Breadth-First-Search-Algorithm.gif) 10 | 11 | ## References 12 | 13 | - [Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search) 14 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 15 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 16 | -------------------------------------------------------------------------------- /src/algorithms/graph/bridges/README.md: -------------------------------------------------------------------------------- 1 | # Bridges in Graph 2 | 3 | In graph theory, a **bridge**, **isthmus**, **cut-edge**, or **cut arc** is an edge 4 | of a graph whose deletion increases its number of connected components. Equivalently, 5 | an edge is a bridge if and only if it is not contained in any cycle. A graph is said 6 | to be bridgeless or isthmus-free if it contains no bridges. 7 | 8 | ![Bridges in graph](https://upload.wikimedia.org/wikipedia/commons/d/df/Graph_cut_edges.svg) 9 | 10 | A graph with 16 vertices and 6 bridges (highlighted in red) 11 | 12 | ![Bridgeless](https://upload.wikimedia.org/wikipedia/commons/b/bf/Undirected.svg) 13 | 14 | An undirected connected graph with no cut edges 15 | 16 | ![Bridges in graph](https://www.geeksforgeeks.org/wp-content/uploads/Bridge1.png) 17 | 18 | ![Bridges in graph](https://www.geeksforgeeks.org/wp-content/uploads/Bridge2.png) 19 | 20 | ![Bridges in graph](https://www.geeksforgeeks.org/wp-content/uploads/Bridge3.png) 21 | 22 | ## References 23 | 24 | - [GeeksForGeeks on YouTube](https://www.youtube.com/watch?v=thLQYBlz2DM&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 25 | - [Wikipedia](https://en.wikipedia.org/wiki/Bridge_%28graph_theory%29#Tarjan.27s_Bridge-finding_algorithm) 26 | - [GeeksForGeeks](https://www.geeksforgeeks.org/bridge-in-a-graph/) 27 | -------------------------------------------------------------------------------- /src/algorithms/graph/depth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Depth-First Search (DFS) 2 | 3 | Depth-first search (DFS) is an algorithm for traversing or 4 | searching tree or graph data structures. One starts at 5 | the root (selecting some arbitrary node as the root in 6 | the case of a graph) and explores as far as possible 7 | along each branch before backtracking. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/7/7f/Depth-First-Search.gif) 10 | 11 | ## References 12 | 13 | - [Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search) 14 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 15 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 16 | -------------------------------------------------------------------------------- /src/algorithms/graph/depth-first-search/depthFirstSearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Callbacks 3 | * 4 | * @property {function(vertices: Object): boolean} [allowTraversal] - 5 | * Determines whether DFS should traverse from the vertex to its neighbor 6 | * (along the edge). By default prohibits visiting the same vertex again. 7 | * 8 | * @property {function(vertices: Object)} [enterVertex] - Called when DFS enters the vertex. 9 | * 10 | * @property {function(vertices: Object)} [leaveVertex] - Called when DFS leaves the vertex. 11 | */ 12 | 13 | /** 14 | * @param {Callbacks} [callbacks] 15 | * @returns {Callbacks} 16 | */ 17 | function initCallbacks(callbacks = {}) { 18 | const initiatedCallback = callbacks; 19 | 20 | const stubCallback = () => {}; 21 | 22 | const allowTraversalCallback = ( 23 | () => { 24 | const seen = {}; 25 | return ({ nextVertex }) => { 26 | if (!seen[nextVertex.getKey()]) { 27 | seen[nextVertex.getKey()] = true; 28 | return true; 29 | } 30 | return false; 31 | }; 32 | } 33 | )(); 34 | 35 | initiatedCallback.allowTraversal = callbacks.allowTraversal || allowTraversalCallback; 36 | initiatedCallback.enterVertex = callbacks.enterVertex || stubCallback; 37 | initiatedCallback.leaveVertex = callbacks.leaveVertex || stubCallback; 38 | 39 | return initiatedCallback; 40 | } 41 | 42 | /** 43 | * @param {Graph} graph 44 | * @param {GraphVertex} currentVertex 45 | * @param {GraphVertex} previousVertex 46 | * @param {Callbacks} callbacks 47 | */ 48 | function depthFirstSearchRecursive(graph, currentVertex, previousVertex, callbacks) { 49 | callbacks.enterVertex({ currentVertex, previousVertex }); 50 | 51 | graph.getNeighbors(currentVertex).forEach((nextVertex) => { 52 | if (callbacks.allowTraversal({ previousVertex, currentVertex, nextVertex })) { 53 | depthFirstSearchRecursive(graph, nextVertex, currentVertex, callbacks); 54 | } 55 | }); 56 | 57 | callbacks.leaveVertex({ currentVertex, previousVertex }); 58 | } 59 | 60 | /** 61 | * @param {Graph} graph 62 | * @param {GraphVertex} startVertex 63 | * @param {Callbacks} [callbacks] 64 | */ 65 | export default function depthFirstSearch(graph, startVertex, callbacks) { 66 | const previousVertex = null; 67 | depthFirstSearchRecursive(graph, startVertex, previousVertex, initCallbacks(callbacks)); 68 | } 69 | -------------------------------------------------------------------------------- /src/algorithms/graph/detect-cycle/__test__/detectDirectedCycle.test.js: -------------------------------------------------------------------------------- 1 | import GraphVertex from '../../../../data-structures/graph/GraphVertex'; 2 | import GraphEdge from '../../../../data-structures/graph/GraphEdge'; 3 | import Graph from '../../../../data-structures/graph/Graph'; 4 | import detectDirectedCycle from '../detectDirectedCycle'; 5 | 6 | describe('detectDirectedCycle', () => { 7 | it('should detect directed cycle', () => { 8 | const vertexA = new GraphVertex('A'); 9 | const vertexB = new GraphVertex('B'); 10 | const vertexC = new GraphVertex('C'); 11 | const vertexD = new GraphVertex('D'); 12 | const vertexE = new GraphVertex('E'); 13 | const vertexF = new GraphVertex('F'); 14 | 15 | const edgeAB = new GraphEdge(vertexA, vertexB); 16 | const edgeBC = new GraphEdge(vertexB, vertexC); 17 | const edgeAC = new GraphEdge(vertexA, vertexC); 18 | const edgeDA = new GraphEdge(vertexD, vertexA); 19 | const edgeDE = new GraphEdge(vertexD, vertexE); 20 | const edgeEF = new GraphEdge(vertexE, vertexF); 21 | const edgeFD = new GraphEdge(vertexF, vertexD); 22 | 23 | const graph = new Graph(true); 24 | graph 25 | .addEdge(edgeAB) 26 | .addEdge(edgeBC) 27 | .addEdge(edgeAC) 28 | .addEdge(edgeDA) 29 | .addEdge(edgeDE) 30 | .addEdge(edgeEF); 31 | 32 | expect(detectDirectedCycle(graph)).toBeNull(); 33 | 34 | graph.addEdge(edgeFD); 35 | 36 | expect(detectDirectedCycle(graph)).toEqual({ 37 | D: vertexF, 38 | F: vertexE, 39 | E: vertexD, 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/algorithms/graph/detect-cycle/__test__/detectUndirectedCycle.test.js: -------------------------------------------------------------------------------- 1 | import GraphVertex from '../../../../data-structures/graph/GraphVertex'; 2 | import GraphEdge from '../../../../data-structures/graph/GraphEdge'; 3 | import Graph from '../../../../data-structures/graph/Graph'; 4 | import detectUndirectedCycle from '../detectUndirectedCycle'; 5 | 6 | describe('detectUndirectedCycle', () => { 7 | it('should detect undirected cycle', () => { 8 | const vertexA = new GraphVertex('A'); 9 | const vertexB = new GraphVertex('B'); 10 | const vertexC = new GraphVertex('C'); 11 | const vertexD = new GraphVertex('D'); 12 | const vertexE = new GraphVertex('E'); 13 | const vertexF = new GraphVertex('F'); 14 | 15 | const edgeAF = new GraphEdge(vertexA, vertexF); 16 | const edgeAB = new GraphEdge(vertexA, vertexB); 17 | const edgeBE = new GraphEdge(vertexB, vertexE); 18 | const edgeBC = new GraphEdge(vertexB, vertexC); 19 | const edgeCD = new GraphEdge(vertexC, vertexD); 20 | const edgeDE = new GraphEdge(vertexD, vertexE); 21 | 22 | const graph = new Graph(); 23 | graph 24 | .addEdge(edgeAF) 25 | .addEdge(edgeAB) 26 | .addEdge(edgeBE) 27 | .addEdge(edgeBC) 28 | .addEdge(edgeCD); 29 | 30 | expect(detectUndirectedCycle(graph)).toBeNull(); 31 | 32 | graph.addEdge(edgeDE); 33 | 34 | expect(detectUndirectedCycle(graph)).toEqual({ 35 | B: vertexC, 36 | C: vertexD, 37 | D: vertexE, 38 | E: vertexB, 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/algorithms/graph/detect-cycle/__test__/detectUndirectedCycleUsingDisjointSet.test.js: -------------------------------------------------------------------------------- 1 | import GraphVertex from '../../../../data-structures/graph/GraphVertex'; 2 | import GraphEdge from '../../../../data-structures/graph/GraphEdge'; 3 | import Graph from '../../../../data-structures/graph/Graph'; 4 | import detectUndirectedCycleUsingDisjointSet from '../detectUndirectedCycleUsingDisjointSet'; 5 | 6 | describe('detectUndirectedCycleUsingDisjointSet', () => { 7 | it('should detect undirected cycle', () => { 8 | const vertexA = new GraphVertex('A'); 9 | const vertexB = new GraphVertex('B'); 10 | const vertexC = new GraphVertex('C'); 11 | const vertexD = new GraphVertex('D'); 12 | const vertexE = new GraphVertex('E'); 13 | const vertexF = new GraphVertex('F'); 14 | 15 | const edgeAF = new GraphEdge(vertexA, vertexF); 16 | const edgeAB = new GraphEdge(vertexA, vertexB); 17 | const edgeBE = new GraphEdge(vertexB, vertexE); 18 | const edgeBC = new GraphEdge(vertexB, vertexC); 19 | const edgeCD = new GraphEdge(vertexC, vertexD); 20 | const edgeDE = new GraphEdge(vertexD, vertexE); 21 | 22 | const graph = new Graph(); 23 | graph 24 | .addEdge(edgeAF) 25 | .addEdge(edgeAB) 26 | .addEdge(edgeBE) 27 | .addEdge(edgeBC) 28 | .addEdge(edgeCD); 29 | 30 | expect(detectUndirectedCycleUsingDisjointSet(graph)).toBeFalsy(); 31 | 32 | graph.addEdge(edgeDE); 33 | 34 | expect(detectUndirectedCycleUsingDisjointSet(graph)).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/algorithms/graph/detect-cycle/detectUndirectedCycle.js: -------------------------------------------------------------------------------- 1 | import depthFirstSearch from '../depth-first-search/depthFirstSearch'; 2 | 3 | /** 4 | * Detect cycle in undirected graph using Depth First Search. 5 | * 6 | * @param {Graph} graph 7 | */ 8 | export default function detectUndirectedCycle(graph) { 9 | let cycle = null; 10 | 11 | // List of vertices that we have visited. 12 | const visitedVertices = {}; 13 | 14 | // List of parents vertices for every visited vertex. 15 | const parents = {}; 16 | 17 | // Callbacks for DFS traversing. 18 | const callbacks = { 19 | allowTraversal: ({ currentVertex, nextVertex }) => { 20 | // Don't allow further traversal in case if cycle has been detected. 21 | if (cycle) { 22 | return false; 23 | } 24 | 25 | // Don't allow traversal from child back to its parent. 26 | const currentVertexParent = parents[currentVertex.getKey()]; 27 | const currentVertexParentKey = currentVertexParent ? currentVertexParent.getKey() : null; 28 | 29 | return currentVertexParentKey !== nextVertex.getKey(); 30 | }, 31 | enterVertex: ({ currentVertex, previousVertex }) => { 32 | if (visitedVertices[currentVertex.getKey()]) { 33 | // Compile cycle path based on parents of previous vertices. 34 | cycle = {}; 35 | 36 | let currentCycleVertex = currentVertex; 37 | let previousCycleVertex = previousVertex; 38 | 39 | while (previousCycleVertex.getKey() !== currentVertex.getKey()) { 40 | cycle[currentCycleVertex.getKey()] = previousCycleVertex; 41 | currentCycleVertex = previousCycleVertex; 42 | previousCycleVertex = parents[previousCycleVertex.getKey()]; 43 | } 44 | 45 | cycle[currentCycleVertex.getKey()] = previousCycleVertex; 46 | } else { 47 | // Add next vertex to visited set. 48 | visitedVertices[currentVertex.getKey()] = currentVertex; 49 | parents[currentVertex.getKey()] = previousVertex; 50 | } 51 | }, 52 | }; 53 | 54 | // Start DFS traversing. 55 | const startVertex = graph.getAllVertices()[0]; 56 | depthFirstSearch(graph, startVertex, callbacks); 57 | 58 | return cycle; 59 | } 60 | -------------------------------------------------------------------------------- /src/algorithms/graph/detect-cycle/detectUndirectedCycleUsingDisjointSet.js: -------------------------------------------------------------------------------- 1 | import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet'; 2 | 3 | /** 4 | * Detect cycle in undirected graph using disjoint sets. 5 | * 6 | * @param {Graph} graph 7 | */ 8 | export default function detectUndirectedCycleUsingDisjointSet(graph) { 9 | // Create initial singleton disjoint sets for each graph vertex. 10 | /** @param {GraphVertex} graphVertex */ 11 | const keyExtractor = graphVertex => graphVertex.getKey(); 12 | const disjointSet = new DisjointSet(keyExtractor); 13 | graph.getAllVertices().forEach(graphVertex => disjointSet.makeSet(graphVertex)); 14 | 15 | // Go trough all graph edges one by one and check if edge vertices are from the 16 | // different sets. In this case joint those sets together. Do this until you find 17 | // an edge where to edge vertices are already in one set. This means that current 18 | // edge will create a cycle. 19 | let cycleFound = false; 20 | /** @param {GraphEdge} graphEdge */ 21 | graph.getAllEdges().forEach((graphEdge) => { 22 | if (disjointSet.inSameSet(graphEdge.startVertex, graphEdge.endVertex)) { 23 | // Cycle found. 24 | cycleFound = true; 25 | } else { 26 | disjointSet.union(graphEdge.startVertex, graphEdge.endVertex); 27 | } 28 | }); 29 | 30 | return cycleFound; 31 | } 32 | -------------------------------------------------------------------------------- /src/algorithms/graph/dijkstra/README.md: -------------------------------------------------------------------------------- 1 | # Dijkstra's Algorithm 2 | 3 | Dijkstra's algorithm is an algorithm for finding the shortest 4 | paths between nodes in a graph, which may represent, for example, 5 | road networks. 6 | 7 | The algorithm exists in many variants; Dijkstra's original variant 8 | found the shortest path between two nodes, but a more common 9 | variant fixes a single node as the "source" node and finds 10 | shortest paths from the source to all other nodes in the graph, 11 | producing a shortest-path tree. 12 | 13 | ![Dijkstra](https://upload.wikimedia.org/wikipedia/commons/5/57/Dijkstra_Animation.gif) 14 | 15 | Dijkstra's algorithm to find the shortest path between `a` and `b`. 16 | It picks the unvisited vertex with the lowest distance, 17 | calculates the distance through it to each unvisited neighbor, 18 | and updates the neighbor's distance if smaller. Mark visited 19 | (set to red) when done with neighbors. 20 | 21 | ## References 22 | 23 | - [Wikipedia](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) 24 | - [On YouTube by Nathaniel Fan](https://www.youtube.com/watch?v=gdmfOwyQlcI&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 25 | - [On YouTube by Tushar Roy](https://www.youtube.com/watch?v=lAXZGERcDf4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 26 | -------------------------------------------------------------------------------- /src/algorithms/graph/dijkstra/dijkstra.js: -------------------------------------------------------------------------------- 1 | import PriorityQueue from '../../../data-structures/priority-queue/PriorityQueue'; 2 | 3 | /** 4 | * @param {Graph} graph 5 | * @param {GraphVertex} startVertex 6 | */ 7 | export default function dijkstra(graph, startVertex) { 8 | const distances = {}; 9 | const visitedVertices = {}; 10 | const previousVertices = {}; 11 | const queue = new PriorityQueue(); 12 | 13 | // Init all distances with infinity assuming that currently we can't reach 14 | // any of the vertices except start one. 15 | graph.getAllVertices().forEach((vertex) => { 16 | distances[vertex.getKey()] = Infinity; 17 | previousVertices[vertex.getKey()] = null; 18 | }); 19 | distances[startVertex.getKey()] = 0; 20 | 21 | // Init vertices queue. 22 | queue.add(startVertex, distances[startVertex.getKey()]); 23 | 24 | while (!queue.isEmpty()) { 25 | const currentVertex = queue.poll(); 26 | 27 | graph.getNeighbors(currentVertex).forEach((neighbor) => { 28 | // Don't visit already visited vertices. 29 | if (!visitedVertices[neighbor.getKey()]) { 30 | // Update distances to every neighbor from current vertex. 31 | const edge = graph.findEdge(currentVertex, neighbor); 32 | 33 | const existingDistanceToNeighbor = distances[neighbor.getKey()]; 34 | const distanceToNeighborFromCurrent = distances[currentVertex.getKey()] + edge.weight; 35 | 36 | if (distanceToNeighborFromCurrent < existingDistanceToNeighbor) { 37 | distances[neighbor.getKey()] = distanceToNeighborFromCurrent; 38 | 39 | // Change priority. 40 | if (queue.hasValue(neighbor)) { 41 | queue.changePriority(neighbor, distances[neighbor.getKey()]); 42 | } 43 | 44 | // Remember previous vertex. 45 | previousVertices[neighbor.getKey()] = currentVertex; 46 | } 47 | 48 | // Add neighbor to the queue for further visiting. 49 | if (!queue.hasValue(neighbor)) { 50 | queue.add(neighbor, distances[neighbor.getKey()]); 51 | } 52 | } 53 | }); 54 | 55 | // Add current vertex to visited ones. 56 | visitedVertices[currentVertex.getKey()] = currentVertex; 57 | } 58 | 59 | return { 60 | distances, 61 | previousVertices, 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /src/algorithms/graph/eulerian-path/README.md: -------------------------------------------------------------------------------- 1 | # Eulerian Path 2 | 3 | In graph theory, an **Eulerian trail** (or **Eulerian path**) is a 4 | trail in a finite graph which visits every edge exactly once. 5 | Similarly, an **Eulerian circuit** or **Eulerian cycle** is an 6 | Eulerian trail which starts and ends on the same vertex. 7 | 8 | Euler proved that a necessary condition for the existence of Eulerian 9 | circuits is that all vertices in the graph have an even degree, and 10 | stated that connected graphs with all vertices of even degree have 11 | an Eulerian circuit. 12 | 13 | ![Eulerian Circuit](https://upload.wikimedia.org/wikipedia/commons/7/72/Labelled_Eulergraph.svg) 14 | 15 | Every vertex of this graph has an even degree. Therefore, this is 16 | an Eulerian graph. Following the edges in alphabetical order gives 17 | an Eulerian circuit/cycle. 18 | 19 | For the existence of Eulerian trails it is necessary that zero or 20 | two vertices have an odd degree; this means the Königsberg graph 21 | is not Eulerian. If there are no vertices of odd degree, 22 | all Eulerian trails are circuits. If there are exactly two vertices 23 | of odd degree, all Eulerian trails start at one of them and end at 24 | the other. A graph that has an Eulerian trail but not an Eulerian 25 | circuit is called semi-Eulerian. 26 | 27 | ![Königsberg graph](https://upload.wikimedia.org/wikipedia/commons/9/96/K%C3%B6nigsberg_graph.svg) 28 | 29 | The Königsberg Bridges multigraph. This multigraph is not Eulerian, 30 | therefore, a solution does not exist. 31 | 32 | ## References 33 | 34 | - [Wikipedia](https://en.wikipedia.org/wiki/Eulerian_path) 35 | - [YouTube](https://www.youtube.com/watch?v=vvP4Fg4r-Ns&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 36 | -------------------------------------------------------------------------------- /src/algorithms/graph/hamiltonian-cycle/README.md: -------------------------------------------------------------------------------- 1 | # Hamiltonian Path 2 | 3 | **Hamiltonian path** (or **traceable path**) is a path in an 4 | undirected or directed graph that visits each vertex exactly once. 5 | A **Hamiltonian cycle** (or **Hamiltonian circuit**) is a 6 | Hamiltonian path that is a cycle. Determining whether such paths 7 | and cycles exist in graphs is the **Hamiltonian path problem**. 8 | 9 | ![Hamiltonian cycle](https://upload.wikimedia.org/wikipedia/commons/6/6c/Hamiltonian_path_3d.svg) 10 | 11 | One possible Hamiltonian cycle through every vertex of a 12 | dodecahedron is shown in red – like all platonic solids, the 13 | dodecahedron is Hamiltonian. 14 | 15 | ## Naive Algorithm 16 | 17 | Generate all possible configurations of vertices and print a 18 | configuration that satisfies the given constraints. There 19 | will be `n!` (n factorial) configurations. 20 | 21 | ``` 22 | while there are untried configurations 23 | { 24 | generate the next configuration 25 | if ( there are edges between two consecutive vertices of this 26 | configuration and there is an edge from the last vertex to 27 | the first ). 28 | { 29 | print this configuration; 30 | break; 31 | } 32 | } 33 | ``` 34 | 35 | ## Backtracking Algorithm 36 | 37 | Create an empty path array and add vertex `0` to it. Add other 38 | vertices, starting from the vertex `1`. Before adding a vertex, 39 | check for whether it is adjacent to the previously added vertex 40 | and not already added. If we find such a vertex, we add the 41 | vertex as part of the solution. If we do not find a vertex 42 | then we return false. 43 | 44 | ## References 45 | 46 | - [Hamiltonian path on Wikipedia](https://en.wikipedia.org/wiki/Hamiltonian_path) 47 | - [Hamiltonian path on YouTube](https://www.youtube.com/watch?v=dQr4wZCiJJ4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 48 | - [Hamiltonian cycle on GeeksForGeeks](https://www.geeksforgeeks.org/backtracking-set-7-hamiltonian-cycle/) 49 | -------------------------------------------------------------------------------- /src/algorithms/graph/kruskal/kruskal.js: -------------------------------------------------------------------------------- 1 | import Graph from '../../../data-structures/graph/Graph'; 2 | import QuickSort from '../../sorting/quick-sort/QuickSort'; 3 | import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet'; 4 | 5 | /** 6 | * @param {Graph} graph 7 | * @return {Graph} 8 | */ 9 | export default function kruskal(graph) { 10 | // It should fire error if graph is directed since the algorithm works only 11 | // for undirected graphs. 12 | if (graph.isDirected) { 13 | throw new Error('Prim\'s algorithms works only for undirected graphs'); 14 | } 15 | 16 | // Init new graph that will contain minimum spanning tree of original graph. 17 | const minimumSpanningTree = new Graph(); 18 | 19 | // Sort all graph edges in increasing order. 20 | const sortingCallbacks = { 21 | /** 22 | * @param {GraphEdge} graphEdgeA 23 | * @param {GraphEdge} graphEdgeB 24 | */ 25 | compareCallback: (graphEdgeA, graphEdgeB) => { 26 | if (graphEdgeA.weight === graphEdgeB.weight) { 27 | return 1; 28 | } 29 | 30 | return graphEdgeA.weight <= graphEdgeB.weight ? -1 : 1; 31 | }, 32 | }; 33 | const sortedEdges = new QuickSort(sortingCallbacks).sort(graph.getAllEdges()); 34 | 35 | // Create disjoint sets for all graph vertices. 36 | const keyCallback = graphVertex => graphVertex.getKey(); 37 | const disjointSet = new DisjointSet(keyCallback); 38 | 39 | graph.getAllVertices().forEach((graphVertex) => { 40 | disjointSet.makeSet(graphVertex); 41 | }); 42 | 43 | // Go through all edges started from the minimum one and try to add them 44 | // to minimum spanning tree. The criteria of adding the edge would be whether 45 | // it is forms the cycle or not (if it connects two vertices from one disjoint 46 | // set or not). 47 | for (let edgeIndex = 0; edgeIndex < sortedEdges.length; edgeIndex += 1) { 48 | /** @var {GraphEdge} currentEdge */ 49 | const currentEdge = sortedEdges[edgeIndex]; 50 | 51 | // Check if edge forms the cycle. If it does then skip it. 52 | if (!disjointSet.inSameSet(currentEdge.startVertex, currentEdge.endVertex)) { 53 | // Unite two subsets into one. 54 | disjointSet.union(currentEdge.startVertex, currentEdge.endVertex); 55 | 56 | // Add this edge to spanning tree. 57 | minimumSpanningTree.addEdge(currentEdge); 58 | } 59 | } 60 | 61 | return minimumSpanningTree; 62 | } 63 | -------------------------------------------------------------------------------- /src/algorithms/graph/strongly-connected-components/README.md: -------------------------------------------------------------------------------- 1 | # Strongly Connected Component 2 | 3 | A directed graph is called **strongly connected** if there is a path 4 | in each direction between each pair of vertices of the graph. 5 | In a directed graph G that may not itself be strongly connected, 6 | a pair of vertices `u` and `v` are said to be strongly connected 7 | to each other if there is a path in each direction between them. 8 | 9 | ![Strongly Connected](https://upload.wikimedia.org/wikipedia/commons/5/5c/Scc.png) 10 | 11 | Graph with strongly connected components marked 12 | 13 | ## References 14 | 15 | - [Wikipedia](https://en.wikipedia.org/wiki/Strongly_connected_component) 16 | - [YouTube](https://www.youtube.com/watch?v=RpgcYiky7uw&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 17 | -------------------------------------------------------------------------------- /src/algorithms/graph/topological-sorting/__test__/topologicalSort.test.js: -------------------------------------------------------------------------------- 1 | import GraphVertex from '../../../../data-structures/graph/GraphVertex'; 2 | import GraphEdge from '../../../../data-structures/graph/GraphEdge'; 3 | import Graph from '../../../../data-structures/graph/Graph'; 4 | import topologicalSort from '../topologicalSort'; 5 | 6 | describe('topologicalSort', () => { 7 | it('should do topological sorting on graph', () => { 8 | const vertexA = new GraphVertex('A'); 9 | const vertexB = new GraphVertex('B'); 10 | const vertexC = new GraphVertex('C'); 11 | const vertexD = new GraphVertex('D'); 12 | const vertexE = new GraphVertex('E'); 13 | const vertexF = new GraphVertex('F'); 14 | const vertexG = new GraphVertex('G'); 15 | const vertexH = new GraphVertex('H'); 16 | 17 | const edgeAC = new GraphEdge(vertexA, vertexC); 18 | const edgeBC = new GraphEdge(vertexB, vertexC); 19 | const edgeBD = new GraphEdge(vertexB, vertexD); 20 | const edgeCE = new GraphEdge(vertexC, vertexE); 21 | const edgeDF = new GraphEdge(vertexD, vertexF); 22 | const edgeEF = new GraphEdge(vertexE, vertexF); 23 | const edgeEH = new GraphEdge(vertexE, vertexH); 24 | const edgeFG = new GraphEdge(vertexF, vertexG); 25 | 26 | const graph = new Graph(true); 27 | 28 | graph 29 | .addEdge(edgeAC) 30 | .addEdge(edgeBC) 31 | .addEdge(edgeBD) 32 | .addEdge(edgeCE) 33 | .addEdge(edgeDF) 34 | .addEdge(edgeEF) 35 | .addEdge(edgeEH) 36 | .addEdge(edgeFG); 37 | 38 | const sortedVertices = topologicalSort(graph); 39 | 40 | expect(sortedVertices).toBeDefined(); 41 | expect(sortedVertices.length).toBe(graph.getAllVertices().length); 42 | expect(sortedVertices).toEqual([ 43 | vertexB, 44 | vertexD, 45 | vertexA, 46 | vertexC, 47 | vertexE, 48 | vertexH, 49 | vertexF, 50 | vertexG, 51 | ]); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/algorithms/graph/topological-sorting/topologicalSort.js: -------------------------------------------------------------------------------- 1 | import Stack from '../../../data-structures/stack/Stack'; 2 | import depthFirstSearch from '../depth-first-search/depthFirstSearch'; 3 | 4 | /** 5 | * @param {Graph} graph 6 | */ 7 | export default function topologicalSort(graph) { 8 | // Create a set of all vertices we want to visit. 9 | const unvisitedSet = {}; 10 | graph.getAllVertices().forEach((vertex) => { 11 | unvisitedSet[vertex.getKey()] = vertex; 12 | }); 13 | 14 | // Create a set for all vertices that we've already visited. 15 | const visitedSet = {}; 16 | 17 | // Create a stack of already ordered vertices. 18 | const sortedStack = new Stack(); 19 | 20 | const dfsCallbacks = { 21 | enterVertex: ({ currentVertex }) => { 22 | // Add vertex to visited set in case if all its children has been explored. 23 | visitedSet[currentVertex.getKey()] = currentVertex; 24 | 25 | // Remove this vertex from unvisited set. 26 | delete unvisitedSet[currentVertex.getKey()]; 27 | }, 28 | leaveVertex: ({ currentVertex }) => { 29 | // If the vertex has been totally explored then we may push it to stack. 30 | sortedStack.push(currentVertex); 31 | }, 32 | allowTraversal: ({ nextVertex }) => { 33 | return !visitedSet[nextVertex.getKey()]; 34 | }, 35 | }; 36 | 37 | // Let's go and do DFS for all unvisited nodes. 38 | while (Object.keys(unvisitedSet).length) { 39 | const currentVertexKey = Object.keys(unvisitedSet)[0]; 40 | const currentVertex = unvisitedSet[currentVertexKey]; 41 | 42 | // Do DFS for current node. 43 | depthFirstSearch(graph, currentVertex, dfsCallbacks); 44 | } 45 | 46 | return sortedStack.toArray(); 47 | } 48 | -------------------------------------------------------------------------------- /src/algorithms/graph/travelling-salesman/README.md: -------------------------------------------------------------------------------- 1 | # Travelling Salesman Problem 2 | 3 | The travelling salesman problem (TSP) asks the following question: 4 | "Given a list of cities and the distances between each pair of 5 | cities, what is the shortest possible route that visits each city 6 | and returns to the origin city?" 7 | 8 | ![Travelling Salesman](https://upload.wikimedia.org/wikipedia/commons/1/11/GLPK_solution_of_a_travelling_salesman_problem.svg) 9 | 10 | Solution of a travelling salesman problem: the black line shows 11 | the shortest possible loop that connects every red dot. 12 | 13 | ![Travelling Salesman Graph](https://upload.wikimedia.org/wikipedia/commons/3/30/Weighted_K4.svg) 14 | 15 | TSP can be modelled as an undirected weighted graph, such that 16 | cities are the graph's vertices, paths are the graph's edges, 17 | and a path's distance is the edge's weight. It is a minimization 18 | problem starting and finishing at a specified vertex after having 19 | visited each other vertex exactly once. Often, the model is a 20 | complete graph (i.e. each pair of vertices is connected by an 21 | edge). If no path exists between two cities, adding an arbitrarily 22 | long edge will complete the graph without affecting the optimal tour. 23 | 24 | ## References 25 | 26 | - [Wikipedia](https://en.wikipedia.org/wiki/Travelling_salesman_problem) 27 | -------------------------------------------------------------------------------- /src/algorithms/graph/travelling-salesman/__test__/bfTravellingSalesman.test.js: -------------------------------------------------------------------------------- 1 | import GraphVertex from '../../../../data-structures/graph/GraphVertex'; 2 | import GraphEdge from '../../../../data-structures/graph/GraphEdge'; 3 | import Graph from '../../../../data-structures/graph/Graph'; 4 | import bfTravellingSalesman from '../bfTravellingSalesman'; 5 | 6 | describe('bfTravellingSalesman', () => { 7 | it('should solve problem for simple graph', () => { 8 | const vertexA = new GraphVertex('A'); 9 | const vertexB = new GraphVertex('B'); 10 | const vertexC = new GraphVertex('C'); 11 | const vertexD = new GraphVertex('D'); 12 | 13 | const edgeAB = new GraphEdge(vertexA, vertexB, 1); 14 | const edgeBD = new GraphEdge(vertexB, vertexD, 1); 15 | const edgeDC = new GraphEdge(vertexD, vertexC, 1); 16 | const edgeCA = new GraphEdge(vertexC, vertexA, 1); 17 | 18 | const edgeBA = new GraphEdge(vertexB, vertexA, 5); 19 | const edgeDB = new GraphEdge(vertexD, vertexB, 8); 20 | const edgeCD = new GraphEdge(vertexC, vertexD, 7); 21 | const edgeAC = new GraphEdge(vertexA, vertexC, 4); 22 | const edgeAD = new GraphEdge(vertexA, vertexD, 2); 23 | const edgeDA = new GraphEdge(vertexD, vertexA, 3); 24 | const edgeBC = new GraphEdge(vertexB, vertexC, 3); 25 | const edgeCB = new GraphEdge(vertexC, vertexB, 9); 26 | 27 | const graph = new Graph(true); 28 | graph 29 | .addEdge(edgeAB) 30 | .addEdge(edgeBD) 31 | .addEdge(edgeDC) 32 | .addEdge(edgeCA) 33 | .addEdge(edgeBA) 34 | .addEdge(edgeDB) 35 | .addEdge(edgeCD) 36 | .addEdge(edgeAC) 37 | .addEdge(edgeAD) 38 | .addEdge(edgeDA) 39 | .addEdge(edgeBC) 40 | .addEdge(edgeCB); 41 | 42 | const salesmanPath = bfTravellingSalesman(graph); 43 | 44 | expect(salesmanPath.length).toBe(4); 45 | 46 | expect(salesmanPath[0].getKey()).toEqual(vertexA.getKey()); 47 | expect(salesmanPath[1].getKey()).toEqual(vertexB.getKey()); 48 | expect(salesmanPath[2].getKey()).toEqual(vertexD.getKey()); 49 | expect(salesmanPath[3].getKey()).toEqual(vertexC.getKey()); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-algorithm/__test__/euclieanAlgorithm.test.js: -------------------------------------------------------------------------------- 1 | import euclideanAlgorithm from '../euclideanAlgorithm'; 2 | 3 | describe('euclideanAlgorithm', () => { 4 | it('should calculate GCD', () => { 5 | expect(euclideanAlgorithm(0, 0)).toBeNull(); 6 | expect(euclideanAlgorithm(2, 0)).toBe(2); 7 | expect(euclideanAlgorithm(0, 2)).toBe(2); 8 | expect(euclideanAlgorithm(1, 2)).toBe(1); 9 | expect(euclideanAlgorithm(2, 1)).toBe(1); 10 | expect(euclideanAlgorithm(6, 6)).toBe(6); 11 | expect(euclideanAlgorithm(2, 4)).toBe(2); 12 | expect(euclideanAlgorithm(4, 2)).toBe(2); 13 | expect(euclideanAlgorithm(12, 4)).toBe(4); 14 | expect(euclideanAlgorithm(4, 12)).toBe(4); 15 | expect(euclideanAlgorithm(5, 13)).toBe(1); 16 | expect(euclideanAlgorithm(27, 13)).toBe(1); 17 | expect(euclideanAlgorithm(24, 60)).toBe(12); 18 | expect(euclideanAlgorithm(60, 24)).toBe(12); 19 | expect(euclideanAlgorithm(252, 105)).toBe(21); 20 | expect(euclideanAlgorithm(105, 252)).toBe(21); 21 | expect(euclideanAlgorithm(1071, 462)).toBe(21); 22 | expect(euclideanAlgorithm(462, 1071)).toBe(21); 23 | expect(euclideanAlgorithm(462, -1071)).toBe(21); 24 | expect(euclideanAlgorithm(-462, -1071)).toBe(21); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-algorithm/euclideanAlgorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} originalA 3 | * @param {number} originalB 4 | * @return {number|null} 5 | */ 6 | export default function euclideanAlgorithm(originalA, originalB) { 7 | const a = Math.abs(originalA); 8 | const b = Math.abs(originalB); 9 | 10 | if (a === 0 && b === 0) { 11 | return null; 12 | } 13 | 14 | if (a === 0 && b !== 0) { 15 | return b; 16 | } 17 | 18 | if (a !== 0 && b === 0) { 19 | return a; 20 | } 21 | 22 | // Normally we need to do subtraction (a - b) but to prevent 23 | // recursion occurs to often we may shorten subtraction to (a % b). 24 | // Since (a % b) is normally means that we've subtracted b from a 25 | // many times until the difference became less then a. 26 | 27 | if (a > b) { 28 | return euclideanAlgorithm(a % b, b); 29 | } 30 | 31 | return euclideanAlgorithm(b % a, a); 32 | } 33 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial/README.md: -------------------------------------------------------------------------------- 1 | # Factorial 2 | 3 | In mathematics, the factorial of a non-negative integer `n`, 4 | denoted by `n!`, is the product of all positive integers less 5 | than or equal to `n`. For example: 6 | 7 | ``` 8 | 5! = 5 * 4 * 3 * 2 * 1 = 120 9 | ``` 10 | 11 | | n | n! | 12 | | ----- | --------------------------: | 13 | | 0 | 1 | 14 | | 1 | 1 | 15 | | 2 | 2 | 16 | | 3 | 6 | 17 | | 4 | 24 | 18 | | 5 | 120 | 19 | | 6 | 720 | 20 | | 7 | 5 040 | 21 | | 8 | 40 320 | 22 | | 9 | 362 880 | 23 | | 10 | 3 628 800 | 24 | | 11 | 39 916 800 | 25 | | 12 | 479 001 600 | 26 | | 13 | 6 227 020 800 | 27 | | 14 | 87 178 291 200 | 28 | | 15 | 1 307 674 368 000 | 29 | 30 | ## References 31 | 32 | [Wikipedia](https://en.wikipedia.org/wiki/Factorial) 33 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial/__test__/factorial.test.js: -------------------------------------------------------------------------------- 1 | import factorial from '../factorial'; 2 | 3 | describe('factorial', () => { 4 | it('should calculate factorial', () => { 5 | expect(factorial(0)).toBe(1); 6 | expect(factorial(1)).toBe(1); 7 | expect(factorial(5)).toBe(120); 8 | expect(factorial(8)).toBe(40320); 9 | expect(factorial(10)).toBe(3628800); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial/factorial.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {number} 4 | */ 5 | export default function factorial(number) { 6 | let result = 1; 7 | 8 | for (let i = 1; i <= number; i += 1) { 9 | result *= i; 10 | } 11 | 12 | return result; 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/README.md: -------------------------------------------------------------------------------- 1 | # Fibonacci Number 2 | 3 | In mathematics, the Fibonacci numbers are the numbers in the following 4 | integer sequence, called the Fibonacci sequence, and characterized by 5 | the fact that every number after the first two is the sum of the two 6 | preceding ones: 7 | 8 | `0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...` 9 | 10 | A tiling with squares whose side lengths are successive Fibonacci numbers 11 | 12 | ![Fibonacci](https://upload.wikimedia.org/wikipedia/commons/d/db/34%2A21-FibonacciBlocks.png) 13 | 14 | The Fibonacci spiral: an approximation of the golden spiral created by drawing circular arcs connecting the opposite corners of squares in the Fibonacci tiling;[4] this one uses squares of sizes 1, 1, 2, 3, 5, 8, 13 and 21. 15 | 16 | ![Fibonacci Spiral](https://upload.wikimedia.org/wikipedia/commons/2/2e/FibonacciSpiral.svg) 17 | 18 | ## References 19 | 20 | [Wikipedia](https://en.wikipedia.org/wiki/Fibonacci_number) 21 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/__test__/fibonacci.test.js: -------------------------------------------------------------------------------- 1 | import fibonacci from '../fibonacci'; 2 | 3 | describe('fibonacci', () => { 4 | it('should calculate fibonacci correctly', () => { 5 | expect(fibonacci(1)).toBe(1); 6 | expect(fibonacci(2)).toBe(1); 7 | expect(fibonacci(3)).toBe(2); 8 | expect(fibonacci(4)).toBe(3); 9 | expect(fibonacci(5)).toBe(5); 10 | expect(fibonacci(6)).toBe(8); 11 | expect(fibonacci(7)).toBe(13); 12 | expect(fibonacci(8)).toBe(21); 13 | expect(fibonacci(20)).toBe(6765); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci/fibonacci.js: -------------------------------------------------------------------------------- 1 | // Calculate fibonacci number at specific position using Dynamic Programming approach. 2 | export default function fibonacci(numberPosition) { 3 | if (numberPosition === 1) { 4 | return 1; 5 | } 6 | 7 | let iterationsCounter = numberPosition - 1; 8 | 9 | // Calculated fibonacci number. 10 | let fib = null; 11 | // Previous fibonacci number. 12 | let fibPrev = 1; 13 | // Before previous fibonacci number. 14 | let fibPrevPrev = 0; 15 | 16 | while (iterationsCounter) { 17 | // Calculate current value using two previous ones. 18 | fib = fibPrev + fibPrevPrev; 19 | // Shift previous values. 20 | fibPrevPrev = fibPrev; 21 | fibPrev = fib; 22 | iterationsCounter -= 1; 23 | } 24 | 25 | return fib; 26 | } 27 | -------------------------------------------------------------------------------- /src/algorithms/math/integer-partition/README.md: -------------------------------------------------------------------------------- 1 | # Integer Partition 2 | 3 | In number theory and combinatorics, a partition of a positive 4 | integer `n`, also called an **integer partition**, is a way of 5 | writing `n` as a sum of positive integers. 6 | 7 | Two sums that differ only in the order of their summands are 8 | considered the same partition. For example, `4` can be partitioned 9 | in five distinct ways: 10 | 11 | ``` 12 | 4 13 | 3 + 1 14 | 2 + 2 15 | 2 + 1 + 1 16 | 1 + 1 + 1 + 1 17 | ``` 18 | 19 | The order-dependent composition `1 + 3` is the same partition 20 | as `3 + 1`, while the two distinct 21 | compositions `1 + 2 + 1` and `1 + 1 + 2` represent the same 22 | partition `2 + 1 + 1`. 23 | 24 | Young diagrams associated to the partitions of the positive 25 | integers `1` through `8`. They are arranged so that images 26 | under the reflection about the main diagonal of the square 27 | are conjugate partitions. 28 | 29 | ![Integer Partition](https://upload.wikimedia.org/wikipedia/commons/d/d8/Ferrer_partitioning_diagrams.svg) 30 | 31 | ## References 32 | 33 | - [Wikipedia](https://en.wikipedia.org/wiki/Partition_(number_theory)) 34 | - [YouTube](https://www.youtube.com/watch?v=ZaVM057DuzE&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 35 | -------------------------------------------------------------------------------- /src/algorithms/math/integer-partition/__test__/integerPartition.test.js: -------------------------------------------------------------------------------- 1 | import integerPartition from '../integerPartition'; 2 | 3 | describe('integerPartition', () => { 4 | it('should partition the number', () => { 5 | expect(integerPartition(1)).toBe(1); 6 | expect(integerPartition(2)).toBe(2); 7 | expect(integerPartition(3)).toBe(3); 8 | expect(integerPartition(4)).toBe(5); 9 | expect(integerPartition(8)).toBe(22); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/algorithms/math/least-common-multiple/README.md: -------------------------------------------------------------------------------- 1 | # Least common multiple 2 | 3 | In arithmetic and number theory, the least common multiple, 4 | lowest common multiple, or smallest common multiple of 5 | two integers `a` and `b`, usually denoted by `LCM(a, b)`, is 6 | the smallest positive integer that is divisible by 7 | both `a` and `b`. Since division of integers by zero is 8 | undefined, this definition has meaning only if `a` and `b` are 9 | both different from zero. However, some authors define `lcm(a,0)` 10 | as `0` for all `a`, which is the result of taking the `lcm` 11 | to be the least upper bound in the lattice of divisibility. 12 | 13 | ## Example 14 | 15 | What is the LCM of 4 and 6? 16 | 17 | Multiples of `4` are: 18 | 19 | ``` 20 | 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, ... 21 | ``` 22 | 23 | and the multiples of `6` are: 24 | 25 | ``` 26 | 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, ... 27 | ``` 28 | 29 | Common multiples of `4` and `6` are simply the numbers 30 | that are in both lists: 31 | 32 | ``` 33 | 12, 24, 36, 48, 60, 72, .... 34 | ``` 35 | 36 | So, from this list of the first few common multiples of 37 | the numbers `4` and `6`, their least common multiple is `12`. 38 | 39 | ## Computing the least common multiple 40 | 41 | The following formula reduces the problem of computing the 42 | least common multiple to the problem of computing the greatest 43 | common divisor (GCD), also known as the greatest common factor: 44 | 45 | ``` 46 | lcm(a, b) = |a * b| / gcd(a, b) 47 | ``` 48 | 49 | ![LCM](https://upload.wikimedia.org/wikipedia/commons/c/c9/Symmetrical_5-set_Venn_diagram_LCM_2_3_4_5_7.svg) 50 | 51 | A Venn diagram showing the least common multiples of 52 | combinations of `2`, `3`, `4`, `5` and `7` (`6` is skipped as 53 | it is `2 × 3`, both of which are already represented). 54 | 55 | For example, a card game which requires its cards to be 56 | divided equally among up to `5` players requires at least `60` 57 | cards, the number at the intersection of the `2`, `3`, `4` 58 | and `5` sets, but not the `7` set. 59 | 60 | ## References 61 | 62 | [Wikipedia](https://en.wikipedia.org/wiki/Least_common_multiple) 63 | -------------------------------------------------------------------------------- /src/algorithms/math/least-common-multiple/__test__/leastCommonMultiple.test.js: -------------------------------------------------------------------------------- 1 | import leastCommonMultiple from '../leastCommonMultiple'; 2 | 3 | describe('leastCommonMultiple', () => { 4 | it('should find least common multiple', () => { 5 | expect(leastCommonMultiple(0, 0)).toBe(0); 6 | expect(leastCommonMultiple(1, 0)).toBe(0); 7 | expect(leastCommonMultiple(0, 1)).toBe(0); 8 | expect(leastCommonMultiple(4, 6)).toBe(12); 9 | expect(leastCommonMultiple(6, 21)).toBe(42); 10 | expect(leastCommonMultiple(7, 2)).toBe(14); 11 | expect(leastCommonMultiple(3, 5)).toBe(15); 12 | expect(leastCommonMultiple(7, 3)).toBe(21); 13 | expect(leastCommonMultiple(1000000, 2)).toBe(1000000); 14 | expect(leastCommonMultiple(-9, -18)).toBe(18); 15 | expect(leastCommonMultiple(-7, -9)).toBe(63); 16 | expect(leastCommonMultiple(-7, 9)).toBe(63); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/algorithms/math/least-common-multiple/leastCommonMultiple.js: -------------------------------------------------------------------------------- 1 | import euclideanAlgorithm from '../euclidean-algorithm/euclideanAlgorithm'; 2 | 3 | /** 4 | * @param {number} a 5 | * @param {number} b 6 | * @return {number} 7 | */ 8 | 9 | export default function leastCommonMultiple(a, b) { 10 | if (a === 0 && b === 0) { 11 | return 0; 12 | } 13 | 14 | return Math.abs(a * b) / euclideanAlgorithm(a, b); 15 | } 16 | -------------------------------------------------------------------------------- /src/algorithms/math/primality-test/README.md: -------------------------------------------------------------------------------- 1 | # Primality Test 2 | 3 | A primality test is an algorithm for determining whether an input 4 | number is prime. Among other fields of mathematics, it is used 5 | for cryptography. Unlike integer factorization, primality tests 6 | do not generally give prime factors, only stating whether the 7 | input number is prime or not. Factorization is thought to be 8 | a computationally difficult problem, whereas primality testing 9 | is comparatively easy (its running time is polynomial in the 10 | size of the input). 11 | 12 | ## References 13 | 14 | [Wikipedia](https://en.wikipedia.org/wiki/Primality_test) 15 | -------------------------------------------------------------------------------- /src/algorithms/math/primality-test/__test__/trialDivision.test.js: -------------------------------------------------------------------------------- 1 | import trialDivision from '../trialDivision'; 2 | 3 | /** 4 | * @param {function(n: number)} testFunction 5 | */ 6 | function primalityTest(testFunction) { 7 | expect(testFunction(1)).toBeFalsy(); 8 | expect(testFunction(2)).toBeTruthy(); 9 | expect(testFunction(3)).toBeTruthy(); 10 | expect(testFunction(5)).toBeTruthy(); 11 | expect(testFunction(11)).toBeTruthy(); 12 | expect(testFunction(191)).toBeTruthy(); 13 | expect(testFunction(191)).toBeTruthy(); 14 | expect(testFunction(199)).toBeTruthy(); 15 | 16 | expect(testFunction(-1)).toBeFalsy(); 17 | expect(testFunction(0)).toBeFalsy(); 18 | expect(testFunction(4)).toBeFalsy(); 19 | expect(testFunction(6)).toBeFalsy(); 20 | expect(testFunction(12)).toBeFalsy(); 21 | expect(testFunction(14)).toBeFalsy(); 22 | expect(testFunction(25)).toBeFalsy(); 23 | expect(testFunction(192)).toBeFalsy(); 24 | expect(testFunction(200)).toBeFalsy(); 25 | expect(testFunction(400)).toBeFalsy(); 26 | } 27 | 28 | describe('trialDivision', () => { 29 | it('should detect prime numbers', () => { 30 | primalityTest(trialDivision); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/algorithms/math/primality-test/trialDivision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} number 3 | * @return {boolean} 4 | */ 5 | export default function trialDivision(number) { 6 | if (number <= 1) { 7 | // If number is less than one then it isn't prime by definition. 8 | return false; 9 | } else if (number <= 3) { 10 | // All numbers from 2 to 3 are prime. 11 | return true; 12 | } 13 | 14 | // If the number is not divided by 2 then we may eliminate all further even dividers. 15 | if (number % 2 === 0) { 16 | return false; 17 | } 18 | 19 | // If there is no dividers up to square root of n then there is no higher dividers as well. 20 | const dividerLimit = Math.sqrt(number); 21 | for (let divider = 3; divider <= dividerLimit; divider += 2) { 22 | if (number % divider === 0) { 23 | return false; 24 | } 25 | } 26 | 27 | return true; 28 | } 29 | -------------------------------------------------------------------------------- /src/algorithms/search/binary-search/README.md: -------------------------------------------------------------------------------- 1 | # Binary Search 2 | 3 | In computer science, binary search, also known as half-interval 4 | search, logarithmic search, or binary chop, is a search algorithm 5 | that finds the position of a target value within a sorted 6 | array. Binary search compares the target value to the middle 7 | element of the array; if they are unequal, the half in which 8 | the target cannot lie is eliminated and the search continues 9 | on the remaining half until it is successful. If the search 10 | ends with the remaining half being empty, the target is not 11 | in the array. 12 | 13 | ![Binary Search](https://upload.wikimedia.org/wikipedia/commons/8/83/Binary_Search_Depiction.svg) 14 | 15 | ## References 16 | 17 | - [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_algorithm) 18 | - [YouTube](https://www.youtube.com/watch?v=P3YID7liBug&index=29&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 19 | -------------------------------------------------------------------------------- /src/algorithms/search/binary-search/__test__/binarySearch.test.js: -------------------------------------------------------------------------------- 1 | import binarySearch from '../binarySearch'; 2 | 3 | describe('binarySearch', () => { 4 | it('should search number in sorted array', () => { 5 | expect(binarySearch([], 1)).toBe(-1); 6 | expect(binarySearch([1], 1)).toBe(0); 7 | expect(binarySearch([1, 2], 1)).toBe(0); 8 | expect(binarySearch([1, 2], 2)).toBe(1); 9 | expect(binarySearch([1, 5, 10, 12], 1)).toBe(0); 10 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 17)).toBe(5); 11 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 1)).toBe(0); 12 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 100)).toBe(7); 13 | expect(binarySearch([1, 5, 10, 12, 14, 17, 22, 100], 0)).toBe(-1); 14 | }); 15 | 16 | it('should search object in sorted array', () => { 17 | const sortedArrayOfObjects = [ 18 | { key: 1, value: 'value1' }, 19 | { key: 2, value: 'value2' }, 20 | { key: 3, value: 'value3' }, 21 | ]; 22 | 23 | const comparator = (a, b) => { 24 | if (a.key === b.key) return 0; 25 | return a.key < b.key ? -1 : 1; 26 | }; 27 | 28 | expect(binarySearch([], { key: 1 }, comparator)).toBe(-1); 29 | expect(binarySearch(sortedArrayOfObjects, { key: 4 }, comparator)).toBe(-1); 30 | expect(binarySearch(sortedArrayOfObjects, { key: 1 }, comparator)).toBe(0); 31 | expect(binarySearch(sortedArrayOfObjects, { key: 2 }, comparator)).toBe(1); 32 | expect(binarySearch(sortedArrayOfObjects, { key: 3 }, comparator)).toBe(2); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/algorithms/search/binary-search/binarySearch.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @param {*[]} sortedArray 5 | * @param {*} seekElement 6 | * @param {function(a, b)} [comparatorCallback] 7 | * @return {number} 8 | */ 9 | 10 | export default function binarySearch(sortedArray, seekElement, comparatorCallback) { 11 | const comparator = new Comparator(comparatorCallback); 12 | 13 | let startIndex = 0; 14 | let endIndex = sortedArray.length - 1; 15 | 16 | while (startIndex <= endIndex) { 17 | const middleIndex = startIndex + Math.floor((endIndex - startIndex) / 2); 18 | 19 | // If we've found the element just return its position. 20 | if (comparator.equal(sortedArray[middleIndex], seekElement)) { 21 | return middleIndex; 22 | } 23 | 24 | // Decide which half to choose for seeking next: left or right one. 25 | if (comparator.lessThan(sortedArray[middleIndex], seekElement)) { 26 | // Go to the right half of the array. 27 | startIndex = middleIndex + 1; 28 | } else { 29 | // Go to the left half of the array. 30 | endIndex = middleIndex - 1; 31 | } 32 | } 33 | 34 | return -1; 35 | } 36 | -------------------------------------------------------------------------------- /src/algorithms/search/linear-search/README.md: -------------------------------------------------------------------------------- 1 | # Linear Search 2 | In computer science, linear search or sequential search is a 3 | method for finding a target value within a list. It sequentially 4 | checks each element of the list for the target value until a 5 | match is found or until all the elements have been searched. 6 | Linear search runs in at worst linear time and makes at most `n` 7 | comparisons, where `n` is the length of the list. 8 | 9 | ![Linear Search](https://www.tutorialspoint.com/data_structures_algorithms/images/linear_search.gif) 10 | 11 | ## References 12 | - [Wikipedia](https://en.wikipedia.org/wiki/Linear_search) 13 | - [TutorialsPoint](https://www.tutorialspoint.com/data_structures_algorithms/linear_search_algorithm.htm) 14 | - [Youtube](https://www.youtube.com/watch?v=SGU9duLE30w) 15 | -------------------------------------------------------------------------------- /src/algorithms/search/linear-search/__test__/linearSearch.test.js: -------------------------------------------------------------------------------- 1 | import linearSearch from '../linearSearch'; 2 | 3 | describe('linearSearch', () => { 4 | it('should search all numbers in array', () => { 5 | const array = [1, 2, 4, 6, 2]; 6 | 7 | expect(linearSearch(array, 10)).toEqual([]); 8 | expect(linearSearch(array, 1)).toEqual([0]); 9 | expect(linearSearch(array, 2)).toEqual([1, 4]); 10 | }); 11 | 12 | it('should search all strings in array', () => { 13 | const array = ['a', 'b', 'a']; 14 | 15 | expect(linearSearch(array, 'c')).toEqual([]); 16 | expect(linearSearch(array, 'b')).toEqual([1]); 17 | expect(linearSearch(array, 'a')).toEqual([0, 2]); 18 | }); 19 | 20 | it('should search through objects as well', () => { 21 | const comparatorCallback = (a, b) => { 22 | if (a.key === b.key) { 23 | return 0; 24 | } 25 | 26 | return a.key <= b.key ? -1 : 1; 27 | }; 28 | 29 | const array = [ 30 | { key: 5 }, 31 | { key: 6 }, 32 | { key: 7 }, 33 | { key: 6 }, 34 | ]; 35 | 36 | expect(linearSearch(array, { key: 10 }, comparatorCallback)).toEqual([]); 37 | expect(linearSearch(array, { key: 5 }, comparatorCallback)).toEqual([0]); 38 | expect(linearSearch(array, { key: 6 }, comparatorCallback)).toEqual([1, 3]); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/algorithms/search/linear-search/linearSearch.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @param {*[]} array 5 | * @param {*} seekElement 6 | * @param {function(a, b)} [comparatorCallback] 7 | * @return {number[]} 8 | */ 9 | export default function linearSearch(array, seekElement, comparatorCallback) { 10 | const comparator = new Comparator(comparatorCallback); 11 | const foundIndices = []; 12 | 13 | array.forEach((element, index) => { 14 | if (comparator.equal(element, seekElement)) { 15 | foundIndices.push(index); 16 | } 17 | }); 18 | 19 | return foundIndices; 20 | } 21 | -------------------------------------------------------------------------------- /src/algorithms/sets/cartesian-product/README.md: -------------------------------------------------------------------------------- 1 | # Cartesian Product 2 | 3 | In set theory a Cartesian product is a mathematical operation that returns a set 4 | (or product set or simply product) from multiple sets. That is, for sets A and B, 5 | the Cartesian product A × B is the set of all ordered pairs (a, b) 6 | where a ∈ A and b ∈ B. 7 | 8 | Cartesian product `AxB` of two sets `A={x,y,z}` and `B={1,2,3}` 9 | 10 | ![Cartesian Product of Two Sets](https://upload.wikimedia.org/wikipedia/commons/4/4e/Cartesian_Product_qtl1.svg) 11 | 12 | ## References 13 | 14 | [Wikipedia](https://en.wikipedia.org/wiki/Cartesian_product) 15 | -------------------------------------------------------------------------------- /src/algorithms/sets/cartesian-product/__test__/cartesianProduct.test.js: -------------------------------------------------------------------------------- 1 | import cartesianProduct from '../cartesianProduct'; 2 | 3 | describe('cartesianProduct', () => { 4 | it('should return null if there is not enough info for calculation', () => { 5 | const product1 = cartesianProduct([1], null); 6 | const product2 = cartesianProduct([], null); 7 | 8 | expect(product1).toBeNull(); 9 | expect(product2).toBeNull(); 10 | }); 11 | 12 | it('should calculate the product of two sets', () => { 13 | const product1 = cartesianProduct([1], [1]); 14 | const product2 = cartesianProduct([1, 2], [3, 5]); 15 | 16 | expect(product1).toEqual([[1, 1]]); 17 | expect(product2).toEqual([[1, 3], [1, 5], [2, 3], [2, 5]]); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/algorithms/sets/cartesian-product/cartesianProduct.js: -------------------------------------------------------------------------------- 1 | export default function cartesianProduct(setA, setB) { 2 | if (!setA || !setB || !setA.length || !setB.length) { 3 | return null; 4 | } 5 | 6 | const product = []; 7 | 8 | for (let indexA = 0; indexA < setA.length; indexA += 1) { 9 | for (let indexB = 0; indexB < setB.length; indexB += 1) { 10 | product.push([setA[indexA], setB[indexB]]); 11 | } 12 | } 13 | 14 | return product; 15 | } 16 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/README.md: -------------------------------------------------------------------------------- 1 | # Combinations 2 | 3 | When the order doesn't matter, it is a **Combination**. 4 | 5 | When the order **does** matter it is a **Permutation**. 6 | 7 | **"My fruit salad is a combination of apples, grapes and bananas"** 8 | We don't care what order the fruits are in, they could also be 9 | "bananas, grapes and apples" or "grapes, apples and bananas", 10 | its the same fruit salad. 11 | 12 | ## Combinations without repetitions 13 | 14 | This is how lotteries work. The numbers are drawn one at a 15 | time, and if we have the lucky numbers (no matter what order) 16 | we win! 17 | 18 | No Repetition: such as lottery numbers `(2,14,15,27,30,33)` 19 | 20 | **Number of combinations** 21 | 22 | ![Formula](https://www.mathsisfun.com/combinatorics/images/combinations-no-repeat.png) 23 | 24 | where `n` is the number of things to choose from, and we choose `r` of them, 25 | no repetition, order doesn't matter. 26 | 27 | It is often called "n choose r" (such as "16 choose 3"). And is also known as the Binomial Coefficient. 28 | 29 | ## Combinations with repetitions 30 | 31 | Repetition is Allowed: such as coins in your pocket `(5,5,5,10,10)` 32 | 33 | Or let us say there are five flavours of icecream: 34 | `banana`, `chocolate`, `lemon`, `strawberry` and `vanilla`. 35 | 36 | We can have three scoops. How many variations will there be? 37 | 38 | Let's use letters for the flavours: `{b, c, l, s, v}`. 39 | Example selections include: 40 | 41 | - `{c, c, c}` (3 scoops of chocolate) 42 | - `{b, l, v}` (one each of banana, lemon and vanilla) 43 | - `{b, v, v}` (one of banana, two of vanilla) 44 | 45 | **Number of combinations** 46 | 47 | ![Formula](https://www.mathsisfun.com/combinatorics/images/combinations-repeat.gif) 48 | 49 | Where `n` is the number of things to choose from, and we 50 | choose `r` of them. Repetition allowed, 51 | order doesn't matter. 52 | 53 | ## References 54 | 55 | [Math Is Fun](https://www.mathsisfun.com/combinatorics/combinations-permutations.html) 56 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/__test__/combineWithRepetitions.test.js: -------------------------------------------------------------------------------- 1 | import combineWithRepetitions from '../combineWithRepetitions'; 2 | import factorial from '../../../math/factorial/factorial'; 3 | 4 | describe('combineWithRepetitions', () => { 5 | it('should combine string with repetitions', () => { 6 | expect(combineWithRepetitions(['A'], 1)).toEqual([ 7 | ['A'], 8 | ]); 9 | 10 | expect(combineWithRepetitions(['A', 'B'], 1)).toEqual([ 11 | ['A'], 12 | ['B'], 13 | ]); 14 | 15 | expect(combineWithRepetitions(['A', 'B'], 2)).toEqual([ 16 | ['A', 'A'], 17 | ['A', 'B'], 18 | ['B', 'B'], 19 | ]); 20 | 21 | expect(combineWithRepetitions(['A', 'B'], 3)).toEqual([ 22 | ['A', 'A', 'A'], 23 | ['A', 'A', 'B'], 24 | ['A', 'B', 'B'], 25 | ['B', 'B', 'B'], 26 | ]); 27 | 28 | expect(combineWithRepetitions(['A', 'B', 'C'], 2)).toEqual([ 29 | ['A', 'A'], 30 | ['A', 'B'], 31 | ['A', 'C'], 32 | ['B', 'B'], 33 | ['B', 'C'], 34 | ['C', 'C'], 35 | ]); 36 | 37 | expect(combineWithRepetitions(['A', 'B', 'C'], 3)).toEqual([ 38 | ['A', 'A', 'A'], 39 | ['A', 'A', 'B'], 40 | ['A', 'A', 'C'], 41 | ['A', 'B', 'B'], 42 | ['A', 'B', 'C'], 43 | ['A', 'C', 'C'], 44 | ['B', 'B', 'B'], 45 | ['B', 'B', 'C'], 46 | ['B', 'C', 'C'], 47 | ['C', 'C', 'C'], 48 | ]); 49 | 50 | const combinationOptions = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; 51 | const combinationSlotsNumber = 4; 52 | const combinations = combineWithRepetitions(combinationOptions, combinationSlotsNumber); 53 | const n = combinationOptions.length; 54 | const r = combinationSlotsNumber; 55 | const expectedNumberOfCombinations = factorial((r + n) - 1) / (factorial(r) * factorial(n - 1)); 56 | 57 | expect(combinations.length).toBe(expectedNumberOfCombinations); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/__test__/combineWithoutRepetitions.test.js: -------------------------------------------------------------------------------- 1 | import combineWithoutRepetitions from '../combineWithoutRepetitions'; 2 | import factorial from '../../../math/factorial/factorial'; 3 | 4 | describe('combineWithoutRepetitions', () => { 5 | it('should combine string without repetitions', () => { 6 | expect(combineWithoutRepetitions(['A', 'B'], 3)).toEqual([]); 7 | 8 | expect(combineWithoutRepetitions(['A', 'B'], 1)).toEqual([ 9 | ['A'], 10 | ['B'], 11 | ]); 12 | 13 | expect(combineWithoutRepetitions(['A'], 1)).toEqual([ 14 | ['A'], 15 | ]); 16 | 17 | expect(combineWithoutRepetitions(['A', 'B'], 2)).toEqual([ 18 | ['A', 'B'], 19 | ]); 20 | 21 | expect(combineWithoutRepetitions(['A', 'B', 'C'], 2)).toEqual([ 22 | ['A', 'B'], 23 | ['A', 'C'], 24 | ['B', 'C'], 25 | ]); 26 | 27 | expect(combineWithoutRepetitions(['A', 'B', 'C'], 3)).toEqual([ 28 | ['A', 'B', 'C'], 29 | ]); 30 | 31 | expect(combineWithoutRepetitions(['A', 'B', 'C', 'D'], 3)).toEqual([ 32 | ['A', 'B', 'C'], 33 | ['A', 'B', 'D'], 34 | ['A', 'C', 'D'], 35 | ['B', 'C', 'D'], 36 | ]); 37 | 38 | expect(combineWithoutRepetitions(['A', 'B', 'C', 'D', 'E'], 3)).toEqual([ 39 | ['A', 'B', 'C'], 40 | ['A', 'B', 'D'], 41 | ['A', 'B', 'E'], 42 | ['A', 'C', 'D'], 43 | ['A', 'C', 'E'], 44 | ['A', 'D', 'E'], 45 | ['B', 'C', 'D'], 46 | ['B', 'C', 'E'], 47 | ['B', 'D', 'E'], 48 | ['C', 'D', 'E'], 49 | ]); 50 | 51 | const combinationOptions = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; 52 | const combinationSlotsNumber = 4; 53 | const combinations = combineWithoutRepetitions(combinationOptions, combinationSlotsNumber); 54 | const n = combinationOptions.length; 55 | const r = combinationSlotsNumber; 56 | const expectedNumberOfCombinations = factorial(n) / (factorial(r) * factorial(n - r)); 57 | 58 | expect(combinations.length).toBe(expectedNumberOfCombinations); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/combineWithRepetitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} combinationOptions 3 | * @param {number} combinationLength 4 | * @return {*[]} 5 | */ 6 | 7 | export default function combineWithRepetitions(combinationOptions, combinationLength) { 8 | // If combination length equal to 0 then return empty combination. 9 | if (combinationLength === 0) { 10 | return [[]]; 11 | } 12 | 13 | // If combination options are empty then return "no-combinations" array. 14 | if (combinationOptions.length === 0) { 15 | return []; 16 | } 17 | 18 | // Init combinations array. 19 | const combos = []; 20 | 21 | // Find all shorter combinations and attach head to each of those. 22 | const headCombo = [combinationOptions[0]]; 23 | const shorterCombos = combineWithRepetitions(combinationOptions, combinationLength - 1); 24 | 25 | for (let combinationIndex = 0; combinationIndex < shorterCombos.length; combinationIndex += 1) { 26 | const combo = headCombo.concat(shorterCombos[combinationIndex]); 27 | combos.push(combo); 28 | } 29 | 30 | // Let's shift head to the right and calculate all the rest combinations. 31 | const combinationsWithoutHead = combineWithRepetitions( 32 | combinationOptions.slice(1), 33 | combinationLength, 34 | ); 35 | 36 | // Join all combinations and return them. 37 | return combos.concat(combinationsWithoutHead); 38 | } 39 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/combineWithoutRepetitions.js: -------------------------------------------------------------------------------- 1 | /* 2 | @see: https://stackoverflow.com/a/127898/7794070 3 | 4 | Lets say your array of letters looks like this: "ABCDEFGH". 5 | You have three indices (i, j, k) indicating which letters you 6 | are going to use for the current word, You start with: 7 | 8 | A B C D E F G H 9 | ^ ^ ^ 10 | i j k 11 | 12 | First you vary k, so the next step looks like that: 13 | 14 | A B C D E F G H 15 | ^ ^ ^ 16 | i j k 17 | 18 | If you reached the end you go on and vary j and then k again. 19 | 20 | A B C D E F G H 21 | ^ ^ ^ 22 | i j k 23 | 24 | A B C D E F G H 25 | ^ ^ ^ 26 | i j k 27 | 28 | Once you j reached G you start also to vary i. 29 | 30 | A B C D E F G H 31 | ^ ^ ^ 32 | i j k 33 | 34 | A B C D E F G H 35 | ^ ^ ^ 36 | i j k 37 | ... 38 | */ 39 | 40 | /** 41 | * @param {*[]} combinationOptions 42 | * @param {number} combinationLength 43 | * @return {*[]} 44 | */ 45 | export default function combineWithoutRepetitions(combinationOptions, combinationLength) { 46 | // If combination length is just 1 then return combinationOptions. 47 | if (combinationLength === 1) { 48 | return combinationOptions.map(option => [option]); 49 | } 50 | 51 | // Init combinations array. 52 | const combinations = []; 53 | 54 | for (let i = 0; i <= (combinationOptions.length - combinationLength); i += 1) { 55 | const smallerCombinations = combineWithoutRepetitions( 56 | combinationOptions.slice(i + 1), 57 | combinationLength - 1, 58 | ); 59 | 60 | for (let j = 0; j < smallerCombinations.length; j += 1) { 61 | const combination = [combinationOptions[i]].concat(smallerCombinations[j]); 62 | combinations.push(combination); 63 | } 64 | } 65 | 66 | // Return all calculated combinations. 67 | return combinations; 68 | } 69 | -------------------------------------------------------------------------------- /src/algorithms/sets/fisher-yates/README.md: -------------------------------------------------------------------------------- 1 | # Fisher–Yates shuffle 2 | 3 | The Fisher–Yates shuffle is an algorithm for generating a random 4 | permutation of a finite sequence—in plain terms, the algorithm 5 | shuffles the sequence. The algorithm effectively puts all the 6 | elements into a hat; it continually determines the next element 7 | by randomly drawing an element from the hat until no elements 8 | remain. The algorithm produces an unbiased permutation: every 9 | permutation is equally likely. The modern version of the 10 | algorithm is efficient: it takes time proportional to the 11 | number of items being shuffled and shuffles them in place. 12 | 13 | ## References 14 | 15 | [Wikipedia](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 16 | -------------------------------------------------------------------------------- /src/algorithms/sets/fisher-yates/__test__/fisherYates.test.js: -------------------------------------------------------------------------------- 1 | import fisherYates from '../fisherYates'; 2 | import { sortedArr } from '../../../sorting/SortTester'; 3 | import QuickSort from '../../../sorting/quick-sort/QuickSort'; 4 | 5 | describe('fisherYates', () => { 6 | it('should shuffle small arrays', () => { 7 | expect(fisherYates([])).toEqual([]); 8 | expect(fisherYates([1])).toEqual([1]); 9 | }); 10 | 11 | it('should shuffle array randomly', () => { 12 | const shuffledArray = fisherYates(sortedArr); 13 | const sorter = new QuickSort(); 14 | 15 | expect(shuffledArray.length).toBe(sortedArr.length); 16 | expect(shuffledArray).not.toEqual(sortedArr); 17 | expect(sorter.sort(shuffledArray)).toEqual(sortedArr); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/algorithms/sets/fisher-yates/fisherYates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} originalArray 3 | * @return {*[]} 4 | */ 5 | export default function fisherYates(originalArray) { 6 | // Clone array from preventing original array from modification (for testing purpose). 7 | const array = originalArray.slice(0); 8 | 9 | if (array.length <= 1) { 10 | return array; 11 | } 12 | 13 | for (let i = (array.length - 1); i > 0; i -= 1) { 14 | const randomIndex = Math.floor(Math.random() * (i + 1)); 15 | const tmp = array[randomIndex]; 16 | array[randomIndex] = array[i]; 17 | array[i] = tmp; 18 | } 19 | 20 | return array; 21 | } 22 | -------------------------------------------------------------------------------- /src/algorithms/sets/knapsack-problem/KnapsackItem.js: -------------------------------------------------------------------------------- 1 | export default class KnapsackItem { 2 | /** 3 | * @param {Object} itemSettings - knapsack item settings, 4 | * @param {number} itemSettings.value - value of the item. 5 | * @param {number} itemSettings.weight - weight of the item. 6 | * @param {number} itemSettings.itemsInStock - how many items are available to be added. 7 | */ 8 | constructor({ value, weight, itemsInStock = 1 }) { 9 | this.value = value; 10 | this.weight = weight; 11 | this.itemsInStock = itemsInStock; 12 | // Actual number of items that is going to be added to knapsack. 13 | this.quantity = 1; 14 | } 15 | 16 | get totalValue() { 17 | return this.value * this.quantity; 18 | } 19 | 20 | get totalWeight() { 21 | return this.weight * this.quantity; 22 | } 23 | 24 | // This coefficient shows how valuable the 1 unit of weight is 25 | // for current item. 26 | get valuePerWeightRatio() { 27 | return this.value / this.weight; 28 | } 29 | 30 | toString() { 31 | return `v${this.value} w${this.weight} x ${this.quantity}`; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/algorithms/sets/knapsack-problem/__test__/KnapsackItem.test.js: -------------------------------------------------------------------------------- 1 | import KnapsackItem from '../KnapsackItem'; 2 | 3 | describe('KnapsackItem', () => { 4 | it('should create knapsack item and count its total weight and value', () => { 5 | const knapsackItem = new KnapsackItem({ value: 3, weight: 2 }); 6 | 7 | expect(knapsackItem.value).toBe(3); 8 | expect(knapsackItem.weight).toBe(2); 9 | expect(knapsackItem.quantity).toBe(1); 10 | expect(knapsackItem.valuePerWeightRatio).toBe(1.5); 11 | expect(knapsackItem.toString()).toBe('v3 w2 x 1'); 12 | expect(knapsackItem.totalValue).toBe(3); 13 | expect(knapsackItem.totalWeight).toBe(2); 14 | 15 | knapsackItem.quantity = 0; 16 | 17 | expect(knapsackItem.value).toBe(3); 18 | expect(knapsackItem.weight).toBe(2); 19 | expect(knapsackItem.quantity).toBe(0); 20 | expect(knapsackItem.valuePerWeightRatio).toBe(1.5); 21 | expect(knapsackItem.toString()).toBe('v3 w2 x 0'); 22 | expect(knapsackItem.totalValue).toBe(0); 23 | expect(knapsackItem.totalWeight).toBe(0); 24 | 25 | knapsackItem.quantity = 2; 26 | 27 | expect(knapsackItem.value).toBe(3); 28 | expect(knapsackItem.weight).toBe(2); 29 | expect(knapsackItem.quantity).toBe(2); 30 | expect(knapsackItem.valuePerWeightRatio).toBe(1.5); 31 | expect(knapsackItem.toString()).toBe('v3 w2 x 2'); 32 | expect(knapsackItem.totalValue).toBe(6); 33 | expect(knapsackItem.totalWeight).toBe(4); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequnce/README.md: -------------------------------------------------------------------------------- 1 | # Longest common subsequence problem 2 | 3 | The longest common subsequence (LCS) problem is the problem of finding 4 | the longest subsequence common to all sequences in a set of sequences 5 | (often just two sequences). It differs from the longest common substring 6 | problem: unlike substrings, subsequences are not required to occupy 7 | consecutive positions within the original sequences. 8 | 9 | ## Application 10 | 11 | The longest common subsequence problem is a classic computer science 12 | problem, the basis of data comparison programs such as the diff utility, 13 | and has applications in bioinformatics. It is also widely used by 14 | revision control systems such as Git for reconciling multiple changes 15 | made to a revision-controlled collection of files. 16 | 17 | ## Example 18 | 19 | - LCS for input Sequences `ABCDGH` and `AEDFHR` is `ADH` of length 3. 20 | - LCS for input Sequences `AGGTAB` and `GXTXAYB` is `GTAB` of length 4. 21 | 22 | ## References 23 | 24 | - [Wikipedia](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem) 25 | - [YouTube](https://www.youtube.com/watch?v=NnD96abizww&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 26 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequnce/__test__/longestCommonSubsequnce.test.js: -------------------------------------------------------------------------------- 1 | import longestCommonSubsequnce from '../longestCommonSubsequnce'; 2 | 3 | describe('longestCommonSubsequnce', () => { 4 | it('should find longest common subsequence for two strings', () => { 5 | expect(longestCommonSubsequnce([''], [''])).toEqual(['']); 6 | 7 | expect(longestCommonSubsequnce([''], ['A', 'B', 'C'])).toEqual(['']); 8 | 9 | expect(longestCommonSubsequnce(['A', 'B', 'C'], [''])).toEqual(['']); 10 | 11 | expect(longestCommonSubsequnce( 12 | ['A', 'B', 'C'], 13 | ['D', 'E', 'F', 'G'], 14 | )).toEqual(['']); 15 | 16 | expect(longestCommonSubsequnce( 17 | ['A', 'B', 'C', 'D', 'G', 'H'], 18 | ['A', 'E', 'D', 'F', 'H', 'R'], 19 | )).toEqual(['A', 'D', 'H']); 20 | 21 | expect(longestCommonSubsequnce( 22 | ['A', 'G', 'G', 'T', 'A', 'B'], 23 | ['G', 'X', 'T', 'X', 'A', 'Y', 'B'], 24 | )).toEqual(['G', 'T', 'A', 'B']); 25 | 26 | expect(longestCommonSubsequnce( 27 | ['A', 'B', 'C', 'D', 'A', 'F'], 28 | ['A', 'C', 'B', 'C', 'F'], 29 | )).toEqual(['A', 'B', 'C', 'F']); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequnce/longestCommonSubsequnce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} set1 3 | * @param {string[]} set2 4 | * @return {string[]} 5 | */ 6 | export default function longestCommonSubsequnce(set1, set2) { 7 | // Init LCS matrix. 8 | const lcsMatrix = Array(set2.length + 1).fill(null).map(() => Array(set1.length + 1).fill(null)); 9 | 10 | // Fill first row with zeros. 11 | for (let columnIndex = 0; columnIndex <= set1.length; columnIndex += 1) { 12 | lcsMatrix[0][columnIndex] = 0; 13 | } 14 | 15 | // Fill first column with zeros. 16 | for (let rowIndex = 0; rowIndex <= set2.length; rowIndex += 1) { 17 | lcsMatrix[rowIndex][0] = 0; 18 | } 19 | 20 | // Fill rest of the column that correspond to each of two strings. 21 | for (let rowIndex = 1; rowIndex <= set2.length; rowIndex += 1) { 22 | for (let columnIndex = 1; columnIndex <= set1.length; columnIndex += 1) { 23 | if (set1[columnIndex - 1] === set2[rowIndex - 1]) { 24 | lcsMatrix[rowIndex][columnIndex] = lcsMatrix[rowIndex - 1][columnIndex - 1] + 1; 25 | } else { 26 | lcsMatrix[rowIndex][columnIndex] = Math.max( 27 | lcsMatrix[rowIndex - 1][columnIndex], 28 | lcsMatrix[rowIndex][columnIndex - 1], 29 | ); 30 | } 31 | } 32 | } 33 | 34 | // Calculate LCS based on LCS matrix. 35 | if (!lcsMatrix[set2.length][set1.length]) { 36 | // If the length of largest common string is zero then return empty string. 37 | return ['']; 38 | } 39 | 40 | const longestSequence = []; 41 | let columnIndex = set1.length; 42 | let rowIndex = set2.length; 43 | 44 | while (columnIndex > 0 || rowIndex > 0) { 45 | if (set1[columnIndex - 1] === set2[rowIndex - 1]) { 46 | // Move by diagonal left-top. 47 | longestSequence.unshift(set1[columnIndex - 1]); 48 | columnIndex -= 1; 49 | rowIndex -= 1; 50 | } else if (lcsMatrix[rowIndex][columnIndex] === lcsMatrix[rowIndex][columnIndex - 1]) { 51 | // Move left. 52 | columnIndex -= 1; 53 | } else { 54 | // Move up. 55 | rowIndex -= 1; 56 | } 57 | } 58 | 59 | return longestSequence; 60 | } 61 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-increasing-subsequence/README.md: -------------------------------------------------------------------------------- 1 | # Longest Increasing Subsequence 2 | 3 | The longest increasing subsequence problem is to find a subsequence of a 4 | given sequence in which the subsequence's elements are in sorted order, 5 | lowest to highest, and in which the subsequence is as long as possible. 6 | This subsequence is not necessarily contiguous, or unique. 7 | 8 | ## Complexity 9 | 10 | The longest increasing subsequence problem is solvable in 11 | time `O(n log n)`, where `n` denotes the length of the input sequence. 12 | 13 | Dynamic programming approach has complexity `O(n * n)`. 14 | 15 | ## Example 16 | 17 | In the first 16 terms of the binary Van der Corput sequence 18 | 19 | ``` 20 | 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 21 | ``` 22 | 23 | a longest increasing subsequence is 24 | 25 | ``` 26 | 0, 2, 6, 9, 11, 15. 27 | ``` 28 | 29 | This subsequence has length six; 30 | the input sequence has no seven-member increasing subsequences. 31 | The longest increasing subsequence in this example is not unique: for 32 | instance, 33 | 34 | ``` 35 | 0, 4, 6, 9, 11, 15 or 36 | 0, 2, 6, 9, 13, 15 or 37 | 0, 4, 6, 9, 13, 15 38 | ``` 39 | 40 | are other increasing subsequences of equal length in the same 41 | input sequence. 42 | 43 | ## References 44 | 45 | - [Wikipedia](https://en.wikipedia.org/wiki/Longest_increasing_subsequence) 46 | - [Dynamic Programming Approach on YouTube](https://www.youtube.com/watch?v=CE2b_-XfVDk&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 47 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-increasing-subsequence/__test__/dpLongestIncreasingSubsequence.test.js: -------------------------------------------------------------------------------- 1 | import dpLongestIncreasingSubsequence from '../dpLongestIncreasingSubsequence'; 2 | 3 | describe('dpLongestIncreasingSubsequence', () => { 4 | it('should find longest increasing subsequence length', () => { 5 | // Should be: 6 | // 9 or 7 | // 8 or 8 | // 7 or 9 | // 6 or 10 | // ... 11 | expect(dpLongestIncreasingSubsequence([ 12 | 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 13 | ])).toBe(1); 14 | 15 | // Should be: 16 | // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 17 | expect(dpLongestIncreasingSubsequence([ 18 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 19 | ])).toBe(10); 20 | 21 | // Should be: 22 | // -1, 0, 2, 3 23 | expect(dpLongestIncreasingSubsequence([ 24 | 3, 4, -1, 0, 6, 2, 3, 25 | ])).toBe(4); 26 | 27 | // Should be: 28 | // 0, 2, 6, 9, 11, 15 or 29 | // 0, 4, 6, 9, 11, 15 or 30 | // 0, 2, 6, 9, 13, 15 or 31 | // 0, 4, 6, 9, 13, 15 32 | expect(dpLongestIncreasingSubsequence([ 33 | 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15, 34 | ])).toBe(6); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-increasing-subsequence/dpLongestIncreasingSubsequence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dynamic programming approach to find longest increasing subsequence. 3 | * Complexity: O(n * n) 4 | * 5 | * @param {number[]} sequence 6 | * @return {number} 7 | */ 8 | export default function dpLongestIncreasingSubsequence(sequence) { 9 | // Create array with longest increasing substrings length and 10 | // fill it with 1-s that would mean that each element of the sequence 11 | // is itself a minimum increasing subsequence. 12 | const lengthsArray = Array(sequence.length).fill(1); 13 | 14 | let previousElementIndex = 0; 15 | let currentElementIndex = 1; 16 | 17 | while (currentElementIndex < sequence.length) { 18 | if (sequence[previousElementIndex] < sequence[currentElementIndex]) { 19 | // If current element is bigger then the previous one then 20 | // current element is a part of increasing subsequence which 21 | // length is by one bigger then the length of increasing subsequence 22 | // for previous element. 23 | const newLength = lengthsArray[previousElementIndex] + 1; 24 | if (newLength > lengthsArray[currentElementIndex]) { 25 | // Increase only if previous element would give us bigger subsequence length 26 | // then we already have for current element. 27 | lengthsArray[currentElementIndex] = newLength; 28 | } 29 | } 30 | 31 | // Move previous element index right. 32 | previousElementIndex += 1; 33 | 34 | // If previous element index equals to current element index then 35 | // shift current element right and reset previous element index to zero. 36 | if (previousElementIndex === currentElementIndex) { 37 | currentElementIndex += 1; 38 | previousElementIndex = 0; 39 | } 40 | } 41 | 42 | // Find the biggest element in lengthsArray. 43 | // This number is the biggest length of increasing subsequence. 44 | let longestIncreasingLength = 0; 45 | 46 | for (let i = 0; i < lengthsArray.length; i += 1) { 47 | if (lengthsArray[i] > longestIncreasingLength) { 48 | longestIncreasingLength = lengthsArray[i]; 49 | } 50 | } 51 | 52 | return longestIncreasingLength; 53 | } 54 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/README.md: -------------------------------------------------------------------------------- 1 | # Maximum subarray problem 2 | 3 | The maximum subarray problem is the task of finding the contiguous 4 | subarray within a one-dimensional array, `a[1...n]`, of numbers 5 | which has the largest sum, where, 6 | 7 | ![Maximum subarray](https://wikimedia.org/api/rest_v1/media/math/render/svg/e8960f093107b71b21827e726e2bad8b023779b2) 8 | 9 | ## Example 10 | 11 | The list usually contains both positive and negative numbers along 12 | with `0`. For example, for the array of 13 | values `−2, 1, −3, 4, −1, 2, 1, −5, 4` the contiguous subarray 14 | with the largest sum is `4, −1, 2, 1`, with sum `6`. 15 | 16 | ## References 17 | 18 | - [Wikipedia](https://en.wikipedia.org/wiki/Maximum_subarray_problem) 19 | - [YouTube](https://www.youtube.com/watch?v=ohHWQf1HDfU&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 20 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/__test__/bfMaximumSubarray.test.js: -------------------------------------------------------------------------------- 1 | import bfMaximumSubarray from '../bfMaximumSubarray'; 2 | 3 | describe('bfMaximumSubarray', () => { 4 | it('should find maximum subarray using brute force algorithm', () => { 5 | expect(bfMaximumSubarray([])).toEqual([]); 6 | expect(bfMaximumSubarray([-1, -2, -3, -4, -5])).toEqual([-1]); 7 | expect(bfMaximumSubarray([1, 2, 3, 2, 3, 4, 5])).toEqual([1, 2, 3, 2, 3, 4, 5]); 8 | expect(bfMaximumSubarray([-2, 1, -3, 4, -1, 2, 1, -5, 4])).toEqual([4, -1, 2, 1]); 9 | expect(bfMaximumSubarray([-2, -3, 4, -1, -2, 1, 5, -3])).toEqual([4, -1, -2, 1, 5]); 10 | expect(bfMaximumSubarray([1, -3, 2, -5, 7, 6, -1, 4, 11, -23])).toEqual([7, 6, -1, 4, 11]); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/__test__/dpMaximumSubarray.test.js: -------------------------------------------------------------------------------- 1 | import dpMaximumSubarray from '../dpMaximumSubarray'; 2 | 3 | describe('dpMaximumSubarray', () => { 4 | it('should find maximum subarray using dynamic programming algorithm', () => { 5 | expect(dpMaximumSubarray([])).toEqual([]); 6 | expect(dpMaximumSubarray([-1, -2, -3, -4, -5])).toEqual([-1]); 7 | expect(dpMaximumSubarray([1, 2, 3, 2, 3, 4, 5])).toEqual([1, 2, 3, 2, 3, 4, 5]); 8 | expect(dpMaximumSubarray([-2, 1, -3, 4, -1, 2, 1, -5, 4])).toEqual([4, -1, 2, 1]); 9 | expect(dpMaximumSubarray([-2, -3, 4, -1, -2, 1, 5, -3])).toEqual([4, -1, -2, 1, 5]); 10 | expect(dpMaximumSubarray([1, -3, 2, -5, 7, 6, -1, 4, 11, -23])).toEqual([7, 6, -1, 4, 11]); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/bfMaximumSubarray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Brute Force solution. 3 | * Complexity: O(n^2) 4 | * 5 | * @param {Number[]} inputArray 6 | * @return {Number[]} 7 | */ 8 | export default function bfMaximumSubarray(inputArray) { 9 | let maxSubarrayStartIndex = 0; 10 | let maxSubarrayLength = 0; 11 | let maxSubarraySum = null; 12 | 13 | for (let startIndex = 0; startIndex < inputArray.length; startIndex += 1) { 14 | let subarraySum = 0; 15 | for (let arrLength = 1; arrLength <= (inputArray.length - startIndex); arrLength += 1) { 16 | subarraySum += inputArray[startIndex + (arrLength - 1)]; 17 | if (maxSubarraySum === null || subarraySum > maxSubarraySum) { 18 | maxSubarraySum = subarraySum; 19 | maxSubarrayStartIndex = startIndex; 20 | maxSubarrayLength = arrLength; 21 | } 22 | } 23 | } 24 | 25 | return inputArray.slice(maxSubarrayStartIndex, maxSubarrayStartIndex + maxSubarrayLength); 26 | } 27 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/dpMaximumSubarray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dynamic Programming solution. 3 | * Complexity: O(n) 4 | * 5 | * @param {Number[]} inputArray 6 | * @return {Number[]} 7 | */ 8 | export default function dpMaximumSubarray(inputArray) { 9 | // Check if all elements of inputArray are negative ones and return the highest 10 | // one in this case. 11 | let allNegative = true; 12 | let highestElementValue = null; 13 | for (let i = 0; i < inputArray.length; i += 1) { 14 | if (inputArray[i] >= 0) { 15 | allNegative = false; 16 | } 17 | 18 | if (highestElementValue === null || highestElementValue < inputArray[i]) { 19 | highestElementValue = inputArray[i]; 20 | } 21 | } 22 | 23 | if (allNegative && highestElementValue !== null) { 24 | return [highestElementValue]; 25 | } 26 | 27 | // Let's assume that there is at list one positive integer exists in array. 28 | // And thus the maximum sum will for sure be grater then 0. Thus we're able 29 | // to always reset max sum to zero. 30 | let maxSum = 0; 31 | 32 | // This array will keep a combination that gave the highest sum. 33 | let maxSubArray = []; 34 | 35 | // Current sum and subarray that will memoize all previous computations. 36 | let currentSum = 0; 37 | let currentSubArray = []; 38 | 39 | for (let i = 0; i < inputArray.length; i += 1) { 40 | // Let's add current element value to the current sum. 41 | currentSum += inputArray[i]; 42 | 43 | if (currentSum < 0) { 44 | // If the sum went below zero then reset it and don't add current element to max subarray. 45 | currentSum = 0; 46 | // Reset current subarray. 47 | currentSubArray = []; 48 | } else { 49 | // If current sum stays positive then add current element to current sub array. 50 | currentSubArray.push(inputArray[i]); 51 | 52 | if (currentSum > maxSum) { 53 | // If current sum became greater then max registered sum then update 54 | // max sum and max subarray. 55 | maxSum = currentSum; 56 | maxSubArray = currentSubArray.slice(); 57 | } 58 | } 59 | } 60 | 61 | return maxSubArray; 62 | } 63 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/README.md: -------------------------------------------------------------------------------- 1 | # Permutations 2 | 3 | When the order doesn't matter, it is a **Combination**. 4 | 5 | When the order **does** matter it is a **Permutation**. 6 | 7 | **"The combination to the safe is 472"**. We do care about the order. `724` won't work, nor will `247`. 8 | It has to be exactly `4-7-2`. 9 | 10 | ## Permutations without repetitions 11 | 12 | A permutation, also called an “arrangement number” or “order”, is a rearrangement of 13 | the elements of an ordered list `S` into a one-to-one correspondence with `S` itself. 14 | 15 | Below are the permutations of string `ABC`. 16 | 17 | `ABC ACB BAC BCA CBA CAB` 18 | 19 | Or for example the first three people in a running race: you can't be first and second. 20 | 21 | **Number of combinations** 22 | 23 | ``` 24 | n * (n-1) * (n -2) * ... * 1 = n! 25 | ``` 26 | 27 | ## Permutations with repetitions 28 | 29 | When repetition is allowed we have permutations with repetitions. 30 | For example the the lock below: it could be `333`. 31 | 32 | ![Permutation Lock](https://www.mathsisfun.com/combinatorics/images/combination-lock.jpg) 33 | 34 | **Number of combinations** 35 | 36 | ``` 37 | n * n * n ... (r times) = n^r 38 | ``` 39 | 40 | ## References 41 | 42 | [Math Is Fun](https://www.mathsisfun.com/combinatorics/combinations-permutations.html) 43 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/__test__/permutateWithRepetitions.test.js: -------------------------------------------------------------------------------- 1 | import permutateWithRepetitions from '../permutateWithRepetitions'; 2 | 3 | describe('permutateWithRepetitions', () => { 4 | it('should permutate string with repetition', () => { 5 | const permutations0 = permutateWithRepetitions([]); 6 | expect(permutations0).toEqual([]); 7 | 8 | const permutations1 = permutateWithRepetitions(['A']); 9 | expect(permutations1).toEqual([ 10 | ['A'], 11 | ]); 12 | 13 | const permutations2 = permutateWithRepetitions(['A', 'B']); 14 | expect(permutations2).toEqual([ 15 | ['A', 'A'], 16 | ['A', 'B'], 17 | ['B', 'A'], 18 | ['B', 'B'], 19 | ]); 20 | 21 | const permutations3 = permutateWithRepetitions(['A', 'B', 'C']); 22 | expect(permutations3).toEqual([ 23 | ['A', 'A', 'A'], 24 | ['A', 'A', 'B'], 25 | ['A', 'A', 'C'], 26 | ['A', 'B', 'A'], 27 | ['A', 'B', 'B'], 28 | ['A', 'B', 'C'], 29 | ['A', 'C', 'A'], 30 | ['A', 'C', 'B'], 31 | ['A', 'C', 'C'], 32 | ['B', 'A', 'A'], 33 | ['B', 'A', 'B'], 34 | ['B', 'A', 'C'], 35 | ['B', 'B', 'A'], 36 | ['B', 'B', 'B'], 37 | ['B', 'B', 'C'], 38 | ['B', 'C', 'A'], 39 | ['B', 'C', 'B'], 40 | ['B', 'C', 'C'], 41 | ['C', 'A', 'A'], 42 | ['C', 'A', 'B'], 43 | ['C', 'A', 'C'], 44 | ['C', 'B', 'A'], 45 | ['C', 'B', 'B'], 46 | ['C', 'B', 'C'], 47 | ['C', 'C', 'A'], 48 | ['C', 'C', 'B'], 49 | ['C', 'C', 'C'], 50 | ]); 51 | 52 | const permutations4 = permutateWithRepetitions(['A', 'B', 'C', 'D']); 53 | expect(permutations4.length).toBe(4 * 4 * 4 * 4); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/permutateWithRepetitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} permutationOptions 3 | * @return {*[]} 4 | */ 5 | export default function permutateWithRepetitions(permutationOptions) { 6 | // There is no permutations for empty array. 7 | if (!permutationOptions || permutationOptions.length === 0) { 8 | return []; 9 | } 10 | 11 | // There is only one permutation for the 1-element array. 12 | if (permutationOptions.length === 1) { 13 | return [permutationOptions]; 14 | } 15 | 16 | // Let's create initial set of permutations. 17 | let previousPermutations = permutationOptions.map(option => [option]); 18 | let currentPermutations = []; 19 | let permutationSize = 1; 20 | 21 | // While the size of each permutation is less then or equal to options length... 22 | while (permutationSize < permutationOptions.length) { 23 | // Reset all current permutations. 24 | currentPermutations = []; 25 | 26 | for (let permIndex = 0; permIndex < previousPermutations.length; permIndex += 1) { 27 | for (let optionIndex = 0; optionIndex < permutationOptions.length; optionIndex += 1) { 28 | let currentPermutation = previousPermutations[permIndex]; 29 | currentPermutation = currentPermutation.concat([permutationOptions[optionIndex]]); 30 | currentPermutations.push(currentPermutation); 31 | } 32 | } 33 | 34 | // Make current permutations to be the previous ones. 35 | previousPermutations = currentPermutations.slice(0); 36 | 37 | // Increase permutation size counter. 38 | permutationSize += 1; 39 | } 40 | 41 | return currentPermutations; 42 | } 43 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/permutateWithoutRepetitions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {*[]} permutationOptions 3 | * @return {*[]} 4 | */ 5 | export default function permutateWithoutRepetitions(permutationOptions) { 6 | if (permutationOptions.length === 0) { 7 | return []; 8 | } 9 | 10 | if (permutationOptions.length === 1) { 11 | return [permutationOptions]; 12 | } 13 | 14 | const permutations = []; 15 | 16 | // Get all permutations of length (n - 1). 17 | const previousOptions = permutationOptions.slice(0, permutationOptions.length - 1); 18 | const previousPermutations = permutateWithoutRepetitions(previousOptions); 19 | 20 | // Insert last option into every possible position of every previous permutation. 21 | const lastOption = permutationOptions.slice(permutationOptions.length - 1); 22 | 23 | for ( 24 | let permutationIndex = 0; 25 | permutationIndex < previousPermutations.length; 26 | permutationIndex += 1 27 | ) { 28 | const currentPermutation = previousPermutations[permutationIndex]; 29 | 30 | // Insert last option into every possible position of currentPermutation. 31 | for (let positionIndex = 0; positionIndex <= currentPermutation.length; positionIndex += 1) { 32 | const permutationPrefix = currentPermutation.slice(0, positionIndex); 33 | const permutationSuffix = currentPermutation.slice(positionIndex); 34 | permutations.push(permutationPrefix.concat(lastOption, permutationSuffix)); 35 | } 36 | } 37 | 38 | return permutations; 39 | } 40 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/README.md: -------------------------------------------------------------------------------- 1 | # Power Set 2 | 3 | Power set of a set A is the set of all of the subsets of A. 4 | 5 | Eg. for `{x, y, z}`, the subsets are : `{{}, {x}, {y}, {z}, {x, y}, {x, z}, {y, z}, {x, y, z}}` 6 | 7 | ![Power Set](https://www.mathsisfun.com/sets/images/power-set.svg) 8 | 9 | ## References 10 | 11 | * [Wikipedia](https://en.wikipedia.org/wiki/Power_set) 12 | * [Math is Fun](https://www.mathsisfun.com/sets/power-set.html) 13 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/__test__/powerSet.test.js: -------------------------------------------------------------------------------- 1 | import powerSet from '../powerSet'; 2 | 3 | describe('powerSet', () => { 4 | it('should calculate power set of given set', () => { 5 | const powerSets1 = powerSet([1]); 6 | const powerSets2 = powerSet([1, 2, 3]); 7 | 8 | expect(powerSets1).toEqual([ 9 | [], 10 | [1], 11 | ]); 12 | 13 | expect(powerSets2).toEqual([ 14 | [], 15 | [1], 16 | [2], 17 | [1, 2], 18 | [3], 19 | [1, 3], 20 | [2, 3], 21 | [1, 2, 3], 22 | ]); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/powerSet.js: -------------------------------------------------------------------------------- 1 | export default function powerSet(originalSet) { 2 | const subSets = []; 3 | 4 | // We will have 2^n possible combinations (where n is a length of original set). 5 | // It is because for every element of original set we will decide whether to include 6 | // it or not (2 options for each set element). 7 | const numberOfCombinations = 2 ** originalSet.length; 8 | 9 | // Each number in binary representation in a range from 0 to 2^n does exactly what we need: 10 | // it shoes by its bits (0 or 1) whether to include related element from the set or not. 11 | // For example, for the set {1, 2, 3} the binary number of 010 would mean that we need to 12 | // include only "2" to the current set. 13 | for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) { 14 | const subSet = []; 15 | 16 | for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) { 17 | // Decide whether we need to include current element into the subset or not. 18 | if (combinationIndex & (1 << setElementIndex)) { 19 | subSet.push(originalSet[setElementIndex]); 20 | } 21 | } 22 | 23 | // Add current subset to the list of all subsets. 24 | subSets.push(subSet); 25 | } 26 | 27 | return subSets; 28 | } 29 | -------------------------------------------------------------------------------- /src/algorithms/sets/shortest-common-supersequence/README.md: -------------------------------------------------------------------------------- 1 | # Shortest Common Supersequence 2 | 3 | The shortest common supersequence (SCS) of two sequences `X` and `Y` 4 | is the shortest sequence which has `X` and `Y` as subsequences. 5 | 6 | In other words assume we're given two strings str1 and str2, find 7 | the shortest string that has both str1 and str2 as subsequences. 8 | 9 | This is a problem closely related to the longest common 10 | subsequence problem. 11 | 12 | ## Example 13 | 14 | ``` 15 | Input: str1 = "geek", str2 = "eke" 16 | Output: "geeke" 17 | 18 | Input: str1 = "AGGTAB", str2 = "GXTXAYB" 19 | Output: "AGXGTXAYB" 20 | ``` 21 | 22 | ## References 23 | 24 | - [GeeksForGeeks](https://www.geeksforgeeks.org/shortest-common-supersequence/) 25 | -------------------------------------------------------------------------------- /src/algorithms/sets/shortest-common-supersequence/__test__/shortestCommonSupersequence.test.js: -------------------------------------------------------------------------------- 1 | import shortestCommonSupersequence from '../shortestCommonSupersequence'; 2 | 3 | describe('shortestCommonSupersequence', () => { 4 | it('should find shortest common supersequence of two sequences', () => { 5 | // LCS (longest common subsequence) is empty 6 | expect(shortestCommonSupersequence( 7 | ['A', 'B', 'C'], 8 | ['D', 'E', 'F'], 9 | )).toEqual(['A', 'B', 'C', 'D', 'E', 'F']); 10 | 11 | // LCS (longest common subsequence) is "EE" 12 | expect(shortestCommonSupersequence( 13 | ['G', 'E', 'E', 'K'], 14 | ['E', 'K', 'E'], 15 | )).toEqual(['G', 'E', 'K', 'E', 'K']); 16 | 17 | // LCS (longest common subsequence) is "GTAB" 18 | expect(shortestCommonSupersequence( 19 | ['A', 'G', 'G', 'T', 'A', 'B'], 20 | ['G', 'X', 'T', 'X', 'A', 'Y', 'B'], 21 | )).toEqual(['A', 'G', 'G', 'X', 'T', 'X', 'A', 'Y', 'B']); 22 | 23 | // LCS (longest common subsequence) is "BCBA". 24 | expect(shortestCommonSupersequence( 25 | ['A', 'B', 'C', 'B', 'D', 'A', 'B'], 26 | ['B', 'D', 'C', 'A', 'B', 'A'], 27 | )).toEqual(['A', 'B', 'D', 'C', 'A', 'B', 'D', 'A', 'B']); 28 | 29 | // LCS (longest common subsequence) is "BDABA". 30 | expect(shortestCommonSupersequence( 31 | ['B', 'D', 'C', 'A', 'B', 'A'], 32 | ['A', 'B', 'C', 'B', 'D', 'A', 'B', 'A', 'C'], 33 | )).toEqual(['A', 'B', 'C', 'B', 'D', 'C', 'A', 'B', 'A', 'C']); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/algorithms/sets/shortest-common-supersequence/shortestCommonSupersequence.js: -------------------------------------------------------------------------------- 1 | import longestCommonSubsequnce from '../longest-common-subsequnce/longestCommonSubsequnce'; 2 | 3 | /** 4 | * @param {string[]} set1 5 | * @param {string[]} set2 6 | * @return {string[]} 7 | */ 8 | 9 | export default function shortestCommonSupersequence(set1, set2) { 10 | // Let's first find the longest common subsequence of two sets. 11 | const lcs = longestCommonSubsequnce(set1, set2); 12 | 13 | // If LCS is empty then the shortest common supersequnce would be just 14 | // concatenation of two sequences. 15 | if (lcs.length === 1 && lcs[0] === '') { 16 | return set1.concat(set2); 17 | } 18 | 19 | // Now let's add elements of set1 and set2 in order before/inside/after the LCS. 20 | let supersequence = []; 21 | 22 | let setIndex1 = 0; 23 | let setIndex2 = 0; 24 | let lcsIndex = 0; 25 | let setOnHold1 = false; 26 | let setOnHold2 = false; 27 | 28 | while (lcsIndex < lcs.length) { 29 | // Add elements of the first set to supersequence in correct order. 30 | if (setIndex1 < set1.length) { 31 | if (!setOnHold1 && set1[setIndex1] !== lcs[lcsIndex]) { 32 | supersequence.push(set1[setIndex1]); 33 | setIndex1 += 1; 34 | } else { 35 | setOnHold1 = true; 36 | } 37 | } 38 | 39 | // Add elements of the second set to supersequence in correct order. 40 | if (setIndex2 < set2.length) { 41 | if (!setOnHold2 && set2[setIndex2] !== lcs[lcsIndex]) { 42 | supersequence.push(set2[setIndex2]); 43 | setIndex2 += 1; 44 | } else { 45 | setOnHold2 = true; 46 | } 47 | } 48 | 49 | // Add LCS element to the supersequence in correct order. 50 | if (setOnHold1 && setOnHold2) { 51 | supersequence.push(lcs[lcsIndex]); 52 | lcsIndex += 1; 53 | setIndex1 += 1; 54 | setIndex2 += 1; 55 | setOnHold1 = false; 56 | setOnHold2 = false; 57 | } 58 | } 59 | 60 | // Attach set1 leftovers. 61 | if (setIndex1 < set1.length) { 62 | supersequence = supersequence.concat(set1.slice(setIndex1)); 63 | } 64 | 65 | // Attach set2 leftovers. 66 | if (setIndex2 < set2.length) { 67 | supersequence = supersequence.concat(set2.slice(setIndex2)); 68 | } 69 | 70 | return supersequence; 71 | } 72 | -------------------------------------------------------------------------------- /src/algorithms/sorting/Sort.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/comparator/Comparator'; 2 | 3 | /** 4 | * @typedef {Object} SorterCallbacks 5 | * @property {function(a: *, b: *)} compareCallback - If provided then all elements comparisons 6 | * will be done through this callback. 7 | * @property {function(a: *)} visitingCallback - If provided it will be called each time the sorting 8 | * function is visiting the next element. 9 | */ 10 | 11 | export default class Sort { 12 | constructor(originalCallbacks) { 13 | this.callbacks = Sort.initSortingCallbacks(originalCallbacks); 14 | this.comparator = new Comparator(this.callbacks.compareCallback); 15 | } 16 | 17 | /** 18 | * @param {SorterCallbacks} originalCallbacks 19 | * @returns {SorterCallbacks} 20 | */ 21 | static initSortingCallbacks(originalCallbacks) { 22 | const callbacks = originalCallbacks || {}; 23 | const stubCallback = () => {}; 24 | 25 | callbacks.compareCallback = callbacks.compareCallback || undefined; 26 | callbacks.visitingCallback = callbacks.visitingCallback || stubCallback; 27 | 28 | return callbacks; 29 | } 30 | 31 | sort() { 32 | throw new Error('sort method must be implemented'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__test__/Sort.test.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | describe('Sort', () => { 4 | it('should throw an error when trying to call Sort.sort() method directly', () => { 5 | function doForbiddenSort() { 6 | const sorter = new Sort(); 7 | sorter.sort(); 8 | } 9 | 10 | expect(doForbiddenSort).toThrow(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/sorting/bubble-sort/BubbleSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class BubbleSort extends Sort { 4 | sort(originalArray) { 5 | // Flag that holds info about whether the swap has occur or not. 6 | let swapped = false; 7 | // Clone original array to prevent its modification. 8 | const array = [...originalArray]; 9 | 10 | for (let i = 1; i < array.length; i += 1) { 11 | swapped = false; 12 | 13 | // Call visiting callback. 14 | this.callbacks.visitingCallback(array[i]); 15 | 16 | for (let j = 0; j < array.length - i; j += 1) { 17 | // Call visiting callback. 18 | this.callbacks.visitingCallback(array[j]); 19 | 20 | // Swap elements if they are in wrong order. 21 | if (this.comparator.lessThan(array[j + 1], array[j])) { 22 | const tmp = array[j + 1]; 23 | array[j + 1] = array[j]; 24 | array[j] = tmp; 25 | 26 | // Register the swap. 27 | swapped = true; 28 | } 29 | } 30 | 31 | // If there were no swaps then array is already sorted and there is 32 | // no need to proceed. 33 | if (!swapped) { 34 | return array; 35 | } 36 | } 37 | 38 | return array; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/algorithms/sorting/bubble-sort/README.md: -------------------------------------------------------------------------------- 1 | # Bubble Sort 2 | 3 | Bubble sort, sometimes referred to as sinking sort, is a 4 | simple sorting algorithm that repeatedly steps through 5 | the list to be sorted, compares each pair of adjacent 6 | items and swaps them if they are in the wrong order. 7 | The pass through the list is repeated until no swaps 8 | are needed, which indicates that the list is sorted. 9 | 10 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/c/c8/Bubble-sort-example-300px.gif) 11 | 12 | ## References 13 | 14 | - [Wikipedia](https://en.wikipedia.org/wiki/Bubble_sort) 15 | - [YouTube](https://www.youtube.com/watch?v=6Gv8vg0kcHc&index=27&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 16 | -------------------------------------------------------------------------------- /src/algorithms/sorting/bubble-sort/__test__/BubbleSort.test.js: -------------------------------------------------------------------------------- 1 | import BubbleSort from '../BubbleSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 20; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 189; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; 14 | const EQUAL_ARRAY_VISITING_COUNT = 20; 15 | 16 | describe('BubbleSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(BubbleSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(BubbleSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(BubbleSort); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | BubbleSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | BubbleSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | BubbleSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | BubbleSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/algorithms/sorting/heap-sort/HeapSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | import MinHeap from '../../../data-structures/heap/MinHeap'; 3 | 4 | export default class HeapSort extends Sort { 5 | sort(originalArray) { 6 | const sortedArray = []; 7 | const minHeap = new MinHeap(this.callbacks.compareCallback); 8 | 9 | // Insert all array elements to the heap. 10 | originalArray.forEach((element) => { 11 | // Call visiting callback. 12 | this.callbacks.visitingCallback(element); 13 | 14 | minHeap.add(element); 15 | }); 16 | 17 | // Now we have min heap with minimal element always on top. 18 | // Let's poll that minimal element one by one and thus form the sorted array. 19 | while (!minHeap.isEmpty()) { 20 | const nextMinElement = minHeap.poll(); 21 | 22 | // Call visiting callback. 23 | this.callbacks.visitingCallback(nextMinElement); 24 | 25 | sortedArray.push(nextMinElement); 26 | } 27 | 28 | return sortedArray; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/algorithms/sorting/heap-sort/README.md: -------------------------------------------------------------------------------- 1 | # Heap Sort 2 | 3 | Heapsort is a comparison-based sorting algorithm. 4 | Heapsort can be thought of as an improved selection 5 | sort: like that algorithm, it divides its input into 6 | a sorted and an unsorted region, and it iteratively 7 | shrinks the unsorted region by extracting the largest 8 | element and moving that to the sorted region. The 9 | improvement consists of the use of a heap data structure 10 | rather than a linear-time search to find the maximum. 11 | 12 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/1/1b/Sorting_heapsort_anim.gif) 13 | 14 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/4/4d/Heapsort-example.gif) 15 | 16 | ## References 17 | 18 | [Wikipedia](https://en.wikipedia.org/wiki/Heapsort) 19 | -------------------------------------------------------------------------------- /src/algorithms/sorting/heap-sort/__test__/HeapSort.test.js: -------------------------------------------------------------------------------- 1 | import HeapSort from '../HeapSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | // These numbers don't take into account up/dow heapifying of the heap. 12 | // Thus these numbers are higher in reality. 13 | const SORTED_ARRAY_VISITING_COUNT = 40; 14 | const NOT_SORTED_ARRAY_VISITING_COUNT = 40; 15 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 40; 16 | const EQUAL_ARRAY_VISITING_COUNT = 40; 17 | 18 | describe('HeapSort', () => { 19 | it('should sort array', () => { 20 | SortTester.testSort(HeapSort); 21 | }); 22 | 23 | it('should sort array with custom comparator', () => { 24 | SortTester.testSortWithCustomComparator(HeapSort); 25 | }); 26 | 27 | it('should visit EQUAL array element specified number of times', () => { 28 | SortTester.testAlgorithmTimeComplexity( 29 | HeapSort, 30 | equalArr, 31 | EQUAL_ARRAY_VISITING_COUNT, 32 | ); 33 | }); 34 | 35 | it('should visit SORTED array element specified number of times', () => { 36 | SortTester.testAlgorithmTimeComplexity( 37 | HeapSort, 38 | sortedArr, 39 | SORTED_ARRAY_VISITING_COUNT, 40 | ); 41 | }); 42 | 43 | it('should visit NOT SORTED array element specified number of times', () => { 44 | SortTester.testAlgorithmTimeComplexity( 45 | HeapSort, 46 | notSortedArr, 47 | NOT_SORTED_ARRAY_VISITING_COUNT, 48 | ); 49 | }); 50 | 51 | it('should visit REVERSE SORTED array element specified number of times', () => { 52 | SortTester.testAlgorithmTimeComplexity( 53 | HeapSort, 54 | reverseArr, 55 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/algorithms/sorting/insertion-sort/InsertionSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class InsertionSort extends Sort { 4 | sort(originalArray) { 5 | const array = [...originalArray]; 6 | 7 | // Go through all array elements... 8 | for (let i = 0; i < array.length; i += 1) { 9 | let currentIndex = i; 10 | 11 | // Call visiting callback. 12 | this.callbacks.visitingCallback(array[i]); 13 | 14 | // Go and check if previous elements and greater then current one. 15 | // If this is the case then swap that elements. 16 | while ( 17 | array[currentIndex - 1] && 18 | this.comparator.lessThan(array[currentIndex], array[currentIndex - 1]) 19 | ) { 20 | // Call visiting callback. 21 | this.callbacks.visitingCallback(array[currentIndex - 1]); 22 | 23 | // Swap the elements. 24 | const tmp = array[currentIndex - 1]; 25 | array[currentIndex - 1] = array[currentIndex]; 26 | array[currentIndex] = tmp; 27 | 28 | // Shift current index left. 29 | currentIndex -= 1; 30 | } 31 | } 32 | 33 | return array; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/algorithms/sorting/insertion-sort/README.md: -------------------------------------------------------------------------------- 1 | # Insertion Sort 2 | 3 | Insertion sort is a simple sorting algorithm that builds 4 | the final sorted array (or list) one item at a time. 5 | It is much less efficient on large lists than more 6 | advanced algorithms such as quicksort, heapsort, or merge 7 | sort. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/4/42/Insertion_sort.gif) 10 | 11 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/0/0f/Insertion-sort-example-300px.gif) 12 | 13 | ## References 14 | 15 | [Wikipedia](https://en.wikipedia.org/wiki/Insertion_sort) 16 | -------------------------------------------------------------------------------- /src/algorithms/sorting/insertion-sort/__test__/InsertionSort.test.js: -------------------------------------------------------------------------------- 1 | import InsertionSort from '../InsertionSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 20; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 101; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 210; 14 | const EQUAL_ARRAY_VISITING_COUNT = 20; 15 | 16 | describe('InsertionSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(InsertionSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(InsertionSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(InsertionSort); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | InsertionSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | InsertionSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | InsertionSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | InsertionSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/algorithms/sorting/merge-sort/MergeSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class MergeSort extends Sort { 4 | sort(originalArray) { 5 | // Call visiting callback. 6 | this.callbacks.visitingCallback(null); 7 | 8 | // If array is empty or consists of one element then return this array since it is sorted. 9 | if (originalArray.length <= 1) { 10 | return originalArray; 11 | } 12 | 13 | // Split array on two halves. 14 | const middleIndex = Math.floor(originalArray.length / 2); 15 | const leftArray = originalArray.slice(0, middleIndex); 16 | const rightArray = originalArray.slice(middleIndex, originalArray.length); 17 | 18 | // Sort two halves of split array 19 | const leftSortedArray = this.sort(leftArray); 20 | const rightSortedArray = this.sort(rightArray); 21 | 22 | // Merge two sorted arrays into one. 23 | return this.mergeSortedArrays(leftSortedArray, rightSortedArray); 24 | } 25 | 26 | mergeSortedArrays(leftArray, rightArray) { 27 | let sortedArray = []; 28 | 29 | // In case if arrays are not of size 1. 30 | while (leftArray.length && rightArray.length) { 31 | let minimumElement = null; 32 | 33 | // Find minimum element of two arrays. 34 | if (this.comparator.lessThanOrEqual(leftArray[0], rightArray[0])) { 35 | minimumElement = leftArray.shift(); 36 | } else { 37 | minimumElement = rightArray.shift(); 38 | } 39 | 40 | // Call visiting callback. 41 | this.callbacks.visitingCallback(minimumElement); 42 | 43 | // Push the minimum element of two arrays to the sorted array. 44 | sortedArray.push(minimumElement); 45 | } 46 | 47 | // If one of two array still have elements we need to just concatenate 48 | // this element to the sorted array since it is already sorted. 49 | if (leftArray.length) { 50 | sortedArray = sortedArray.concat(leftArray); 51 | } 52 | 53 | if (rightArray.length) { 54 | sortedArray = sortedArray.concat(rightArray); 55 | } 56 | 57 | return sortedArray; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/algorithms/sorting/merge-sort/README.md: -------------------------------------------------------------------------------- 1 | # Merge Sort 2 | 3 | In computer science, merge sort (also commonly spelled 4 | mergesort) is an efficient, general-purpose, 5 | comparison-based sorting algorithm. Most implementations 6 | produce a stable sort, which means that the implementation 7 | preserves the input order of equal elements in the sorted 8 | output. Mergesort is a divide and conquer algorithm that 9 | was invented by John von Neumann in 1945. 10 | 11 | An example of merge sort. First divide the list into 12 | the smallest unit (1 element), then compare each 13 | element with the adjacent list to sort and merge the 14 | two adjacent lists. Finally all the elements are sorted 15 | and merged. 16 | 17 | ![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif) 18 | 19 | A recursive merge sort algorithm used to sort an array of 7 20 | integer values. These are the steps a human would take to 21 | emulate merge sort (top-down). 22 | 23 | ![Merge Sort](https://upload.wikimedia.org/wikipedia/commons/e/e6/Merge_sort_algorithm_diagram.svg) 24 | 25 | ## References 26 | 27 | - [Wikipedia](https://en.wikipedia.org/wiki/Merge_sort) 28 | - [YouTube](https://www.youtube.com/watch?v=KF2j-9iSf4Q&index=27&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 29 | -------------------------------------------------------------------------------- /src/algorithms/sorting/merge-sort/__test__/MergeSort.test.js: -------------------------------------------------------------------------------- 1 | import MergeSort from '../MergeSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 79; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 102; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 87; 14 | const EQUAL_ARRAY_VISITING_COUNT = 79; 15 | 16 | describe('MergeSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(MergeSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(MergeSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(MergeSort); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | MergeSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | MergeSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | MergeSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | MergeSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/algorithms/sorting/quick-sort/QuickSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class QuickSort extends Sort { 4 | sort(originalArray) { 5 | // Clone original array to prevent it from modification. 6 | const array = [...originalArray]; 7 | 8 | // If array has less then or equal to one elements then it is already sorted. 9 | if (array.length <= 1) { 10 | return array; 11 | } 12 | 13 | // Init left and right arrays. 14 | const leftArray = []; 15 | const rightArray = []; 16 | 17 | // Take the first element of array as a pivot. 18 | const pivotElement = array.shift(); 19 | const centerArray = [pivotElement]; 20 | 21 | // Split all array elements between left, center and right arrays. 22 | while (array.length) { 23 | const currentElement = array.shift(); 24 | 25 | // Call visiting callback. 26 | this.callbacks.visitingCallback(currentElement); 27 | 28 | if (this.comparator.equal(currentElement, pivotElement)) { 29 | centerArray.push(currentElement); 30 | } else if (this.comparator.lessThan(currentElement, pivotElement)) { 31 | leftArray.push(currentElement); 32 | } else { 33 | rightArray.push(currentElement); 34 | } 35 | } 36 | 37 | // Sort left and right arrays. 38 | const leftArraySorted = this.sort(leftArray); 39 | const rightArraySorted = this.sort(rightArray); 40 | 41 | // Let's now join sorted left array with center array and with sorted right array. 42 | return leftArraySorted.concat(centerArray, rightArraySorted); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/algorithms/sorting/quick-sort/README.md: -------------------------------------------------------------------------------- 1 | # Quicksort 2 | 3 | Quicksort is a divide and conquer algorithm. 4 | Quicksort first divides a large array into two smaller 5 | sub-arrays: the low elements and the high elements. 6 | Quicksort can then recursively sort the sub-arrays 7 | 8 | The steps are: 9 | 10 | 1. Pick an element, called a pivot, from the array. 11 | 2. Partitioning: reorder the array so that all elements with 12 | values less than the pivot come before the pivot, while all 13 | elements with values greater than the pivot come after it 14 | (equal values can go either way). After this partitioning, 15 | the pivot is in its final position. This is called the 16 | partition operation. 17 | 3. Recursively apply the above steps to the sub-array of 18 | elements with smaller values and separately to the 19 | sub-array of elements with greater values. 20 | 21 | Animated visualization of the quicksort algorithm. 22 | The horizontal lines are pivot values. 23 | 24 | ![Quicksort](https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif) 25 | 26 | ## References 27 | 28 | - [Wikipedia](https://en.wikipedia.org/wiki/Quicksort) 29 | - [YouTube](https://www.youtube.com/watch?v=SLauY6PpjW4&index=28&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 30 | -------------------------------------------------------------------------------- /src/algorithms/sorting/quick-sort/__test__/QuickSort.test.js: -------------------------------------------------------------------------------- 1 | import QuickSort from '../QuickSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 190; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 62; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 190; 14 | const EQUAL_ARRAY_VISITING_COUNT = 19; 15 | 16 | describe('QuickSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(QuickSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(QuickSort); 23 | }); 24 | 25 | it('should do stable sorting', () => { 26 | SortTester.testSortStability(QuickSort); 27 | }); 28 | 29 | it('should visit EQUAL array element specified number of times', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | QuickSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ); 35 | }); 36 | 37 | it('should visit SORTED array element specified number of times', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | QuickSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ); 43 | }); 44 | 45 | it('should visit NOT SORTED array element specified number of times', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | QuickSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ); 51 | }); 52 | 53 | it('should visit REVERSE SORTED array element specified number of times', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | QuickSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/algorithms/sorting/selection-sort/README.md: -------------------------------------------------------------------------------- 1 | # Selection Sort 2 | 3 | Selection sort is a sorting algorithm, specifically an 4 | in-place comparison sort. It has O(n2) time complexity, 5 | making it inefficient on large lists, and generally 6 | performs worse than the similar insertion sort. 7 | Selection sort is noted for its simplicity, and it has 8 | performance advantages over more complicated algorithms 9 | in certain situations, particularly where auxiliary 10 | memory is limited. 11 | 12 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/b/b0/Selection_sort_animation.gif) 13 | 14 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/9/94/Selection-Sort-Animation.gif) 15 | 16 | ## References 17 | 18 | [Wikipedia](https://en.wikipedia.org/wiki/Selection_sort) 19 | -------------------------------------------------------------------------------- /src/algorithms/sorting/selection-sort/SelectionSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class SelectionSort extends Sort { 4 | sort(originalArray) { 5 | // Clone original array to prevent its modification. 6 | const array = [...originalArray]; 7 | 8 | for (let i = 0; i < array.length - 1; i += 1) { 9 | let minIndex = i; 10 | 11 | // Call visiting callback. 12 | this.callbacks.visitingCallback(array[i]); 13 | 14 | // Find minimum element in the rest of array. 15 | for (let j = i + 1; j < array.length; j += 1) { 16 | // Call visiting callback. 17 | this.callbacks.visitingCallback(array[j]); 18 | 19 | if (this.comparator.lessThan(array[j], array[minIndex])) { 20 | minIndex = j; 21 | } 22 | } 23 | 24 | // If new minimum element has been found then swap it with current i-th element. 25 | if (minIndex !== i) { 26 | const tmp = array[i]; 27 | array[i] = array[minIndex]; 28 | array[minIndex] = tmp; 29 | } 30 | } 31 | 32 | return array; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/algorithms/sorting/selection-sort/__test__/SelectionSort.test.js: -------------------------------------------------------------------------------- 1 | import SelectionSort from '../SelectionSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 209; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 209; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209; 14 | const EQUAL_ARRAY_VISITING_COUNT = 209; 15 | 16 | describe('SelectionSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(SelectionSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(SelectionSort); 23 | }); 24 | 25 | it('should visit EQUAL array element specified number of times', () => { 26 | SortTester.testAlgorithmTimeComplexity( 27 | SelectionSort, 28 | equalArr, 29 | EQUAL_ARRAY_VISITING_COUNT, 30 | ); 31 | }); 32 | 33 | it('should visit SORTED array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | SelectionSort, 36 | sortedArr, 37 | SORTED_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit NOT SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | SelectionSort, 44 | notSortedArr, 45 | NOT_SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit REVERSE SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | SelectionSort, 52 | reverseArr, 53 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/algorithms/sorting/shell-sort/README.md: -------------------------------------------------------------------------------- 1 | # Shellsort 2 | 3 | Shellsort, also known as Shell sort or Shell's method, 4 | is an in-place comparison sort. It can be seen as either a 5 | generalization of sorting by exchange (bubble sort) or sorting 6 | by insertion (insertion sort). The method starts by sorting 7 | pairs of elements far apart from each other, then progressively 8 | reducing the gap between elements to be compared. Starting 9 | with far apart elements, it can move some out-of-place 10 | elements into position faster than a simple nearest neighbor 11 | exchange 12 | 13 | ![Shellsort](https://upload.wikimedia.org/wikipedia/commons/d/d8/Sorting_shellsort_anim.gif) 14 | 15 | ## How Shell Sort Works 16 | 17 | For our example and ease of understanding, we take the interval 18 | of `4`. Make a virtual sub-list of all values located at the 19 | interval of 4 positions. Here these values are 20 | `{35, 14}`, `{33, 19}`, `{42, 27}` and `{10, 44}` 21 | 22 | ![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort_gap_4.jpg) 23 | 24 | We compare values in each sub-list and swap them (if necessary) 25 | in the original array. After this step, the new array should 26 | look like this 27 | 28 | ![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort_step_1.jpg) 29 | 30 | Then, we take interval of 2 and this gap generates two sub-lists 31 | - `{14, 27, 35, 42}`, `{19, 10, 33, 44}` 32 | 33 | ![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort_gap_2.jpg) 34 | 35 | We compare and swap the values, if required, in the original array. 36 | After this step, the array should look like this 37 | 38 | ![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort_step_2.jpg) 39 | 40 | Finally, we sort the rest of the array using interval of value 1. 41 | Shell sort uses insertion sort to sort the array. 42 | 43 | ![Shellsort](https://www.tutorialspoint.com/data_structures_algorithms/images/shell_sort.jpg) 44 | 45 | ## References 46 | 47 | * [Tutorials Point](https://www.tutorialspoint.com/data_structures_algorithms/shell_sort_algorithm.htm) 48 | * [Wikipedia](https://en.wikipedia.org/wiki/Shellsort) 49 | -------------------------------------------------------------------------------- /src/algorithms/sorting/shell-sort/ShellSort.js: -------------------------------------------------------------------------------- 1 | import Sort from '../Sort'; 2 | 3 | export default class ShellSort extends Sort { 4 | sort(originalArray) { 5 | // Prevent original array from mutations. 6 | const array = [...originalArray]; 7 | 8 | // Define a gap distance. 9 | let gap = Math.floor(array.length / 2); 10 | 11 | // Until gap is bigger then zero do elements comparisons and swaps. 12 | while (gap > 0) { 13 | // Go and compare all distant element pairs. 14 | for (let i = 0; i < (array.length - gap); i += 1) { 15 | let currentIndex = i; 16 | let gapShiftedIndex = i + gap; 17 | 18 | while (currentIndex >= 0) { 19 | // Call visiting callback. 20 | this.callbacks.visitingCallback(array[currentIndex]); 21 | 22 | // Compare and swap array elements if needed. 23 | if (this.comparator.lessThan(array[gapShiftedIndex], array[currentIndex])) { 24 | const tmp = array[currentIndex]; 25 | array[currentIndex] = array[gapShiftedIndex]; 26 | array[gapShiftedIndex] = tmp; 27 | } 28 | 29 | gapShiftedIndex = currentIndex; 30 | currentIndex -= gap; 31 | } 32 | } 33 | 34 | // Shrink the gap. 35 | gap = Math.floor(gap / 2); 36 | } 37 | 38 | // Return sorted copy of an original array. 39 | return array; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/algorithms/sorting/shell-sort/__test__/ShellSort.test.js: -------------------------------------------------------------------------------- 1 | import ShellSort from '../ShellSort'; 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../../SortTester'; 9 | 10 | // Complexity constants. 11 | const SORTED_ARRAY_VISITING_COUNT = 320; 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 320; 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 320; 14 | const EQUAL_ARRAY_VISITING_COUNT = 320; 15 | 16 | describe('ShellSort', () => { 17 | it('should sort array', () => { 18 | SortTester.testSort(ShellSort); 19 | }); 20 | 21 | it('should sort array with custom comparator', () => { 22 | SortTester.testSortWithCustomComparator(ShellSort); 23 | }); 24 | 25 | it('should visit EQUAL array element specified number of times', () => { 26 | SortTester.testAlgorithmTimeComplexity( 27 | ShellSort, 28 | equalArr, 29 | EQUAL_ARRAY_VISITING_COUNT, 30 | ); 31 | }); 32 | 33 | it('should visit SORTED array element specified number of times', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | ShellSort, 36 | sortedArr, 37 | SORTED_ARRAY_VISITING_COUNT, 38 | ); 39 | }); 40 | 41 | it('should visit NOT SORTED array element specified number of times', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | ShellSort, 44 | notSortedArr, 45 | NOT_SORTED_ARRAY_VISITING_COUNT, 46 | ); 47 | }); 48 | 49 | it('should visit REVERSE SORTED array element specified number of times', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | ShellSort, 52 | reverseArr, 53 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 54 | ); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/algorithms/string/hamming-distance/README.md: -------------------------------------------------------------------------------- 1 | # Hamming Distance 2 | 3 | the Hamming distance between two strings of equal length is the 4 | number of positions at which the corresponding symbols are 5 | different. In other words, it measures the minimum number of 6 | substitutions required to change one string into the other, or 7 | the minimum number of errors that could have transformed one 8 | string into the other. In a more general context, the Hamming 9 | distance is one of several string metrics for measuring the 10 | edit distance between two sequences. 11 | 12 | ## Examples 13 | 14 | The Hamming distance between: 15 | 16 | - "ka**rol**in" and "ka**thr**in" is **3**. 17 | - "k**a**r**ol**in" and "k**e**r**st**in" is **3**. 18 | - 10**1**1**1**01 and 10**0**1**0**01 is **2**. 19 | - 2**17**3**8**96 and 2**23**3**7**96 is **3**. 20 | 21 | ## References 22 | 23 | [Wikipedia](https://en.wikipedia.org/wiki/Hamming_distance) 24 | -------------------------------------------------------------------------------- /src/algorithms/string/hamming-distance/__test__/hammingDistance.test.js: -------------------------------------------------------------------------------- 1 | import hammingDistance from '../hammingDistance'; 2 | 3 | describe('hammingDistance', () => { 4 | it('should throw an error when trying to compare the strings of different lengths', () => { 5 | const compareStringsOfDifferentLength = () => { 6 | hammingDistance('a', 'aa'); 7 | }; 8 | 9 | expect(compareStringsOfDifferentLength).toThrowError(); 10 | }); 11 | 12 | it('should calculate difference between two strings', () => { 13 | expect(hammingDistance('a', 'a')).toBe(0); 14 | expect(hammingDistance('a', 'b')).toBe(1); 15 | expect(hammingDistance('abc', 'add')).toBe(2); 16 | expect(hammingDistance('karolin', 'kathrin')).toBe(3); 17 | expect(hammingDistance('karolin', 'kerstin')).toBe(3); 18 | expect(hammingDistance('1011101', '1001001')).toBe(2); 19 | expect(hammingDistance('2173896', '2233796')).toBe(3); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/algorithms/string/hamming-distance/hammingDistance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} a 3 | * @param {string} b 4 | * @return {number} 5 | */ 6 | export default function hammingDistance(a, b) { 7 | if (a.length !== b.length) { 8 | throw new Error('Strings must be of the same length'); 9 | } 10 | 11 | let distance = 0; 12 | 13 | for (let i = 0; i < a.length; i += 1) { 14 | if (a[i] !== b[i]) { 15 | distance += 1; 16 | } 17 | } 18 | 19 | return distance; 20 | } 21 | -------------------------------------------------------------------------------- /src/algorithms/string/knuth-morris-pratt/README.md: -------------------------------------------------------------------------------- 1 | # Knuth–Morris–Pratt Algorithm 2 | 3 | The Knuth–Morris–Pratt string searching algorithm (or 4 | KMP algorithm) searches for occurrences of a "word" `W` 5 | within a main "text string" `T` by employing the 6 | observation that when a mismatch occurs, the word itself 7 | embodies sufficient information to determine where the 8 | next match could begin, thus bypassing re-examination 9 | of previously matched characters. 10 | 11 | ## Complexity 12 | 13 | - **Time:** `O(|W| + |T|)` (much faster comparing to trivial `O(|W| * |T|)`) 14 | - **Space:** `O(|W|)` 15 | 16 | ## References 17 | 18 | - [Wikipedia](https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm) 19 | - [YouTube](https://www.youtube.com/watch?v=GTJr8OvyEVQ&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 20 | -------------------------------------------------------------------------------- /src/algorithms/string/knuth-morris-pratt/__test__/knuthMorrisPratt.test.js: -------------------------------------------------------------------------------- 1 | import knuthMorrisPratt from '../knuthMorrisPratt'; 2 | 3 | describe('knuthMorrisPratt', () => { 4 | it('should find word position in given text', () => { 5 | expect(knuthMorrisPratt('abcbcglx', 'abca')).toBe(-1); 6 | expect(knuthMorrisPratt('abcbcglx', 'bcgl')).toBe(3); 7 | expect(knuthMorrisPratt('abcxabcdabxabcdabcdabcy', 'abcdabcy')).toBe(15); 8 | expect(knuthMorrisPratt('abcxabcdabxabcdabcdabcy', 'abcdabca')).toBe(-1); 9 | expect(knuthMorrisPratt('abcxabcdabxaabcdabcabcdabcdabcy', 'abcdabca')).toBe(12); 10 | expect(knuthMorrisPratt('abcxabcdabxaabaabaaaabcdabcdabcy', 'aabaabaaa')).toBe(11); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/algorithms/string/knuth-morris-pratt/knuthMorrisPratt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://www.youtube.com/watch?v=GTJr8OvyEVQ 3 | * @param {string} word 4 | * @return {number[]} 5 | */ 6 | function buildPatternTable(word) { 7 | const patternTable = [0]; 8 | let prefixIndex = 0; 9 | let suffixIndex = 1; 10 | 11 | while (suffixIndex < word.length) { 12 | if (word[prefixIndex] === word[suffixIndex]) { 13 | patternTable[suffixIndex] = prefixIndex + 1; 14 | suffixIndex += 1; 15 | prefixIndex += 1; 16 | } else if (prefixIndex === 0) { 17 | patternTable[suffixIndex] = 0; 18 | suffixIndex += 1; 19 | } else { 20 | prefixIndex = patternTable[prefixIndex - 1]; 21 | } 22 | } 23 | 24 | return patternTable; 25 | } 26 | 27 | /** 28 | * @param {string} text 29 | * @param {string} word 30 | * @return {number} 31 | */ 32 | export default function knuthMorrisPratt(text, word) { 33 | let textIndex = 0; 34 | let wordIndex = 0; 35 | 36 | const patternTable = buildPatternTable(word); 37 | 38 | while (textIndex < text.length) { 39 | if (text[textIndex] === word[wordIndex]) { 40 | // We've found a match. 41 | if (wordIndex === word.length - 1) { 42 | return (textIndex - word.length) + 1; 43 | } 44 | wordIndex += 1; 45 | textIndex += 1; 46 | } else if (wordIndex > 0) { 47 | wordIndex = patternTable[wordIndex - 1]; 48 | } else { 49 | wordIndex = 0; 50 | textIndex += 1; 51 | } 52 | } 53 | 54 | return -1; 55 | } 56 | -------------------------------------------------------------------------------- /src/algorithms/string/levenshtein-distance/README.md: -------------------------------------------------------------------------------- 1 | # Levenshtein Distance 2 | 3 | The Levenshtein distance is a string metric for measuring the 4 | difference between two sequences. Informally, the Levenshtein 5 | distance between two words is the minimum number of 6 | single-character edits (insertions, deletions or substitutions) 7 | required to change one word into the other. 8 | 9 | ## Definition 10 | 11 | Mathematically, the Levenshtein distance between two strings 12 | `a` and `b` (of length `|a|` and `|b|` respectively) is given by 13 | ![Levenshtein](https://wikimedia.org/api/rest_v1/media/math/render/svg/4cf357d8f2135035207088d2c7b890fb4b64e410) 14 | where 15 | 16 | ![Levenshtein](https://wikimedia.org/api/rest_v1/media/math/render/svg/f0a48ecfc9852c042382fdc33c19e11a16948e85) 17 | 18 | where 19 | ![Levenshtein](https://wikimedia.org/api/rest_v1/media/math/render/svg/52512ede08444b13838c570ba4a3fc71d54dbce9) 20 | is the indicator function equal to `0` when 21 | ![Levenshtein](https://wikimedia.org/api/rest_v1/media/math/render/svg/231fda9ee578f0328c5ca28088d01928bb0aaaec) 22 | and equal to 1 otherwise, and 23 | ![Levenshtein](https://wikimedia.org/api/rest_v1/media/math/render/svg/bdc0315678caad28648aafedb6ebafb16bd1655c) 24 | is the distance between the first `i` characters of `a` and the first 25 | `j` characters of `b`. 26 | 27 | Note that the first element in the minimum corresponds to 28 | deletion (from `a` to `b`), the second to insertion and 29 | the third to match or mismatch, depending on whether the 30 | respective symbols are the same. 31 | 32 | ## Example 33 | 34 | For example, the Levenshtein distance between `kitten` and 35 | `sitting` is `3`, since the following three edits change one 36 | into the other, and there is no way to do it with fewer than 37 | three edits: 38 | 39 | 1. **k**itten → **s**itten (substitution of "s" for "k") 40 | 2. sitt**e**n → sitt**i**n (substitution of "i" for "e") 41 | 3. sittin → sittin**g** (insertion of "g" at the end). 42 | 43 | ## References 44 | 45 | - [Wikipedia](https://en.wikipedia.org/wiki/Levenshtein_distance) 46 | - [YouTube](https://www.youtube.com/watch?v=We3YDTzNXEk&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 47 | -------------------------------------------------------------------------------- /src/algorithms/string/levenshtein-distance/__test__/levenshteinDistance.test.js: -------------------------------------------------------------------------------- 1 | import levenshteinDistance from '../levenshteinDistance'; 2 | 3 | describe('levenshteinDistance', () => { 4 | it('should calculate edit distance between two strings', () => { 5 | expect(levenshteinDistance('', '')).toBe(0); 6 | expect(levenshteinDistance('a', '')).toBe(1); 7 | expect(levenshteinDistance('', 'a')).toBe(1); 8 | expect(levenshteinDistance('abc', '')).toBe(3); 9 | expect(levenshteinDistance('', 'abc')).toBe(3); 10 | 11 | // Should just add I to the beginning. 12 | expect(levenshteinDistance('islander', 'slander')).toBe(1); 13 | 14 | // Needs to substitute M by K, T by M and add an A to the end 15 | expect(levenshteinDistance('mart', 'karma')).toBe(3); 16 | 17 | // Substitute K by S, E by I and insert G at the end. 18 | expect(levenshteinDistance('kitten', 'sitting')).toBe(3); 19 | 20 | // Should add 4 letters FOOT at the beginning. 21 | expect(levenshteinDistance('ball', 'football')).toBe(4); 22 | 23 | // Should delete 4 letters FOOT at the beginning. 24 | expect(levenshteinDistance('football', 'foot')).toBe(4); 25 | 26 | // Needs to substitute the first 5 chars: INTEN by EXECU 27 | expect(levenshteinDistance('intention', 'execution')).toBe(5); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/algorithms/string/levenshtein-distance/levenshteinDistance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} a 3 | * @param {string} b 4 | * @return {number} 5 | */ 6 | export default function levenshteinDistance(a, b) { 7 | // Create empty edit distance matrix for all possible modifications of 8 | // substrings of a to substrings of b. 9 | const distanceMatrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null)); 10 | 11 | // Fill the first row of the matrix. 12 | // If this is first row then we're transforming empty string to a. 13 | // In this case the number of transformations equals to size of a substring. 14 | for (let i = 0; i <= a.length; i += 1) { 15 | distanceMatrix[0][i] = i; 16 | } 17 | 18 | // Fill the first column of the matrix. 19 | // If this is first column then we're transforming empty string to b. 20 | // In this case the number of transformations equals to size of b substring. 21 | for (let j = 0; j <= b.length; j += 1) { 22 | distanceMatrix[j][0] = j; 23 | } 24 | 25 | for (let j = 1; j <= b.length; j += 1) { 26 | for (let i = 1; i <= a.length; i += 1) { 27 | const indicator = a[i - 1] === b[j - 1] ? 0 : 1; 28 | distanceMatrix[j][i] = Math.min( 29 | distanceMatrix[j][i - 1] + 1, // deletion 30 | distanceMatrix[j - 1][i] + 1, // insertion 31 | distanceMatrix[j - 1][i - 1] + indicator, // substitution 32 | ); 33 | } 34 | } 35 | 36 | return distanceMatrix[b.length][a.length]; 37 | } 38 | -------------------------------------------------------------------------------- /src/algorithms/string/longest-common-substring/README.md: -------------------------------------------------------------------------------- 1 | # Longest Common Substring Problem 2 | 3 | The longest common substring problem is to find the longest string 4 | (or strings) that is a substring (or are substrings) of two or more 5 | strings. 6 | 7 | ## Example 8 | 9 | The longest common substring of the strings `ABABC`, `BABCA` and 10 | `ABCBA` is string `ABC` of length 3. Other common substrings are 11 | `A`, `AB`, `B`, `BA`, `BC` and `C`. 12 | 13 | ``` 14 | ABABC 15 | ||| 16 | BABCA 17 | ||| 18 | ABCBA 19 | ``` 20 | 21 | ## References 22 | 23 | - [Wikipedia](https://en.wikipedia.org/wiki/Longest_common_substring_problem) 24 | - [YouTube](https://www.youtube.com/watch?v=BysNXJHzCEs&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 25 | -------------------------------------------------------------------------------- /src/algorithms/string/longest-common-substring/__test__/longestCommonSubstring.test.js: -------------------------------------------------------------------------------- 1 | import longestCommonSubstring from '../longestCommonSubstring'; 2 | 3 | describe('longestCommonSubstring', () => { 4 | it('should find longest common substring between two strings', () => { 5 | expect(longestCommonSubstring('', '')).toBe(''); 6 | expect(longestCommonSubstring('ABC', '')).toBe(''); 7 | expect(longestCommonSubstring('', 'ABC')).toBe(''); 8 | expect(longestCommonSubstring('ABABC', 'BABCA')).toBe('BABC'); 9 | expect(longestCommonSubstring('BABCA', 'ABCBA')).toBe('ABC'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/algorithms/string/longest-common-substring/longestCommonSubstring.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s1 3 | * @param {string} s2 4 | * @return {string} 5 | */ 6 | export default function longestCommonSubstring(s1, s2) { 7 | // Init the matrix of all substring lengths to use Dynamic Programming approach. 8 | const substringMatrix = Array(s2.length + 1).fill(null).map(() => { 9 | return Array(s1.length + 1).fill(null); 10 | }); 11 | 12 | // Fill the first row and first column with zeros to provide initial values. 13 | for (let columnIndex = 0; columnIndex <= s1.length; columnIndex += 1) { 14 | substringMatrix[0][columnIndex] = 0; 15 | } 16 | 17 | for (let rowIndex = 0; rowIndex <= s2.length; rowIndex += 1) { 18 | substringMatrix[rowIndex][0] = 0; 19 | } 20 | 21 | // Build the matrix of all substring lengths to use Dynamic Programming approach. 22 | let longestSubstringLength = 0; 23 | let longestSubstringColumn = 0; 24 | let longestSubstringRow = 0; 25 | 26 | for (let rowIndex = 1; rowIndex <= s2.length; rowIndex += 1) { 27 | for (let columnIndex = 1; columnIndex <= s1.length; columnIndex += 1) { 28 | if (s1[columnIndex - 1] === s2[rowIndex - 1]) { 29 | substringMatrix[rowIndex][columnIndex] = substringMatrix[rowIndex - 1][columnIndex - 1] + 1; 30 | } else { 31 | substringMatrix[rowIndex][columnIndex] = 0; 32 | } 33 | 34 | // Try to find the biggest length of all common substring lengths 35 | // and to memorize its last character position (indices) 36 | if (substringMatrix[rowIndex][columnIndex] > longestSubstringLength) { 37 | longestSubstringLength = substringMatrix[rowIndex][columnIndex]; 38 | longestSubstringColumn = columnIndex; 39 | longestSubstringRow = rowIndex; 40 | } 41 | } 42 | } 43 | 44 | if (longestSubstringLength === 0) { 45 | // Longest common substring has not been found. 46 | return ''; 47 | } 48 | 49 | // Detect the longest substring from the matrix. 50 | let longestSubstring = ''; 51 | 52 | while (substringMatrix[longestSubstringRow][longestSubstringColumn] > 0) { 53 | longestSubstring = s1[longestSubstringColumn - 1] + longestSubstring; 54 | longestSubstringRow -= 1; 55 | longestSubstringColumn -= 1; 56 | } 57 | 58 | return longestSubstring; 59 | } 60 | -------------------------------------------------------------------------------- /src/algorithms/string/rabin-karp/README.md: -------------------------------------------------------------------------------- 1 | # Rabin Karp Algorithm 2 | 3 | In computer science, the Rabin–Karp algorithm or Karp–Rabin algorithm 4 | is a string searching algorithm created by Richard M. Karp and 5 | Michael O. Rabin (1987) that uses hashing to find any one of a set 6 | of pattern strings in a text. 7 | 8 | ## Complexity 9 | 10 | For text of length `n` and `p` patterns 11 | of combined length `m`, its average and best case running time is 12 | `O(n + m)` in space `O(p)`, but its worst-case time is `O(n * m)`. 13 | 14 | ## Application 15 | 16 | A practical application of the algorithm is detecting plagiarism. 17 | Given source material, the algorithm can rapidly search through a paper 18 | for instances of sentences from the source material, ignoring details 19 | such as case and punctuation. Because of the abundance of the sought 20 | strings, single-string searching algorithms are impractical. 21 | 22 | ## References 23 | 24 | - [Wikipedia](https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm) 25 | - [YouTube](https://www.youtube.com/watch?v=H4VrKHVG5qI&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 26 | -------------------------------------------------------------------------------- /src/algorithms/string/rabin-karp/__test__/rabinKarp.test.js: -------------------------------------------------------------------------------- 1 | import { rabinKarp, hashWord, reHashWord } from '../rabinKarp'; 2 | 3 | describe('rabinKarp', () => { 4 | it('should correctly calculates hash and re-hash', () => { 5 | expect(hashWord('a')).toBe(97); 6 | expect(hashWord('b')).toBe(98); 7 | expect(hashWord('abc')).toBe(941094); 8 | expect(hashWord('bcd')).toBe(950601); 9 | expect(reHashWord(hashWord('abc'), 'abc', 'bcd')).toBe(950601); 10 | expect(reHashWord(hashWord('abc'), 'abc', 'bcd')).toBe(hashWord('bcd')); 11 | }); 12 | 13 | it('should find substring in a string', () => { 14 | expect(rabinKarp('abcbcglx', 'abca')).toBe(-1); 15 | expect(rabinKarp('abcbcglx', 'bcgl')).toBe(3); 16 | expect(rabinKarp('abcxabcdabxabcdabcdabcy', 'abcdabcy')).toBe(15); 17 | expect(rabinKarp('abcxabcdabxabcdabcdabcy', 'abcdabca')).toBe(-1); 18 | expect(rabinKarp('abcxabcdabxaabcdabcabcdabcdabcy', 'abcdabca')).toBe(12); 19 | expect(rabinKarp('abcxabcdabxaabaabaaaabcdabcdabcy', 'aabaabaaa')).toBe(11); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/algorithms/tree/breadth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Breadth-First Search (BFS) 2 | 3 | Breadth-first search (BFS) is an algorithm for traversing 4 | or searching tree or graph data structures. It starts at 5 | the tree root (or some arbitrary node of a graph, sometimes 6 | referred to as a 'search key') and explores the neighbor 7 | nodes first, before moving to the next level neighbors. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/5/5d/Breadth-First-Search-Algorithm.gif) 10 | 11 | ## References 12 | 13 | - [Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search) 14 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 15 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 16 | -------------------------------------------------------------------------------- /src/algorithms/tree/breadth-first-search/breadthFirstSearch.js: -------------------------------------------------------------------------------- 1 | import Queue from '../../../data-structures/queue/Queue'; 2 | 3 | /** 4 | * @typedef {Object} Callbacks 5 | * @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal - 6 | * Determines whether DFS should traverse from the node to its child. 7 | * @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node. 8 | * @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node. 9 | */ 10 | 11 | /** 12 | * @param {Callbacks} [callbacks] 13 | * @returns {Callbacks} 14 | */ 15 | function initCallbacks(callbacks = {}) { 16 | const initiatedCallback = callbacks; 17 | 18 | const stubCallback = () => {}; 19 | const defaultAllowTraversal = () => true; 20 | 21 | initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal; 22 | initiatedCallback.enterNode = callbacks.enterNode || stubCallback; 23 | initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback; 24 | 25 | return initiatedCallback; 26 | } 27 | 28 | /** 29 | * @param {BinaryTreeNode} rootNode 30 | * @param {Callbacks} [originalCallbacks] 31 | */ 32 | export default function breadthFirstSearch(rootNode, originalCallbacks) { 33 | const callbacks = initCallbacks(originalCallbacks); 34 | const nodeQueue = new Queue(); 35 | 36 | // Do initial queue setup. 37 | nodeQueue.enqueue(rootNode); 38 | 39 | while (!nodeQueue.isEmpty()) { 40 | const currentNode = nodeQueue.dequeue(); 41 | 42 | callbacks.enterNode(currentNode); 43 | 44 | // Add all children to the queue for future traversals. 45 | 46 | // Traverse left branch. 47 | if (currentNode.left && callbacks.allowTraversal(currentNode, currentNode.left)) { 48 | nodeQueue.enqueue(currentNode.left); 49 | } 50 | 51 | // Traverse right branch. 52 | if (currentNode.right && callbacks.allowTraversal(currentNode, currentNode.right)) { 53 | nodeQueue.enqueue(currentNode.right); 54 | } 55 | 56 | callbacks.leaveNode(currentNode); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/algorithms/tree/depth-first-search/README.md: -------------------------------------------------------------------------------- 1 | # Depth-First Search (DFS) 2 | 3 | Depth-first search (DFS) is an algorithm for traversing or 4 | searching tree or graph data structures. One starts at 5 | the root (selecting some arbitrary node as the root in 6 | the case of a graph) and explores as far as possible 7 | along each branch before backtracking. 8 | 9 | ![Algorithm Visualization](https://upload.wikimedia.org/wikipedia/commons/7/7f/Depth-First-Search.gif) 10 | 11 | ## References 12 | 13 | - [Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search) 14 | - [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/) 15 | - [BFS vs DFS](https://www.geeksforgeeks.org/bfs-vs-dfs-binary-tree/) 16 | -------------------------------------------------------------------------------- /src/algorithms/tree/depth-first-search/depthFirstSearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {Object} Callbacks 3 | * @property {function(node: BinaryTreeNode, child: BinaryTreeNode): boolean} allowTraversal - 4 | * Determines whether DFS should traverse from the node to its child. 5 | * @property {function(node: BinaryTreeNode)} enterNode - Called when DFS enters the node. 6 | * @property {function(node: BinaryTreeNode)} leaveNode - Called when DFS leaves the node. 7 | */ 8 | 9 | /** 10 | * @param {Callbacks} [callbacks] 11 | * @returns {Callbacks} 12 | */ 13 | function initCallbacks(callbacks = {}) { 14 | const initiatedCallback = callbacks; 15 | 16 | const stubCallback = () => {}; 17 | const defaultAllowTraversal = () => true; 18 | 19 | initiatedCallback.allowTraversal = callbacks.allowTraversal || defaultAllowTraversal; 20 | initiatedCallback.enterNode = callbacks.enterNode || stubCallback; 21 | initiatedCallback.leaveNode = callbacks.leaveNode || stubCallback; 22 | 23 | return initiatedCallback; 24 | } 25 | 26 | /** 27 | * @param {BinaryTreeNode} node 28 | * @param {Callbacks} callbacks 29 | */ 30 | export function depthFirstSearchRecursive(node, callbacks) { 31 | callbacks.enterNode(node); 32 | 33 | // Traverse left branch. 34 | if (node.left && callbacks.allowTraversal(node, node.left)) { 35 | depthFirstSearchRecursive(node.left, callbacks); 36 | } 37 | 38 | // Traverse right branch. 39 | if (node.right && callbacks.allowTraversal(node, node.right)) { 40 | depthFirstSearchRecursive(node.right, callbacks); 41 | } 42 | 43 | callbacks.leaveNode(node); 44 | } 45 | 46 | /** 47 | * @param {BinaryTreeNode} rootNode 48 | * @param {Callbacks} [callbacks] 49 | */ 50 | export default function depthFirstSearch(rootNode, callbacks) { 51 | depthFirstSearchRecursive(rootNode, initCallbacks(callbacks)); 52 | } 53 | -------------------------------------------------------------------------------- /src/algorithms/uncategorized/hanoi-tower/README.md: -------------------------------------------------------------------------------- 1 | # Tower of Hanoi 2 | 3 | The Tower of Hanoi (also called the Tower of Brahma or Lucas' 4 | Tower and sometimes pluralized) is a mathematical game or puzzle. 5 | It consists of three rods and a number of disks of different sizes, 6 | which can slide onto any rod. The puzzle starts with the disks in 7 | a neat stack in ascending order of size on one rod, the smallest 8 | at the top, thus making a conical shape. 9 | 10 | The objective of the puzzle is to move the entire stack to another 11 | rod, obeying the following simple rules: 12 | 13 | - Only one disk can be moved at a time. 14 | - Each move consists of taking the upper disk from one of the 15 | stacks and placing it on top of another stack or on an empty rod. 16 | - No disk may be placed on top of a smaller disk. 17 | 18 | ![Hanoi Tower](https://upload.wikimedia.org/wikipedia/commons/8/8d/Iterative_algorithm_solving_a_6_disks_Tower_of_Hanoi.gif) 19 | 20 | Animation of an iterative algorithm solving 6-disk problem 21 | 22 | With `3` disks, the puzzle can be solved in `7` moves. The minimal 23 | number of moves required to solve a Tower of Hanoi puzzle 24 | is `2^n − 1`, where `n` is the number of disks. 25 | 26 | ## References 27 | 28 | - [Wikipedia](https://en.wikipedia.org/wiki/Tower_of_Hanoi) 29 | - [HackerEarth](https://www.hackerearth.com/blog/algorithms/tower-hanoi-recursion-game-algorithm-explained/) 30 | -------------------------------------------------------------------------------- /src/algorithms/uncategorized/hanoi-tower/__test__/hanoiTower.test.js: -------------------------------------------------------------------------------- 1 | import hanoiTower from '../hanoiTower'; 2 | import Stack from '../../../../data-structures/stack/Stack'; 3 | 4 | describe('hanoiTower', () => { 5 | it('should solve tower of hanoi puzzle with 2 discs', () => { 6 | const moveCallback = jest.fn(); 7 | const numberOfDiscs = 2; 8 | 9 | const fromPole = new Stack(); 10 | const withPole = new Stack(); 11 | const toPole = new Stack(); 12 | 13 | hanoiTower({ 14 | numberOfDiscs, 15 | moveCallback, 16 | fromPole, 17 | withPole, 18 | toPole, 19 | }); 20 | 21 | expect(moveCallback).toHaveBeenCalledTimes((2 ** numberOfDiscs) - 1); 22 | 23 | expect(fromPole.toArray()).toEqual([]); 24 | expect(toPole.toArray()).toEqual([1, 2]); 25 | 26 | expect(moveCallback.mock.calls[0][0]).toBe(1); 27 | expect(moveCallback.mock.calls[0][1]).toEqual([1, 2]); 28 | expect(moveCallback.mock.calls[0][2]).toEqual([]); 29 | 30 | expect(moveCallback.mock.calls[1][0]).toBe(2); 31 | expect(moveCallback.mock.calls[1][1]).toEqual([2]); 32 | expect(moveCallback.mock.calls[1][2]).toEqual([]); 33 | 34 | expect(moveCallback.mock.calls[2][0]).toBe(1); 35 | expect(moveCallback.mock.calls[2][1]).toEqual([1]); 36 | expect(moveCallback.mock.calls[2][2]).toEqual([2]); 37 | }); 38 | 39 | it('should solve tower of hanoi puzzle with 3 discs', () => { 40 | const moveCallback = jest.fn(); 41 | const numberOfDiscs = 3; 42 | 43 | hanoiTower({ 44 | numberOfDiscs, 45 | moveCallback, 46 | }); 47 | 48 | expect(moveCallback).toHaveBeenCalledTimes((2 ** numberOfDiscs) - 1); 49 | }); 50 | 51 | it('should solve tower of hanoi puzzle with 6 discs', () => { 52 | const moveCallback = jest.fn(); 53 | const numberOfDiscs = 6; 54 | 55 | hanoiTower({ 56 | numberOfDiscs, 57 | moveCallback, 58 | }); 59 | 60 | expect(moveCallback).toHaveBeenCalledTimes((2 ** numberOfDiscs) - 1); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/algorithms/uncategorized/knight-tour/README.md: -------------------------------------------------------------------------------- 1 | # Knight's Tour 2 | 3 | A **knight's tour** is a sequence of moves of a knight on a chessboard 4 | such that the knight visits every square only once. If the knight 5 | ends on a square that is one knight's move from the beginning 6 | square (so that it could tour the board again immediately, 7 | following the same path), the tour is **closed**, otherwise it 8 | is **open**. 9 | 10 | The **knight's tour problem** is the mathematical problem of 11 | finding a knight's tour. Creating a program to find a knight's 12 | tour is a common problem given to computer science students. 13 | Variations of the knight's tour problem involve chessboards of 14 | different sizes than the usual `8×8`, as well as irregular 15 | (non-rectangular) boards. 16 | 17 | The knight's tour problem is an instance of the more 18 | general **Hamiltonian path problem** in graph theory. The problem of finding 19 | a closed knight's tour is similarly an instance of the Hamiltonian 20 | cycle problem. 21 | 22 | ![Knight's Tour](https://upload.wikimedia.org/wikipedia/commons/d/da/Knight%27s_tour_anim_2.gif) 23 | 24 | An open knight's tour of a chessboard. 25 | 26 | ![Knight's Tour](https://upload.wikimedia.org/wikipedia/commons/c/ca/Knights-Tour-Animation.gif) 27 | 28 | An animation of an open knight's tour on a 5 by 5 board. 29 | 30 | ## References 31 | 32 | - [Wikipedia](https://en.wikipedia.org/wiki/Knight%27s_tour) 33 | - [GeeksForGeeks](https://www.geeksforgeeks.org/backtracking-set-1-the-knights-tour-problem/) 34 | -------------------------------------------------------------------------------- /src/algorithms/uncategorized/knight-tour/__test__/knightTour.test.js: -------------------------------------------------------------------------------- 1 | import knightTour from '../knightTour'; 2 | 3 | describe('knightTour', () => { 4 | it('should not find solution on 3x3 board', () => { 5 | const moves = knightTour(3); 6 | 7 | expect(moves.length).toBe(0); 8 | }); 9 | 10 | it('should find one solution to do knight tour on 5x5 board', () => { 11 | const moves = knightTour(5); 12 | 13 | expect(moves.length).toBe(25); 14 | 15 | expect(moves).toEqual([ 16 | [0, 0], 17 | [1, 2], 18 | [2, 0], 19 | [0, 1], 20 | [1, 3], 21 | [3, 4], 22 | [2, 2], 23 | [4, 1], 24 | [3, 3], 25 | [1, 4], 26 | [0, 2], 27 | [1, 0], 28 | [3, 1], 29 | [4, 3], 30 | [2, 4], 31 | [0, 3], 32 | [1, 1], 33 | [3, 0], 34 | [4, 2], 35 | [2, 1], 36 | [4, 0], 37 | [3, 2], 38 | [4, 4], 39 | [2, 3], 40 | [0, 4], 41 | ]); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/algorithms/uncategorized/n-queens/QueenPosition.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class that represents queen position on the chessboard. 3 | */ 4 | export default class QueenPosition { 5 | /** 6 | * @param {number} rowIndex 7 | * @param {number} columnIndex 8 | */ 9 | constructor(rowIndex, columnIndex) { 10 | this.rowIndex = rowIndex; 11 | this.columnIndex = columnIndex; 12 | } 13 | 14 | /** 15 | * @return {number} 16 | */ 17 | get leftDiagonal() { 18 | // Each position on the same left (\) diagonal has the same difference of 19 | // rowIndex and columnIndex. This fact may be used to quickly check if two 20 | // positions (queens) are on the same left diagonal. 21 | // @see https://youtu.be/xouin83ebxE?t=1m59s 22 | return this.rowIndex - this.columnIndex; 23 | } 24 | 25 | /** 26 | * @return {number} 27 | */ 28 | get rightDiagonal() { 29 | // Each position on the same right diagonal (/) has the same 30 | // sum of rowIndex and columnIndex. This fact may be used to quickly 31 | // check if two positions (queens) are on the same right diagonal. 32 | // @see https://youtu.be/xouin83ebxE?t=1m59s 33 | return this.rowIndex + this.columnIndex; 34 | } 35 | 36 | toString() { 37 | return `${this.rowIndex},${this.columnIndex}`; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/algorithms/uncategorized/n-queens/__test__/QueensPosition.test.js: -------------------------------------------------------------------------------- 1 | import QueenPosition from '../QueenPosition'; 2 | 3 | describe('QueenPosition', () => { 4 | it('should store queen position on chessboard', () => { 5 | const position1 = new QueenPosition(0, 0); 6 | const position2 = new QueenPosition(2, 1); 7 | 8 | expect(position2.columnIndex).toBe(1); 9 | expect(position2.rowIndex).toBe(2); 10 | expect(position1.leftDiagonal).toBe(0); 11 | expect(position1.rightDiagonal).toBe(0); 12 | expect(position2.leftDiagonal).toBe(1); 13 | expect(position2.rightDiagonal).toBe(3); 14 | expect(position2.toString()).toBe('2,1'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/algorithms/uncategorized/n-queens/__test__/nQueens.test.js: -------------------------------------------------------------------------------- 1 | import nQueens from '../nQueens'; 2 | 3 | describe('nQueens', () => { 4 | it('should not hae solution for 3 queens', () => { 5 | const solutions = nQueens(3); 6 | expect(solutions.length).toBe(0); 7 | }); 8 | 9 | it('should solve n-queens problem for 4 queens', () => { 10 | const solutions = nQueens(4); 11 | expect(solutions.length).toBe(2); 12 | 13 | // First solution. 14 | expect(solutions[0][0].toString()).toBe('0,1'); 15 | expect(solutions[0][1].toString()).toBe('1,3'); 16 | expect(solutions[0][2].toString()).toBe('2,0'); 17 | expect(solutions[0][3].toString()).toBe('3,2'); 18 | 19 | // Second solution (mirrored). 20 | expect(solutions[1][0].toString()).toBe('0,2'); 21 | expect(solutions[1][1].toString()).toBe('1,0'); 22 | expect(solutions[1][2].toString()).toBe('2,3'); 23 | expect(solutions[1][3].toString()).toBe('3,1'); 24 | }); 25 | 26 | it('should solve n-queens problem for 6 queens', () => { 27 | const solutions = nQueens(6); 28 | expect(solutions.length).toBe(4); 29 | 30 | // First solution. 31 | expect(solutions[0][0].toString()).toBe('0,1'); 32 | expect(solutions[0][1].toString()).toBe('1,3'); 33 | expect(solutions[0][2].toString()).toBe('2,5'); 34 | expect(solutions[0][3].toString()).toBe('3,0'); 35 | expect(solutions[0][4].toString()).toBe('4,2'); 36 | expect(solutions[0][5].toString()).toBe('5,4'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/data-structures/disjoint-set/DisjointSetItem.js: -------------------------------------------------------------------------------- 1 | export default class DisjointSetItem { 2 | /** 3 | * @param {*} value 4 | * @param {function(value: *)} [keyCallback] 5 | */ 6 | constructor(value, keyCallback) { 7 | this.value = value; 8 | this.keyCallback = keyCallback; 9 | /** @var {DisjointSetItem} this.parent */ 10 | this.parent = null; 11 | this.children = {}; 12 | } 13 | 14 | /** 15 | * @return {*} 16 | */ 17 | getKey() { 18 | // Allow user to define custom key generator. 19 | if (this.keyCallback) { 20 | return this.keyCallback(this.value); 21 | } 22 | 23 | // Otherwise use value as a key by default. 24 | return this.value; 25 | } 26 | 27 | /** 28 | * @return {DisjointSetItem} 29 | */ 30 | getRoot() { 31 | return this.isRoot() ? this : this.parent.getRoot(); 32 | } 33 | 34 | /** 35 | * @return {boolean} 36 | */ 37 | isRoot() { 38 | return this.parent === null; 39 | } 40 | 41 | /** 42 | * Rank basically means the number of all ancestors. 43 | * 44 | * @return {number} 45 | */ 46 | getRank() { 47 | if (this.getChildren().length === 0) { 48 | return 0; 49 | } 50 | 51 | let rank = 0; 52 | 53 | /** @var {DisjointSetItem} child */ 54 | this.getChildren().forEach((child) => { 55 | // Count child itself. 56 | rank += 1; 57 | 58 | // Also add all children of current child. 59 | rank += child.getRank(); 60 | }); 61 | 62 | return rank; 63 | } 64 | 65 | /** 66 | * @return {DisjointSetItem[]} 67 | */ 68 | getChildren() { 69 | return Object.values(this.children); 70 | } 71 | 72 | /** 73 | * @param {DisjointSetItem} parentItem 74 | * @param {boolean} forceSettingParentChild 75 | * @return {DisjointSetItem} 76 | */ 77 | setParent(parentItem, forceSettingParentChild = true) { 78 | this.parent = parentItem; 79 | if (forceSettingParentChild) { 80 | parentItem.addChild(this); 81 | } 82 | 83 | return this; 84 | } 85 | 86 | /** 87 | * @param {DisjointSetItem} childItem 88 | * @return {DisjointSetItem} 89 | */ 90 | addChild(childItem) { 91 | this.children[childItem.getKey()] = childItem; 92 | childItem.setParent(this, false); 93 | 94 | return this; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/data-structures/disjoint-set/README.md: -------------------------------------------------------------------------------- 1 | # Disjoint Set 2 | 3 | **Disjoint-set** data structure (also called a union–find data structure or merge–find set) is a data 4 | structure that tracks a set of elements partitioned into a number of disjoint (non-overlapping) subsets. 5 | It provides near-constant-time operations (bounded by the inverse Ackermann function) to *add new sets*, 6 | to *merge existing sets*, and to *determine whether elements are in the same set*. 7 | In addition to many other uses (see the Applications section), disjoint-sets play a key role in Kruskal's algorithm for finding the minimum spanning tree of a graph. 8 | 9 | ![disjoint set](https://upload.wikimedia.org/wikipedia/commons/6/67/Dsu_disjoint_sets_init.svg) 10 | 11 | *MakeSet* creates 8 singletons. 12 | 13 | ![disjoint set](https://upload.wikimedia.org/wikipedia/commons/a/ac/Dsu_disjoint_sets_final.svg) 14 | 15 | After some operations of *Union*, some sets are grouped together. 16 | 17 | ## References 18 | 19 | - [Wikipedia](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) 20 | - [By Abdul Bari on YouTube](https://www.youtube.com/watch?v=wU6udHRIkcc&index=14&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 21 | -------------------------------------------------------------------------------- /src/data-structures/graph/GraphEdge.js: -------------------------------------------------------------------------------- 1 | export default class GraphEdge { 2 | /** 3 | * @param {GraphVertex} startVertex 4 | * @param {GraphVertex} endVertex 5 | * @param {number} [weight=1] 6 | */ 7 | constructor(startVertex, endVertex, weight = 0) { 8 | this.startVertex = startVertex; 9 | this.endVertex = endVertex; 10 | this.weight = weight; 11 | } 12 | 13 | /** 14 | * @return {string} 15 | */ 16 | getKey() { 17 | const startVertexKey = this.startVertex.getKey(); 18 | const endVertexKey = this.endVertex.getKey(); 19 | 20 | return `${startVertexKey}_${endVertexKey}`; 21 | } 22 | 23 | /** 24 | * @return {GraphEdge} 25 | */ 26 | reverse() { 27 | const tmp = this.startVertex; 28 | this.startVertex = this.endVertex; 29 | this.endVertex = tmp; 30 | 31 | return this; 32 | } 33 | 34 | /** 35 | * @return {string} 36 | */ 37 | toString() { 38 | return this.getKey(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/data-structures/graph/README.md: -------------------------------------------------------------------------------- 1 | # Graph 2 | 3 | In computer science, a graph is an abstract data type 4 | that is meant to implement the undirected graph and 5 | directed graph concepts from mathematics, specifically 6 | the field of graph theory 7 | 8 | A graph data structure consists of a finite (and possibly 9 | mutable) set of vertices or nodes or points, together 10 | with a set of unordered pairs of these vertices for an 11 | undirected graph or a set of ordered pairs for a 12 | directed graph. These pairs are known as edges, arcs, 13 | or lines for an undirected graph and as arrows, 14 | directed edges, directed arcs, or directed lines 15 | for a directed graph. The vertices may be part of 16 | the graph structure, or may be external entities 17 | represented by integer indices or references. 18 | 19 | ![Graph](https://www.tutorialspoint.com/data_structures_algorithms/images/graph.jpg) 20 | 21 | ## References 22 | 23 | - [Wikipedia](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)) 24 | - [Introduction to Graphs on YouTube](https://www.youtube.com/watch?v=gXgEDyodOJU&index=9&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 25 | - [Graphs representation on YouTube](https://www.youtube.com/watch?v=k1wraWzqtvQ&index=10&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 26 | -------------------------------------------------------------------------------- /src/data-structures/graph/__test__/GraphEdge.test.js: -------------------------------------------------------------------------------- 1 | import GraphEdge from '../GraphEdge'; 2 | import GraphVertex from '../GraphVertex'; 3 | 4 | describe('GraphEdge', () => { 5 | it('should create graph edge with default weight', () => { 6 | const startVertex = new GraphVertex('A'); 7 | const endVertex = new GraphVertex('B'); 8 | const edge = new GraphEdge(startVertex, endVertex); 9 | 10 | expect(edge.getKey()).toBe('A_B'); 11 | expect(edge.toString()).toBe('A_B'); 12 | expect(edge.startVertex).toEqual(startVertex); 13 | expect(edge.endVertex).toEqual(endVertex); 14 | expect(edge.weight).toEqual(0); 15 | }); 16 | 17 | it('should create graph edge with predefined weight', () => { 18 | const startVertex = new GraphVertex('A'); 19 | const endVertex = new GraphVertex('B'); 20 | const edge = new GraphEdge(startVertex, endVertex, 10); 21 | 22 | expect(edge.startVertex).toEqual(startVertex); 23 | expect(edge.endVertex).toEqual(endVertex); 24 | expect(edge.weight).toEqual(10); 25 | }); 26 | 27 | it('should be possible to do edge reverse', () => { 28 | const vertexA = new GraphVertex('A'); 29 | const vertexB = new GraphVertex('B'); 30 | const edge = new GraphEdge(vertexA, vertexB, 10); 31 | 32 | expect(edge.startVertex).toEqual(vertexA); 33 | expect(edge.endVertex).toEqual(vertexB); 34 | expect(edge.weight).toEqual(10); 35 | 36 | edge.reverse(); 37 | 38 | expect(edge.startVertex).toEqual(vertexB); 39 | expect(edge.endVertex).toEqual(vertexA); 40 | expect(edge.weight).toEqual(10); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/data-structures/hash-table/HashTable.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list/LinkedList'; 2 | 3 | const defaultHashTableSize = 32; 4 | 5 | export default class HashTable { 6 | constructor(hashTableSize = defaultHashTableSize) { 7 | // Create hash table of certain size and fill each bucket with empty linked list. 8 | this.buckets = Array(hashTableSize).fill(null).map(() => new LinkedList()); 9 | } 10 | 11 | // Converts key string to hash number. 12 | hash(key) { 13 | const hash = Array.from(key).reduce( 14 | (hashAccumulator, keySymbol) => (hashAccumulator + keySymbol.charCodeAt(0)), 15 | 0, 16 | ); 17 | 18 | // Reduce hash number so it would fit hash table size. 19 | return hash % this.buckets.length; 20 | } 21 | 22 | insert(key, value) { 23 | const bucketLinkedList = this.buckets[this.hash(key)]; 24 | const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); 25 | 26 | if (!node) { 27 | // Insert new node. 28 | bucketLinkedList.append({ key, value }); 29 | } else { 30 | // Update value of existing node. 31 | node.value.value = value; 32 | } 33 | } 34 | 35 | delete(key) { 36 | const bucketLinkedList = this.buckets[this.hash(key)]; 37 | const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); 38 | 39 | if (node) { 40 | return bucketLinkedList.delete(node.value); 41 | } 42 | 43 | return null; 44 | } 45 | 46 | get(key) { 47 | const bucketLinkedList = this.buckets[this.hash(key)]; 48 | const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); 49 | 50 | return node ? node.value.value : null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/data-structures/hash-table/README.md: -------------------------------------------------------------------------------- 1 | # Hash Table 2 | 3 | In computing, a hash table (hash map) is a data 4 | structure which implements an associative array 5 | abstract data type, a structure that can map keys 6 | to values. A hash table uses a hash function to 7 | compute an index into an array of buckets or slots, 8 | from which the desired value can be found 9 | 10 | Ideally, the hash function will assign each key to a 11 | unique bucket, but most hash table designs employ an 12 | imperfect hash function, which might cause hash 13 | collisions where the hash function generates the same 14 | index for more than one key. Such collisions must be 15 | accommodated in some way. 16 | 17 | ![Hash Table](https://upload.wikimedia.org/wikipedia/commons/7/7d/Hash_table_3_1_1_0_1_0_0_SP.svg) 18 | 19 | Hash collision resolved by separate chaining. 20 | 21 | ![Hash Collision](https://upload.wikimedia.org/wikipedia/commons/d/d0/Hash_table_5_0_1_1_1_1_1_LL.svg) 22 | 23 | ## References 24 | 25 | - [Wikipedia](https://en.wikipedia.org/wiki/Hash_table) 26 | - [YouTube](https://www.youtube.com/watch?v=shs0KM3wKv8&index=4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 27 | -------------------------------------------------------------------------------- /src/data-structures/hash-table/__test__/HashTable.test.js: -------------------------------------------------------------------------------- 1 | import HashTable from '../HashTable'; 2 | 3 | describe('HashTable', () => { 4 | it('should create hash table of certain size', () => { 5 | const defaultHashTable = new HashTable(); 6 | expect(defaultHashTable.buckets.length).toBe(32); 7 | 8 | const biggerHashTable = new HashTable(64); 9 | expect(biggerHashTable.buckets.length).toBe(64); 10 | }); 11 | 12 | it('should generate proper hash for specified keys', () => { 13 | const hashTable = new HashTable(); 14 | 15 | expect(hashTable.hash('a')).toBe(1); 16 | expect(hashTable.hash('b')).toBe(2); 17 | expect(hashTable.hash('abc')).toBe(6); 18 | }); 19 | 20 | it('should insert, read and delete data with collisions', () => { 21 | const hashTable = new HashTable(3); 22 | 23 | expect(hashTable.hash('a')).toBe(1); 24 | expect(hashTable.hash('b')).toBe(2); 25 | expect(hashTable.hash('c')).toBe(0); 26 | expect(hashTable.hash('d')).toBe(1); 27 | 28 | hashTable.insert('a', 'sky-old'); 29 | hashTable.insert('a', 'sky'); 30 | hashTable.insert('b', 'sea'); 31 | hashTable.insert('c', 'earth'); 32 | hashTable.insert('d', 'ocean'); 33 | 34 | const stringifier = value => `${value.key}:${value.value}`; 35 | 36 | expect(hashTable.buckets[0].toString(stringifier)).toBe('c:earth'); 37 | expect(hashTable.buckets[1].toString(stringifier)).toBe('a:sky,d:ocean'); 38 | expect(hashTable.buckets[2].toString(stringifier)).toBe('b:sea'); 39 | 40 | expect(hashTable.get('a')).toBe('sky'); 41 | expect(hashTable.get('d')).toBe('ocean'); 42 | 43 | hashTable.delete('a'); 44 | 45 | expect(hashTable.delete('not-existing')).toBeNull(); 46 | 47 | expect(hashTable.get('a')).toBeNull(); 48 | expect(hashTable.get('d')).toBe('ocean'); 49 | 50 | hashTable.insert('d', 'ocean-new'); 51 | expect(hashTable.get('d')).toBe('ocean-new'); 52 | }); 53 | 54 | it('should be possible to add objects to hash table', () => { 55 | const hashTable = new HashTable(); 56 | 57 | hashTable.insert('objectKey', { prop1: 'a', prop2: 'b' }); 58 | 59 | const object = hashTable.get('objectKey'); 60 | expect(object).toBeDefined(); 61 | expect(object.prop1).toBe('a'); 62 | expect(object.prop2).toBe('b'); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/data-structures/heap/README.md: -------------------------------------------------------------------------------- 1 | # Heap (data-structure) 2 | 3 | In computer science, a heap is a specialized tree-based 4 | data structure that satisfies the heap property: if `P` 5 | is a parent node of `C`, then the key (the value) of `P` 6 | is either greater than or equal to (in a max heap) or 7 | less than or equal to (in a min heap) the key of `C`. 8 | The node at the "top" of the heap (with no parents) is 9 | called the root node. 10 | 11 | ![Heap](https://upload.wikimedia.org/wikipedia/commons/3/38/Max-Heap.svg) 12 | 13 | ## References 14 | 15 | - [Wikipedia](https://en.wikipedia.org/wiki/Heap_(data_structure)) 16 | - [YouTube](https://www.youtube.com/watch?v=t0Cq6tVNRBA&index=5&t=0s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 17 | -------------------------------------------------------------------------------- /src/data-structures/linked-list/LinkedListNode.js: -------------------------------------------------------------------------------- 1 | export default class LinkedListNode { 2 | constructor(value, next = null) { 3 | this.value = value; 4 | this.next = next; 5 | } 6 | 7 | toString(callback) { 8 | return callback ? callback(this.value) : `${this.value}`; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/data-structures/linked-list/README.md: -------------------------------------------------------------------------------- 1 | # Linked List 2 | 3 | In computer science, a linked list is a linear collection 4 | of data elements, in which linear order is not given by 5 | their physical placement in memory. Instead, each 6 | element points to the next. It is a data structure 7 | consisting of a group of nodes which together represent 8 | a sequence. Under the simplest form, each node is 9 | composed of data and a reference (in other words, 10 | a link) to the next node in the sequence. This structure 11 | allows for efficient insertion or removal of elements 12 | from any position in the sequence during iteration. 13 | More complex variants add additional links, allowing 14 | efficient insertion or removal from arbitrary element 15 | references. A drawback of linked lists is that access 16 | time is linear (and difficult to pipeline). Faster 17 | access, such as random access, is not feasible. Arrays 18 | have better cache locality as compared to linked lists. 19 | 20 | ![Linked List](https://upload.wikimedia.org/wikipedia/commons/6/6d/Singly-linked-list.svg) 21 | 22 | ## References 23 | 24 | - [Wikipedia](https://en.wikipedia.org/wiki/Linked_list) 25 | - [YouTube](https://www.youtube.com/watch?v=njTh_OwMljA&index=2&t=1s&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8) 26 | -------------------------------------------------------------------------------- /src/data-structures/linked-list/__test__/LinkedListNode.test.js: -------------------------------------------------------------------------------- 1 | import LinkedListNode from '../LinkedListNode'; 2 | 3 | describe('LinkedListNode', () => { 4 | it('should create list node with value', () => { 5 | const node = new LinkedListNode(1); 6 | 7 | expect(node.value).toBe(1); 8 | expect(node.next).toBeNull(); 9 | }); 10 | 11 | it('should create list node with object as a value', () => { 12 | const nodeValue = { value: 1, key: 'test' }; 13 | const node = new LinkedListNode(nodeValue); 14 | 15 | expect(node.value.value).toBe(1); 16 | expect(node.value.key).toBe('test'); 17 | expect(node.next).toBeNull(); 18 | }); 19 | 20 | it('should link nodes together', () => { 21 | const node2 = new LinkedListNode(2); 22 | const node1 = new LinkedListNode(1, node2); 23 | 24 | expect(node1.next).toBeDefined(); 25 | expect(node2.next).toBeNull(); 26 | expect(node1.value).toBe(1); 27 | expect(node1.next.value).toBe(2); 28 | }); 29 | 30 | it('should convert node to string', () => { 31 | const node = new LinkedListNode(1); 32 | 33 | expect(node.toString()).toBe('1'); 34 | 35 | node.value = 'string value'; 36 | expect(node.toString()).toBe('string value'); 37 | }); 38 | 39 | it('should convert node to string with custom stringifier', () => { 40 | const nodeValue = { value: 1, key: 'test' }; 41 | const node = new LinkedListNode(nodeValue); 42 | const toStringCallback = value => `value: ${value.value}, key: ${value.key}`; 43 | 44 | expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/data-structures/priority-queue/PriorityQueue.js: -------------------------------------------------------------------------------- 1 | import MinHeap from '../heap/MinHeap'; 2 | import Comparator from '../../utils/comparator/Comparator'; 3 | 4 | // It is the same as min heap except that when comparing to elements 5 | // we take into account not element's value but rather its priority. 6 | export default class PriorityQueue extends MinHeap { 7 | constructor() { 8 | super(); 9 | this.priorities = {}; 10 | this.compare = new Comparator(this.comparePriority.bind(this)); 11 | } 12 | 13 | /** 14 | * @param {*} item 15 | * @param {number} [priority] 16 | * @return {PriorityQueue} 17 | */ 18 | add(item, priority = 0) { 19 | this.priorities[item] = priority; 20 | super.add(item); 21 | 22 | return this; 23 | } 24 | 25 | /** 26 | * @param {*} item 27 | * @param {Comparator} [customFindingComparator] 28 | * @return {PriorityQueue} 29 | */ 30 | remove(item, customFindingComparator) { 31 | super.remove(item, customFindingComparator); 32 | delete this.priorities[item]; 33 | 34 | return this; 35 | } 36 | 37 | /** 38 | * @param {*} item 39 | * @param {number} priority 40 | * @return {PriorityQueue} 41 | */ 42 | changePriority(item, priority) { 43 | this.remove(item, new Comparator(this.compareValue)); 44 | this.add(item, priority); 45 | 46 | return this; 47 | } 48 | 49 | /** 50 | * @param {*} item 51 | * @return {Number[]} 52 | */ 53 | findByValue(item) { 54 | return this.find(item, new Comparator(this.compareValue)); 55 | } 56 | 57 | /** 58 | * @param {*} item 59 | * @return {boolean} 60 | */ 61 | hasValue(item) { 62 | return this.findByValue(item).length > 0; 63 | } 64 | 65 | /** 66 | * @param {*} a 67 | * @param {*} b 68 | * @return {number} 69 | */ 70 | comparePriority(a, b) { 71 | if (this.priorities[a] === this.priorities[b]) { 72 | return 0; 73 | } 74 | 75 | return this.priorities[a] < this.priorities[b] ? -1 : 1; 76 | } 77 | 78 | /** 79 | * @param {*} a 80 | * @param {*} b 81 | * @return {number} 82 | */ 83 | compareValue(a, b) { 84 | if (a === b) { 85 | return 0; 86 | } 87 | 88 | return a < b ? -1 : 1; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/data-structures/priority-queue/README.md: -------------------------------------------------------------------------------- 1 | # Priority Queue 2 | 3 | In computer science, a priority queue is an abstract data type 4 | which is like a regular queue or stack data structure, but where 5 | additionally each element has a "priority" associated with it. 6 | In a priority queue, an element with high priority is served before 7 | an element with low priority. If two elements have the same 8 | priority, they are served according to their order in the queue. 9 | 10 | While priority queues are often implemented with heaps, they are 11 | conceptually distinct from heaps. A priority queue is an abstract 12 | concept like "a list" or "a map"; just as a list can be implemented 13 | with a linked list or an array, a priority queue can be implemented 14 | with a heap or a variety of other methods such as an unordered 15 | array. 16 | 17 | ## References 18 | 19 | - [Wikipedia](https://en.wikipedia.org/wiki/Priority_queue) 20 | - [YouTube](https://www.youtube.com/watch?v=wptevk0bshY&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=6) 21 | -------------------------------------------------------------------------------- /src/data-structures/queue/Queue.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list/LinkedList'; 2 | 3 | export default class Queue { 4 | constructor() { 5 | this.linkedList = new LinkedList(); 6 | } 7 | 8 | isEmpty() { 9 | return !this.linkedList.tail; 10 | } 11 | 12 | peek() { 13 | if (!this.linkedList.head) { 14 | return null; 15 | } 16 | 17 | return this.linkedList.head.value; 18 | } 19 | 20 | enqueue(value) { 21 | this.linkedList.append(value); 22 | } 23 | 24 | dequeue() { 25 | const removedHead = this.linkedList.deleteHead(); 26 | return removedHead ? removedHead.value : null; 27 | } 28 | 29 | toString(callback) { 30 | return this.linkedList.toString(callback); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/data-structures/queue/README.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | 3 | In computer science, a queue is a particular kind of abstract data 4 | type or collection in which the entities in the collection are 5 | kept in order and the principle (or only) operations on the 6 | collection are the addition of entities to the rear terminal 7 | position, known as enqueue, and removal of entities from the 8 | front terminal position, known as dequeue. This makes the queue 9 | a First-In-First-Out (FIFO) data structure. In a FIFO data 10 | structure, the first element added to the queue will be the 11 | first one to be removed. This is equivalent to the requirement 12 | that once a new element is added, all elements that were added 13 | before have to be removed before the new element can be removed. 14 | Often a peek or front operation is also entered, returning the 15 | value of the front element without dequeuing it. A queue is an 16 | example of a linear data structure, or more abstractly a 17 | sequential collection. 18 | 19 | Representation of a FIFO (first in, first out) queue 20 | 21 | ![Queue](https://upload.wikimedia.org/wikipedia/commons/5/52/Data_Queue.svg) 22 | 23 | ## References 24 | 25 | - [Wikipedia](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) 26 | - [YouTube](https://www.youtube.com/watch?v=wjI1WNcIntg&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=3&) 27 | -------------------------------------------------------------------------------- /src/data-structures/queue/__test__/Queue.test.js: -------------------------------------------------------------------------------- 1 | import Queue from '../Queue'; 2 | 3 | describe('Queue', () => { 4 | it('should create empty queue', () => { 5 | const queue = new Queue(); 6 | expect(queue).not.toBeNull(); 7 | expect(queue.linkedList).not.toBeNull(); 8 | }); 9 | 10 | it('should enqueue data to queue', () => { 11 | const queue = new Queue(); 12 | 13 | queue.enqueue(1); 14 | queue.enqueue(2); 15 | 16 | expect(queue.toString()).toBe('1,2'); 17 | }); 18 | 19 | it('should be possible to enqueue/dequeue objects', () => { 20 | const queue = new Queue(); 21 | 22 | queue.enqueue({ value: 'test1', key: 'key1' }); 23 | queue.enqueue({ value: 'test2', key: 'key2' }); 24 | 25 | const stringifier = value => `${value.key}:${value.value}`; 26 | 27 | expect(queue.toString(stringifier)).toBe('key1:test1,key2:test2'); 28 | expect(queue.dequeue().value).toBe('test1'); 29 | expect(queue.dequeue().value).toBe('test2'); 30 | }); 31 | 32 | it('should peek data from queue', () => { 33 | const queue = new Queue(); 34 | 35 | expect(queue.peek()).toBeNull(); 36 | 37 | queue.enqueue(1); 38 | queue.enqueue(2); 39 | 40 | expect(queue.peek()).toBe(1); 41 | expect(queue.peek()).toBe(1); 42 | }); 43 | 44 | it('should check if queue is empty', () => { 45 | const queue = new Queue(); 46 | 47 | expect(queue.isEmpty()).toBeTruthy(); 48 | 49 | queue.enqueue(1); 50 | 51 | expect(queue.isEmpty()).toBeFalsy(); 52 | }); 53 | 54 | it('should dequeue from queue in FIFO order', () => { 55 | const queue = new Queue(); 56 | 57 | queue.enqueue(1); 58 | queue.enqueue(2); 59 | 60 | expect(queue.dequeue()).toBe(1); 61 | expect(queue.dequeue()).toBe(2); 62 | expect(queue.dequeue()).toBeNull(); 63 | expect(queue.isEmpty()).toBeTruthy(); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/data-structures/stack/README.md: -------------------------------------------------------------------------------- 1 | # Stack 2 | 3 | In computer science, a stack is an abstract data type that serves 4 | as a collection of elements, with two principal operations: 5 | 6 | * **push**, which adds an element to the collection, and 7 | * **pop**, which removes the most recently added element that was not yet removed. 8 | 9 | The order in which elements come off a stack gives rise to its 10 | alternative name, LIFO (last in, first out). Additionally, a 11 | peek operation may give access to the top without modifying 12 | the stack. The name "stack" for this type of structure comes 13 | from the analogy to a set of physical items stacked on top of 14 | each other, which makes it easy to take an item off the top 15 | of the stack, while getting to an item deeper in the stack 16 | may require taking off multiple other items first 17 | 18 | Simple representation of a stack runtime with push and pop operations. 19 | 20 | ![Stack](https://upload.wikimedia.org/wikipedia/commons/b/b4/Lifo_stack.png) 21 | 22 | ## References 23 | 24 | - [Wikipedia](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) 25 | - [YouTube](https://www.youtube.com/watch?v=wjI1WNcIntg&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=3&) 26 | -------------------------------------------------------------------------------- /src/data-structures/stack/Stack.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../linked-list/LinkedList'; 2 | 3 | export default class Stack { 4 | constructor() { 5 | this.linkedList = new LinkedList(); 6 | } 7 | 8 | /** 9 | * @return {boolean} 10 | */ 11 | isEmpty() { 12 | return !this.linkedList.tail; 13 | } 14 | 15 | /** 16 | * @return {*} 17 | */ 18 | peek() { 19 | if (this.isEmpty()) { 20 | return null; 21 | } 22 | 23 | return this.linkedList.tail.value; 24 | } 25 | 26 | /** 27 | * @param {*} value 28 | */ 29 | push(value) { 30 | this.linkedList.append(value); 31 | } 32 | 33 | /** 34 | * @return {*} 35 | */ 36 | pop() { 37 | const removedTail = this.linkedList.deleteTail(); 38 | return removedTail ? removedTail.value : null; 39 | } 40 | 41 | /** 42 | * @return {*[]} 43 | */ 44 | toArray() { 45 | return this.linkedList 46 | .toArray() 47 | .map(linkedListNode => linkedListNode.value) 48 | .reverse(); 49 | } 50 | 51 | /** 52 | * @param {function} [callback] 53 | * @return {string} 54 | */ 55 | toString(callback) { 56 | return this.linkedList.toString(callback); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/data-structures/stack/__test__/Stack.test.js: -------------------------------------------------------------------------------- 1 | import Stack from '../Stack'; 2 | 3 | describe('Stack', () => { 4 | it('should create empty stack', () => { 5 | const stack = new Stack(); 6 | expect(stack).not.toBeNull(); 7 | expect(stack.linkedList).not.toBeNull(); 8 | }); 9 | 10 | it('should stack data to stack', () => { 11 | const stack = new Stack(); 12 | 13 | stack.push(1); 14 | stack.push(2); 15 | 16 | expect(stack.toString()).toBe('1,2'); 17 | }); 18 | 19 | it('should peek data from stack', () => { 20 | const stack = new Stack(); 21 | 22 | expect(stack.peek()).toBeNull(); 23 | 24 | stack.push(1); 25 | stack.push(2); 26 | 27 | expect(stack.peek()).toBe(2); 28 | expect(stack.peek()).toBe(2); 29 | }); 30 | 31 | it('should check if stack is empty', () => { 32 | const stack = new Stack(); 33 | 34 | expect(stack.isEmpty()).toBeTruthy(); 35 | 36 | stack.push(1); 37 | 38 | expect(stack.isEmpty()).toBeFalsy(); 39 | }); 40 | 41 | it('should pop data from stack', () => { 42 | const stack = new Stack(); 43 | 44 | stack.push(1); 45 | stack.push(2); 46 | 47 | expect(stack.pop()).toBe(2); 48 | expect(stack.pop()).toBe(1); 49 | expect(stack.pop()).toBeNull(); 50 | expect(stack.isEmpty()).toBeTruthy(); 51 | }); 52 | 53 | it('should be possible to push/pop objects', () => { 54 | const stack = new Stack(); 55 | 56 | stack.push({ value: 'test1', key: 'key1' }); 57 | stack.push({ value: 'test2', key: 'key2' }); 58 | 59 | const stringifier = value => `${value.key}:${value.value}`; 60 | 61 | expect(stack.toString(stringifier)).toBe('key1:test1,key2:test2'); 62 | expect(stack.pop().value).toBe('test2'); 63 | expect(stack.pop().value).toBe('test1'); 64 | }); 65 | 66 | it('should be possible to convert stack to array', () => { 67 | const stack = new Stack(); 68 | 69 | expect(stack.peek()).toBeNull(); 70 | 71 | stack.push(1); 72 | stack.push(2); 73 | stack.push(3); 74 | 75 | expect(stack.toArray()).toEqual([3, 2, 1]); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /src/data-structures/tree/README.md: -------------------------------------------------------------------------------- 1 | # Tree 2 | 3 | * [Binary Search Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/binary-search-tree) 4 | * [AVL Tree](https://github.com/trekhleb/javascript-algorithms/tree/master/src/data-structures/tree/avl-tree) 5 | 6 | In computer science, a tree is a widely used abstract data 7 | type (ADT) — or data structure implementing this ADT—that 8 | simulates a hierarchical tree structure, with a root value 9 | and subtrees of children with a parent node, represented as 10 | a set of linked nodes. 11 | 12 | A tree data structure can be defined recursively (locally) 13 | as a collection of nodes (starting at a root node), where 14 | each node is a data structure consisting of a value, 15 | together with a list of references to nodes (the "children"), 16 | with the constraints that no reference is duplicated, and none 17 | points to the root. 18 | 19 | A simple unordered tree; in this diagram, the node labeled 7 has 20 | two children, labeled 2 and 6, and one parent, labeled 2. The 21 | root node, at the top, has no parent. 22 | 23 | ![Tree](https://upload.wikimedia.org/wikipedia/commons/f/f7/Binary_tree.svg) 24 | 25 | ## References 26 | 27 | - [Wikipedia](https://en.wikipedia.org/wiki/Tree_(data_structure)) 28 | - [YouTube](https://www.youtube.com/watch?v=oSWTXtMglKE&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=8) 29 | -------------------------------------------------------------------------------- /src/data-structures/tree/avl-tree/README.md: -------------------------------------------------------------------------------- 1 | # AVL Tree 2 | 3 | In computer science, an AVL tree (named after inventors 4 | Adelson-Velsky and Landis) is a self-balancing binary search 5 | tree. It was the first such data structure to be invented. 6 | In an AVL tree, the heights of the two child subtrees of any 7 | node differ by at most one; if at any time they differ by 8 | more than one, rebalancing is done to restore this property. 9 | Lookup, insertion, and deletion all take `O(log n)` time in 10 | both the average and worst cases, where n is the number of 11 | nodes in the tree prior to the operation. Insertions and 12 | deletions may require the tree to be rebalanced by one or 13 | more tree rotations. 14 | 15 | Animation showing the insertion of several elements into an AVL 16 | tree. It includes left, right, left-right and right-left rotations. 17 | 18 | ![AVL Tree](https://upload.wikimedia.org/wikipedia/commons/f/fd/AVL_Tree_Example.gif) 19 | 20 | AVL tree with balance factors (green) 21 | 22 | ![AVL Tree](https://upload.wikimedia.org/wikipedia/commons/a/ad/AVL-tree-wBalance_K.svg) 23 | 24 | ### AVL Tree Rotations 25 | 26 | **Left-Left Rotation** 27 | 28 | ![Left-Left Rotation](http://btechsmartclass.com/DS/images/LL%20Rotation.png) 29 | 30 | **Right-Right Rotation** 31 | 32 | ![Right-Right Rotation](http://btechsmartclass.com/DS/images/RR%20Rotation.png) 33 | 34 | **Left-Right Rotation** 35 | 36 | ![Left-Right Rotation](http://btechsmartclass.com/DS/images/LR%20Rotation.png) 37 | 38 | **Right-Left Rotation** 39 | 40 | ![Right-Right Rotation](http://btechsmartclass.com/DS/images/RL%20Rotation.png) 41 | 42 | ## References 43 | 44 | * [Wikipedia](https://en.wikipedia.org/wiki/AVL_tree) 45 | * [Tutorials Point](https://www.tutorialspoint.com/data_structures_algorithms/avl_tree_algorithm.htm) 46 | * [BTech](http://btechsmartclass.com/DS/U5_T2.html) 47 | * [AVL Tree Insertion on YouTube](https://www.youtube.com/watch?v=rbg7Qf8GkQ4&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=12&) 48 | -------------------------------------------------------------------------------- /src/data-structures/tree/binary-search-tree/BinarySearchTree.js: -------------------------------------------------------------------------------- 1 | import BinarySearchTreeNode from './BinarySearchTreeNode'; 2 | 3 | export default class BinarySearchTree { 4 | constructor() { 5 | this.root = new BinarySearchTreeNode(); 6 | } 7 | 8 | insert(value) { 9 | this.root.insert(value); 10 | } 11 | 12 | contains(value) { 13 | return this.root.contains(value); 14 | } 15 | 16 | remove(value) { 17 | return this.root.remove(value); 18 | } 19 | 20 | toString() { 21 | return this.root.toString(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/data-structures/tree/binary-search-tree/README.md: -------------------------------------------------------------------------------- 1 | # Binary Search Tree 2 | 3 | In computer science, binary search trees (BST), sometimes called 4 | ordered or sorted binary trees, are a particular type of container: 5 | data structures that store "items" (such as numbers, names etc.) 6 | in memory. They allow fast lookup, addition and removal of 7 | items, and can be used to implement either dynamic sets of 8 | items, or lookup tables that allow finding an item by its key 9 | (e.g., finding the phone number of a person by name). 10 | 11 | Binary search trees keep their keys in sorted order, so that lookup 12 | and other operations can use the principle of binary search: 13 | when looking for a key in a tree (or a place to insert a new key), 14 | they traverse the tree from root to leaf, making comparisons to 15 | keys stored in the nodes of the tree and deciding, on the basis 16 | of the comparison, to continue searching in the left or right 17 | subtrees. On average, this means that each comparison allows 18 | the operations to skip about half of the tree, so that each 19 | lookup, insertion or deletion takes time proportional to the 20 | logarithm of the number of items stored in the tree. This is 21 | much better than the linear time required to find items by key 22 | in an (unsorted) array, but slower than the corresponding 23 | operations on hash tables. 24 | 25 | A binary search tree of size 9 and depth 3, with 8 at the root. 26 | The leaves are not drawn. 27 | 28 | ![Binary Search Tree](https://upload.wikimedia.org/wikipedia/commons/d/da/Binary_search_tree.svg) 29 | 30 | ## References 31 | 32 | - [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_tree) 33 | - [Inserting to BST on YouTube](https://www.youtube.com/watch?v=wcIRPqTR3Kc&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=9&t=0s) 34 | -------------------------------------------------------------------------------- /src/data-structures/tree/binary-search-tree/__test__/BinarySearchTree.test.js: -------------------------------------------------------------------------------- 1 | import BinarySearchTree from '../BinarySearchTree'; 2 | 3 | describe('BinarySearchTree', () => { 4 | it('should create binary search tree', () => { 5 | const bst = new BinarySearchTree(); 6 | 7 | expect(bst).toBeDefined(); 8 | expect(bst.root).toBeDefined(); 9 | expect(bst.root.value).toBeNull(); 10 | expect(bst.root.left).toBeNull(); 11 | expect(bst.root.right).toBeNull(); 12 | }); 13 | 14 | it('should insert values', () => { 15 | const bst = new BinarySearchTree(); 16 | 17 | bst.insert(10); 18 | bst.insert(20); 19 | bst.insert(5); 20 | 21 | expect(bst.toString()).toBe('5,10,20'); 22 | }); 23 | 24 | it('should check if value exists', () => { 25 | const bst = new BinarySearchTree(); 26 | 27 | bst.insert(10); 28 | bst.insert(20); 29 | bst.insert(5); 30 | 31 | expect(bst.contains(20)).toBeTruthy(); 32 | expect(bst.contains(40)).toBeFalsy(); 33 | }); 34 | 35 | it('should remove nodes', () => { 36 | const bst = new BinarySearchTree(); 37 | 38 | bst.insert(10); 39 | bst.insert(20); 40 | bst.insert(5); 41 | 42 | expect(bst.toString()).toBe('5,10,20'); 43 | 44 | bst.remove(5); 45 | expect(bst.toString()).toBe('10,20'); 46 | bst.remove(20); 47 | expect(bst.toString()).toBe('10'); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/data-structures/trie/README.md: -------------------------------------------------------------------------------- 1 | # Trie 2 | 3 | In computer science, a trie, also called digital tree and sometimes 4 | radix tree or prefix tree (as they can be searched by prefixes), 5 | is a kind of search tree—an ordered tree data structure that is 6 | used to store a dynamic set or associative array where the keys 7 | are usually strings. Unlike a binary search tree, no node in the 8 | tree stores the key associated with that node; instead, its 9 | position in the tree defines the key with which it is associated. 10 | All the descendants of a node have a common prefix of the string 11 | associated with that node, and the root is associated with the 12 | empty string. Values are not necessarily associated with every 13 | node. Rather, values tend only to be associated with leaves, 14 | and with some inner nodes that correspond to keys of interest. 15 | For the space-optimized presentation of prefix tree, see compact 16 | prefix tree. 17 | 18 | ![Trie](https://upload.wikimedia.org/wikipedia/commons/b/be/Trie_example.svg) 19 | 20 | ## References 21 | 22 | - [Wikipedia](https://en.wikipedia.org/wiki/Trie) 23 | - [YouTube](https://www.youtube.com/watch?v=zIjfhVPRZCg&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=7&t=0s) 24 | -------------------------------------------------------------------------------- /src/data-structures/trie/Trie.js: -------------------------------------------------------------------------------- 1 | import TrieNode from './TrieNode'; 2 | 3 | const HEAD_CHARACTER = '*'; 4 | 5 | export default class Trie { 6 | constructor() { 7 | this.head = new TrieNode(HEAD_CHARACTER); 8 | } 9 | 10 | addWord(word) { 11 | const characters = Array.from(word); 12 | let currentNode = this.head; 13 | for (let charIndex = 0; charIndex < characters.length; charIndex += 1) { 14 | const isComplete = charIndex === characters.length - 1; 15 | currentNode = currentNode.addChild(characters[charIndex], isComplete); 16 | } 17 | } 18 | 19 | suggestNextCharacters(word) { 20 | const lastCharacter = this.getLastCharacterNode(word); 21 | 22 | if (!lastCharacter) { 23 | return null; 24 | } 25 | 26 | return lastCharacter.suggestChildren(); 27 | } 28 | 29 | doesWordExist(word) { 30 | return !!this.getLastCharacterNode(word); 31 | } 32 | 33 | getLastCharacterNode(word) { 34 | const characters = Array.from(word); 35 | let currentNode = this.head; 36 | for (let charIndex = 0; charIndex < characters.length; charIndex += 1) { 37 | if (!currentNode.hasChild(characters[charIndex])) { 38 | return null; 39 | } 40 | currentNode = currentNode.getChild(characters[charIndex]); 41 | } 42 | 43 | return currentNode; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/data-structures/trie/TrieNode.js: -------------------------------------------------------------------------------- 1 | export default class TrieNode { 2 | constructor(character, isCompleteWord = false) { 3 | this.character = character; 4 | this.isCompleteWord = isCompleteWord; 5 | this.children = {}; 6 | } 7 | 8 | getChild(character) { 9 | if (!Object.prototype.hasOwnProperty.call(this.children, character)) { 10 | return null; 11 | } 12 | 13 | return this.children[character]; 14 | } 15 | 16 | addChild(character, isCompleteWord = false) { 17 | if (!this.children[character]) { 18 | this.children[character] = new TrieNode(character, isCompleteWord); 19 | } 20 | 21 | return this.children[character]; 22 | } 23 | 24 | hasChild(character) { 25 | return !!this.children[character]; 26 | } 27 | 28 | suggestChildren() { 29 | return Object.keys(this.children); 30 | } 31 | 32 | toString() { 33 | let childrenAsString = Object.keys(this.children).toString(); 34 | childrenAsString = childrenAsString ? `:${childrenAsString}` : ''; 35 | const isCompleteString = this.isCompleteWord ? '*' : ''; 36 | 37 | return `${this.character}${isCompleteString}${childrenAsString}`; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/data-structures/trie/__test__/Trie.test.js: -------------------------------------------------------------------------------- 1 | import Trie from '../Trie'; 2 | 3 | describe('Trie', () => { 4 | it('should create trie', () => { 5 | const trie = new Trie(); 6 | 7 | expect(trie).toBeDefined(); 8 | expect(trie.head.toString()).toBe('*'); 9 | }); 10 | 11 | it('should add words to trie', () => { 12 | const trie = new Trie(); 13 | 14 | trie.addWord('cat'); 15 | 16 | expect(trie.head.toString()).toBe('*:c'); 17 | expect(trie.head.getChild('c').toString()).toBe('c:a'); 18 | 19 | trie.addWord('car'); 20 | expect(trie.head.toString()).toBe('*:c'); 21 | expect(trie.head.getChild('c').toString()).toBe('c:a'); 22 | expect(trie.head.getChild('c').getChild('a').toString()).toBe('a:t,r'); 23 | expect(trie.head.getChild('c').getChild('a').getChild('t').toString()).toBe('t*'); 24 | }); 25 | 26 | it('should suggests next characters', () => { 27 | const trie = new Trie(); 28 | 29 | trie.addWord('cat'); 30 | trie.addWord('cats'); 31 | trie.addWord('car'); 32 | trie.addWord('caption'); 33 | 34 | expect(trie.suggestNextCharacters('ca')).toEqual(['t', 'r', 'p']); 35 | expect(trie.suggestNextCharacters('cat')).toEqual(['s']); 36 | expect(trie.suggestNextCharacters('cab')).toBeNull(); 37 | }); 38 | 39 | it('should check if word exists', () => { 40 | const trie = new Trie(); 41 | 42 | trie.addWord('cat'); 43 | trie.addWord('cats'); 44 | trie.addWord('car'); 45 | trie.addWord('caption'); 46 | 47 | expect(trie.doesWordExist('cat')).toBeTruthy(); 48 | expect(trie.doesWordExist('cap')).toBeTruthy(); 49 | expect(trie.doesWordExist('call')).toBeFalsy(); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/data-structures/trie/__test__/TrieNode.test.js: -------------------------------------------------------------------------------- 1 | import TrieNode from '../TrieNode'; 2 | 3 | describe('TrieNode', () => { 4 | it('should create trie node', () => { 5 | const trieNode = new TrieNode('c', true); 6 | 7 | expect(trieNode.character).toBe('c'); 8 | expect(trieNode.isCompleteWord).toBeTruthy(); 9 | expect(trieNode.toString()).toBe('c*'); 10 | }); 11 | 12 | it('should add child nodes', () => { 13 | const trieNode = new TrieNode('c'); 14 | 15 | trieNode.addChild('a', true); 16 | trieNode.addChild('o'); 17 | 18 | expect(trieNode.toString()).toBe('c:a,o'); 19 | }); 20 | 21 | it('should get child nodes', () => { 22 | const trieNode = new TrieNode('c'); 23 | 24 | trieNode.addChild('a'); 25 | trieNode.addChild('o'); 26 | 27 | expect(trieNode.getChild('a').toString()).toBe('a'); 28 | expect(trieNode.getChild('o').toString()).toBe('o'); 29 | expect(trieNode.getChild('b')).toBeNull(); 30 | }); 31 | 32 | it('should check if node has specific child', () => { 33 | const trieNode = new TrieNode('c'); 34 | 35 | trieNode.addChild('a'); 36 | trieNode.addChild('o'); 37 | 38 | expect(trieNode.hasChild('a')).toBeTruthy(); 39 | expect(trieNode.hasChild('o')).toBeTruthy(); 40 | expect(trieNode.hasChild('b')).toBeFalsy(); 41 | }); 42 | 43 | it('should suggest next children', () => { 44 | const trieNode = new TrieNode('c'); 45 | 46 | trieNode.addChild('a'); 47 | trieNode.addChild('o'); 48 | 49 | expect(trieNode.suggestChildren()).toEqual(['a', 'o']); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/playground/README.md: -------------------------------------------------------------------------------- 1 | # Playground 2 | 3 | You may use `playground.js` file to play with data 4 | structures and algorithms. The code from `playground.js` may 5 | be tested in `./__test__/playground.test.js` file. 6 | 7 | To run tests simply run: 8 | 9 | ``` 10 | npm test -- -t 'playground' 11 | ``` 12 | -------------------------------------------------------------------------------- /src/playground/__test__/playground.test.js: -------------------------------------------------------------------------------- 1 | describe('playground', () => { 2 | it('should perform playground tasks', () => { 3 | // Place your playground tests here. 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /src/playground/playground.js: -------------------------------------------------------------------------------- 1 | // Place your playground code here. 2 | -------------------------------------------------------------------------------- /src/utils/comparator/Comparator.js: -------------------------------------------------------------------------------- 1 | export default class Comparator { 2 | /** 3 | * @param {function(a: *, b: *)} [compareFunction] 4 | */ 5 | constructor(compareFunction) { 6 | this.compare = compareFunction || Comparator.defaultCompareFunction; 7 | } 8 | 9 | /** 10 | * @param {(string|number)} a 11 | * @param {(string|number)} b 12 | * @returns {number} 13 | */ 14 | static defaultCompareFunction(a, b) { 15 | if (a === b) { 16 | return 0; 17 | } 18 | 19 | return a < b ? -1 : 1; 20 | } 21 | 22 | equal(a, b) { 23 | return this.compare(a, b) === 0; 24 | } 25 | 26 | lessThan(a, b) { 27 | return this.compare(a, b) < 0; 28 | } 29 | 30 | greaterThan(a, b) { 31 | return this.compare(a, b) > 0; 32 | } 33 | 34 | lessThanOrEqual(a, b) { 35 | return this.lessThan(a, b) || this.equal(a, b); 36 | } 37 | 38 | greaterThanOrEqual(a, b) { 39 | return this.greaterThan(a, b) || this.equal(a, b); 40 | } 41 | 42 | reverse() { 43 | const compareOriginal = this.compare; 44 | this.compare = (a, b) => compareOriginal(b, a); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/comparator/__test__/Comparator.test.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../Comparator'; 2 | 3 | describe('Comparator', () => { 4 | it('should compare with default comparator function', () => { 5 | const comparator = new Comparator(); 6 | 7 | expect(comparator.equal(0, 0)).toBeTruthy(); 8 | expect(comparator.equal(0, 1)).toBeFalsy(); 9 | expect(comparator.equal('a', 'a')).toBeTruthy(); 10 | expect(comparator.lessThan(1, 2)).toBeTruthy(); 11 | expect(comparator.lessThan(-1, 2)).toBeTruthy(); 12 | expect(comparator.lessThan('a', 'b')).toBeTruthy(); 13 | expect(comparator.lessThan('a', 'ab')).toBeTruthy(); 14 | expect(comparator.lessThan(10, 2)).toBeFalsy(); 15 | expect(comparator.lessThanOrEqual(10, 2)).toBeFalsy(); 16 | expect(comparator.lessThanOrEqual(1, 1)).toBeTruthy(); 17 | expect(comparator.lessThanOrEqual(0, 0)).toBeTruthy(); 18 | expect(comparator.greaterThan(0, 0)).toBeFalsy(); 19 | expect(comparator.greaterThan(10, 0)).toBeTruthy(); 20 | expect(comparator.greaterThanOrEqual(10, 0)).toBeTruthy(); 21 | expect(comparator.greaterThanOrEqual(10, 10)).toBeTruthy(); 22 | expect(comparator.greaterThanOrEqual(0, 10)).toBeFalsy(); 23 | }); 24 | 25 | it('should compare with custom comparator function', () => { 26 | const comparator = new Comparator((a, b) => { 27 | if (a.length === b.length) { 28 | return 0; 29 | } 30 | 31 | return a.length < b.length ? -1 : 1; 32 | }); 33 | 34 | expect(comparator.equal('a', 'b')).toBeTruthy(); 35 | expect(comparator.equal('a', '')).toBeFalsy(); 36 | expect(comparator.lessThan('b', 'aa')).toBeTruthy(); 37 | expect(comparator.greaterThanOrEqual('a', 'aa')).toBeFalsy(); 38 | expect(comparator.greaterThanOrEqual('aa', 'a')).toBeTruthy(); 39 | expect(comparator.greaterThanOrEqual('a', 'a')).toBeTruthy(); 40 | 41 | comparator.reverse(); 42 | 43 | expect(comparator.equal('a', 'b')).toBeTruthy(); 44 | expect(comparator.equal('a', '')).toBeFalsy(); 45 | expect(comparator.lessThan('b', 'aa')).toBeFalsy(); 46 | expect(comparator.greaterThanOrEqual('a', 'aa')).toBeTruthy(); 47 | expect(comparator.greaterThanOrEqual('aa', 'a')).toBeFalsy(); 48 | expect(comparator.greaterThanOrEqual('a', 'a')).toBeTruthy(); 49 | }); 50 | }); 51 | --------------------------------------------------------------------------------