├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── jest.config.js ├── main.js ├── package-lock.json ├── package.json ├── src ├── algorithms │ ├── cryptography │ │ └── polynomial-hash.js │ ├── graphs │ │ ├── __tests__ │ │ │ ├── articulation-points.test.js │ │ │ ├── bellman-ford.test.js │ │ │ ├── breadth-first-search.test.js │ │ │ ├── bridges.test.js │ │ │ ├── depth-first-search.test.js │ │ │ ├── dijkstra.test.js │ │ │ ├── floyd-warshall.test.js │ │ │ ├── kruskal.test.js │ │ │ ├── prim.test.js │ │ │ ├── strongly-connected-components.test.js │ │ │ ├── topological-sort.test.js │ │ │ └── traveling-salesman.test.js │ │ ├── articulation-points.js │ │ ├── bellman-ford.js │ │ ├── breadth-first-search.js │ │ ├── bridges.js │ │ ├── depth-first-search.js │ │ ├── detect-cycle │ │ │ ├── __tests__ │ │ │ │ ├── directed.test.js │ │ │ │ ├── undirected-disjoint-set.test.js │ │ │ │ └── undirected.test.js │ │ │ ├── directed.js │ │ │ ├── undirected-disjoint-set.js │ │ │ └── undirected.js │ │ ├── dijkstra.js │ │ ├── floyd-warshall.js │ │ ├── kruskal.js │ │ ├── prim.js │ │ ├── strongly-connected-components.js │ │ ├── topological-sort.js │ │ └── traveling-salesman.js │ ├── linked-lists │ │ ├── __tests__ │ │ │ ├── reversal-traverse.test.js │ │ │ └── traverse.test.js │ │ ├── reversal-traverse.js │ │ └── traverse.js │ ├── machine-learning │ │ ├── __tests__ │ │ │ ├── k-means.test.js │ │ │ └── k-nn.test.js │ │ ├── k-means.js │ │ ├── k-nn.js │ │ └── nano-neuron.js │ ├── math │ │ ├── __tests__ │ │ │ ├── degree-to-radian.test.js │ │ │ ├── euclidean-algorithm-iterative.test.js │ │ │ ├── euclidean-algorithm.test.js │ │ │ ├── euclidean-distance.test.js │ │ │ ├── factorial-recursive.test.js │ │ │ ├── factorial.test.js │ │ │ ├── fast-powering.test.js │ │ │ ├── fibonacci-nth.test.js │ │ │ ├── fibonacci.test.js │ │ │ ├── is-power-of-two-bitwise.test.js │ │ │ ├── is-power-of-two.test.js │ │ │ ├── least-common-multiple.test.js │ │ │ ├── matrix.test.js │ │ │ ├── prime-factors.test.js │ │ │ ├── radian-to-degree.test.js │ │ │ ├── sieve-of-eratosthenes.test.js │ │ │ └── trial-division.test.js │ │ ├── binary-floating-point │ │ │ ├── __tests__ │ │ │ │ ├── bits-to-float.test.js │ │ │ │ └── float-as-binary-string.test.js │ │ │ ├── bits-to-float.js │ │ │ ├── float-as-binary-string.js │ │ │ └── test-cases.js │ │ ├── bits │ │ │ ├── __tests__ │ │ │ │ ├── bit-length.test.js │ │ │ │ ├── bits-diff.test.js │ │ │ │ ├── clear-bit.test.js │ │ │ │ ├── count-set-bits.test.js │ │ │ │ ├── divide-by-two.test.js │ │ │ │ ├── get-bit.test.js │ │ │ │ ├── is-even.test.js │ │ │ │ ├── is-positive.js │ │ │ │ ├── multiply-by-two.test.js │ │ │ │ ├── set-bit.test.js │ │ │ │ ├── switch-sign.test.js │ │ │ │ └── update-bit.test.js │ │ │ ├── bit-length.js │ │ │ ├── bits-diff.js │ │ │ ├── clear-bit.js │ │ │ ├── count-set-bits.js │ │ │ ├── divide-by-two.js │ │ │ ├── get-bit.js │ │ │ ├── is-even.js │ │ │ ├── is-positive.js │ │ │ ├── multiply-by-two.js │ │ │ ├── set-bit.js │ │ │ ├── switch-sign.js │ │ │ └── update-bit.js │ │ ├── degree-to-radian.js │ │ ├── euclidean-algorithm-iterative.js │ │ ├── euclidean-algorithm.js │ │ ├── euclidean-distance.js │ │ ├── factorial-recursive.js │ │ ├── factorial.js │ │ ├── fast-powering.js │ │ ├── fibonacci-nth.js │ │ ├── fibonacci.js │ │ ├── is-power-of-two-bitwise.js │ │ ├── is-power-of-two.js │ │ ├── least-common-multiple.js │ │ ├── matrix.js │ │ ├── prime-factors.js │ │ ├── radian-to-degree.js │ │ ├── sieve-of-eratosthenes.js │ │ └── trial-division.js │ ├── other │ │ ├── __tests__ │ │ │ ├── hanoi-tower.test.js │ │ │ ├── knight-tour.test.js │ │ │ └── square-matrix-rotation.test.js │ │ ├── hanoi-tower.js │ │ ├── knight-tour.js │ │ ├── n-queens-problem │ │ │ ├── __tests__ │ │ │ │ └── n-queens.test.js │ │ │ ├── n-queens.js │ │ │ └── queen-position.js │ │ ├── rain-terraces │ │ │ ├── __tests__ │ │ │ │ ├── bf.test.js │ │ │ │ └── dp.test.js │ │ │ ├── bf.js │ │ │ └── dp.js │ │ ├── recursive-staircase │ │ │ ├── __tests__ │ │ │ │ ├── bf.test.js │ │ │ │ ├── dp.test.js │ │ │ │ ├── it.test.js │ │ │ │ └── mm.test.js │ │ │ ├── bf.js │ │ │ ├── dp.js │ │ │ ├── it.js │ │ │ └── mm.js │ │ └── square-matrix-rotation.js │ ├── searches │ │ ├── __tests__ │ │ │ ├── binary-search.test.js │ │ │ ├── interpolation-search.test.js │ │ │ ├── jump-search.test.js │ │ │ └── linear-search.test.js │ │ ├── binary-search.js │ │ ├── interpolation-search.js │ │ ├── jump-search.js │ │ └── linear-search.js │ ├── sets │ │ ├── __tests__ │ │ │ ├── cartesian-product.test.js │ │ │ ├── combination-sum.test.js │ │ │ ├── fisher-yates.test.js │ │ │ └── shortest-common-supersequence.test.js │ │ ├── cartesian-product.js │ │ ├── combination-sum.js │ │ ├── combinations │ │ │ ├── __test__ │ │ │ │ ├── with-repetitions.test.js │ │ │ │ └── without-repetitions.test.js │ │ │ ├── with-repetitions.js │ │ │ └── without-repetitions.js │ │ ├── fisher-yates.js │ │ ├── longest-common-subsequence │ │ │ ├── __tests__ │ │ │ │ ├── matrix.test.js │ │ │ │ └── recursive.test.js │ │ │ ├── matrix.js │ │ │ └── recursive.js │ │ ├── maximum-subarray │ │ │ ├── __tests__ │ │ │ │ ├── brute-force.test.js │ │ │ │ ├── divide-conquer.test.js │ │ │ │ └── dynamic-programming.test.js │ │ │ ├── brute-force.js │ │ │ ├── divide-conquer.js │ │ │ └── dynamic-programming.js │ │ ├── permutations │ │ │ ├── __tests__ │ │ │ │ ├── with-repetitions.test.js │ │ │ │ └── without-repetitions.test.js │ │ │ ├── with-repetitions.js │ │ │ └── without-repetitions.js │ │ ├── power-set │ │ │ ├── __tests__ │ │ │ │ ├── backtracking.test.js │ │ │ │ ├── bitwise.test.js │ │ │ │ └── cascading.test.js │ │ │ ├── backtracking.js │ │ │ ├── bitwise.js │ │ │ └── cascading.js │ │ └── shortest-common-supersequence.js │ ├── sorting │ │ ├── __tests__ │ │ │ ├── bubble-sort.test.js │ │ │ ├── bucket-sort.test.js │ │ │ ├── counting-sort.test.js │ │ │ ├── heap-sort.test.js │ │ │ ├── insertion-sort.test.js │ │ │ ├── merge-sort.test.js │ │ │ ├── quick-sort.test.js │ │ │ ├── radix-sort.test.js │ │ │ ├── selection-sort.test.js │ │ │ └── shell-sort.test.js │ │ ├── bubble-sort.js │ │ ├── bucket-sort.js │ │ ├── counting-sort.js │ │ ├── heap-sort.js │ │ ├── insertion-sort.js │ │ ├── merge-sort.js │ │ ├── quick-sort.js │ │ ├── radix-sort.js │ │ ├── selection-sort.js │ │ ├── shell-sort.js │ │ ├── sort-tester.js │ │ └── sort.js │ ├── statistics │ │ ├── __tests__ │ │ │ └── weighted-random.test.js │ │ └── weighted-random.js │ ├── strings │ │ ├── __tests__ │ │ │ ├── hamming-distance.test.js │ │ │ ├── knuth-morris-pratt.test.js │ │ │ ├── levenshtein-distance.test.js │ │ │ ├── longest-common-substring.test.js │ │ │ └── rabin-karp.test.js │ │ ├── hamming-distance.js │ │ ├── knuth-morris-pratt.js │ │ ├── levenshtein-distance.js │ │ ├── longest-common-substring.js │ │ └── rabin-karp.js │ └── trees │ │ ├── __tests__ │ │ ├── breadth-first-search.test.js │ │ └── depth-first-search.test.js │ │ ├── breadth-first-search.js │ │ └── depth-first-search.js ├── data-structures │ ├── __tests__ │ │ ├── bloom-filter.test.js │ │ ├── doubly-linked-list.test.js │ │ ├── hash-table.test.js │ │ ├── linked-list.test.js │ │ ├── lru-cache-on-map.test.js │ │ ├── lru-cache.test.js │ │ ├── priority-queue.test.js │ │ ├── queue.test.js │ │ └── stack.test.js │ ├── bloom-filter.js │ ├── disjoint-set │ │ ├── __tests__ │ │ │ ├── ad-hoc.test.js │ │ │ ├── disjoint-set.test.js │ │ │ └── item.test.js │ │ ├── ad-hoc.js │ │ ├── index.js │ │ └── item.js │ ├── doubly-linked-list.js │ ├── graph │ │ ├── __tests__ │ │ │ ├── edge.test.js │ │ │ ├── graph.test.js │ │ │ └── node.test.js │ │ ├── edge.js │ │ ├── index.js │ │ └── node.js │ ├── hash-table.js │ ├── heap │ │ ├── __tests__ │ │ │ ├── heap.test.js │ │ │ ├── max-heap.test.js │ │ │ └── min-heap.test.js │ │ ├── index.js │ │ ├── max-heap.js │ │ └── min-heap.js │ ├── linked-list.js │ ├── lru-cache-on-map.js │ ├── lru-cache.js │ ├── priority-queue.js │ ├── queue.js │ ├── stack.js │ ├── tree │ │ ├── __tests__ │ │ │ ├── avl-tree.test.js │ │ │ ├── binary-search-tree-node.test.js │ │ │ ├── binary-search-tree.test.js │ │ │ ├── binary-tree-node.test.js │ │ │ ├── fenwick-tree.test.js │ │ │ ├── red-black-tree.test.js │ │ │ └── segment-tree.test.js │ │ ├── avl-tree.js │ │ ├── binary-search-tree.js │ │ ├── binary-tree-node.js │ │ ├── fenwick-tree.js │ │ ├── red-black-tree.js │ │ └── segment-tree.js │ └── trie │ │ ├── __tests__ │ │ ├── node.test.js │ │ └── trie.test.js │ │ ├── index.js │ │ └── node.js └── utils │ └── comparator.js └── style.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Igor Agapov 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Структуры данных и алгоритмы на JavaScript 2 | 3 | > wip 4 | 5 | Структуры данных и алгоритмы, реализованные на JavaScript. 6 | 7 | - [Раздел на MyJavaScript](https://my-js.org) 8 | - [Источник вдохновения](https://github.com/trekhleb/javascript-algorithms) 9 | 10 | ## Запуск 11 | 12 | ```bash 13 | # Клонируем репозиторий 14 | git clone https://github.com/harryheman/algorithms-data-structures.git 15 | 16 | # Переходим в директорию проекта 17 | cd algorithms-data-structures 18 | 19 | # Устанавливаем зависимости 20 | npm i 21 | 22 | # Запускаем сервер для разработки 23 | npm run dev 24 | 25 | # Запускаем тестирование 26 | npm run test 27 | ``` 28 | 29 | ## Лицензия 30 | 31 | Проект находится под лицензией [MIT](LICENSE). -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Если эта настройка имеет значение `true`, Jest перестанет выполнять тесты 3 | // после первого провала. 4 | bail: false, 5 | 6 | // Если эта настройка имеет значение `true`, Jest будет представлять отчет о каждом тесте. 7 | verbose: false, 8 | 9 | // Индикатор сбора информации о покрытии во время выполнения тестов. 10 | collectCoverage: false, 11 | 12 | // Директория для файлов о покрытии. 13 | coverageDirectory: './coverage/', 14 | 15 | // Паттерны игнорируемых тестов. 16 | testPathIgnorePatterns: ['/node_modules/'], 17 | 18 | // Паттерны тестов, игнорируемых при сборе информации о покрытии. 19 | coveragePathIgnorePatterns: ['/node_modules/'], 20 | 21 | // Паттерн тестовых файлов. 22 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', 23 | 24 | // @see: https://github.com/facebook/jest/issues/6769 25 | testEnvironmentOptions: { 26 | url: 'http://localhost/', 27 | }, 28 | 29 | // @see: https://jestjs.io/docs/en/configuration#coveragethreshold-object 30 | coverageThreshold: { 31 | global: { 32 | statements: 100, 33 | branches: 95, 34 | functions: 100, 35 | lines: 100, 36 | }, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harryheman/algorithms-data-structures/0cca677023da1bf122e8be77dc38db38ed4969aa/main.js -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algorithms-data-structures", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "test": "jest" 10 | }, 11 | "devDependencies": { 12 | "vite": "^5.2.0" 13 | }, 14 | "dependencies": { 15 | "@babel/preset-env": "^7.24.6", 16 | "jest": "^29.7.0" 17 | } 18 | } -------------------------------------------------------------------------------- /src/algorithms/graphs/__tests__/topological-sort.test.js: -------------------------------------------------------------------------------- 1 | import GraphEdge from '../../../data-structures/graph/edge' 2 | import Graph from '../../../data-structures/graph/index' 3 | import GraphNode from '../../../data-structures/graph/node' 4 | import topologicalSort from '../topological-sort' 5 | 6 | describe('topologicalSort', () => { 7 | it('должен выполнить топологическую сортировку узлов графа', () => { 8 | const nodeA = new GraphNode('A') 9 | const nodeB = new GraphNode('B') 10 | const nodeC = new GraphNode('C') 11 | const nodeD = new GraphNode('D') 12 | const nodeE = new GraphNode('E') 13 | const nodeF = new GraphNode('F') 14 | const nodeG = new GraphNode('G') 15 | const nodeH = new GraphNode('H') 16 | 17 | const edgeAC = new GraphEdge(nodeA, nodeC) 18 | const edgeBC = new GraphEdge(nodeB, nodeC) 19 | const edgeBD = new GraphEdge(nodeB, nodeD) 20 | const edgeCE = new GraphEdge(nodeC, nodeE) 21 | const edgeDF = new GraphEdge(nodeD, nodeF) 22 | const edgeEF = new GraphEdge(nodeE, nodeF) 23 | const edgeEH = new GraphEdge(nodeE, nodeH) 24 | const edgeFG = new GraphEdge(nodeF, nodeG) 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.getAllNodes().length) 42 | expect(sortedVertices).toEqual([ 43 | nodeB, 44 | nodeD, 45 | nodeA, 46 | nodeC, 47 | nodeE, 48 | nodeH, 49 | nodeF, 50 | nodeG, 51 | ]) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /src/algorithms/graphs/__tests__/traveling-salesman.test.js: -------------------------------------------------------------------------------- 1 | import GraphEdge from '../../../data-structures/graph/edge' 2 | import Graph from '../../../data-structures/graph/index' 3 | import GraphNode from '../../../data-structures/graph/node' 4 | import bfTravellingSalesman from '../traveling-salesman' 5 | 6 | describe('bfTravelingSalesman', () => { 7 | it('должен решить задачу для простого графа', () => { 8 | const nodeA = new GraphNode('A') 9 | const nodeB = new GraphNode('B') 10 | const nodeC = new GraphNode('C') 11 | const nodeD = new GraphNode('D') 12 | 13 | const edgeAB = new GraphEdge(nodeA, nodeB, 1) 14 | const edgeBD = new GraphEdge(nodeB, nodeD, 1) 15 | const edgeDC = new GraphEdge(nodeD, nodeC, 1) 16 | const edgeCA = new GraphEdge(nodeC, nodeA, 1) 17 | 18 | const edgeBA = new GraphEdge(nodeB, nodeA, 5) 19 | const edgeDB = new GraphEdge(nodeD, nodeB, 8) 20 | const edgeCD = new GraphEdge(nodeC, nodeD, 7) 21 | const edgeAC = new GraphEdge(nodeA, nodeC, 4) 22 | const edgeAD = new GraphEdge(nodeA, nodeD, 2) 23 | const edgeDA = new GraphEdge(nodeD, nodeA, 3) 24 | const edgeBC = new GraphEdge(nodeB, nodeC, 3) 25 | const edgeCB = new GraphEdge(nodeC, nodeB, 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(nodeA.getKey()) 47 | expect(salesmanPath[1].getKey()).toEqual(nodeB.getKey()) 48 | expect(salesmanPath[2].getKey()).toEqual(nodeD.getKey()) 49 | expect(salesmanPath[3].getKey()).toEqual(nodeC.getKey()) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /src/algorithms/graphs/bellman-ford.js: -------------------------------------------------------------------------------- 1 | // Функция принимает граф и начальную вершину 2 | export default function bellmanFord(graph, startNode) { 3 | // Расстояния 4 | const distances = {} 5 | // Предыдущие вершины 6 | const previous = {} 7 | 8 | // Расстояние до начальной вершины равняется 0 9 | distances[startNode.getKey()] = 0 10 | // Все остальные расстояния равняются бесконечности 11 | graph.getAllNodes().forEach((node) => { 12 | if (node.getKey() !== startNode.getKey()) { 13 | distances[node.getKey()] = Infinity 14 | } 15 | previous[node.getKey()] = null 16 | }) 17 | 18 | // Нам требуется `V - 1` итераций, где `V` - множество вершин 19 | for (let i = 0; i < graph.getAllNodes().length - 1; i++) { 20 | // Перебираем все вершины на каждой итерации 21 | Object.keys(distances).forEach((key) => { 22 | const node = graph.getNodeByKey(key) 23 | 24 | // Перебираем все ребра 25 | graph.getNeighbors(node).forEach((neighbor) => { 26 | const edge = graph.findEdge(node, neighbor) 27 | // Проверяем, является ли расстояние до соседа 28 | // на этой итерации меньше, чем на предыдущей 29 | const distanceToNeighbor = distances[node.getKey()] + edge.weight 30 | if (distanceToNeighbor < distances[neighbor.getKey()]) { 31 | distances[neighbor.getKey()] = distanceToNeighbor 32 | previous[neighbor.getKey()] = node 33 | } 34 | }) 35 | }) 36 | } 37 | 38 | return { 39 | distances, 40 | previous, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/algorithms/graphs/breadth-first-search.js: -------------------------------------------------------------------------------- 1 | import Queue from '../../data-structures/queue' 2 | 3 | // Функция инициализации обработчиков 4 | function initCallbacks(callbacks = {}) { 5 | const initiatedCallbacks = {} 6 | const stubCallback = () => {} 7 | // Замыкание 8 | const defaultAllowTraverseCallback = (() => { 9 | // Посещенные узлы 10 | const traversed = {} 11 | return ({ nextNode }) => { 12 | // Пропускаем следующий узел, если он уже был посещен 13 | if (!traversed[nextNode.getKey()]) { 14 | traversed[nextNode.getKey()] = true 15 | return true 16 | } 17 | return false 18 | } 19 | })() 20 | initiatedCallbacks.allowTraverse = 21 | callbacks.allowTraverse || defaultAllowTraverseCallback 22 | initiatedCallbacks.enterNode = callbacks.enterNode || stubCallback 23 | initiatedCallbacks.leaveNode = callbacks.leaveNode || stubCallback 24 | return initiatedCallbacks 25 | } 26 | 27 | // Функция принимает граф, начальный узел и обработчики 28 | export default function breadthFirstSearch(graph, startNode, callbacks) { 29 | // Инициализируем обработчики 30 | const _callbacks = initCallbacks(callbacks) 31 | // Создаем очередь 32 | const queue = new Queue() 33 | 34 | // Добавляем начальный узел в конец очереди 35 | queue.enqueue(startNode) 36 | 37 | let previousNode = null 38 | 39 | // Пока очередь не является пустой 40 | while (!queue.isEmpty()) { 41 | // Извлекаем узел из начала очереди 42 | const currentNode = queue.dequeue() 43 | 44 | // Вызываем обработчик вхождения в узел 45 | _callbacks.enterNode({ currentNode, previousNode }) 46 | 47 | // Перебираем соседей текущего узла 48 | graph.getNeighbors(currentNode).forEach((nextNode) => { 49 | // Если посещение следующего узла разрешено 50 | if (_callbacks.allowTraverse({ previousNode, currentNode, nextNode })) { 51 | // Помещаем его в очередь 52 | queue.enqueue(nextNode) 53 | } 54 | }) 55 | 56 | // Вызываем обработчик выхода из узла 57 | _callbacks.leaveNode({ currentNode, previousNode }) 58 | 59 | // Запоминаем текущий узел перед следующей итерацией 60 | previousNode = currentNode 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/algorithms/graphs/depth-first-search.js: -------------------------------------------------------------------------------- 1 | // Функция инициализации обработчиков 2 | function initCallbacks(callbacks = {}) { 3 | const initiatedCallbacks = {} 4 | const stubCallback = () => {} 5 | // Замыкание 6 | const defaultAllowTraverseCallback = (() => { 7 | // Посещенные узлы 8 | const traversed = {} 9 | return ({ nextNode }) => { 10 | // Пропускаем следующий узел, если он уже был посещен 11 | if (!traversed[nextNode.getKey()]) { 12 | traversed[nextNode.getKey()] = true 13 | return true 14 | } 15 | return false 16 | } 17 | })() 18 | initiatedCallbacks.allowTraverse = 19 | callbacks.allowTraverse || defaultAllowTraverseCallback 20 | initiatedCallbacks.enterNode = callbacks.enterNode || stubCallback 21 | initiatedCallbacks.leaveNode = callbacks.leaveNode || stubCallback 22 | return initiatedCallbacks 23 | } 24 | 25 | // Функция принимает граф, текущий и предыдущий узлы, а также обработчики 26 | function depthFirstSearchRecursive( 27 | graph, 28 | currentNode, 29 | previousNode, 30 | callbacks, 31 | ) { 32 | // Вызываем обработчик вхождения в узел 33 | callbacks.enterNode({ currentNode, previousNode }) 34 | 35 | // Перебираем соседей текущего узла 36 | graph.getNeighbors(currentNode).forEach((nextNode) => { 37 | // Если узел не был посещен 38 | if (callbacks.allowTraverse({ previousNode, currentNode, nextNode })) { 39 | // Обходим его 40 | depthFirstSearchRecursive(graph, nextNode, currentNode, callbacks) 41 | } 42 | }) 43 | 44 | // Вызываем обработчик выхода из узла 45 | callbacks.leaveNode({ currentNode, previousNode }) 46 | } 47 | 48 | // Функция принимает граф, начальный узел и обработчики 49 | export default function depthFirstSearch(graph, startNode, callbacks) { 50 | // Инициализируем обработчики 51 | const _callbacks = initCallbacks(callbacks) 52 | // Обходим граф 53 | depthFirstSearchRecursive(graph, startNode, null, _callbacks) 54 | } 55 | -------------------------------------------------------------------------------- /src/algorithms/graphs/detect-cycle/__tests__/directed.test.js: -------------------------------------------------------------------------------- 1 | import GraphEdge from '../../../../data-structures/graph/edge' 2 | import Graph from '../../../../data-structures/graph/index' 3 | import GraphNode from '../../../../data-structures/graph/node' 4 | import detectDirectedCycle from '../directed' 5 | 6 | describe('detectDirectedCycle', () => { 7 | it('должен обнаружить цикл в направленном графе', () => { 8 | const nodeA = new GraphNode('A') 9 | const nodeB = new GraphNode('B') 10 | const nodeC = new GraphNode('C') 11 | const nodeD = new GraphNode('D') 12 | const nodeE = new GraphNode('E') 13 | const nodeF = new GraphNode('F') 14 | 15 | const edgeAB = new GraphEdge(nodeA, nodeB) 16 | const edgeBC = new GraphEdge(nodeB, nodeC) 17 | const edgeAC = new GraphEdge(nodeA, nodeC) 18 | const edgeDA = new GraphEdge(nodeD, nodeA) 19 | const edgeDE = new GraphEdge(nodeD, nodeE) 20 | const edgeEF = new GraphEdge(nodeE, nodeF) 21 | const edgeFD = new GraphEdge(nodeF, nodeD) 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: nodeF, 38 | F: nodeE, 39 | E: nodeD, 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /src/algorithms/graphs/detect-cycle/__tests__/undirected-disjoint-set.test.js: -------------------------------------------------------------------------------- 1 | import GraphEdge from '../../../../data-structures/graph/edge' 2 | import Graph from '../../../../data-structures/graph/index' 3 | import GraphNode from '../../../../data-structures/graph/node' 4 | import detectUndirectedCycleUsingDisjointSet from '../undirected-disjoint-set' 5 | 6 | describe('detectUndirectedCycleUsingDisjointSet', () => { 7 | it('должен обнаружить цикл в ненаправленном графе', () => { 8 | const nodeA = new GraphNode('A') 9 | const nodeB = new GraphNode('B') 10 | const nodeC = new GraphNode('C') 11 | const nodeD = new GraphNode('D') 12 | const nodeE = new GraphNode('E') 13 | const nodeF = new GraphNode('F') 14 | 15 | const edgeAF = new GraphEdge(nodeA, nodeF) 16 | const edgeAB = new GraphEdge(nodeA, nodeB) 17 | const edgeBE = new GraphEdge(nodeB, nodeE) 18 | const edgeBC = new GraphEdge(nodeB, nodeC) 19 | const edgeCD = new GraphEdge(nodeC, nodeD) 20 | const edgeDE = new GraphEdge(nodeD, nodeE) 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)).toBe(false) 31 | 32 | graph.addEdge(edgeDE) 33 | 34 | expect(detectUndirectedCycleUsingDisjointSet(graph)).toBe(true) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /src/algorithms/graphs/detect-cycle/__tests__/undirected.test.js: -------------------------------------------------------------------------------- 1 | import GraphEdge from '../../../../data-structures/graph/edge' 2 | import Graph from '../../../../data-structures/graph/index' 3 | import GraphNode from '../../../../data-structures/graph/node' 4 | import detectUndirectedCycle from '../undirected' 5 | 6 | describe('detectUndirectedCycle', () => { 7 | it('должен обнаружить цикл в ненаправленном графе', () => { 8 | const nodeA = new GraphNode('A') 9 | const nodeB = new GraphNode('B') 10 | const nodeC = new GraphNode('C') 11 | const nodeD = new GraphNode('D') 12 | const nodeE = new GraphNode('E') 13 | const nodeF = new GraphNode('F') 14 | 15 | const edgeAF = new GraphEdge(nodeA, nodeF) 16 | const edgeAB = new GraphEdge(nodeA, nodeB) 17 | const edgeBE = new GraphEdge(nodeB, nodeE) 18 | const edgeBC = new GraphEdge(nodeB, nodeC) 19 | const edgeCD = new GraphEdge(nodeC, nodeD) 20 | const edgeDE = new GraphEdge(nodeD, nodeE) 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: nodeC, 36 | C: nodeD, 37 | D: nodeE, 38 | E: nodeB, 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /src/algorithms/graphs/detect-cycle/undirected-disjoint-set.js: -------------------------------------------------------------------------------- 1 | import DisjoinSet from '../../../data-structures/disjoint-set' 2 | 3 | export default function detectUndirectedCycleUsingDisjointSet(graph) { 4 | // Создаем непересекающиеся одноэлементные множества для каждого узла графа 5 | const keyExtractor = (node) => node.getKey() 6 | const disjointSet = new DisjoinSet(keyExtractor) 7 | graph.getAllNodes().forEach((node) => disjointSet.makeSet(node)) 8 | 9 | // Перебираем все ребра графа и проверяем, что узлы ребра принадлежат 10 | // разным множествам. В этом случае объединяем множества. Делаем это 11 | // до обнаружения узлов, которые принадлежат обоим множествам. Это 12 | // означает обнаружение цикла 13 | let cycle = false 14 | graph.getAllEdges().forEach((edge) => { 15 | if (disjointSet.isSameSet(edge.from, edge.to)) { 16 | cycle = true 17 | } else { 18 | disjointSet.union(edge.from, edge.to) 19 | } 20 | }) 21 | 22 | return cycle 23 | } 24 | -------------------------------------------------------------------------------- /src/algorithms/graphs/detect-cycle/undirected.js: -------------------------------------------------------------------------------- 1 | import depthFirstSearch from '../depth-first-search' 2 | 3 | // Функция принимает граф 4 | export default function detectUndirectedCycle(graph) { 5 | let cycle = null 6 | 7 | // Список посещенных узлов 8 | const visited = {} 9 | 10 | // Список предков каждого посещенного узла 11 | const parents = {} 12 | 13 | // Обработчики для DFS 14 | const callbacks = { 15 | // Обработчик вхождения в узел 16 | enterNode: ({ currentNode, previousNode }) => { 17 | // Если узел уже посещен, то обнаружен цикл 18 | if (visited[currentNode.getKey()]) { 19 | // Вычисляем его путь 20 | cycle = {} 21 | 22 | let current = currentNode 23 | let previous = previousNode 24 | 25 | while (currentNode.getKey() !== previous.getKey()) { 26 | cycle[current.getKey()] = previous 27 | current = previous 28 | previous = parents[previous.getKey()] 29 | } 30 | 31 | cycle[current.getKey()] = previous 32 | } else { 33 | // Добавляем текущий узел в посещенные 34 | visited[currentNode.getKey()] = currentNode 35 | // Обновляем список предков 36 | parents[currentNode.getKey()] = previousNode 37 | } 38 | }, 39 | // Обработчик определения допустимости обхода 40 | allowTraverse: ({ currentNode, nextNode }) => { 41 | // Запрещаем обход цикла 42 | if (cycle) { 43 | return false 44 | } 45 | 46 | // Запрещаем возвращаться к предку 47 | const currentNodeParent = parents[currentNode.getKey()] 48 | 49 | return currentNodeParent?.getKey() !== nextNode.getKey() 50 | }, 51 | } 52 | 53 | // Берем первый узел 54 | const startNode = graph.getAllNodes()[0] 55 | // Запускаем поиск в глубину 56 | depthFirstSearch(graph, startNode, callbacks) 57 | 58 | return cycle 59 | } 60 | -------------------------------------------------------------------------------- /src/algorithms/graphs/kruskal.js: -------------------------------------------------------------------------------- 1 | import DisjoinSet from '../../data-structures/disjoint-set/index' 2 | import Graph from '../../data-structures/graph/index' 3 | import QuickSort from '../sorting/quick-sort' 4 | 5 | // Функция принимает граф 6 | export default function kruskal(graph) { 7 | // При передаче направленного графа должно выбрасываться исключение 8 | if (graph.isDirected) { 9 | throw new Error( 10 | 'Алгоритм Краскала работает только с ненаправленными графами', 11 | ) 12 | } 13 | 14 | // Создаем новый граф, который будет содержать 15 | // минимальное остовное дерево исходного графа 16 | const minimumSpanningTree = new Graph() 17 | 18 | // Сортируем ребра графа в порядке возрастания веса 19 | const sortingCallbacks = { 20 | compareCallback: (a, b) => { 21 | if (a.weight === b.weight) { 22 | return 0 23 | } 24 | 25 | return a.weight < b.weight ? -1 : 1 26 | }, 27 | } 28 | const sortedEdges = new QuickSort(sortingCallbacks).sort(graph.getAllEdges()) 29 | 30 | // Создаем непересекающиеся одноэлементные множества для всех вершин графа 31 | const keyCb = (node) => node.getKey() 32 | const disjointSet = new DisjoinSet(keyCb) 33 | graph.getAllNodes().forEach((node) => disjointSet.makeSet(node)) 34 | 35 | // Перебираем ребра графа, начиная с минимального, и пытаемся добавить их 36 | // в минимальное остовное дерево. Критерием добавления ребра является 37 | // формирование им цикла (если оно соединяет два узла одного подмножества) 38 | sortedEdges.forEach((edge) => { 39 | // Если добавление ребра не формирует цикл 40 | if (!disjointSet.isSameSet(edge.from, edge.to)) { 41 | // Объединяем два подмножества в одно 42 | disjointSet.union(edge.from, edge.to) 43 | 44 | // Добавляем ребро в дерево 45 | minimumSpanningTree.addEdge(edge) 46 | } 47 | }) 48 | 49 | return minimumSpanningTree 50 | } 51 | -------------------------------------------------------------------------------- /src/algorithms/graphs/topological-sort.js: -------------------------------------------------------------------------------- 1 | import Stack from '../../data-structures/stack' 2 | import depthFirstSearch from './depth-first-search' 3 | 4 | // Функция принимает граф 5 | export default function topologicalSort(graph) { 6 | // Узлы, которые мы хотим посетить 7 | const unvisited = graph.getAllNodes().reduce((a, c) => { 8 | a[c.getKey()] = c 9 | return a 10 | }, {}) 11 | 12 | // Посещенные узлы 13 | const visited = {} 14 | 15 | // Стек отсортированных узлов 16 | const stack = new Stack() 17 | 18 | // Обработчики для DFS 19 | const callbacks = { 20 | // Обработчик вхождения в узел 21 | enterNode: ({ currentNode }) => { 22 | // Добавляем узел в посещенные, если все его потомки были исследованы 23 | visited[currentNode.getKey()] = currentNode 24 | 25 | // Удаляем узел из непосещенных 26 | delete unvisited[currentNode.getKey()] 27 | }, 28 | // Обработчик выхода из узла 29 | leaveNode: ({ currentNode }) => { 30 | // Помещаем полностью исследованный узел в стек 31 | stack.push(currentNode) 32 | }, 33 | // Обработчик определения допустимости обхода следующего узла 34 | allowTraverse: ({ nextNode }) => { 35 | // Запрещаем обход посещенных узлов 36 | return !visited[nextNode.getKey()] 37 | }, 38 | } 39 | 40 | // Перебираем непосещенные узлы 41 | while (Object.keys(unvisited).length) { 42 | const currentKey = Object.keys(unvisited)[0] 43 | const currentNode = unvisited[currentKey] 44 | 45 | depthFirstSearch(graph, currentNode, callbacks) 46 | } 47 | 48 | // Преобразуем стек в массив и возвращаем его 49 | return stack.toArray() 50 | } 51 | -------------------------------------------------------------------------------- /src/algorithms/linked-lists/__tests__/reversal-traverse.test.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../../../data-structures/linked-list' 2 | import reversalTraverse from '../reversal-traverse' 3 | 4 | describe('reversalTraverse', () => { 5 | it('должен обойти связный список в обратном порядке', () => { 6 | const linkedList = new LinkedList() 7 | 8 | linkedList.append(1).append(2).append(3) 9 | 10 | const nodeValues = [] 11 | const traversalCallback = (nodeValue) => { 12 | nodeValues.push(nodeValue) 13 | } 14 | 15 | reversalTraverse(linkedList, traversalCallback) 16 | 17 | expect(nodeValues).toEqual([3, 2, 1]) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/algorithms/linked-lists/__tests__/traverse.test.js: -------------------------------------------------------------------------------- 1 | import LinkedList from '../../../data-structures/linked-list' 2 | import traverse from '../traverse' 3 | 4 | describe('traverse', () => { 5 | it('должен обойти связный список в прямом порядке', () => { 6 | const linkedList = new LinkedList() 7 | 8 | linkedList.append(1).append(2).append(3) 9 | 10 | const nodeValues = [] 11 | const traversalCallback = (nodeValue) => { 12 | nodeValues.push(nodeValue) 13 | } 14 | 15 | traverse(linkedList, traversalCallback) 16 | 17 | expect(nodeValues).toEqual([1, 2, 3]) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/algorithms/linked-lists/reversal-traverse.js: -------------------------------------------------------------------------------- 1 | // Функция принимает узел и обработчик его посещения 2 | function reversalTraverseRecursive(node, cb) { 3 | // Пока есть узел 4 | if (node) { 5 | // Вызываем функцию со следующим узлом 6 | reversalTraverseRecursive(node.next, cb) 7 | // Обрабатываем узел 8 | cb(node.value) 9 | } 10 | } 11 | 12 | // Функция принимает связный список и обработчик посещения узла 13 | export default function reversalTraverse(list, cb) { 14 | // Для того, чтобы понять рекурсию, надо сначала понять рекурсию :) 15 | reversalTraverseRecursive(list.head, cb) 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/linked-lists/traverse.js: -------------------------------------------------------------------------------- 1 | // Функция принимает связный список и обработчик посещения узла 2 | export default function traverse(list, cb) { 3 | // Берем головной узел 4 | let node = list.head 5 | 6 | // Пока есть узлы 7 | while (node) { 8 | // Обрабатываем узел 9 | cb(node.value) 10 | // Берем следующий 11 | node = node.next 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/machine-learning/__tests__/k-means.test.js: -------------------------------------------------------------------------------- 1 | import KMeans from '../k-means' 2 | 3 | describe('kMeans', () => { 4 | it('при невалидных данных должно выбрасываться исключение', () => { 5 | expect(() => { 6 | KMeans() 7 | }).toThrowError('Отсутствуют данные для классификации') 8 | }) 9 | 10 | it('при несогласованных данных должно выбрасываться исключение', () => { 11 | expect(() => { 12 | KMeans([[1, 2], [1]], 2) 13 | }).toThrowError('Матрицы имеют разную форму') 14 | }) 15 | 16 | it('должен выполнить кластеризацию', () => { 17 | const data = [ 18 | [1, 1], 19 | [6, 2], 20 | [3, 3], 21 | [4, 5], 22 | [9, 2], 23 | [2, 4], 24 | [8, 7], 25 | ] 26 | const k = 2 27 | const expectedClusters = [0, 1, 0, 1, 1, 0, 1] 28 | expect(KMeans(data, k)).toEqual(expectedClusters) 29 | 30 | expect( 31 | KMeans( 32 | [ 33 | [0, 0], 34 | [0, 1], 35 | [10, 10], 36 | ], 37 | 2, 38 | ), 39 | ).toEqual([0, 0, 1]) 40 | }) 41 | 42 | it('должен выполнить кластеризацию точек на одинаковых расстояниях', () => { 43 | const dataSet = [ 44 | [0, 0], 45 | [1, 1], 46 | [2, 2], 47 | ] 48 | const k = 3 49 | const expectedCluster = [0, 1, 2] 50 | expect(KMeans(dataSet, k)).toEqual(expectedCluster) 51 | }) 52 | 53 | it('должен выполнить кластеризацию точек в трехмерном пространстве', () => { 54 | const dataSet = [ 55 | [0, 0, 0], 56 | [0, 1, 0], 57 | [2, 0, 2], 58 | ] 59 | const k = 2 60 | const expectedCluster = [1, 1, 0] 61 | expect(KMeans(dataSet, k)).toEqual(expectedCluster) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /src/algorithms/machine-learning/k-nn.js: -------------------------------------------------------------------------------- 1 | // Функция для вычисления евклидова расстояния 2 | import euclideanDistance from '../math/euclidean-distance' 3 | 4 | /** Функция принимает: 5 | * data - данные 6 | * labels - метки 7 | * target - тестовый/целевой образец 8 | * k - количество ближайших соседей 9 | */ 10 | export default function kNN(data, labels, target, k = 3) { 11 | if (!data || !labels || !target) { 12 | throw new Error('Отсутствует обязательный параметр') 13 | } 14 | 15 | // Вычисляем расстояние от `target` до каждой точки `data`. 16 | // Сохраняем расстояние и метку точки в списке 17 | const distances = [] 18 | 19 | for (let i = 0; i < data.length; i++) { 20 | distances.push({ 21 | distance: euclideanDistance([data[i]], [target]), 22 | label: labels[i], 23 | }) 24 | } 25 | 26 | // Сортируем расстояния по возрастанию (от ближайшего к дальнему). 27 | // Берем `k` значений 28 | const kn = distances 29 | .sort((a, b) => { 30 | if (a.distance === b.distance) { 31 | return 0 32 | } 33 | return a.distance < b.distance ? -1 : 1 34 | }) 35 | .slice(0, k) 36 | 37 | // Считаем количество экземпляров каждого класса 38 | const _labels = {} 39 | let topClass = 0 40 | let topClassCount = 0 41 | 42 | for (let i = 0; i < kn.length; i++) { 43 | if (kn[i].label in _labels) { 44 | _labels[kn[i].label] += 1 45 | } else { 46 | _labels[kn[i].label] = 1 47 | } 48 | 49 | if (_labels[kn[i].label] > topClassCount) { 50 | topClassCount = _labels[kn[i].label] 51 | topClass = kn[i].label 52 | } 53 | } 54 | 55 | // Возвращает класс с наибольшим количеством экземпляров 56 | return topClass 57 | } 58 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/degree-to-radian.test.js: -------------------------------------------------------------------------------- 1 | import degreeToRadian from '../degree-to-radian' 2 | 3 | describe('degreeToRadian', () => { 4 | it('должен конвертировать градусы в радианы', () => { 5 | expect(degreeToRadian(0)).toBe(0) 6 | expect(degreeToRadian(45)).toBe(Math.PI / 4) 7 | expect(degreeToRadian(90)).toBe(Math.PI / 2) 8 | expect(degreeToRadian(180)).toBe(Math.PI) 9 | expect(degreeToRadian(270)).toBe((3 * Math.PI) / 2) 10 | expect(degreeToRadian(360)).toBe(2 * Math.PI) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/euclidean-algorithm-iterative.test.js: -------------------------------------------------------------------------------- 1 | import euclideanAlgorithmIterative from '../euclidean-algorithm-iterative' 2 | 3 | describe('euclideanAlgorithmIterative', () => { 4 | it('должен итеративно вычислить НОД', () => { 5 | expect(euclideanAlgorithmIterative(0, 0)).toBe(0) 6 | expect(euclideanAlgorithmIterative(2, 0)).toBe(2) 7 | expect(euclideanAlgorithmIterative(0, 2)).toBe(2) 8 | expect(euclideanAlgorithmIterative(1, 2)).toBe(1) 9 | expect(euclideanAlgorithmIterative(2, 1)).toBe(1) 10 | expect(euclideanAlgorithmIterative(6, 6)).toBe(6) 11 | expect(euclideanAlgorithmIterative(2, 4)).toBe(2) 12 | expect(euclideanAlgorithmIterative(4, 2)).toBe(2) 13 | expect(euclideanAlgorithmIterative(12, 4)).toBe(4) 14 | expect(euclideanAlgorithmIterative(4, 12)).toBe(4) 15 | expect(euclideanAlgorithmIterative(5, 13)).toBe(1) 16 | expect(euclideanAlgorithmIterative(27, 13)).toBe(1) 17 | expect(euclideanAlgorithmIterative(24, 60)).toBe(12) 18 | expect(euclideanAlgorithmIterative(60, 24)).toBe(12) 19 | expect(euclideanAlgorithmIterative(252, 105)).toBe(21) 20 | expect(euclideanAlgorithmIterative(105, 252)).toBe(21) 21 | expect(euclideanAlgorithmIterative(1071, 462)).toBe(21) 22 | expect(euclideanAlgorithmIterative(462, 1071)).toBe(21) 23 | expect(euclideanAlgorithmIterative(462, -1071)).toBe(21) 24 | expect(euclideanAlgorithmIterative(-462, -1071)).toBe(21) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/euclidean-algorithm.test.js: -------------------------------------------------------------------------------- 1 | import euclideanAlgorithm from '../euclidean-algorithm' 2 | 3 | describe('euclideanAlgorithm', () => { 4 | it('должен рекурсивно вычислить НОД', () => { 5 | expect(euclideanAlgorithm(0, 0)).toBe(0) 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/__tests__/euclidean-distance.test.js: -------------------------------------------------------------------------------- 1 | import euclideanDistance from '../euclidean-distance' 2 | 3 | describe('euclideanDistance', () => { 4 | it('должен вычислить евклидово расстояние между векторами', () => { 5 | expect(euclideanDistance([[1]], [[2]])).toEqual(1) 6 | expect(euclideanDistance([[2]], [[1]])).toEqual(1) 7 | expect(euclideanDistance([[5, 8]], [[7, 3]])).toEqual(5.39) 8 | expect(euclideanDistance([[5], [8]], [[7], [3]])).toEqual(5.39) 9 | expect(euclideanDistance([[8, 2, 6]], [[3, 5, 7]])).toEqual(5.92) 10 | expect(euclideanDistance([[8], [2], [6]], [[3], [5], [7]])).toEqual(5.92) 11 | expect( 12 | euclideanDistance([[[8]], [[2]], [[6]]], [[[3]], [[5]], [[7]]]), 13 | ).toEqual(5.92) 14 | }) 15 | 16 | it('должен выбрасывать исключения, если матрицы имеют разную форму', () => { 17 | expect(() => euclideanDistance([[1]], [[[2]]])).toThrowError( 18 | 'Матрицы имеют разные направления', 19 | ) 20 | 21 | expect(() => euclideanDistance([[1]], [[2, 3]])).toThrowError( 22 | 'Матрицы имеют разную форму', 23 | ) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/factorial-recursive.test.js: -------------------------------------------------------------------------------- 1 | import factorialRecursive from '../factorial-recursive' 2 | 3 | describe('factorialRecursive', () => { 4 | it('должен рекурсивно вычислить факториалы чисел', () => { 5 | expect(factorialRecursive(0)).toBe(1) 6 | expect(factorialRecursive(1)).toBe(1) 7 | expect(factorialRecursive(5)).toBe(120) 8 | expect(factorialRecursive(8)).toBe(40320) 9 | expect(factorialRecursive(10)).toBe(3628800) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/factorial.test.js: -------------------------------------------------------------------------------- 1 | import factorial from '../factorial' 2 | 3 | describe('factorial', () => { 4 | it('должен вычислить факториалы чисел', () => { 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/__tests__/fast-powering.test.js: -------------------------------------------------------------------------------- 1 | import fastPowering from '../fast-powering' 2 | 3 | describe('fastPowering', () => { 4 | it('должен вычислить степень за время log(n)', () => { 5 | expect(fastPowering(1, 1)).toBe(1) 6 | expect(fastPowering(2, 0)).toBe(1) 7 | expect(fastPowering(2, 2)).toBe(4) 8 | expect(fastPowering(2, 3)).toBe(8) 9 | expect(fastPowering(2, 4)).toBe(16) 10 | expect(fastPowering(2, 5)).toBe(32) 11 | expect(fastPowering(2, 6)).toBe(64) 12 | expect(fastPowering(2, 7)).toBe(128) 13 | expect(fastPowering(2, 8)).toBe(256) 14 | expect(fastPowering(3, 4)).toBe(81) 15 | expect(fastPowering(190, 2)).toBe(36100) 16 | expect(fastPowering(11, 5)).toBe(161051) 17 | expect(fastPowering(13, 11)).toBe(1792160394037) 18 | expect(fastPowering(9, 16)).toBe(1853020188851841) 19 | expect(fastPowering(16, 16)).toBe(18446744073709552000) 20 | expect(fastPowering(7, 21)).toBe(558545864083284000) 21 | expect(fastPowering(100, 9)).toBe(1000000000000000000) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/fibonacci-nth.test.js: -------------------------------------------------------------------------------- 1 | import fibonacciNth from '../fibonacci-nth' 2 | 3 | describe('fibonacciNth', () => { 4 | it('должен вычислить n-ые числа Фибоначчи', () => { 5 | expect(fibonacciNth(1)).toBe(1) 6 | expect(fibonacciNth(2)).toBe(1) 7 | expect(fibonacciNth(3)).toBe(2) 8 | expect(fibonacciNth(4)).toBe(3) 9 | expect(fibonacciNth(5)).toBe(5) 10 | expect(fibonacciNth(6)).toBe(8) 11 | expect(fibonacciNth(7)).toBe(13) 12 | expect(fibonacciNth(8)).toBe(21) 13 | expect(fibonacciNth(20)).toBe(6765) 14 | expect(fibonacciNth(30)).toBe(832040) 15 | expect(fibonacciNth(50)).toBe(12586269025) 16 | expect(fibonacciNth(70)).toBe(190392490709135) 17 | expect(fibonacciNth(71)).toBe(308061521170129) 18 | expect(fibonacciNth(72)).toBe(498454011879264) 19 | expect(fibonacciNth(73)).toBe(806515533049393) 20 | expect(fibonacciNth(74)).toBe(1304969544928657) 21 | expect(fibonacciNth(75)).toBe(2111485077978050) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/fibonacci.test.js: -------------------------------------------------------------------------------- 1 | import fibonacci from '../fibonacci' 2 | 3 | describe('fibonacci', () => { 4 | it('должен вычислить числа Фибоначчи', () => { 5 | expect(fibonacci(1)).toEqual([1]) 6 | expect(fibonacci(2)).toEqual([1, 1]) 7 | expect(fibonacci(3)).toEqual([1, 1, 2]) 8 | expect(fibonacci(4)).toEqual([1, 1, 2, 3]) 9 | expect(fibonacci(5)).toEqual([1, 1, 2, 3, 5]) 10 | expect(fibonacci(6)).toEqual([1, 1, 2, 3, 5, 8]) 11 | expect(fibonacci(7)).toEqual([1, 1, 2, 3, 5, 8, 13]) 12 | expect(fibonacci(8)).toEqual([1, 1, 2, 3, 5, 8, 13, 21]) 13 | expect(fibonacci(9)).toEqual([1, 1, 2, 3, 5, 8, 13, 21, 34]) 14 | expect(fibonacci(10)).toEqual([1, 1, 2, 3, 5, 8, 13, 21, 34, 55]) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/is-power-of-two-bitwise.test.js: -------------------------------------------------------------------------------- 1 | import isPowerOfTwoBitwise from '../is-power-of-two-bitwise' 2 | 3 | describe('isPowerOfTwoBitwise', () => { 4 | it('должен проверить, является ли переданное число результатом возведения числа 2 в какую-либо степень, используя битовые операции', () => { 5 | expect(isPowerOfTwoBitwise(-1)).toBe(false) 6 | expect(isPowerOfTwoBitwise(0)).toBe(false) 7 | expect(isPowerOfTwoBitwise(1)).toBe(true) 8 | expect(isPowerOfTwoBitwise(2)).toBe(true) 9 | expect(isPowerOfTwoBitwise(3)).toBe(false) 10 | expect(isPowerOfTwoBitwise(4)).toBe(true) 11 | expect(isPowerOfTwoBitwise(5)).toBe(false) 12 | expect(isPowerOfTwoBitwise(6)).toBe(false) 13 | expect(isPowerOfTwoBitwise(7)).toBe(false) 14 | expect(isPowerOfTwoBitwise(8)).toBe(true) 15 | expect(isPowerOfTwoBitwise(10)).toBe(false) 16 | expect(isPowerOfTwoBitwise(12)).toBe(false) 17 | expect(isPowerOfTwoBitwise(16)).toBe(true) 18 | expect(isPowerOfTwoBitwise(31)).toBe(false) 19 | expect(isPowerOfTwoBitwise(64)).toBe(true) 20 | expect(isPowerOfTwoBitwise(1024)).toBe(true) 21 | expect(isPowerOfTwoBitwise(1023)).toBe(false) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/is-power-of-two.test.js: -------------------------------------------------------------------------------- 1 | import isPowerOfTwo from '../is-power-of-two' 2 | 3 | describe('isPowerOfTwo', () => { 4 | it('должен проверить, является ли переданное число результатом возведения числа 2 в какую-либо степень', () => { 5 | expect(isPowerOfTwo(-1)).toBe(false) 6 | expect(isPowerOfTwo(0)).toBe(false) 7 | expect(isPowerOfTwo(1)).toBe(true) 8 | expect(isPowerOfTwo(2)).toBe(true) 9 | expect(isPowerOfTwo(3)).toBe(false) 10 | expect(isPowerOfTwo(4)).toBe(true) 11 | expect(isPowerOfTwo(5)).toBe(false) 12 | expect(isPowerOfTwo(6)).toBe(false) 13 | expect(isPowerOfTwo(7)).toBe(false) 14 | expect(isPowerOfTwo(8)).toBe(true) 15 | expect(isPowerOfTwo(10)).toBe(false) 16 | expect(isPowerOfTwo(12)).toBe(false) 17 | expect(isPowerOfTwo(16)).toBe(true) 18 | expect(isPowerOfTwo(31)).toBe(false) 19 | expect(isPowerOfTwo(64)).toBe(true) 20 | expect(isPowerOfTwo(1024)).toBe(true) 21 | expect(isPowerOfTwo(1023)).toBe(false) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/least-common-multiple.test.js: -------------------------------------------------------------------------------- 1 | import leastCommonMultiple from '../least-common-multiple' 2 | 3 | describe('leastCommonMultiple', () => { 4 | it('должен вычислить НОМ', () => { 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/__tests__/radian-to-degree.test.js: -------------------------------------------------------------------------------- 1 | import radianToDegree from '../radian-to-degree' 2 | 3 | describe('radianToDegree', () => { 4 | it('должен конвертировать радианы в градусы', () => { 5 | expect(radianToDegree(0)).toBe(0) 6 | expect(radianToDegree(Math.PI / 4)).toBe(45) 7 | expect(radianToDegree(Math.PI / 2)).toBe(90) 8 | expect(radianToDegree(Math.PI)).toBe(180) 9 | expect(radianToDegree((3 * Math.PI) / 2)).toBe(270) 10 | expect(radianToDegree(2 * Math.PI)).toBe(360) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/sieve-of-eratosthenes.test.js: -------------------------------------------------------------------------------- 1 | import sieveOfEratosthenes from '../sieve-of-eratosthenes' 2 | 3 | describe('sieveOfEratosthenes', () => { 4 | it('должен вычислить все простые числа, меньшие или равные n', () => { 5 | expect(sieveOfEratosthenes(5)).toEqual([2, 3, 5]) 6 | expect(sieveOfEratosthenes(10)).toEqual([2, 3, 5, 7]) 7 | expect(sieveOfEratosthenes(100)).toEqual([ 8 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 9 | 71, 73, 79, 83, 89, 97, 10 | ]) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/algorithms/math/__tests__/trial-division.test.js: -------------------------------------------------------------------------------- 1 | import trialDivision from '../trial-division' 2 | 3 | describe('trialDivision', () => { 4 | it('должен определить простые числа', () => { 5 | expect(trialDivision(1)).toBe(false) 6 | expect(trialDivision(2)).toBe(true) 7 | expect(trialDivision(3)).toBe(true) 8 | expect(trialDivision(5)).toBe(true) 9 | expect(trialDivision(11)).toBe(true) 10 | expect(trialDivision(191)).toBe(true) 11 | expect(trialDivision(191)).toBe(true) 12 | expect(trialDivision(199)).toBe(true) 13 | 14 | expect(trialDivision(-1)).toBe(false) 15 | expect(trialDivision(0)).toBe(false) 16 | expect(trialDivision(4)).toBe(false) 17 | expect(trialDivision(6)).toBe(false) 18 | expect(trialDivision(12)).toBe(false) 19 | expect(trialDivision(14)).toBe(false) 20 | expect(trialDivision(25)).toBe(false) 21 | expect(trialDivision(192)).toBe(false) 22 | expect(trialDivision(200)).toBe(false) 23 | expect(trialDivision(400)).toBe(false) 24 | 25 | expect(trialDivision(0.5)).toBe(false) 26 | expect(trialDivision(1.3)).toBe(false) 27 | expect(trialDivision(10.5)).toBe(false) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /src/algorithms/math/binary-floating-point/__tests__/bits-to-float.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | testCases16Bits, 3 | testCases32Bits, 4 | testCases64Bits, 5 | } from '../test-cases' 6 | import { bitsToFloat16, bitsToFloat32, bitsToFloat64 } from '../bits-to-float' 7 | 8 | describe('bitsToFloat16', () => { 9 | it('должен конвертировать бинарное представление 16-битного числа с плавающей точкой в его десятичное представление', () => { 10 | for (let i = 0; i < testCases16Bits.length; i++) { 11 | const [decimal, binary] = testCases16Bits[i] 12 | const bits = binary.split('').map((bitString) => parseInt(bitString, 10)) 13 | expect(bitsToFloat16(bits)).toBeCloseTo(decimal, 4) 14 | } 15 | }) 16 | }) 17 | 18 | describe('bitsToFloat32', () => { 19 | it('должен конвертировать бинарное представление 32-битного числа с плавающей точкой в его десятичное представление', () => { 20 | for (let i = 0; i < testCases32Bits.length; i++) { 21 | const [decimal, binary] = testCases32Bits[i] 22 | const bits = binary.split('').map((bitString) => parseInt(bitString, 10)) 23 | expect(bitsToFloat32(bits)).toBeCloseTo(decimal, 7) 24 | } 25 | }) 26 | }) 27 | 28 | describe('bitsToFloat64', () => { 29 | it('должен конвертировать бинарное представление 64-битного числа с плавающей точкой в его десятичное представление', () => { 30 | for (let i = 0; i < testCases64Bits.length; i++) { 31 | const [decimal, binary] = testCases64Bits[i] 32 | const bits = binary.split('').map((bitString) => parseInt(bitString, 10)) 33 | expect(bitsToFloat64(bits)).toBeCloseTo(decimal, 14) 34 | } 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /src/algorithms/math/binary-floating-point/__tests__/float-as-binary-string.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | floatAsBinaryString32, 3 | floatAsBinaryString64, 4 | } from '../float-as-binary-string' 5 | import { testCases32Bits, testCases64Bits } from '../test-cases' 6 | 7 | describe('floatAs32Binary', () => { 8 | it('должен создать бинарное представление 32-битного числа с плавающей точкой', () => { 9 | for (let i = 0; i < testCases32Bits.length; i++) { 10 | const [decimal, binary] = testCases32Bits[i] 11 | expect(floatAsBinaryString32(decimal)).toBe(binary) 12 | } 13 | }) 14 | }) 15 | 16 | describe('floatAs64Binary', () => { 17 | it('должен создать бинарное представление 64-битного числа с плавающей точкой', () => { 18 | for (let i = 0; i < testCases64Bits.length; i++) { 19 | const [decimal, binary] = testCases64Bits[i] 20 | expect(floatAsBinaryString64(decimal)).toBe(binary) 21 | } 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/algorithms/math/binary-floating-point/bits-to-float.js: -------------------------------------------------------------------------------- 1 | const precisionConfigs = { 2 | // @see: https://en.wikipedia.org/wiki/Half-precision_floating-point_format 3 | half: { 4 | signBitsCount: 1, 5 | exponentBitsCount: 5, 6 | fractionBitsCount: 10, 7 | }, 8 | // @see: https://en.wikipedia.org/wiki/Single-precision_floating-point_format 9 | single: { 10 | signBitsCount: 1, 11 | exponentBitsCount: 8, 12 | fractionBitsCount: 23, 13 | }, 14 | // @see: https://en.wikipedia.org/wiki/Double-precision_floating-point_format 15 | double: { 16 | signBitsCount: 1, 17 | exponentBitsCount: 11, 18 | fractionBitsCount: 52, 19 | }, 20 | } 21 | 22 | /** 23 | * Преобразует двоичное представление числа с плавающей запятой в его десятичное представление 24 | */ 25 | function bitsToFloat(bits, precisionConfig) { 26 | const { signBitsCount, exponentBitsCount } = precisionConfig 27 | 28 | // Определяем знак 29 | const sign = (-1) ** bits[0] // -1^1 = -1, -1^0 = 1 30 | 31 | // Вычисляем значение экспоненты 32 | const exponentBias = 2 ** (exponentBitsCount - 1) - 1 33 | const exponentBits = bits.slice( 34 | signBitsCount, 35 | signBitsCount + exponentBitsCount, 36 | ) 37 | const exponentUnbiased = exponentBits.reduce( 38 | (exponentSoFar, currentBit, bitIndex) => { 39 | const bitPowerOfTwo = 2 ** (exponentBitsCount - bitIndex - 1) 40 | return exponentSoFar + currentBit * bitPowerOfTwo 41 | }, 42 | 0, 43 | ) 44 | const exponent = exponentUnbiased - exponentBias 45 | 46 | // Вычисляем значение дробной части 47 | const fractionBits = bits.slice(signBitsCount + exponentBitsCount) 48 | const fraction = fractionBits.reduce( 49 | (fractionSoFar, currentBit, bitIndex) => { 50 | const bitPowerOfTwo = 2 ** -(bitIndex + 1) 51 | return fractionSoFar + currentBit * bitPowerOfTwo 52 | }, 53 | 0, 54 | ) 55 | 56 | // Вычисляем число 57 | return sign * 2 ** exponent * (1 + fraction) 58 | } 59 | 60 | export function bitsToFloat16(bits) { 61 | return bitsToFloat(bits, precisionConfigs.half) 62 | } 63 | 64 | export function bitsToFloat32(bits) { 65 | return bitsToFloat(bits, precisionConfigs.single) 66 | } 67 | 68 | export function bitsToFloat64(bits) { 69 | return bitsToFloat(bits, precisionConfigs.double) 70 | } 71 | -------------------------------------------------------------------------------- /src/algorithms/math/binary-floating-point/float-as-binary-string.js: -------------------------------------------------------------------------------- 1 | // @see: https://en.wikipedia.org/wiki/Single-precision_floating-point_format 2 | const singlePrecisionBytesLength = 4 // 32 бита 3 | 4 | // @see: https://en.wikipedia.org/wiki/Double-precision_floating-point_format 5 | const doublePrecisionBytesLength = 8 // 64 бита 6 | 7 | const bitsInByte = 8 8 | const byteOffset = 0 9 | const littleEndian = false 10 | 11 | /** 12 | * Преобразует число с плавающей запятой в двоичное представление согласно IEEE 754 13 | */ 14 | function floatAsBinaryString(floatNumber, byteLength) { 15 | let numberAsBinaryString = '' 16 | 17 | const arrayBuffer = new ArrayBuffer(byteLength) 18 | const dataView = new DataView(arrayBuffer) 19 | 20 | if (byteLength === singlePrecisionBytesLength) { 21 | dataView.setFloat32(byteOffset, floatNumber, littleEndian) 22 | } else { 23 | dataView.setFloat64(byteOffset, floatNumber, littleEndian) 24 | } 25 | 26 | for (let i = 0; i < byteLength; i++) { 27 | let bits = dataView.getUint8(i).toString(2) 28 | if (bits.length < bitsInByte) { 29 | bits = '0'.repeat(bitsInByte - bits.length) + bits 30 | } 31 | numberAsBinaryString += bits 32 | } 33 | 34 | return numberAsBinaryString 35 | } 36 | 37 | export function floatAsBinaryString32(floatNumber) { 38 | return floatAsBinaryString(floatNumber, singlePrecisionBytesLength) 39 | } 40 | 41 | export function floatAsBinaryString64(floatNumber) { 42 | return floatAsBinaryString(floatNumber, doublePrecisionBytesLength) 43 | } 44 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/bit-length.test.js: -------------------------------------------------------------------------------- 1 | import bitLength from '../bit-length' 2 | 3 | describe('bitLength', () => { 4 | it('должен вычислить количество битов, из которых состоят числа', () => { 5 | expect(bitLength(0b0)).toBe(0) 6 | expect(bitLength(0b1)).toBe(1) 7 | expect(bitLength(0b01)).toBe(1) 8 | expect(bitLength(0b101)).toBe(3) 9 | expect(bitLength(0b0101)).toBe(3) 10 | expect(bitLength(0b10101)).toBe(5) 11 | expect(bitLength(0b11110101)).toBe(8) 12 | expect(bitLength(0b00011110101)).toBe(8) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/bits-diff.test.js: -------------------------------------------------------------------------------- 1 | import bitsDiff from '../bits-diff' 2 | 3 | describe('bitsDiff', () => { 4 | it('должен вычислить разницу между двумя числами в битах', () => { 5 | expect(bitsDiff(0, 0)).toBe(0) 6 | expect(bitsDiff(1, 1)).toBe(0) 7 | expect(bitsDiff(124, 124)).toBe(0) 8 | expect(bitsDiff(0, 1)).toBe(1) 9 | expect(bitsDiff(1, 0)).toBe(1) 10 | expect(bitsDiff(1, 2)).toBe(2) 11 | expect(bitsDiff(1, 3)).toBe(1) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/clear-bit.test.js: -------------------------------------------------------------------------------- 1 | import clearBit from '../clear-bit' 2 | 3 | describe('clearBit', () => { 4 | it('должен очистить биты на указанных позициях', () => { 5 | // 1 = 0b0001 6 | expect(clearBit(1, 0)).toBe(0) 7 | expect(clearBit(1, 1)).toBe(1) 8 | expect(clearBit(1, 2)).toBe(1) 9 | 10 | // 10 = 0b1010 11 | expect(clearBit(10, 0)).toBe(10) 12 | expect(clearBit(10, 1)).toBe(8) 13 | expect(clearBit(10, 3)).toBe(2) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/count-set-bits.test.js: -------------------------------------------------------------------------------- 1 | import countSetBits from '../count-set-bits' 2 | 3 | describe('countSetBits', () => { 4 | it('должен вернуть количество установленных битов', () => { 5 | expect(countSetBits(0)).toBe(0) 6 | expect(countSetBits(1)).toBe(1) 7 | expect(countSetBits(2)).toBe(1) 8 | expect(countSetBits(3)).toBe(2) 9 | expect(countSetBits(4)).toBe(1) 10 | expect(countSetBits(5)).toBe(2) 11 | expect(countSetBits(21)).toBe(3) 12 | expect(countSetBits(255)).toBe(8) 13 | expect(countSetBits(1023)).toBe(10) 14 | expect(countSetBits(-1)).toBe(32) 15 | expect(countSetBits(-21)).toBe(30) 16 | expect(countSetBits(-255)).toBe(25) 17 | expect(countSetBits(-1023)).toBe(23) 18 | expect(countSetBits(-4294967296)).toBe(0) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/divide-by-two.test.js: -------------------------------------------------------------------------------- 1 | import divideByTwo from '../divide-by-two' 2 | 3 | describe('divideByTwo', () => { 4 | it('должен разделить числа на 2 с помощью бинарных операций', () => { 5 | expect(divideByTwo(0)).toBe(0) 6 | expect(divideByTwo(1)).toBe(0) 7 | expect(divideByTwo(3)).toBe(1) 8 | expect(divideByTwo(10)).toBe(5) 9 | expect(divideByTwo(17)).toBe(8) 10 | expect(divideByTwo(125)).toBe(62) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/get-bit.test.js: -------------------------------------------------------------------------------- 1 | import getBit from '../get-bit' 2 | 3 | describe('getBit', () => { 4 | it('должен извлечь биты на указанных позициях', () => { 5 | // 1 = 0b0001 6 | expect(getBit(1, 0)).toBe(1) 7 | expect(getBit(1, 1)).toBe(0) 8 | 9 | // 2 = 0b0010 10 | expect(getBit(2, 0)).toBe(0) 11 | expect(getBit(2, 1)).toBe(1) 12 | 13 | // 3 = 0b0011 14 | expect(getBit(3, 0)).toBe(1) 15 | expect(getBit(3, 1)).toBe(1) 16 | 17 | // 10 = 0b1010 18 | expect(getBit(10, 0)).toBe(0) 19 | expect(getBit(10, 1)).toBe(1) 20 | expect(getBit(10, 2)).toBe(0) 21 | expect(getBit(10, 3)).toBe(1) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/is-even.test.js: -------------------------------------------------------------------------------- 1 | import isEven from '../is-even' 2 | 3 | describe('isEven', () => { 4 | it('должен определить, являются ли числа четными', () => { 5 | expect(isEven(0)).toBe(true) 6 | expect(isEven(2)).toBe(true) 7 | expect(isEven(-2)).toBe(true) 8 | expect(isEven(1)).toBe(false) 9 | expect(isEven(-1)).toBe(false) 10 | expect(isEven(-3)).toBe(false) 11 | expect(isEven(3)).toBe(false) 12 | expect(isEven(8)).toBe(true) 13 | expect(isEven(9)).toBe(false) 14 | expect(isEven(121)).toBe(false) 15 | expect(isEven(122)).toBe(true) 16 | expect(isEven(1201)).toBe(false) 17 | expect(isEven(1202)).toBe(true) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/is-positive.js: -------------------------------------------------------------------------------- 1 | import isPositive from '../is-positive' 2 | 3 | describe('isPositive', () => { 4 | it('должен определять, являются ли числа положительными', () => { 5 | expect(isPositive(1)).toBe(true) 6 | expect(isPositive(2)).toBe(true) 7 | expect(isPositive(3)).toBe(true) 8 | expect(isPositive(5665)).toBe(true) 9 | expect(isPositive(56644325)).toBe(true) 10 | 11 | expect(isPositive(0)).toBe(false) 12 | expect(isPositive(-0)).toBe(false) 13 | expect(isPositive(-1)).toBe(false) 14 | expect(isPositive(-2)).toBe(false) 15 | expect(isPositive(-126)).toBe(false) 16 | expect(isPositive(-5665)).toBe(false) 17 | expect(isPositive(-56644325)).toBe(false) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/multiply-by-two.test.js: -------------------------------------------------------------------------------- 1 | import multiplyByTwo from '../multiply-by-two' 2 | 3 | describe('multiplyByTwo', () => { 4 | it('должен умножить числа на 2 с помощью бинарных операций', () => { 5 | expect(multiplyByTwo(0)).toBe(0) 6 | expect(multiplyByTwo(1)).toBe(2) 7 | expect(multiplyByTwo(3)).toBe(6) 8 | expect(multiplyByTwo(10)).toBe(20) 9 | expect(multiplyByTwo(17)).toBe(34) 10 | expect(multiplyByTwo(125)).toBe(250) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/set-bit.test.js: -------------------------------------------------------------------------------- 1 | import setBit from '../set-bit' 2 | 3 | describe('setBit', () => { 4 | it('должен установить биты на указанные позиции', () => { 5 | // 1 = 0b0001 6 | expect(setBit(1, 0)).toBe(1) 7 | expect(setBit(1, 1)).toBe(3) 8 | expect(setBit(1, 2)).toBe(5) 9 | 10 | // 10 = 0b1010 11 | expect(setBit(10, 0)).toBe(11) 12 | expect(setBit(10, 1)).toBe(10) 13 | expect(setBit(10, 2)).toBe(14) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/switch-sign.test.js: -------------------------------------------------------------------------------- 1 | import switchSign from '../switch-sign' 2 | 3 | describe('switchSign', () => { 4 | it('должен менять знаки чисел', () => { 5 | expect(switchSign(0)).toBe(0) 6 | expect(switchSign(1)).toBe(-1) 7 | expect(switchSign(-1)).toBe(1) 8 | expect(switchSign(32)).toBe(-32) 9 | expect(switchSign(-32)).toBe(32) 10 | expect(switchSign(23)).toBe(-23) 11 | expect(switchSign(-23)).toBe(23) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/__tests__/update-bit.test.js: -------------------------------------------------------------------------------- 1 | import updateBit from '../update-bit' 2 | 3 | describe('updateBit', () => { 4 | it('должен обновить биты на указанных позициях', () => { 5 | // 1 = 0b0001 6 | expect(updateBit(1, 0, 1)).toBe(1) 7 | expect(updateBit(1, 0, 0)).toBe(0) 8 | expect(updateBit(1, 1, 1)).toBe(3) 9 | expect(updateBit(1, 2, 1)).toBe(5) 10 | 11 | // 10 = 0b1010 12 | expect(updateBit(10, 0, 1)).toBe(11) 13 | expect(updateBit(10, 0, 0)).toBe(10) 14 | expect(updateBit(10, 1, 1)).toBe(10) 15 | expect(updateBit(10, 1, 0)).toBe(8) 16 | expect(updateBit(10, 2, 1)).toBe(14) 17 | expect(updateBit(10, 2, 0)).toBe(10) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/bit-length.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Возвращает количество битов, используемых для двоичного представления числа 3 | */ 4 | export default function bitLength(n) { 5 | let count = 0 6 | while (1 << count <= n) { 7 | count++ 8 | } 9 | return count 10 | } 11 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/bits-diff.js: -------------------------------------------------------------------------------- 1 | import countSetBits from './count-set-bits' 2 | 3 | /** 4 | * Вычисляет количество битов, которые нужно изменить для 5 | * преобразования числа `а` в число `b` 6 | */ 7 | export default function bitsDiff(a, b) { 8 | return countSetBits(a ^ b) 9 | } 10 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/clear-bit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Удаляет бит на указанной позиции 3 | */ 4 | export default function clearBit(number, bitPosition) { 5 | return number & ~(1 << bitPosition) 6 | } 7 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/count-set-bits.js: -------------------------------------------------------------------------------- 1 | export default function countSetBits(n) { 2 | let count = 0 3 | let _n = n 4 | 5 | while (_n) { 6 | // Добавляем последний бит числа к сумме установленных битов 7 | count += _n & 1 8 | // Сдвигаем число вправо на один бит для исследования других битов 9 | _n >>>= 1 10 | } 11 | 12 | return count 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/divide-by-two.js: -------------------------------------------------------------------------------- 1 | export default function divideByTwo(n) { 2 | return n >> 1 3 | } 4 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/get-bit.js: -------------------------------------------------------------------------------- 1 | export default function getBit(number, bitPosition) { 2 | return (number >> bitPosition) & 1 3 | } 4 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/is-even.js: -------------------------------------------------------------------------------- 1 | export default function isEven(n) { 2 | return (n & 1) === 0 3 | } 4 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/is-positive.js: -------------------------------------------------------------------------------- 1 | export default function isPositive(n) { 2 | if (n === 0) return false 3 | 4 | return ((n >> 31) & 1) === 0 5 | } 6 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/multiply-by-two.js: -------------------------------------------------------------------------------- 1 | export default function multiplyByTwo(n) { 2 | return n << 1 3 | } 4 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/set-bit.js: -------------------------------------------------------------------------------- 1 | export default function setBit(number, bitPosition) { 2 | return number | (1 << bitPosition) 3 | } 4 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/switch-sign.js: -------------------------------------------------------------------------------- 1 | // https://ru.wikipedia.org/wiki/%D0%94%D0%BE%D0%BF%D0%BE%D0%BB%D0%BD%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4 2 | export default function switchSign(n) { 3 | return ~n + 1 4 | } 5 | -------------------------------------------------------------------------------- /src/algorithms/math/bits/update-bit.js: -------------------------------------------------------------------------------- 1 | export default function updateBit(number, bitPosition, bitValue) { 2 | const _bitValue = bitValue ? 1 : 0 3 | 4 | return (number & ~(1 << bitPosition)) | (_bitValue << bitPosition) 5 | } 6 | -------------------------------------------------------------------------------- /src/algorithms/math/degree-to-radian.js: -------------------------------------------------------------------------------- 1 | export default function degreeToRadian(degree) { 2 | return (degree * Math.PI) / 180 3 | } 4 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-algorithm-iterative.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Итеративная версия алгоритма Евклида для вычисления наибольшего общего делителя 3 | */ 4 | 5 | export default function euclideanAlgorithmIterative(a, b) { 6 | // Делаем числа положительными 7 | let _a = Math.abs(a) 8 | let _b = Math.abs(b) 9 | 10 | // Вычитаем одно число из другого, пока числа не станут равными. 11 | // Завершаем перебор, когда одно из чисел становится 0 12 | while (_a && _b && _a !== _b) { 13 | ;[_a, _b] = _a > _b ? [_a - _b, _b] : [_a, _b - _a] 14 | } 15 | 16 | return _a || _b 17 | } 18 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-algorithm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Рекурсивная версия алгоритма Евклида для вычисления наибольшего общего делителя 3 | */ 4 | 5 | export default function euclideanAlgorithm(a, b) { 6 | // Делаем числа положительными 7 | const _a = Math.abs(a) 8 | const _b = Math.abs(b) 9 | 10 | // Для ускорения алгоритма вместо вычитания одного числа из другого, 11 | // можно использовать оператор деления по модулю 12 | return _b === 0 ? _a : euclideanAlgorithm(_b, _a % _b) 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/math/euclidean-distance.js: -------------------------------------------------------------------------------- 1 | import * as m from './matrix' 2 | 3 | export default function euclideanDistance(a, b) { 4 | m.validateSameShape(a, b) 5 | 6 | let squares = 0 7 | 8 | m.walk(a, (indices, aCellVal) => { 9 | const bCellVal = m.getCellAtIndex(b, indices) 10 | squares += (aCellVal - bCellVal) ** 2 11 | }) 12 | 13 | return +Math.sqrt(squares).toFixed(2) 14 | } 15 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial-recursive.js: -------------------------------------------------------------------------------- 1 | // https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB 2 | export default function factorialRecursive(n) { 3 | return n > 1 ? n * factorialRecursive(n - 1) : 1 4 | } 5 | -------------------------------------------------------------------------------- /src/algorithms/math/factorial.js: -------------------------------------------------------------------------------- 1 | // https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB 2 | export default function factorial(n) { 3 | let result = 1 4 | for (let i = 2; i <= n; i++) { 5 | result *= i 6 | } 7 | return result 8 | } 9 | -------------------------------------------------------------------------------- /src/algorithms/math/fast-powering.js: -------------------------------------------------------------------------------- 1 | export default function fastPowering(base, power) { 2 | if (power === 0) return 1 3 | 4 | if (power % 2 === 0) { 5 | // Если степень четная: 6 | // x^8 = x^4 * x^4 7 | return fastPowering(base, power / 2) ** 2 8 | } 9 | 10 | // Если степень нечетная: 11 | // x^9 = x^4 * x^4 * x 12 | return base * fastPowering(base, Math.floor(power / 2)) ** 2 13 | } 14 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci-nth.js: -------------------------------------------------------------------------------- 1 | import fibonacci from './fibonacci' 2 | 3 | /** 4 | * Вычисляет n-ое число Фибоначчи 5 | */ 6 | export default function fibonacciNth(n) { 7 | return fibonacci(n)[n - 1] 8 | } 9 | -------------------------------------------------------------------------------- /src/algorithms/math/fibonacci.js: -------------------------------------------------------------------------------- 1 | // https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D0%A4%D0%B8%D0%B1%D0%BE%D0%BD%D0%B0%D1%87%D1%87%D0%B8 2 | export default function fibonacci(n) { 3 | const arr = [1] 4 | if (n === 1) { 5 | return arr 6 | } 7 | 8 | let current = 1 9 | let previous = 0 10 | 11 | let i = n - 1 12 | while (i) { 13 | current += previous 14 | previous = current - previous 15 | arr.push(current) 16 | i-- 17 | } 18 | 19 | return arr 20 | } 21 | -------------------------------------------------------------------------------- /src/algorithms/math/is-power-of-two-bitwise.js: -------------------------------------------------------------------------------- 1 | export default function isPowerOfTwoBitwise(n) { 2 | // 1 (2^0) - это наименьший результат возведения числа 2 в какую-либо степень 3 | if (n < 1) return false 4 | 5 | /** 6 | * Степени 2 в бинарном формате имеют следующий вид: 7 | * 1: 0001 8 | * 2: 0010 9 | * 4: 0100 10 | * 8: 1000 11 | * 12 | * Почти всегда установлен 1 бит. Исключением являются целые числа со знаком. 13 | * Например, -128 выглядит как 10_000_000, но мы уже проверили, что число больше 0 14 | */ 15 | return (n & (n - 1)) === 0 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/math/is-power-of-two.js: -------------------------------------------------------------------------------- 1 | export default function isPowerOfTwo(n) { 2 | // 1 (2^0) - это наименьший результат возведения числа 2 в какую-либо степень 3 | if (n < 1) return false 4 | 5 | // Выясняем, можем ли мы разделить переданное число на 2 без остатка 6 | let _n = n 7 | while (_n !== 1) { 8 | // Ненулевой остаток свидетельствует о том, 9 | // что переданное число не может быть результатом возведения числа 2 10 | // в какую - либо степень 11 | if (_n % 2 !== 0) return false 12 | _n /= 2 13 | } 14 | 15 | return true 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/math/least-common-multiple.js: -------------------------------------------------------------------------------- 1 | import euclideanAlgorithm from './euclidean-algorithm' 2 | 3 | export default function leastCommonMultiple(a, b) { 4 | if (a === 0 || b === 0) { 5 | return 0 6 | } 7 | 8 | return Math.abs(a * b) / euclideanAlgorithm(a, b) 9 | } 10 | -------------------------------------------------------------------------------- /src/algorithms/math/prime-factors.js: -------------------------------------------------------------------------------- 1 | export function primeFactors(n) { 2 | let _n = n 3 | const factors = [] 4 | 5 | for (let i = 2; i <= Math.sqrt(_n); i++) { 6 | while (_n % i === 0) { 7 | _n /= i 8 | factors.push(i) 9 | } 10 | } 11 | 12 | if (_n > 1) { 13 | factors.push(_n) 14 | } 15 | 16 | return factors 17 | } 18 | 19 | /** 20 | * Теорема Харди-Рамануджана 21 | * https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%A5%D0%B0%D1%80%D0%B4%D0%B8_%E2%80%94_%D0%A0%D0%B0%D0%BC%D0%B0%D0%BD%D1%83%D0%B4%D0%B6%D0%B0%D0%BD%D0%B0 22 | */ 23 | export function hardyRamanujan(n) { 24 | return Math.log(Math.log(n)) 25 | } 26 | -------------------------------------------------------------------------------- /src/algorithms/math/radian-to-degree.js: -------------------------------------------------------------------------------- 1 | export default function radianToDegree(radian) { 2 | return (radian * 180) / Math.PI 3 | } 4 | -------------------------------------------------------------------------------- /src/algorithms/math/sieve-of-eratosthenes.js: -------------------------------------------------------------------------------- 1 | // https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D1%88%D0%B5%D1%82%D0%BE_%D0%AD%D1%80%D0%B0%D1%82%D0%BE%D1%81%D1%84%D0%B5%D0%BD%D0%B0 2 | export default function sieveOfEratosthenes(n) { 3 | const sieve = new Array(n + 1).fill(true) 4 | sieve[0] = false 5 | sieve[1] = false 6 | 7 | const primes = [] 8 | 9 | for (let i = 2; i <= n; i++) { 10 | if (sieve[i]) { 11 | primes.push(i) 12 | 13 | /** 14 | * Оптимизация. 15 | * Начинаем отмечать множители `p` с `p * p`, а не с `p * 2`. 16 | * Это работает, поскольку меньшие множители `p` уже помечены как `false`. 17 | */ 18 | let nextN = i * i 19 | 20 | while (nextN <= n) { 21 | sieve[nextN] = false 22 | nextN += i 23 | } 24 | } 25 | } 26 | 27 | return primes 28 | } 29 | -------------------------------------------------------------------------------- /src/algorithms/math/trial-division.js: -------------------------------------------------------------------------------- 1 | export default function trialDivision(n) { 2 | // Проверяем, что число целое 3 | if (!Number.isInteger(n)) { 4 | return false 5 | } 6 | 7 | // Если число меньше 2, оно не простое по определению 8 | if (n < 2) { 9 | return false 10 | } 11 | 12 | // Числа 2 и 3 простые 13 | if (n <= 3) { 14 | return true 15 | } 16 | 17 | // Если число не делится на 2, мы можем исключить все дальнейшие четные делители 18 | if (n % 2 === 0) { 19 | return false 20 | } 21 | 22 | // Если до квадратного корня из `n` делителей нет, то нет и более высоких делителей 23 | const dividerLimit = Math.sqrt(n) 24 | for (let i = 3; i <= dividerLimit; i += 2) { 25 | if (n % i === 0) { 26 | return false 27 | } 28 | } 29 | 30 | return true 31 | } 32 | -------------------------------------------------------------------------------- /src/algorithms/other/__tests__/hanoi-tower.test.js: -------------------------------------------------------------------------------- 1 | import hanoiTower from '../hanoi-tower' 2 | import Stack from '../../../data-structures/stack' 3 | 4 | describe('hanoiTower', () => { 5 | it('должен решить задачу с 2 дисками', () => { 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('должен решить задачу с 3 дисками', () => { 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('должен решить задачу с 6 дисками', () => { 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/other/__tests__/knight-tour.test.js: -------------------------------------------------------------------------------- 1 | import knightTour from '../knight-tour' 2 | 3 | describe('knightTour', () => { 4 | it('должен определить отсутствие решения для доски 3x3', () => { 5 | const moves = knightTour(3) 6 | 7 | expect(moves.length).toBe(0) 8 | }) 9 | 10 | it('должен найти решения для доски 5x5', () => { 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/other/__tests__/square-matrix-rotation.test.js: -------------------------------------------------------------------------------- 1 | import squareMatrixRotation from '../square-matrix-rotation' 2 | 3 | describe('squareMatrixRotation', () => { 4 | it('должен повернуть матрицу #0 на месте', () => { 5 | const matrix = [[1]] 6 | 7 | const rotatedMatrix = [[1]] 8 | 9 | expect(squareMatrixRotation(matrix)).toEqual(rotatedMatrix) 10 | }) 11 | 12 | it('должен повернуть матрицу #1 на месте', () => { 13 | const matrix = [ 14 | [1, 2], 15 | [3, 4], 16 | ] 17 | 18 | const rotatedMatrix = [ 19 | [3, 1], 20 | [4, 2], 21 | ] 22 | 23 | expect(squareMatrixRotation(matrix)).toEqual(rotatedMatrix) 24 | }) 25 | 26 | it('должен повернуть матрицу #2 на месте', () => { 27 | const matrix = [ 28 | [1, 2, 3], 29 | [4, 5, 6], 30 | [7, 8, 9], 31 | ] 32 | 33 | const rotatedMatrix = [ 34 | [7, 4, 1], 35 | [8, 5, 2], 36 | [9, 6, 3], 37 | ] 38 | 39 | expect(squareMatrixRotation(matrix)).toEqual(rotatedMatrix) 40 | }) 41 | 42 | it('должен повернуть матрицу #3 на месте', () => { 43 | const matrix = [ 44 | [5, 1, 9, 11], 45 | [2, 4, 8, 10], 46 | [13, 3, 6, 7], 47 | [15, 14, 12, 16], 48 | ] 49 | 50 | const rotatedMatrix = [ 51 | [15, 13, 2, 5], 52 | [14, 3, 4, 1], 53 | [12, 6, 8, 9], 54 | [16, 7, 10, 11], 55 | ] 56 | 57 | expect(squareMatrixRotation(matrix)).toEqual(rotatedMatrix) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /src/algorithms/other/hanoi-tower.js: -------------------------------------------------------------------------------- 1 | import Stack from '../../data-structures/stack' 2 | 3 | function hanoiTowerRecursive({ 4 | numberOfDiscs, 5 | fromPole, 6 | withPole, 7 | toPole, 8 | moveCallback, 9 | }) { 10 | if (numberOfDiscs === 1) { 11 | moveCallback(fromPole.peek(), fromPole.toArray(), toPole.toArray()) 12 | const disc = fromPole.pop() 13 | toPole.push(disc) 14 | } else { 15 | // Берем нижний диск из стека `fromPole` 16 | hanoiTowerRecursive({ 17 | numberOfDiscs: numberOfDiscs - 1, 18 | fromPole, 19 | withPole: toPole, 20 | toPole: withPole, 21 | moveCallback, 22 | }) 23 | 24 | // Перемещаем этот диск в пункт назначения 25 | hanoiTowerRecursive({ 26 | numberOfDiscs: 1, 27 | fromPole, 28 | withPole, 29 | toPole, 30 | moveCallback, 31 | }) 32 | 33 | // Перемещаем временную башню со вспомогательного поля в пункт назначения 34 | hanoiTowerRecursive({ 35 | numberOfDiscs: numberOfDiscs - 1, 36 | fromPole: withPole, 37 | withPole: fromPole, 38 | toPole, 39 | moveCallback, 40 | }) 41 | } 42 | } 43 | 44 | export default function hanoiTower({ 45 | numberOfDiscs, 46 | moveCallback, 47 | fromPole = new Stack(), 48 | toPole = new Stack(), 49 | withPole = new Stack(), 50 | }) { 51 | // Каждое из трех полей представляет собой стек, который может содержать диски. 52 | // Каждый диск представлен числом. БОльшие диски представлены бОльшими числами 53 | 54 | // Создаем диски и помещаем их в стек `fromPole` 55 | for (let i = numberOfDiscs; i > 0; i--) { 56 | fromPole.push(i) 57 | } 58 | 59 | hanoiTowerRecursive({ 60 | numberOfDiscs, 61 | fromPole, 62 | withPole, 63 | toPole, 64 | moveCallback, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /src/algorithms/other/knight-tour.js: -------------------------------------------------------------------------------- 1 | function getMoves(board, position) { 2 | // Генерируем все возможные ходы рыцаря 3 | // (включая те, которые находятся за пределами доски) 4 | const moves = [ 5 | [position[0] - 1, position[1] - 2], 6 | [position[0] - 2, position[1] - 1], 7 | [position[0] + 1, position[1] - 2], 8 | [position[0] + 2, position[1] - 1], 9 | [position[0] - 2, position[1] + 1], 10 | [position[0] - 1, position[1] + 2], 11 | [position[0] + 1, position[1] + 2], 12 | [position[0] + 2, position[1] + 1], 13 | ] 14 | 15 | // Удаляем ходы, которые выходят за пределы доски 16 | const boardSize = board.length 17 | return moves.filter( 18 | (move) => 19 | move[0] >= 0 && 20 | move[0] < boardSize && 21 | move[1] >= 0 && 22 | move[1] < boardSize, 23 | ) 24 | } 25 | 26 | function isMoveAllowed(board, move) { 27 | return board[move[0]][move[1]] !== 1 28 | } 29 | 30 | function isBoardVisited(board, moves) { 31 | const possibleCount = board.length ** 2 32 | const realCount = moves.length 33 | 34 | return possibleCount === realCount 35 | } 36 | 37 | function knightTourRecursive(board, moves) { 38 | // Если доска полностью посещена, значит, решение найдено 39 | if (isBoardVisited(board, moves)) { 40 | return true 41 | } 42 | 43 | // Получаем следующие возможные ходы 44 | const possibleMoves = getMoves(board, moves.at(-1)) 45 | 46 | for (const move of possibleMoves) { 47 | if (isMoveAllowed(board, move)) { 48 | // Выполняем ход 49 | moves.push(move) 50 | board[move[0]][move[1]] = 1 51 | 52 | // Если дальнейшие ходы, начиная с текущего, являются успешными, 53 | // значит, решение найдено 54 | if (knightTourRecursive(board, moves)) { 55 | return true 56 | } 57 | 58 | // Если текущий ход был неудачным, возвращаемся назад и пробуем сделать другой ход 59 | moves.pop() 60 | board[move[0]][move[1]] = 0 61 | } 62 | } 63 | 64 | return false 65 | } 66 | 67 | export default function knightTour(size) { 68 | const board = new Array(size).fill().map(() => new Array(size).fill(0)) 69 | const moves = [[0, 0]] 70 | board[0][0] = 1 71 | 72 | return knightTourRecursive(board, moves) ? moves : [] 73 | } 74 | -------------------------------------------------------------------------------- /src/algorithms/other/n-queens-problem/__tests__/n-queens.test.js: -------------------------------------------------------------------------------- 1 | import nQueens from '../n-queens' 2 | 3 | describe('nQueens', () => { 4 | it('должен возвращать пустой массив, свидетельствующий об отсутствии решений дл 3 королев', () => { 5 | const solutions = nQueens(3) 6 | expect(solutions.length).toBe(0) 7 | }) 8 | 9 | it('должен решать задачу n-королев для 4 королев', () => { 10 | const solutions = nQueens(4) 11 | expect(solutions.length).toBe(2) 12 | 13 | // Первое решение 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 | // Второе решение (отраженное) 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('должен решать задачу n-королев для 6 королев', () => { 27 | const solutions = nQueens(6) 28 | expect(solutions.length).toBe(4) 29 | 30 | // Решение 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/algorithms/other/n-queens-problem/n-queens.js: -------------------------------------------------------------------------------- 1 | import QueenPosition from './queen-position' 2 | 3 | function isSafe(positions, rowIndex, columnIndex) { 4 | // Новая позиция, в которую будет помещена королева 5 | const newPosition = new QueenPosition(rowIndex, columnIndex) 6 | 7 | // Проверяем, не конфликтует ли новая позиция с другими позициями 8 | for (let i = 0; i < positions.length; i++) { 9 | const position = positions[i] 10 | 11 | if ( 12 | // Позиция уже установлена 13 | position && 14 | // Есть королева на той же колонке 15 | (newPosition.columnIndex === position.columnIndex || 16 | // Есть королева на той же строке 17 | newPosition.rowIndex === position.rowIndex || 18 | // Есть королева на той же диагонали 19 | newPosition.leftDiagonal === position.leftDiagonal || 20 | newPosition.rightDiagonal === position.rightDiagonal) 21 | ) { 22 | // Есть конфликт 23 | return false 24 | } 25 | } 26 | 27 | return true 28 | } 29 | 30 | function nQueensRecursive(solutions, previousPositions, count, rowIndex) { 31 | const positions = previousPositions.slice().map((p) => { 32 | return !p ? p : new QueenPosition(p.rowIndex, p.columnIndex) 33 | }) 34 | 35 | if (rowIndex === count) { 36 | // Мы успешно достигли конца доски. 37 | // Записываем решение в список 38 | solutions.push(positions) 39 | return true 40 | } 41 | 42 | // Пробуем поместить королеву на безопасную позицию 43 | for (let columnIndex = 0; columnIndex < count; columnIndex++) { 44 | if (isSafe(positions, rowIndex, columnIndex)) { 45 | // Помещаем королеву на позицию 46 | positions[rowIndex] = new QueenPosition(rowIndex, columnIndex) 47 | 48 | // Пробуем поместить следующую королеву 49 | nQueensRecursive(solutions, positions, count, rowIndex + 1) 50 | 51 | // Очищаем позицию во избежание возврата `false` функцией `isSafe()` 52 | positions[rowIndex] = null 53 | } 54 | } 55 | 56 | return false 57 | } 58 | 59 | export default function nQueens(count) { 60 | // Массив координат королев в форме `[rowIndex, columnIndex]` 61 | const positions = new Array(count).fill(null) 62 | 63 | const solutions = [] 64 | 65 | nQueensRecursive(solutions, positions, count, 0) 66 | 67 | return solutions 68 | } 69 | -------------------------------------------------------------------------------- /src/algorithms/other/n-queens-problem/queen-position.js: -------------------------------------------------------------------------------- 1 | export default class QueenPosition { 2 | constructor(rowIndex, columnIndex) { 3 | this.rowIndex = rowIndex 4 | this.columnIndex = columnIndex 5 | } 6 | 7 | get leftDiagonal() { 8 | // Каждая позиция на одной левой (\) диагонали имеет одинаковую разницу между 9 | // `rowIndex` и `columnIndex`. Этот факт можно использовать для быстрой проверки нахождения 10 | // двух позиций (королев) на одной левой диагонали. 11 | // @see https://youtu.be/xouin83ebxE?t=1m59s 12 | return this.rowIndex - this.columnIndex 13 | } 14 | 15 | get rightDiagonal() { 16 | // Каждая позиция на одной правой диагонали (/) имеет одинаковую сумму между 17 | // `rowIndex` и `columnIndex`. Это факт может использоваться для быстрой проверки нахождения 18 | // двух позиций (королев) на одной правой диагонали. 19 | // @see https://youtu.be/xouin83ebxE?t=1m59s 20 | return this.rowIndex + this.columnIndex 21 | } 22 | 23 | toString() { 24 | return `${this.rowIndex},${this.columnIndex}` 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/algorithms/other/rain-terraces/__tests__/bf.test.js: -------------------------------------------------------------------------------- 1 | import bfRainTerraces from '../bf' 2 | 3 | describe('bfRainTerraces', () => { 4 | it('должен найти количество воды, собранное после дождя, методом грубой силы', () => { 5 | expect(bfRainTerraces([1])).toBe(0) 6 | expect(bfRainTerraces([1, 0])).toBe(0) 7 | expect(bfRainTerraces([0, 1])).toBe(0) 8 | expect(bfRainTerraces([0, 1, 0])).toBe(0) 9 | expect(bfRainTerraces([0, 1, 0, 0])).toBe(0) 10 | expect(bfRainTerraces([0, 1, 0, 0, 1, 0])).toBe(2) 11 | expect(bfRainTerraces([0, 2, 0, 0, 1, 0])).toBe(2) 12 | expect(bfRainTerraces([2, 0, 2])).toBe(2) 13 | expect(bfRainTerraces([2, 0, 5])).toBe(2) 14 | expect(bfRainTerraces([3, 0, 0, 2, 0, 4])).toBe(10) 15 | expect(bfRainTerraces([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])).toBe(6) 16 | expect(bfRainTerraces([1, 1, 1, 1, 1])).toBe(0) 17 | expect(bfRainTerraces([1, 2, 3, 4, 5])).toBe(0) 18 | expect(bfRainTerraces([4, 1, 3, 1, 2, 1, 2, 1])).toBe(4) 19 | expect(bfRainTerraces([0, 2, 4, 3, 4, 2, 4, 0, 8, 7, 0])).toBe(7) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/algorithms/other/rain-terraces/__tests__/dp.test.js: -------------------------------------------------------------------------------- 1 | import dpRainTerraces from '../dp' 2 | 3 | describe('dpRainTerraces', () => { 4 | it('должен найти количество воды, собранной после дождя, с помощью динамического программирования', () => { 5 | expect(dpRainTerraces([1])).toBe(0) 6 | expect(dpRainTerraces([1, 0])).toBe(0) 7 | expect(dpRainTerraces([0, 1])).toBe(0) 8 | expect(dpRainTerraces([0, 1, 0])).toBe(0) 9 | expect(dpRainTerraces([0, 1, 0, 0])).toBe(0) 10 | expect(dpRainTerraces([0, 1, 0, 0, 1, 0])).toBe(2) 11 | expect(dpRainTerraces([0, 2, 0, 0, 1, 0])).toBe(2) 12 | expect(dpRainTerraces([2, 0, 2])).toBe(2) 13 | expect(dpRainTerraces([2, 0, 5])).toBe(2) 14 | expect(dpRainTerraces([3, 0, 0, 2, 0, 4])).toBe(10) 15 | expect(dpRainTerraces([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])).toBe(6) 16 | expect(dpRainTerraces([1, 1, 1, 1, 1])).toBe(0) 17 | expect(dpRainTerraces([1, 2, 3, 4, 5])).toBe(0) 18 | expect(dpRainTerraces([4, 1, 3, 1, 2, 1, 2, 1])).toBe(4) 19 | expect(dpRainTerraces([0, 2, 4, 3, 4, 2, 4, 0, 8, 7, 0])).toBe(7) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/algorithms/other/rain-terraces/bf.js: -------------------------------------------------------------------------------- 1 | export default function bfRainTerraces(terraces) { 2 | let waterAmount = 0 3 | 4 | for (let i = 0; i < terraces.length; i++) { 5 | let maxLeft = 0 6 | for (let j = i - 1; j >= 0; j--) { 7 | maxLeft = Math.max(maxLeft, terraces[j]) 8 | } 9 | 10 | let maxRight = 0 11 | for (let j = i + 1; j < terraces.length; j++) { 12 | maxRight = Math.max(maxRight, terraces[j]) 13 | } 14 | 15 | const terraceBoundary = Math.min(maxLeft, maxRight) 16 | if (terraceBoundary > terraces[i]) { 17 | waterAmount += terraceBoundary - terraces[i] 18 | } 19 | } 20 | 21 | return waterAmount 22 | } 23 | -------------------------------------------------------------------------------- /src/algorithms/other/rain-terraces/dp.js: -------------------------------------------------------------------------------- 1 | export default function dpRainTerraces(terraces) { 2 | let waterAmount = 0 3 | 4 | let maxLeftArr = new Array(terraces.length).fill(0) 5 | let maxRightArr = new Array(terraces.length).fill(0) 6 | 7 | ;[maxLeftArr[0], maxRightArr[terraces.length - 1]] = [ 8 | terraces[0], 9 | terraces[terraces.length - 1], 10 | ] 11 | 12 | for (let i = 1; i < terraces.length; i++) { 13 | maxLeftArr[i] = Math.max(maxLeftArr[i - 1], terraces[i]) 14 | } 15 | 16 | for (let i = terraces.length - 2; i >= 0; i--) { 17 | maxRightArr[i] = Math.max(maxRightArr[i + 1], terraces[i]) 18 | } 19 | 20 | for (let i = 0; i < terraces.length; i++) { 21 | const terraceBoundary = Math.min(maxLeftArr[i], maxRightArr[i]) 22 | 23 | if (terraceBoundary > terraces[i]) { 24 | waterAmount += terraceBoundary - terraces[i] 25 | } 26 | } 27 | 28 | return waterAmount 29 | } 30 | -------------------------------------------------------------------------------- /src/algorithms/other/recursive-staircase/__tests__/bf.test.js: -------------------------------------------------------------------------------- 1 | import bfRecursiveStaircase from '../bf' 2 | 3 | describe('bfRecursiveStaircase', () => { 4 | it('должен вычислить количество вариантов методом грубой силы', () => { 5 | expect(() => bfRecursiveStaircase(-1)).toThrowError('n меньше нуля') 6 | expect(bfRecursiveStaircase(0)).toBe(0) 7 | expect(bfRecursiveStaircase(1)).toBe(1) 8 | expect(bfRecursiveStaircase(2)).toBe(2) 9 | expect(bfRecursiveStaircase(3)).toBe(3) 10 | expect(bfRecursiveStaircase(4)).toBe(5) 11 | expect(bfRecursiveStaircase(5)).toBe(8) 12 | expect(bfRecursiveStaircase(6)).toBe(13) 13 | expect(bfRecursiveStaircase(7)).toBe(21) 14 | expect(bfRecursiveStaircase(8)).toBe(34) 15 | expect(bfRecursiveStaircase(9)).toBe(55) 16 | expect(bfRecursiveStaircase(10)).toBe(89) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/algorithms/other/recursive-staircase/__tests__/dp.test.js: -------------------------------------------------------------------------------- 1 | import dpRecursiveStaircase from '../dp' 2 | 3 | describe('dpRecursiveStaircase', () => { 4 | it('должен вычислить количество вариантов с помощью динамического программирования', () => { 5 | expect(() => dpRecursiveStaircase(-1)).toThrowError('n меньше нуля') 6 | expect(dpRecursiveStaircase(0)).toBe(0) 7 | expect(dpRecursiveStaircase(1)).toBe(1) 8 | expect(dpRecursiveStaircase(2)).toBe(2) 9 | expect(dpRecursiveStaircase(3)).toBe(3) 10 | expect(dpRecursiveStaircase(4)).toBe(5) 11 | expect(dpRecursiveStaircase(5)).toBe(8) 12 | expect(dpRecursiveStaircase(6)).toBe(13) 13 | expect(dpRecursiveStaircase(7)).toBe(21) 14 | expect(dpRecursiveStaircase(8)).toBe(34) 15 | expect(dpRecursiveStaircase(9)).toBe(55) 16 | expect(dpRecursiveStaircase(10)).toBe(89) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/algorithms/other/recursive-staircase/__tests__/it.test.js: -------------------------------------------------------------------------------- 1 | import itRecursiveStaircase from '../it' 2 | 3 | describe('itRecursiveStaircase', () => { 4 | it('должен вычислить количество вариантов с помощью итеративного подхода', () => { 5 | expect(() => itRecursiveStaircase(-1)).toThrowError('n меньше нуля') 6 | expect(itRecursiveStaircase(0)).toBe(0) 7 | expect(itRecursiveStaircase(1)).toBe(1) 8 | expect(itRecursiveStaircase(2)).toBe(2) 9 | expect(itRecursiveStaircase(3)).toBe(3) 10 | expect(itRecursiveStaircase(4)).toBe(5) 11 | expect(itRecursiveStaircase(5)).toBe(8) 12 | expect(itRecursiveStaircase(6)).toBe(13) 13 | expect(itRecursiveStaircase(7)).toBe(21) 14 | expect(itRecursiveStaircase(8)).toBe(34) 15 | expect(itRecursiveStaircase(9)).toBe(55) 16 | expect(itRecursiveStaircase(10)).toBe(89) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/algorithms/other/recursive-staircase/__tests__/mm.test.js: -------------------------------------------------------------------------------- 1 | import mmRecursiveStaircase from '../mm' 2 | 3 | describe('mmRecursiveStaircase', () => { 4 | it('должен вычислить количество вариантов методом грубой силы с мемоизацией', () => { 5 | expect(() => mmRecursiveStaircase(-1)).toThrowError('n меньше нуля') 6 | expect(mmRecursiveStaircase(0)).toBe(0) 7 | expect(mmRecursiveStaircase(1)).toBe(1) 8 | expect(mmRecursiveStaircase(2)).toBe(2) 9 | expect(mmRecursiveStaircase(3)).toBe(3) 10 | expect(mmRecursiveStaircase(4)).toBe(5) 11 | expect(mmRecursiveStaircase(5)).toBe(8) 12 | expect(mmRecursiveStaircase(6)).toBe(13) 13 | expect(mmRecursiveStaircase(7)).toBe(21) 14 | expect(mmRecursiveStaircase(8)).toBe(34) 15 | expect(mmRecursiveStaircase(9)).toBe(55) 16 | expect(mmRecursiveStaircase(10)).toBe(89) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/algorithms/other/recursive-staircase/bf.js: -------------------------------------------------------------------------------- 1 | export default function bfRecursiveStaircase(n) { 2 | if (n < 0) { 3 | throw new Error('n меньше нуля') 4 | } 5 | switch (n) { 6 | case 0: 7 | return 0 8 | case 1: 9 | return 1 10 | case 2: 11 | return 2 12 | default: 13 | return bfRecursiveStaircase(n - 1) + bfRecursiveStaircase(n - 2) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/algorithms/other/recursive-staircase/dp.js: -------------------------------------------------------------------------------- 1 | export default function dpRecursiveStaircase(n) { 2 | if (n < 0) { 3 | throw new Error('n меньше нуля') 4 | } 5 | 6 | const steps = new Array(n + 1).fill(0) 7 | steps[0] = 0 8 | steps[1] = 1 9 | steps[2] = 2 10 | 11 | for (let i = 3; i <= n; i++) { 12 | steps[i] = steps[i - 1] + steps[i - 2] 13 | } 14 | 15 | return steps[n] 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/other/recursive-staircase/it.js: -------------------------------------------------------------------------------- 1 | export default function itRecursiveStaircase(n) { 2 | if (n < 0) { 3 | throw new Error('n меньше нуля') 4 | } 5 | 6 | switch (n) { 7 | case 0: 8 | return 0 9 | case 1: 10 | return 1 11 | case 2: 12 | return 2 13 | default: 14 | const steps = [1, 2] 15 | 16 | for (let i = 3; i <= n; i++) { 17 | ;[steps[0], steps[1]] = [steps[1], steps[0] + steps[1]] 18 | } 19 | 20 | return steps[1] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/algorithms/other/recursive-staircase/mm.js: -------------------------------------------------------------------------------- 1 | export default function mmRecursiveStaircase(stairs) { 2 | if (stairs < 0) { 3 | throw new Error('n меньше нуля') 4 | } 5 | 6 | const memo = [] 7 | 8 | const getSteps = (n) => { 9 | switch (n) { 10 | case 0: 11 | return 0 12 | case 1: 13 | return 1 14 | case 2: 15 | return 2 16 | case memo[n]: 17 | return memo[n] 18 | default: 19 | memo[n] = getSteps(n - 1) + getSteps(n - 2) 20 | return memo[n] 21 | } 22 | } 23 | 24 | return getSteps(stairs) 25 | } 26 | -------------------------------------------------------------------------------- /src/algorithms/other/square-matrix-rotation.js: -------------------------------------------------------------------------------- 1 | export default function squareMatrixRotation(matrix) { 2 | // Выполняем диагональное отражение матрицы сверху направо/снизу налево 3 | for (let rowIndex = 0; rowIndex < matrix.length; rowIndex++) { 4 | for (let colIndex = rowIndex + 1; colIndex < matrix.length; colIndex++) { 5 | ;[matrix[colIndex][rowIndex], matrix[rowIndex][colIndex]] = [ 6 | matrix[rowIndex][colIndex], 7 | matrix[colIndex][rowIndex], 8 | ] 9 | } 10 | } 11 | 12 | // Выполняем горизонтальное отражение матрицы 13 | for (let rowIndex = 0; rowIndex < matrix.length; rowIndex++) { 14 | for (let colIndex = 0; colIndex < matrix.length / 2; colIndex++) { 15 | ;[ 16 | matrix[rowIndex][matrix.length - 1 - colIndex], 17 | matrix[rowIndex][colIndex], 18 | ] = [ 19 | matrix[rowIndex][colIndex], 20 | matrix[rowIndex][matrix.length - 1 - colIndex], 21 | ] 22 | } 23 | } 24 | 25 | return matrix 26 | } 27 | -------------------------------------------------------------------------------- /src/algorithms/searches/__tests__/binary-search.test.js: -------------------------------------------------------------------------------- 1 | import binarySearch from '../binary-search' 2 | 3 | describe('binarySearch', () => { 4 | it('должен найти числа в отсортированных массивах', () => { 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('должен найти объекты в отсортированном массиве', () => { 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/searches/__tests__/interpolation-search.test.js: -------------------------------------------------------------------------------- 1 | import interpolationSearch from '../interpolation-search' 2 | 3 | describe('interpolationSearch', () => { 4 | it('должен найти числа в отсортированных массивах', () => { 5 | expect(interpolationSearch([], 1)).toBe(-1) 6 | expect(interpolationSearch([1], 1)).toBe(0) 7 | expect(interpolationSearch([1], 0)).toBe(-1) 8 | expect(interpolationSearch([1, 1], 1)).toBe(0) 9 | expect(interpolationSearch([1, 2], 1)).toBe(0) 10 | expect(interpolationSearch([1, 2], 2)).toBe(1) 11 | expect(interpolationSearch([10, 20, 30, 40, 50], 40)).toBe(3) 12 | expect( 13 | interpolationSearch( 14 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 15 | 14, 16 | ), 17 | ).toBe(13) 18 | expect( 19 | interpolationSearch( 20 | [1, 6, 7, 8, 12, 13, 14, 19, 21, 23, 24, 24, 24, 300], 21 | 24, 22 | ), 23 | ).toBe(10) 24 | expect( 25 | interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 600), 26 | ).toBe(-1) 27 | expect( 28 | interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 1), 29 | ).toBe(0) 30 | expect( 31 | interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 2), 32 | ).toBe(1) 33 | expect( 34 | interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 3), 35 | ).toBe(2) 36 | expect( 37 | interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 700), 38 | ).toBe(3) 39 | expect( 40 | interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 800), 41 | ).toBe(4) 42 | expect( 43 | interpolationSearch([0, 2, 3, 700, 800, 1200, 1300, 1400, 1900], 1200), 44 | ).toBe(5) 45 | expect( 46 | interpolationSearch([1, 2, 3, 700, 800, 1200, 1300, 1400, 19000], 800), 47 | ).toBe(4) 48 | expect(interpolationSearch([0, 10, 11, 12, 13, 14, 15], 10)).toBe(1) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /src/algorithms/searches/__tests__/jump-search.test.js: -------------------------------------------------------------------------------- 1 | import jumpSearch from '../jump-search' 2 | 3 | describe('jumpSearch', () => { 4 | it('должен найти числа в отсортированных массивах', () => { 5 | expect(jumpSearch([], 1)).toBe(-1) 6 | expect(jumpSearch([1], 2)).toBe(-1) 7 | expect(jumpSearch([1], 1)).toBe(0) 8 | expect(jumpSearch([1, 2], 1)).toBe(0) 9 | expect(jumpSearch([1, 2], 1)).toBe(0) 10 | expect(jumpSearch([1, 1, 1], 1)).toBe(0) 11 | expect(jumpSearch([1, 2, 5, 10, 20, 21, 24, 30, 48], 2)).toBe(1) 12 | expect(jumpSearch([1, 2, 5, 10, 20, 21, 24, 30, 48], 0)).toBe(-1) 13 | expect(jumpSearch([1, 2, 5, 10, 20, 21, 24, 30, 48], 0)).toBe(-1) 14 | expect(jumpSearch([1, 2, 5, 10, 20, 21, 24, 30, 48], 7)).toBe(-1) 15 | expect(jumpSearch([1, 2, 5, 10, 20, 21, 24, 30, 48], 5)).toBe(2) 16 | expect(jumpSearch([1, 2, 5, 10, 20, 21, 24, 30, 48], 20)).toBe(4) 17 | expect(jumpSearch([1, 2, 5, 10, 20, 21, 24, 30, 48], 30)).toBe(7) 18 | expect(jumpSearch([1, 2, 5, 10, 20, 21, 24, 30, 48], 48)).toBe(8) 19 | }) 20 | 21 | it('должен найти объекты в отсортированном массиве', () => { 22 | const sortedArrayOfObjects = [ 23 | { key: 1, value: 'value1' }, 24 | { key: 2, value: 'value2' }, 25 | { key: 3, value: 'value3' }, 26 | ] 27 | 28 | const comparator = (a, b) => { 29 | if (a.key === b.key) return 0 30 | return a.key < b.key ? -1 : 1 31 | } 32 | 33 | expect(jumpSearch([], { key: 1 }, comparator)).toBe(-1) 34 | expect(jumpSearch(sortedArrayOfObjects, { key: 4 }, comparator)).toBe(-1) 35 | expect(jumpSearch(sortedArrayOfObjects, { key: 1 }, comparator)).toBe(0) 36 | expect(jumpSearch(sortedArrayOfObjects, { key: 2 }, comparator)).toBe(1) 37 | expect(jumpSearch(sortedArrayOfObjects, { key: 3 }, comparator)).toBe(2) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /src/algorithms/searches/__tests__/linear-search.test.js: -------------------------------------------------------------------------------- 1 | import linearSearch from '../linear-search' 2 | 3 | describe('linearSearch', () => { 4 | it('должен найти числа в массиве', () => { 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('должен найти символы в массиве', () => { 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('должен найти объекты в массиве', () => { 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 = [{ key: 5 }, { key: 6 }, { key: 7 }, { key: 6 }] 30 | 31 | expect(linearSearch(array, { key: 10 }, comparatorCallback)).toEqual([]) 32 | expect(linearSearch(array, { key: 5 }, comparatorCallback)).toEqual([0]) 33 | expect(linearSearch(array, { key: 6 }, comparatorCallback)).toEqual([1, 3]) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/algorithms/searches/binary-search.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/comparator' 2 | 3 | export default function binarySearch(sortedArr, target, fn) { 4 | const comparator = new Comparator(fn) 5 | 6 | let start = 0 7 | let end = sortedArr.length - 1 8 | 9 | while (start <= end) { 10 | // Вычисляем индекс центрального элемента 11 | let middle = start + Math.floor((end - start) / 2) 12 | 13 | if (comparator.equal(sortedArr[middle], target)) { 14 | return middle 15 | } 16 | 17 | // Если целевое значение меньше центрального элемента 18 | if (comparator.lessThan(sortedArr[middle], target)) { 19 | // Переходим к правой половине массива 20 | start = middle + 1 21 | } else { 22 | // Переходим к левой половине массива 23 | end = middle - 1 24 | } 25 | } 26 | 27 | return -1 28 | } 29 | -------------------------------------------------------------------------------- /src/algorithms/searches/interpolation-search.js: -------------------------------------------------------------------------------- 1 | export default function interpolationSearch(sortedArr, target) { 2 | let start = 0 3 | let end = sortedArr.length - 1 4 | 5 | while (start <= end) { 6 | const rangeDelta = sortedArr[end] - sortedArr[start] 7 | const indexDelta = end - start 8 | const valueDelta = target - sortedArr[start] 9 | 10 | // Если `valueDelta` равняется `0`, значит, искомый элемент 11 | // в массиве отсутствует 12 | if (valueDelta < 0) return -1 13 | 14 | // Если `rangeDelta` равняется `0`, значит, подмассив содержит 15 | // одинаковые числа, поэтому искать нечего 16 | if (!rangeDelta) { 17 | // Это также позволяет избежать деления на 0 при поиске 18 | // центрального элемента ниже 19 | return sortedArr[start] === target ? start : -1 20 | } 21 | 22 | const middleIndex = 23 | start + Math.floor((valueDelta * indexDelta) / rangeDelta) 24 | 25 | if (sortedArr[middleIndex] === target) { 26 | return middleIndex 27 | } 28 | 29 | if (sortedArr[middleIndex] < target) { 30 | // Переходим к правой половине массива 31 | start = middleIndex + 1 32 | } else { 33 | // переходим к левой половине массива 34 | end = middleIndex - 1 35 | } 36 | } 37 | 38 | return -1 39 | } 40 | -------------------------------------------------------------------------------- /src/algorithms/searches/jump-search.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/comparator' 2 | 3 | export default function jumpSearch(sortedArr, target, fn) { 4 | const comparator = new Comparator(fn) 5 | const length = sortedArr.length 6 | if (!length) return -1 7 | 8 | // Вычисляем оптимальный шаг. 9 | // Общее количество сравнений в худшем случае будет ((length/step) + step - 1). 10 | // Значение функции ((length/step) + step - 1) будет минимальным при step = √length 11 | let step = Math.floor(Math.sqrt(length)) 12 | 13 | let start = 0 14 | let end = step 15 | 16 | // Ищем блок, к которому принадлежит искомый элемент 17 | while (comparator.greaterThan(target, sortedArr[Math.min(end, length) - 1])) { 18 | // Переходим к следующему блоку 19 | start = end 20 | end += step 21 | 22 | if (start > length) return -1 23 | } 24 | 25 | // Выполняем линейный поиск в блоке, к которому принадлежит 26 | // `target`, начиная со `start` 27 | let currentIndex = start 28 | while (currentIndex < Math.min(end, length)) { 29 | if (comparator.equal(target, sortedArr[currentIndex])) { 30 | return currentIndex 31 | } 32 | currentIndex++ 33 | } 34 | 35 | return -1 36 | } 37 | -------------------------------------------------------------------------------- /src/algorithms/searches/linear-search.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/comparator' 2 | 3 | export default function linearSearch(arr, target, fn) { 4 | const comparator = new Comparator(fn) 5 | const result = [] 6 | 7 | arr.forEach((item, index) => { 8 | if (comparator.equal(item, target)) { 9 | result.push(index) 10 | } 11 | }) 12 | 13 | return result 14 | } 15 | -------------------------------------------------------------------------------- /src/algorithms/sets/__tests__/cartesian-product.test.js: -------------------------------------------------------------------------------- 1 | import cartesianProduct from '../cartesian-product' 2 | 3 | describe('cartesianProduct', () => { 4 | it('должен вернуть `null` при отсутствии одного из множеств', () => { 5 | const product1 = cartesianProduct([1], null) 6 | const product2 = cartesianProduct([], null) 7 | 8 | expect(product1).toBeNull() 9 | expect(product2).toBeNull() 10 | }) 11 | 12 | it('должен вычислить произведение двух множеств', () => { 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([ 18 | [1, 3], 19 | [1, 5], 20 | [2, 3], 21 | [2, 5], 22 | ]) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /src/algorithms/sets/__tests__/combination-sum.test.js: -------------------------------------------------------------------------------- 1 | import combinationSum from '../combination-sum' 2 | 3 | describe('combinationSum', () => { 4 | it('должен найти все комбинации чисел для получения указанной суммы', () => { 5 | expect(combinationSum([1], 4)).toEqual([[1, 1, 1, 1]]) 6 | 7 | expect(combinationSum([2, 3, 6, 7], 7)).toEqual([[2, 2, 3], [7]]) 8 | 9 | expect(combinationSum([2, 3, 5], 8)).toEqual([ 10 | [2, 2, 2, 2], 11 | [2, 3, 3], 12 | [3, 5], 13 | ]) 14 | 15 | expect(combinationSum([2, 5], 3)).toEqual([]) 16 | 17 | expect(combinationSum([], 3)).toEqual([]) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/algorithms/sets/__tests__/fisher-yates.test.js: -------------------------------------------------------------------------------- 1 | import fisherYates from '../fisher-yates' 2 | 3 | const sortedArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 4 | 5 | describe('fisherYates', () => { 6 | it('должен тасовать маленькие массивы', () => { 7 | expect(fisherYates([])).toEqual([]) 8 | expect(fisherYates([1])).toEqual([1]) 9 | }) 10 | 11 | it('должен произвольно перетасовать элементы массива', () => { 12 | const shuffledArray = fisherYates(sortedArr) 13 | 14 | expect(shuffledArray.length).toBe(sortedArr.length) 15 | expect(shuffledArray).not.toEqual(sortedArr) 16 | expect(shuffledArray.sort()).toEqual(sortedArr) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/algorithms/sets/__tests__/shortest-common-supersequence.test.js: -------------------------------------------------------------------------------- 1 | import shortestCommonSupersequence from '../shortest-common-supersequence' 2 | 3 | describe('shortestCommonSupersequence', () => { 4 | it('должен найти КОС двух множеств', () => { 5 | // LCS (наибольшая общая последовательность) пустая 6 | expect( 7 | shortestCommonSupersequence(['A', 'B', 'C'], ['D', 'E', 'F']), 8 | ).toEqual(['A', 'B', 'C', 'D', 'E', 'F']) 9 | 10 | // LCS - "EE" 11 | expect( 12 | shortestCommonSupersequence(['G', 'E', 'E', 'K'], ['E', 'K', 'E']), 13 | ).toEqual(['G', 'E', 'K', 'E', 'K']) 14 | 15 | // LCS - "GTAB" 16 | expect( 17 | shortestCommonSupersequence( 18 | ['A', 'G', 'G', 'T', 'A', 'B'], 19 | ['G', 'X', 'T', 'X', 'A', 'Y', 'B'], 20 | ), 21 | ).toEqual(['A', 'G', 'G', 'X', 'T', 'X', 'A', 'Y', 'B']) 22 | 23 | // LCS - "BCBA" 24 | expect( 25 | shortestCommonSupersequence( 26 | ['A', 'B', 'C', 'B', 'D', 'A', 'B'], 27 | ['B', 'D', 'C', 'A', 'B', 'A'], 28 | ), 29 | ).toEqual(['A', 'B', 'D', 'C', 'A', 'B', 'D', 'A', 'B']) 30 | 31 | // LCS - "BDABA" 32 | expect( 33 | shortestCommonSupersequence( 34 | ['B', 'D', 'C', 'A', 'B', 'A'], 35 | ['A', 'B', 'C', 'B', 'D', 'A', 'B', 'A', 'C'], 36 | ), 37 | ).toEqual(['A', 'B', 'C', 'B', 'D', 'C', 'A', 'B', 'A', 'C']) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /src/algorithms/sets/cartesian-product.js: -------------------------------------------------------------------------------- 1 | export default function cartesianProduct(a, b) { 2 | if (!a?.length || !b?.length) return null 3 | 4 | return a.map((x) => b.map((y) => [x, y])).reduce((a, b) => a.concat(b), []) 5 | } 6 | -------------------------------------------------------------------------------- /src/algorithms/sets/combination-sum.js: -------------------------------------------------------------------------------- 1 | function combinationSumRecursive( 2 | candidates, 3 | remainingSum, 4 | finalCombinations = [], 5 | currentCombination = [], 6 | startFrom = 0, 7 | ) { 8 | if (remainingSum < 0) { 9 | // Добавив кандидата, мы опустились ниже `0`. 10 | // Это означает, что последний кандидат неприемлем 11 | return finalCombinations 12 | } 13 | 14 | if (remainingSum === 0) { 15 | // Если после добавления кандидата, мы получили `0`, 16 | // нужно сохранить текущую комбинацию, поскольку 17 | // это одно из искомых решений 18 | finalCombinations.push(currentCombination.slice()) 19 | 20 | return finalCombinations 21 | } 22 | 23 | // Если мы пока не получили `0`, продолжаем добавлять оставшихся кандидатов 24 | for (let i = startFrom; i < candidates.length; i++) { 25 | const currentCandidate = candidates[i] 26 | 27 | currentCombination.push(currentCandidate) 28 | 29 | combinationSumRecursive( 30 | candidates, 31 | remainingSum - currentCandidate, 32 | finalCombinations, 33 | currentCombination, 34 | i, 35 | ) 36 | 37 | // Возвращаемся назад, исключаем текущего кандидата и пробуем другого 38 | currentCombination.pop() 39 | } 40 | 41 | return finalCombinations 42 | } 43 | 44 | export default function combinationSum(candidates, target) { 45 | return combinationSumRecursive(candidates, target) 46 | } 47 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/__test__/with-repetitions.test.js: -------------------------------------------------------------------------------- 1 | import withRepetitions from '../with-repetitions' 2 | import factorial from '../../../math/factorial' 3 | 4 | describe('withRepetitions', () => { 5 | it('должен комбинировать элементы множеств с повторами', () => { 6 | expect(withRepetitions(['A'], 1)).toEqual([['A']]) 7 | 8 | expect(withRepetitions(['A', 'B'], 1)).toEqual([['A'], ['B']]) 9 | 10 | expect(withRepetitions(['A', 'B'], 2)).toEqual([ 11 | ['A', 'A'], 12 | ['A', 'B'], 13 | ['B', 'B'], 14 | ]) 15 | 16 | expect(withRepetitions(['A', 'B'], 3)).toEqual([ 17 | ['A', 'A', 'A'], 18 | ['A', 'A', 'B'], 19 | ['A', 'B', 'B'], 20 | ['B', 'B', 'B'], 21 | ]) 22 | 23 | expect(withRepetitions(['A', 'B', 'C'], 2)).toEqual([ 24 | ['A', 'A'], 25 | ['A', 'B'], 26 | ['A', 'C'], 27 | ['B', 'B'], 28 | ['B', 'C'], 29 | ['C', 'C'], 30 | ]) 31 | 32 | expect(withRepetitions(['A', 'B', 'C'], 3)).toEqual([ 33 | ['A', 'A', 'A'], 34 | ['A', 'A', 'B'], 35 | ['A', 'A', 'C'], 36 | ['A', 'B', 'B'], 37 | ['A', 'B', 'C'], 38 | ['A', 'C', 'C'], 39 | ['B', 'B', 'B'], 40 | ['B', 'B', 'C'], 41 | ['B', 'C', 'C'], 42 | ['C', 'C', 'C'], 43 | ]) 44 | 45 | const combinationOptions = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] 46 | const combinationSlotsNumber = 4 47 | const combinations = withRepetitions( 48 | combinationOptions, 49 | combinationSlotsNumber, 50 | ) 51 | const n = combinationOptions.length 52 | const r = combinationSlotsNumber 53 | const expectedNumberOfCombinations = 54 | factorial(r + n - 1) / (factorial(r) * factorial(n - 1)) 55 | 56 | expect(combinations.length).toBe(expectedNumberOfCombinations) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/__test__/without-repetitions.test.js: -------------------------------------------------------------------------------- 1 | import combineWithoutRepetitions from '../without-repetitions' 2 | import factorial from '../../../math/factorial' 3 | 4 | describe('combineWithoutRepetitions', () => { 5 | it('должен комбинировать элементы множеств без повторов', () => { 6 | expect(combineWithoutRepetitions(['A', 'B'], 3)).toEqual([]) 7 | 8 | expect(combineWithoutRepetitions(['A', 'B'], 1)).toEqual([['A'], ['B']]) 9 | 10 | expect(combineWithoutRepetitions(['A'], 1)).toEqual([['A']]) 11 | 12 | expect(combineWithoutRepetitions(['A', 'B'], 2)).toEqual([['A', 'B']]) 13 | 14 | expect(combineWithoutRepetitions(['A', 'B', 'C'], 2)).toEqual([ 15 | ['A', 'B'], 16 | ['A', 'C'], 17 | ['B', 'C'], 18 | ]) 19 | 20 | expect(combineWithoutRepetitions(['A', 'B', 'C'], 3)).toEqual([ 21 | ['A', 'B', 'C'], 22 | ]) 23 | 24 | expect(combineWithoutRepetitions(['A', 'B', 'C', 'D'], 3)).toEqual([ 25 | ['A', 'B', 'C'], 26 | ['A', 'B', 'D'], 27 | ['A', 'C', 'D'], 28 | ['B', 'C', 'D'], 29 | ]) 30 | 31 | expect(combineWithoutRepetitions(['A', 'B', 'C', 'D', 'E'], 3)).toEqual([ 32 | ['A', 'B', 'C'], 33 | ['A', 'B', 'D'], 34 | ['A', 'B', 'E'], 35 | ['A', 'C', 'D'], 36 | ['A', 'C', 'E'], 37 | ['A', 'D', 'E'], 38 | ['B', 'C', 'D'], 39 | ['B', 'C', 'E'], 40 | ['B', 'D', 'E'], 41 | ['C', 'D', 'E'], 42 | ]) 43 | 44 | const combinationOptions = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] 45 | const combinationSlotsNumber = 4 46 | const combinations = combineWithoutRepetitions( 47 | combinationOptions, 48 | combinationSlotsNumber, 49 | ) 50 | const n = combinationOptions.length 51 | const r = combinationSlotsNumber 52 | const expectedNumberOfCombinations = 53 | factorial(n) / (factorial(r) * factorial(n - r)) 54 | 55 | expect(combinations.length).toBe(expectedNumberOfCombinations) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/with-repetitions.js: -------------------------------------------------------------------------------- 1 | export default function withRepetitions(set, length) { 2 | if (length === 1) { 3 | return set.map((i) => [i]) 4 | } 5 | 6 | const result = [] 7 | 8 | set.forEach((i, idx) => { 9 | const subset = withRepetitions(set.slice(idx), length - 1) 10 | 11 | for (const j of subset) { 12 | result.push([i, ...j]) 13 | } 14 | }) 15 | 16 | return result 17 | } 18 | -------------------------------------------------------------------------------- /src/algorithms/sets/combinations/without-repetitions.js: -------------------------------------------------------------------------------- 1 | export default function withoutRepetitions(set, length) { 2 | if (length === 1) { 3 | return set.map((i) => [i]) 4 | } 5 | 6 | const result = [] 7 | 8 | set.forEach((i, idx) => { 9 | const subset = withoutRepetitions(set.slice(idx + 1), length - 1) 10 | 11 | for (const j of subset) { 12 | result.push([i, ...j]) 13 | } 14 | }) 15 | 16 | return result 17 | } 18 | -------------------------------------------------------------------------------- /src/algorithms/sets/fisher-yates.js: -------------------------------------------------------------------------------- 1 | export default function fisherYates(arr) { 2 | // Эффективно создаем глубокую копию массива 3 | // https://developer.mozilla.org/en-US/docs/Web/API/structuredClone 4 | const arrCopy = structuredClone(arr) 5 | 6 | for (let i = arrCopy.length - 1; i > 0; i--) { 7 | const j = Math.floor(Math.random() * (i + 1)) 8 | ;[arrCopy[i], arrCopy[j]] = [arrCopy[j], arrCopy[i]] 9 | } 10 | 11 | return arrCopy 12 | } 13 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequence/__tests__/matrix.test.js: -------------------------------------------------------------------------------- 1 | import lcs from '../matrix' 2 | 3 | describe('lcs', () => { 4 | it('должен найти НОП двух множеств', () => { 5 | expect(lcs([''], [''])).toEqual(['']) 6 | 7 | expect(lcs([''], ['A', 'B', 'C'])).toEqual(['']) 8 | 9 | expect(lcs(['A', 'B', 'C'], [''])).toEqual(['']) 10 | 11 | expect(lcs(['A', 'B', 'C'], ['D', 'E', 'F', 'G'])).toEqual(['']) 12 | 13 | expect( 14 | lcs(['A', 'B', 'C', 'D', 'G', 'H'], ['A', 'E', 'D', 'F', 'H', 'R']), 15 | ).toEqual(['A', 'D', 'H']) 16 | 17 | expect( 18 | lcs(['A', 'G', 'G', 'T', 'A', 'B'], ['G', 'X', 'T', 'X', 'A', 'Y', 'B']), 19 | ).toEqual(['G', 'T', 'A', 'B']) 20 | 21 | expect( 22 | lcs(['A', 'B', 'C', 'D', 'A', 'F'], ['A', 'C', 'B', 'C', 'F']), 23 | ).toEqual(['A', 'B', 'C', 'F']) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequence/__tests__/recursive.test.js: -------------------------------------------------------------------------------- 1 | import lcsRecursive from '../recursive' 2 | 3 | describe('lcsRecursive', () => { 4 | it('должен рекурсивно найти НОП двух строк', () => { 5 | expect(lcsRecursive('', '')).toBe('') 6 | expect(lcsRecursive('ABC', '')).toBe('') 7 | expect(lcsRecursive('', 'ABC')).toBe('') 8 | expect(lcsRecursive('ABABC', 'BABCA')).toBe('BABC') 9 | expect(lcsRecursive('BABCA', 'ABCBA')).toBe('ABCA') 10 | expect(lcsRecursive('sea', 'eat')).toBe('ea') 11 | expect(lcsRecursive('algorithms', 'rithm')).toBe('rithm') 12 | expect( 13 | lcsRecursive( 14 | 'Algorithms and data structures implemented in JavaScript', 15 | 'Here you may find Algorithms and data structures that are implemented in JavaScript', 16 | ), 17 | ).toBe('Algorithms and data structures implemented in JavaScript') 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequence/matrix.js: -------------------------------------------------------------------------------- 1 | export default function lcs(a, b) { 2 | // Инициализируем матрицу LCS 3 | const matrix = new Array(b.length + 1) 4 | .fill(null) 5 | .map(() => new Array(a.length + 1).fill(null)) 6 | 7 | // Заполняем `0` первую строку 8 | for (let i = 0; i <= a.length; i++) { 9 | matrix[0][i] = 0 10 | } 11 | 12 | // Заполняем `0` первую колонку 13 | for (let i = 0; i <= b.length; i++) { 14 | matrix[i][0] = 0 15 | } 16 | 17 | // Заполняем матрицу LCS 18 | for (let i = 1; i <= b.length; i++) { 19 | for (let j = 1; j <= a.length; j++) { 20 | if (b[i - 1] === a[j - 1]) { 21 | matrix[i][j] = matrix[i - 1][j - 1] + 1 22 | } else { 23 | matrix[i][j] = Math.max(matrix[i - 1][j], matrix[i][j - 1]) 24 | } 25 | } 26 | } 27 | 28 | // Вычисляем LCS на основе матрицы 29 | if (!matrix[b.length][a.length]) { 30 | return [''] 31 | } 32 | 33 | const lcs = [] 34 | let i = a.length 35 | let j = b.length 36 | 37 | while (i > 0 && j > 0) { 38 | if (a[i - 1] === b[j - 1]) { 39 | // Двигаемся по диагонали "лево-верх" 40 | lcs.unshift(a[i - 1]) 41 | i-- 42 | j-- 43 | } else if (matrix[j][i] === matrix[j][i - 1]) { 44 | // Двигаемся влево 45 | i-- 46 | } else { 47 | // Двигаемся вверх 48 | j-- 49 | } 50 | } 51 | 52 | return lcs 53 | } 54 | -------------------------------------------------------------------------------- /src/algorithms/sets/longest-common-subsequence/recursive.js: -------------------------------------------------------------------------------- 1 | export default function lcsRecursive(a, b) { 2 | const lcs = (a, b, memo = {}) => { 3 | if (!a || !b) return '' 4 | 5 | if (memo[`${a},${b}`]) { 6 | return memo[`${a},${b}`] 7 | } 8 | 9 | if (a[0] === b[0]) { 10 | return a[0] + lcs(a.slice(1), b.slice(1), memo) 11 | } 12 | 13 | const next1 = lcs(a.slice(1), b, memo) 14 | const next2 = lcs(a, b.slice(1), memo) 15 | 16 | const nextLongest = next1.length >= next2.length ? next1 : next2 17 | memo[`${a},${b}`] = nextLongest 18 | 19 | return nextLongest 20 | } 21 | 22 | return lcs(a, b) 23 | } 24 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/__tests__/brute-force.test.js: -------------------------------------------------------------------------------- 1 | import bruteForce from '../brute-force' 2 | 3 | describe('bruteForce', () => { 4 | it('должен найти максимальные подмассивы методом грубой силы', () => { 5 | expect(bruteForce([])).toEqual([]) 6 | expect(bruteForce([0, 0])).toEqual([0]) 7 | expect(bruteForce([0, 0, 1])).toEqual([0, 0, 1]) 8 | expect(bruteForce([0, 0, 1, 2])).toEqual([0, 0, 1, 2]) 9 | expect(bruteForce([0, 0, -1, 2])).toEqual([2]) 10 | expect(bruteForce([-1, -2, -3, -4, -5])).toEqual([-1]) 11 | expect(bruteForce([1, 2, 3, 2, 3, 4, 5])).toEqual([1, 2, 3, 2, 3, 4, 5]) 12 | expect(bruteForce([-2, 1, -3, 4, -1, 2, 1, -5, 4])).toEqual([4, -1, 2, 1]) 13 | expect(bruteForce([-2, -3, 4, -1, -2, 1, 5, -3])).toEqual([4, -1, -2, 1, 5]) 14 | expect(bruteForce([1, -3, 2, -5, 7, 6, -1, 4, 11, -23])).toEqual([ 15 | 7, 6, -1, 4, 11, 16 | ]) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/__tests__/divide-conquer.test.js: -------------------------------------------------------------------------------- 1 | import divideConquer from '../divide-conquer' 2 | 3 | describe('dcMaximumSubarraySum', () => { 4 | it("должен найти максимальные подмассивы методом 'Разделяй и властвуй'", () => { 5 | expect(divideConquer([])).toEqual(-Infinity) 6 | expect(divideConquer([0, 0])).toEqual(0) 7 | expect(divideConquer([0, 0, 1])).toEqual(1) 8 | expect(divideConquer([0, 0, 1, 2])).toEqual(3) 9 | expect(divideConquer([0, 0, -1, 2])).toEqual(2) 10 | expect(divideConquer([-1, -2, -3, -4, -5])).toEqual(-1) 11 | expect(divideConquer([1, 2, 3, 2, 3, 4, 5])).toEqual(20) 12 | expect(divideConquer([-2, 1, -3, 4, -1, 2, 1, -5, 4])).toEqual(6) 13 | expect(divideConquer([-2, -3, 4, -1, -2, 1, 5, -3])).toEqual(7) 14 | expect(divideConquer([1, -3, 2, -5, 7, 6, -1, 4, 11, -23])).toEqual(27) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/__tests__/dynamic-programming.test.js: -------------------------------------------------------------------------------- 1 | import dynamicProgramming from '../dynamic-programming' 2 | 3 | describe('dynamicProgramming', () => { 4 | it('должен найти максимальные подмассивы методом динамического программирования', () => { 5 | expect(dynamicProgramming([])).toEqual([]) 6 | expect(dynamicProgramming([0, 0])).toEqual([0]) 7 | expect(dynamicProgramming([0, 0, 1])).toEqual([0, 0, 1]) 8 | expect(dynamicProgramming([0, 0, 1, 2])).toEqual([0, 0, 1, 2]) 9 | expect(dynamicProgramming([0, 0, -1, 2])).toEqual([2]) 10 | expect(dynamicProgramming([-1, -2, -3, -4, -5])).toEqual([-1]) 11 | expect(dynamicProgramming([1, 2, 3, 2, 3, 4, 5])).toEqual([ 12 | 1, 2, 3, 2, 3, 4, 5, 13 | ]) 14 | expect(dynamicProgramming([-2, 1, -3, 4, -1, 2, 1, -5, 4])).toEqual([ 15 | 4, -1, 2, 1, 16 | ]) 17 | expect(dynamicProgramming([-2, -3, 4, -1, -2, 1, 5, -3])).toEqual([ 18 | 4, -1, -2, 1, 5, 19 | ]) 20 | expect(dynamicProgramming([1, -3, 2, -5, 7, 6, -1, 4, 11, -23])).toEqual([ 21 | 7, 6, -1, 4, 11, 22 | ]) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/brute-force.js: -------------------------------------------------------------------------------- 1 | export default function bruteForce(arr) { 2 | let maxStartIdx = 0 3 | let maxLen = 0 4 | let maxSum = null 5 | 6 | for (let i = 0; i < arr.length; i++) { 7 | let sum = 0 8 | for (let j = 1; j <= arr.length - i; j++) { 9 | sum += arr[i + (j - 1)] 10 | if (!maxSum || sum > maxSum) { 11 | maxSum = sum 12 | maxStartIdx = i 13 | maxLen = j 14 | } 15 | } 16 | } 17 | 18 | return arr.slice(maxStartIdx, maxStartIdx + maxLen) 19 | } 20 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/divide-conquer.js: -------------------------------------------------------------------------------- 1 | export default function divideConquer(arr) { 2 | const dc = (idx, pick) => { 3 | if (idx >= arr.length) { 4 | return pick ? 0 : -Infinity 5 | } 6 | 7 | return Math.max( 8 | // Вариант 1: берем текущий элемент и переходим к следующему 9 | arr[idx] + dc(idx + 1, true), 10 | // Вариант 2: не берем текущий элемент 11 | pick ? 0 : dc(idx + 1, false), 12 | ) 13 | } 14 | 15 | return dc(0, false) 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/sets/maximum-subarray/dynamic-programming.js: -------------------------------------------------------------------------------- 1 | export default function dynamicProgramming(arr) { 2 | let maxSum = -Infinity 3 | let sum = 0 4 | 5 | let maxStartIdx = 0 6 | let maxEndIdx = arr.length - 1 7 | let currentStartIdx = 0 8 | 9 | arr.forEach((item, idx) => { 10 | sum += item 11 | 12 | if (maxSum < sum) { 13 | maxSum = sum 14 | maxStartIdx = currentStartIdx 15 | maxEndIdx = idx 16 | } 17 | 18 | if (sum < 0) { 19 | sum = 0 20 | currentStartIdx = idx + 1 21 | } 22 | }) 23 | 24 | return arr.slice(maxStartIdx, maxEndIdx + 1) 25 | } 26 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/__tests__/with-repetitions.test.js: -------------------------------------------------------------------------------- 1 | import withRepetitions from '../with-repetitions' 2 | 3 | describe('withRepetitions', () => { 4 | it('должен переставлять элементы множеств с повторениями', () => { 5 | const permutations1 = withRepetitions(['A']) 6 | expect(permutations1).toEqual([['A']]) 7 | 8 | const permutations2 = withRepetitions(['A', 'B']) 9 | expect(permutations2).toEqual([ 10 | ['A', 'A'], 11 | ['A', 'B'], 12 | ['B', 'A'], 13 | ['B', 'B'], 14 | ]) 15 | 16 | const permutations3 = withRepetitions(['A', 'B', 'C']) 17 | expect(permutations3).toEqual([ 18 | ['A', 'A', 'A'], 19 | ['A', 'A', 'B'], 20 | ['A', 'A', 'C'], 21 | ['A', 'B', 'A'], 22 | ['A', 'B', 'B'], 23 | ['A', 'B', 'C'], 24 | ['A', 'C', 'A'], 25 | ['A', 'C', 'B'], 26 | ['A', 'C', 'C'], 27 | ['B', 'A', 'A'], 28 | ['B', 'A', 'B'], 29 | ['B', 'A', 'C'], 30 | ['B', 'B', 'A'], 31 | ['B', 'B', 'B'], 32 | ['B', 'B', 'C'], 33 | ['B', 'C', 'A'], 34 | ['B', 'C', 'B'], 35 | ['B', 'C', 'C'], 36 | ['C', 'A', 'A'], 37 | ['C', 'A', 'B'], 38 | ['C', 'A', 'C'], 39 | ['C', 'B', 'A'], 40 | ['C', 'B', 'B'], 41 | ['C', 'B', 'C'], 42 | ['C', 'C', 'A'], 43 | ['C', 'C', 'B'], 44 | ['C', 'C', 'C'], 45 | ]) 46 | 47 | const permutations4 = withRepetitions(['A', 'B', 'C', 'D']) 48 | expect(permutations4.length).toBe(4 * 4 * 4 * 4) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/__tests__/without-repetitions.test.js: -------------------------------------------------------------------------------- 1 | import withoutRepetitions from '../without-repetitions' 2 | import factorial from '../../../math/factorial' 3 | 4 | describe('withoutRepetitions', () => { 5 | it('должен переставлять элементы множеств без повторений', () => { 6 | const permutations1 = withoutRepetitions(['A']) 7 | expect(permutations1).toEqual([['A']]) 8 | 9 | const permutations2 = withoutRepetitions(['A', 'B']) 10 | expect(permutations2.length).toBe(2) 11 | expect(permutations2).toEqual([ 12 | ['A', 'B'], 13 | ['B', 'A'], 14 | ]) 15 | 16 | const permutations6 = withoutRepetitions(['A', 'A']) 17 | expect(permutations6.length).toBe(2) 18 | expect(permutations6).toEqual([ 19 | ['A', 'A'], 20 | ['A', 'A'], 21 | ]) 22 | 23 | const permutations3 = withoutRepetitions(['A', 'B', 'C']) 24 | expect(permutations3.length).toBe(factorial(3)) 25 | expect(permutations3).toEqual([ 26 | ['A', 'B', 'C'], 27 | ['B', 'A', 'C'], 28 | ['B', 'C', 'A'], 29 | ['A', 'C', 'B'], 30 | ['C', 'A', 'B'], 31 | ['C', 'B', 'A'], 32 | ]) 33 | 34 | const permutations4 = withoutRepetitions(['A', 'B', 'C', 'D']) 35 | expect(permutations4.length).toBe(factorial(4)) 36 | expect(permutations4).toEqual([ 37 | ['A', 'B', 'C', 'D'], 38 | ['B', 'A', 'C', 'D'], 39 | ['B', 'C', 'A', 'D'], 40 | ['B', 'C', 'D', 'A'], 41 | ['A', 'C', 'B', 'D'], 42 | ['C', 'A', 'B', 'D'], 43 | ['C', 'B', 'A', 'D'], 44 | ['C', 'B', 'D', 'A'], 45 | ['A', 'C', 'D', 'B'], 46 | ['C', 'A', 'D', 'B'], 47 | ['C', 'D', 'A', 'B'], 48 | ['C', 'D', 'B', 'A'], 49 | ['A', 'B', 'D', 'C'], 50 | ['B', 'A', 'D', 'C'], 51 | ['B', 'D', 'A', 'C'], 52 | ['B', 'D', 'C', 'A'], 53 | ['A', 'D', 'B', 'C'], 54 | ['D', 'A', 'B', 'C'], 55 | ['D', 'B', 'A', 'C'], 56 | ['D', 'B', 'C', 'A'], 57 | ['A', 'D', 'C', 'B'], 58 | ['D', 'A', 'C', 'B'], 59 | ['D', 'C', 'A', 'B'], 60 | ['D', 'C', 'B', 'A'], 61 | ]) 62 | 63 | const permutations5 = withoutRepetitions(['A', 'B', 'C', 'D', 'E', 'F']) 64 | expect(permutations5.length).toBe(factorial(6)) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/with-repetitions.js: -------------------------------------------------------------------------------- 1 | export default function withRepetitions(set, length = set.length) { 2 | if (length === 1) { 3 | return set.map((i) => [i]) 4 | } 5 | 6 | const result = [] 7 | 8 | const subset = withRepetitions(set, length - 1) 9 | 10 | for (const i of set) { 11 | for (const j of subset) { 12 | result.push([i, ...j]) 13 | } 14 | } 15 | 16 | return result 17 | } 18 | -------------------------------------------------------------------------------- /src/algorithms/sets/permutations/without-repetitions.js: -------------------------------------------------------------------------------- 1 | export default function withoutRepetitions(set) { 2 | if (set.length === 1) { 3 | return [set] 4 | } 5 | 6 | const result = [] 7 | 8 | const subset = withoutRepetitions(set.slice(1)) 9 | const first = set[0] 10 | 11 | for (let i = 0; i < subset.length; i++) { 12 | const smaller = subset[i] 13 | for (let j = 0; j < smaller.length + 1; j++) { 14 | const permutation = [...smaller.slice(0, j), first, ...smaller.slice(j)] 15 | result.push(permutation) 16 | } 17 | } 18 | 19 | return result 20 | } 21 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/__tests__/backtracking.test.js: -------------------------------------------------------------------------------- 1 | import backtracking from '../backtracking' 2 | 3 | describe('backtracking', () => { 4 | it('должен вычислить множества всех подмножеств с помощью рекурсивного подхода', () => { 5 | expect(backtracking([1])).toEqual([[], [1]]) 6 | 7 | expect(backtracking([1, 2, 3])).toEqual([ 8 | [], 9 | [1], 10 | [1, 2], 11 | [1, 2, 3], 12 | [1, 3], 13 | [2], 14 | [2, 3], 15 | [3], 16 | ]) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/__tests__/bitwise.test.js: -------------------------------------------------------------------------------- 1 | import bitwise from '../bitwise' 2 | 3 | describe('bitwise', () => { 4 | it('должен вычислить множества всех подмножеств с помощью бинарного подхода', () => { 5 | expect(bitwise([1])).toEqual([[], [1]]) 6 | 7 | expect(bitwise([1, 2, 3])).toEqual([ 8 | [], 9 | [1], 10 | [2], 11 | [1, 2], 12 | [3], 13 | [1, 3], 14 | [2, 3], 15 | [1, 2, 3], 16 | ]) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/__tests__/cascading.test.js: -------------------------------------------------------------------------------- 1 | import cascading from '../cascading' 2 | 3 | describe('cascading', () => { 4 | it('должен вычислить множества всех подмножеств с помощью каскадного подхода', () => { 5 | expect(cascading([1])).toEqual([[], [1]]) 6 | 7 | expect(cascading([1, 2])).toEqual([[], [1], [2], [1, 2]]) 8 | 9 | expect(cascading([1, 2, 3])).toEqual([ 10 | [], 11 | [1], 12 | [2], 13 | [1, 2], 14 | [3], 15 | [1, 3], 16 | [2, 3], 17 | [1, 2, 3], 18 | ]) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/backtracking.js: -------------------------------------------------------------------------------- 1 | export default function backtracking( 2 | set, 3 | allSubsets = [[]], 4 | currentSubset = [], 5 | start = 0, 6 | ) { 7 | // Перебираем элементы множества, которые могут быть добавлены в подмножество 8 | // без дублирования (это обеспечивается значением `start`) 9 | for (let i = start; i < set.length; i++) { 10 | // Добавляем текущий элемент в подмножество 11 | currentSubset.push(set[i]) 12 | 13 | // Текущее подмножество является валидным, запоминаем его. 14 | // `structuredClone()` создает копию `currentSubset`. 15 | // Это необходимо, поскольку `currentSubset` будет модифицирован 16 | // в дальнейших рекурсивных вызовах 17 | allSubsets.push(structuredClone(currentSubset)) 18 | 19 | // Генерируем другие подмножества для текущего подмножества. 20 | // В качестве значения `start` передаем `i + 1` во избежание дублирования 21 | backtracking(set, allSubsets, currentSubset, i + 1) 22 | 23 | // Удаляем последний элемент и берем следующий 24 | currentSubset.pop() 25 | } 26 | 27 | return allSubsets 28 | } 29 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/bitwise.js: -------------------------------------------------------------------------------- 1 | export default function bitwise(set) { 2 | const subsets = [] 3 | 4 | // Количество подмножеств - `2^n`, где `n` - количество элементов в `set`. 5 | // Это обусловлено тем, что для каждого элемента `set` мы будем решать, 6 | // включать его в подмножество или нет (2 варианта на каждый элемент) 7 | const numberOfCombinations = 2 ** set.length 8 | 9 | for (let i = 0; i < numberOfCombinations; i++) { 10 | const subset = [] 11 | 12 | for (let j = 0; j < set.length; j++) { 13 | // Решаем, включать текущий элемента в подмножество или нет 14 | if (i & (1 << j)) { 15 | subset.push(set[j]) 16 | } 17 | } 18 | 19 | subsets.push(subset) 20 | } 21 | 22 | return subsets 23 | } 24 | -------------------------------------------------------------------------------- /src/algorithms/sets/power-set/cascading.js: -------------------------------------------------------------------------------- 1 | export default function cascading(set) { 2 | // Начинаем с пустого множества 3 | const sets = [[]] 4 | 5 | for (let i = 0; i < set.length; i++) { 6 | // Без этого мы получим бесконечный цикл, 7 | // поскольку длина `sets` будет увеличиваться на каждой итерации 8 | const len = sets.length 9 | for (let j = 0; j < len; j++) { 10 | const _set = [...sets[j], set[i]] 11 | sets.push(_set) 12 | } 13 | } 14 | 15 | return sets 16 | } 17 | -------------------------------------------------------------------------------- /src/algorithms/sets/shortest-common-supersequence.js: -------------------------------------------------------------------------------- 1 | import lcsFn from './longest-common-subsequence/matrix' 2 | 3 | export default function scs(set1, set2) { 4 | // Находим НОП двух множеств 5 | const lcs = lcsFn(set1, set2) 6 | 7 | // Если НОП пустая, то КОС будет просто 8 | // объединением множеств 9 | if (lcs.length === 1 && lcs[0] === '') { 10 | return set1.concat(set2) 11 | } 12 | 13 | // Добавляем элементы множеств в порядке перед/внутрь/после НОП 14 | let result = [] 15 | 16 | let idx1 = 0 17 | let idx2 = 0 18 | let idx = 0 19 | let onHold1 = false 20 | let onHold2 = false 21 | 22 | while (idx < lcs.length) { 23 | // Добавляем элементы `set1` в правильном порядке 24 | if (idx1 < set1.length) { 25 | if (!onHold1 && set1[idx1] !== lcs[idx]) { 26 | result.push(set1[idx1]) 27 | idx1++ 28 | } else { 29 | onHold1 = true 30 | } 31 | } 32 | 33 | // Добавляем элементы `set2` в правильном порядке 34 | if (idx2 < set2.length) { 35 | if (!onHold2 && set2[idx2] !== lcs[idx]) { 36 | result.push(set2[idx2]) 37 | idx2++ 38 | } else { 39 | onHold2 = true 40 | } 41 | } 42 | 43 | // Добавляем НОП в правильном порядке 44 | if (onHold1 && onHold2) { 45 | result.push(lcs[idx]) 46 | idx++ 47 | idx1++ 48 | idx2++ 49 | onHold1 = false 50 | onHold2 = false 51 | } 52 | } 53 | 54 | // Добавляем остатки `set1` 55 | if (idx1 < set1.length) { 56 | result = result.concat(set1.slice(idx1)) 57 | } 58 | 59 | // Добавляем остатки `set2` 60 | if (idx2 < set2.length) { 61 | result = result.concat(set2.slice(idx2)) 62 | } 63 | 64 | return result 65 | } 66 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/bubble-sort.test.js: -------------------------------------------------------------------------------- 1 | import BubbleSort from '../bubble-sort' 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../sort-tester' 9 | 10 | // Константы временной сложности 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('должен отсортировать массив', () => { 18 | SortTester.testSort(BubbleSort) 19 | }) 20 | 21 | it('должен отсортировать массив с помощью кастомной функции сравнения элементов', () => { 22 | SortTester.testSortWithCustomComparator(BubbleSort) 23 | }) 24 | 25 | it('должен выполнить стабильную сортировку', () => { 26 | SortTester.testSortStability(BubbleSort) 27 | }) 28 | 29 | it('должен отсортировать отрицательные числа', () => { 30 | SortTester.testNegativeNumbersSort(BubbleSort) 31 | }) 32 | 33 | it('должен посетить элементы массива одинаковых элементов указанное количество раз', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | BubbleSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ) 39 | }) 40 | 41 | it('должен посетить элементы отсортированного массива указанное количество раз', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | BubbleSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ) 47 | }) 48 | 49 | it('должен посетить элементы неотсортированного массив указанное количество раз', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | BubbleSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ) 55 | }) 56 | 57 | it('должен посетить элементы инвертированного отсортированного массива указанное количество раз', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | BubbleSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/bucket-sort.test.js: -------------------------------------------------------------------------------- 1 | import BucketSort from '../bucket-sort' 2 | import { equalArr, notSortedArr, reverseArr, sortedArr } from '../sort-tester' 3 | 4 | describe('BucketSort', () => { 5 | it('должен отсортировать массивы чисел с разным количеством блоков', () => { 6 | expect(BucketSort(notSortedArr, 4)).toEqual(sortedArr) 7 | expect(BucketSort(equalArr, 4)).toEqual(equalArr) 8 | expect(BucketSort(reverseArr, 4)).toEqual(sortedArr) 9 | expect(BucketSort(sortedArr, 4)).toEqual(sortedArr) 10 | 11 | expect(BucketSort(notSortedArr, 10)).toEqual(sortedArr) 12 | expect(BucketSort(equalArr, 10)).toEqual(equalArr) 13 | expect(BucketSort(reverseArr, 10)).toEqual(sortedArr) 14 | expect(BucketSort(sortedArr, 10)).toEqual(sortedArr) 15 | 16 | expect(BucketSort(notSortedArr, 50)).toEqual(sortedArr) 17 | expect(BucketSort(equalArr, 50)).toEqual(equalArr) 18 | expect(BucketSort(reverseArr, 50)).toEqual(sortedArr) 19 | expect(BucketSort(sortedArr, 50)).toEqual(sortedArr) 20 | }) 21 | 22 | it('должен отсортировать массивы чисел с одной группой', () => { 23 | expect(BucketSort(notSortedArr)).toEqual(sortedArr) 24 | expect(BucketSort(equalArr)).toEqual(equalArr) 25 | expect(BucketSort(reverseArr)).toEqual(sortedArr) 26 | expect(BucketSort(sortedArr)).toEqual(sortedArr) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/heap-sort.test.js: -------------------------------------------------------------------------------- 1 | import HeapSort from '../heap-sort' 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../sort-tester' 9 | 10 | // Константы временной сложности. 11 | // Обратите внимание, что мы не учитываем время реструктуризации кучи, 12 | // поэтому в реальности числа будут бОльшими 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('должен отсортировать массив', () => { 20 | SortTester.testSort(HeapSort) 21 | }) 22 | 23 | it('должен отсортировать массив с помощью кастомной функции сравнения', () => { 24 | SortTester.testSortWithCustomComparator(HeapSort) 25 | }) 26 | 27 | it('должен отсортировать отрицательные числа', () => { 28 | SortTester.testNegativeNumbersSort(HeapSort) 29 | }) 30 | 31 | it('должен посетить массив одинаковых элементов указанное количество раз', () => { 32 | SortTester.testAlgorithmTimeComplexity( 33 | HeapSort, 34 | equalArr, 35 | EQUAL_ARRAY_VISITING_COUNT, 36 | ) 37 | }) 38 | 39 | it('должен посетить отсортированный массив указанное количество раз', () => { 40 | SortTester.testAlgorithmTimeComplexity( 41 | HeapSort, 42 | sortedArr, 43 | SORTED_ARRAY_VISITING_COUNT, 44 | ) 45 | }) 46 | 47 | it('должен посетить неотсортированный массив указанное количество раз', () => { 48 | SortTester.testAlgorithmTimeComplexity( 49 | HeapSort, 50 | notSortedArr, 51 | NOT_SORTED_ARRAY_VISITING_COUNT, 52 | ) 53 | }) 54 | 55 | it('должен посетить инвертированный отсортированный массив указанное количество раз', () => { 56 | SortTester.testAlgorithmTimeComplexity( 57 | HeapSort, 58 | reverseArr, 59 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 60 | ) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/insertion-sort.test.js: -------------------------------------------------------------------------------- 1 | import InsertionSort from '../insertion-sort' 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../sort-tester' 9 | 10 | // Константы временной сложности 11 | const SORTED_ARRAY_VISITING_COUNT = 19 12 | const NOT_SORTED_ARRAY_VISITING_COUNT = 100 13 | const REVERSE_SORTED_ARRAY_VISITING_COUNT = 209 14 | const EQUAL_ARRAY_VISITING_COUNT = 19 15 | 16 | describe('InsertionSort', () => { 17 | it('должен отсортировать массив', () => { 18 | SortTester.testSort(InsertionSort) 19 | }) 20 | 21 | it('должен отсортировать массив с помощью кастомной функции сравнения', () => { 22 | SortTester.testSortWithCustomComparator(InsertionSort) 23 | }) 24 | 25 | it('должен выполнить стабильную сортировку', () => { 26 | SortTester.testSortStability(InsertionSort) 27 | }) 28 | 29 | it('должен отсортировать отрицательные числа', () => { 30 | SortTester.testNegativeNumbersSort(InsertionSort) 31 | }) 32 | 33 | it('должен посетить элементы массива одинаковых элементов указанное количество раз', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | InsertionSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ) 39 | }) 40 | 41 | it('должен посетить элементы отсортированного массива указанное количество раз', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | InsertionSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ) 47 | }) 48 | 49 | it('должен посетить элементы неотсортированного массива указанное количество раз', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | InsertionSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ) 55 | }) 56 | 57 | it('должен посетить элементы инвертированного отсортированного массива указанное количество раз', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | InsertionSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/merge-sort.test.js: -------------------------------------------------------------------------------- 1 | import MergeSort from '../merge-sort' 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../sort-tester' 9 | 10 | // Константы временной сложности 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('должен отсортировать массив', () => { 18 | SortTester.testSort(MergeSort) 19 | }) 20 | 21 | it('должен отсортировать массив с помощью кастомной функции сравнения', () => { 22 | SortTester.testSortWithCustomComparator(MergeSort) 23 | }) 24 | 25 | it('должен выполнить стабильную сортировку', () => { 26 | SortTester.testSortStability(MergeSort) 27 | }) 28 | 29 | it('должен отсортировать отрицательные числа', () => { 30 | SortTester.testNegativeNumbersSort(MergeSort) 31 | }) 32 | 33 | it('должен посетить массив одинаковых элементов указанное количество раз', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | MergeSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ) 39 | }) 40 | 41 | it('должен посетить отсортированный массив указанное количество раз', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | MergeSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ) 47 | }) 48 | 49 | it('должен посетить неотсортированный массив указанное количество раз', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | MergeSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ) 55 | }) 56 | 57 | it('должен посетить инвертированный отсортированный массив указанное количество раз', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | MergeSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/quick-sort.test.js: -------------------------------------------------------------------------------- 1 | import QuickSort from '../quick-sort' 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../sort-tester' 9 | 10 | // Константы временной сложности 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('должен отсортировать массив', () => { 18 | SortTester.testSort(QuickSort) 19 | }) 20 | 21 | it('должен отсортировать массив с помощью кастомной функции сравнения', () => { 22 | SortTester.testSortWithCustomComparator(QuickSort) 23 | }) 24 | 25 | it('должен выполнить стабильную сортировку', () => { 26 | SortTester.testSortStability(QuickSort) 27 | }) 28 | 29 | it('должен отсортировать отрицательные числа', () => { 30 | SortTester.testNegativeNumbersSort(QuickSort) 31 | }) 32 | 33 | it('должен посетить массив одинаковых элементов указанное количество раз', () => { 34 | SortTester.testAlgorithmTimeComplexity( 35 | QuickSort, 36 | equalArr, 37 | EQUAL_ARRAY_VISITING_COUNT, 38 | ) 39 | }) 40 | 41 | it('должен посетить отсортированный массив указанное количество раз', () => { 42 | SortTester.testAlgorithmTimeComplexity( 43 | QuickSort, 44 | sortedArr, 45 | SORTED_ARRAY_VISITING_COUNT, 46 | ) 47 | }) 48 | 49 | it('должен посетить неотсортированный массив указанное количество раз', () => { 50 | SortTester.testAlgorithmTimeComplexity( 51 | QuickSort, 52 | notSortedArr, 53 | NOT_SORTED_ARRAY_VISITING_COUNT, 54 | ) 55 | }) 56 | 57 | it('должен посетить инвертированный отсортированный массив указанное количество раз', () => { 58 | SortTester.testAlgorithmTimeComplexity( 59 | QuickSort, 60 | reverseArr, 61 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 62 | ) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/radix-sort.test.js: -------------------------------------------------------------------------------- 1 | import RadixSort from '../radix-sort' 2 | import { SortTester } from '../sort-tester' 3 | 4 | // Константы временной сложности 5 | const ARRAY_OF_STRINGS_VISIT_COUNT = 24 6 | const ARRAY_OF_INTEGERS_VISIT_COUNT = 77 7 | 8 | describe('RadixSort', () => { 9 | it('должен отсортировать массивы', () => { 10 | SortTester.testSort(RadixSort) 11 | }) 12 | 13 | it('должен посетить массив строк `n (количество строк) x m (длина самой длинной строки)` раз', () => { 14 | SortTester.testAlgorithmTimeComplexity( 15 | RadixSort, 16 | ['zzz', 'bb', 'a', 'rr', 'rrb', 'rrba'], 17 | ARRAY_OF_STRINGS_VISIT_COUNT, 18 | ) 19 | }) 20 | 21 | it('должен посетить массив целых чисел `n (количество чисел) x m (длина самого длинного числа)` раз', () => { 22 | SortTester.testAlgorithmTimeComplexity( 23 | RadixSort, 24 | [3, 1, 75, 32, 884, 523, 4343456, 232, 123, 656, 343], 25 | ARRAY_OF_INTEGERS_VISIT_COUNT, 26 | ) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/selection-sort.test.js: -------------------------------------------------------------------------------- 1 | import SelectionSort from '../selection-sort' 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../sort-tester' 9 | 10 | // Константы временной сложности 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('должен отсортировать массив', () => { 18 | SortTester.testSort(SelectionSort) 19 | }) 20 | 21 | it('должен отсортировать массив с помощью кастомной функции сравнения', () => { 22 | SortTester.testSortWithCustomComparator(SelectionSort) 23 | }) 24 | 25 | it('должен отсортировать отрицательные числа', () => { 26 | SortTester.testNegativeNumbersSort(SelectionSort) 27 | }) 28 | 29 | it('должен посетить элементы массива одинаковых элементов указанное количество раз', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | SelectionSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ) 35 | }) 36 | 37 | it('должен посетить элементы отсортированного массива указанное количество раз', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | SelectionSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ) 43 | }) 44 | 45 | it('должен посетить элементы неотсортированного массива указанное количество раз', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | SelectionSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ) 51 | }) 52 | 53 | it('должен посетить элементы инвертированного отсортированного массива указанное количество раз', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | SelectionSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /src/algorithms/sorting/__tests__/shell-sort.test.js: -------------------------------------------------------------------------------- 1 | import ShellSort from '../shell-sort' 2 | import { 3 | equalArr, 4 | notSortedArr, 5 | reverseArr, 6 | sortedArr, 7 | SortTester, 8 | } from '../sort-tester' 9 | 10 | // Константы временной сложности 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('должен отсортировать массив', () => { 18 | SortTester.testSort(ShellSort) 19 | }) 20 | 21 | it('должен отсортировать массив с помощью кастомной функции сравнения', () => { 22 | SortTester.testSortWithCustomComparator(ShellSort) 23 | }) 24 | 25 | it('должен отсортировать отрицательные числа', () => { 26 | SortTester.testNegativeNumbersSort(ShellSort) 27 | }) 28 | 29 | it('должен посетить массив одинаковых элементов указанное количество раз', () => { 30 | SortTester.testAlgorithmTimeComplexity( 31 | ShellSort, 32 | equalArr, 33 | EQUAL_ARRAY_VISITING_COUNT, 34 | ) 35 | }) 36 | 37 | it('должен посетить отсортированный массив указанное количество раз', () => { 38 | SortTester.testAlgorithmTimeComplexity( 39 | ShellSort, 40 | sortedArr, 41 | SORTED_ARRAY_VISITING_COUNT, 42 | ) 43 | }) 44 | 45 | it('должен посетить неотсортированный массив указанное количество раз', () => { 46 | SortTester.testAlgorithmTimeComplexity( 47 | ShellSort, 48 | notSortedArr, 49 | NOT_SORTED_ARRAY_VISITING_COUNT, 50 | ) 51 | }) 52 | 53 | it('должен посетить инвертированный отсортированный массив указанное количество раз', () => { 54 | SortTester.testAlgorithmTimeComplexity( 55 | ShellSort, 56 | reverseArr, 57 | REVERSE_SORTED_ARRAY_VISITING_COUNT, 58 | ) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /src/algorithms/sorting/bubble-sort.js: -------------------------------------------------------------------------------- 1 | import Sort from './sort' 2 | 3 | export default class BubbleSort extends Sort { 4 | sort(arr) { 5 | // Индикатор перестановки элементов массива 6 | let swapped = false 7 | // Копируем оригинальный массив во избежание его модификации 8 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/structuredClone 9 | const _arr = structuredClone(arr) 10 | 11 | // Перебираем все элементы массива, начиная со второго 12 | for (let i = 1; i < _arr.length; i++) { 13 | swapped = false 14 | 15 | this.callbacks.visitingCallback(_arr[i]) 16 | 17 | // Обратите внимание, что здесь мы двигаемся до `i` 18 | for (let j = 0; j < _arr.length - i; j++) { 19 | this.callbacks.visitingCallback(_arr[j]) 20 | 21 | // Меняем элементы местами, если они расположены в неправильном порядке 22 | if (this.comparator.lessThan(_arr[j + 1], _arr[j])) { 23 | ;[_arr[j], _arr[j + 1]] = [_arr[j + 1], _arr[j]] 24 | 25 | // Обновляем индикатор 26 | swapped = true 27 | } 28 | } 29 | 30 | // Это означает, что массив отсортирован 31 | if (!swapped) { 32 | return _arr 33 | } 34 | } 35 | 36 | return _arr 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/algorithms/sorting/bucket-sort.js: -------------------------------------------------------------------------------- 1 | import RadixSort from './radix-sort' 2 | 3 | const sorter = new RadixSort() 4 | 5 | export default function bucketSort(arr, bucketSize = 1) { 6 | // Создаем блоки 7 | const buckets = new Array(bucketSize).fill().map(() => []) 8 | 9 | // Находим минимальное значение 10 | const minValue = Math.min(...arr) 11 | // Настрой максимальное значение 12 | const maxValue = Math.max(...arr) 13 | 14 | // Определяем размер блока 15 | const _bucketSize = Math.ceil(Math.max(1, (maxValue - minValue) / bucketSize)) 16 | 17 | // Распределяем элементы исходного массива по группам 18 | for (const item of arr) { 19 | const index = Math.floor((item - minValue) / _bucketSize) 20 | 21 | // Крайний случай для максимального значения 22 | if (index === bucketSize) { 23 | buckets[bucketSize - 1].push(item) 24 | } else { 25 | buckets[index].push(item) 26 | } 27 | } 28 | 29 | // Сортируем блоки 30 | for (let i = 0; i < buckets.length; i += 1) { 31 | // Используем поразрядную сортировку. Это может дать среднюю 32 | // временную сложность `O(n + k)` для сортировки одного блока 33 | // (где `k` - количество цифр самого длинного числа) 34 | buckets[i] = sorter.sort(buckets[i]) 35 | } 36 | 37 | // Объединяем отсортированные блоки в один массив 38 | const sortedArr = buckets.flat() 39 | 40 | return sortedArr 41 | } 42 | -------------------------------------------------------------------------------- /src/algorithms/sorting/counting-sort.js: -------------------------------------------------------------------------------- 1 | import Sort from './sort' 2 | 3 | export default class CountingSort extends Sort { 4 | sort(arr, smallestItem, biggestItem) { 5 | // Инициализируем наименьшее и наибольшее числа 6 | // для построения массива сегментов (buckets) позже 7 | let _smallestItem = smallestItem || 0 8 | let _biggestItem = biggestItem || 0 9 | 10 | if (!smallestItem || !biggestItem) { 11 | arr.forEach((item) => { 12 | this.callbacks.visitingCallback(item) 13 | 14 | // Определяем наибольший элемент 15 | if (this.comparator.greaterThan(item, _biggestItem)) { 16 | _biggestItem = item 17 | } 18 | 19 | // Определяем наименьший элемент 20 | if (this.comparator.lessThan(item, _smallestItem)) { 21 | _smallestItem = item 22 | } 23 | }) 24 | } 25 | 26 | // Инициализируем массив сегментов, который будет содержать 27 | // количество вхождений (частоту) элементов `arr` 28 | const buckets = new Array(_biggestItem - _smallestItem + 1).fill(0) 29 | arr.forEach((item) => { 30 | this.callbacks.visitingCallback(item) 31 | 32 | buckets[item - _smallestItem]++ 33 | }) 34 | 35 | // Добавляем предыдущие частоты к текущей для каждого числа в сегменте, 36 | // чтобы определить, сколько чисел меньше текущего должно стоять 37 | // слева от него 38 | for (let i = 1; i < buckets.length; i++) { 39 | buckets[i] += buckets[i - 1] 40 | } 41 | 42 | // Сдвигаем частоты вправо, чтобы они показывали правильные числа. 43 | // Если мы этого не сделаем, то `buckets[5]`, например, покажет, сколько 44 | // элементов, меньших 5, нужно поместить слева от 5 в отсортированном массиве, 45 | // ВКЛЮЧАЯ 5. После сдвига 5 будет исключено 46 | buckets.pop() 47 | buckets.unshift(0) 48 | 49 | // Формируем отсортированный массив 50 | const sortedArr = new Array(arr.length).fill(null) 51 | arr.forEach((item) => { 52 | this.callbacks.visitingCallback(item) 53 | 54 | // Получаем позицию элемента в отсортированном массиве 55 | const sortedPosition = buckets[item - _smallestItem] 56 | // Добавляем элемент на правильную позицию в отсортированном массиве 57 | sortedArr[sortedPosition] = item 58 | // Увеличиваем позицию текущего элемента в сегменте для будущих правильных размещений 59 | buckets[item - _smallestItem]++ 60 | }) 61 | 62 | return sortedArr 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/algorithms/sorting/heap-sort.js: -------------------------------------------------------------------------------- 1 | import Sort from './sort' 2 | import MinHeap from '../../data-structures/heap/min-heap' 3 | 4 | export default class HeapSort extends Sort { 5 | sort(arr) { 6 | const _arr = [] 7 | // Создаем минимальную кучу 8 | const minHeap = new MinHeap(this.callbacks.compareCallback) 9 | 10 | // Добавляем элементы массива в кучу 11 | for (const item of arr) { 12 | this.callbacks.visitingCallback(item) 13 | 14 | minHeap.add(item) 15 | } 16 | 17 | // Теперь у нас есть куча, в которой минимальный элемент всегда находится на самом верху. 18 | // Извлекаем минимальные элементы по одному для формирования отсортированного массива 19 | while (!minHeap.isEmpty()) { 20 | const item = minHeap.poll() 21 | 22 | this.callbacks.visitingCallback(item) 23 | 24 | _arr.push(item) 25 | } 26 | 27 | return _arr 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/algorithms/sorting/insertion-sort.js: -------------------------------------------------------------------------------- 1 | import Sort from './sort' 2 | 3 | export default class InsertionSort extends Sort { 4 | sort(arr) { 5 | // Копируем оригинальный массив во избежание его модификации 6 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/structuredClone 7 | const _arr = structuredClone(arr) 8 | 9 | // Перебираем все элементы массива, начиная со второго 10 | for (let i = 1; i < _arr.length; i++) { 11 | this.callbacks.visitingCallback(_arr[i]) 12 | 13 | let currentIndex = i 14 | 15 | // Цикл выполняется до тех пор, 16 | // пока у нас имеется предыдущий элемент и 17 | // текущий элемент меньше предыдущего 18 | // (левый элемент больше правого) 19 | while ( 20 | _arr[currentIndex - 1] !== undefined && 21 | this.comparator.lessThan(_arr[currentIndex], _arr[currentIndex - 1]) 22 | ) { 23 | this.callbacks.visitingCallback(_arr[currentIndex - 1]) 24 | // Меняем элементы местами 25 | ;[_arr[currentIndex - 1], _arr[currentIndex]] = [ 26 | _arr[currentIndex], 27 | _arr[currentIndex - 1], 28 | ] 29 | 30 | // Двигаемся влево 31 | currentIndex-- 32 | } 33 | } 34 | 35 | return _arr 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/algorithms/sorting/merge-sort.js: -------------------------------------------------------------------------------- 1 | import Sort from './sort' 2 | 3 | export default class MergeSort extends Sort { 4 | // Сортирует массив методом слияния 5 | sort(arr) { 6 | this.callbacks.visitingCallback(null) 7 | 8 | // Если массив пустой или содержит только один элемент, 9 | // возвращаем его, поскольку он уже отсортирован 10 | if (arr.length <= 1) { 11 | return arr 12 | } 13 | 14 | // Делим массив пополам 15 | const middleIndex = Math.floor(arr.length / 2) 16 | const leftArray = arr.slice(0, middleIndex) 17 | const rightArray = arr.slice(middleIndex, arr.length) 18 | 19 | // Сортируем половины 20 | const leftSortedArray = this.sort(leftArray) 21 | const rightSortedArray = this.sort(rightArray) 22 | 23 | // Объединяем отсортированные половины в один массив 24 | return this.mergeSortedArrays(leftSortedArray, rightSortedArray) 25 | } 26 | 27 | // Объединяет два отсортированных массива 28 | mergeSortedArrays(leftArray, rightArray) { 29 | const _arr = [] 30 | 31 | // Используем указатели для исключения элементов, добавленных в массив 32 | let leftIndex = 0 33 | let rightIndex = 0 34 | 35 | while (leftIndex < leftArray.length && rightIndex < rightArray.length) { 36 | let minItem = null 37 | 38 | // Находим минимальный элемент подмассивов 39 | if ( 40 | this.comparator.lessThanOrEqual( 41 | leftArray[leftIndex], 42 | rightArray[rightIndex], 43 | ) 44 | ) { 45 | minItem = leftArray[leftIndex] 46 | // Двигаемся вправо 47 | leftIndex += 1 48 | } else { 49 | minItem = rightArray[rightIndex] 50 | // Двигаемся влево 51 | rightIndex += 1 52 | } 53 | 54 | // Добавляем минимальный элемент в отсортированный массив 55 | _arr.push(minItem) 56 | 57 | this.callbacks.visitingCallback(minItem) 58 | } 59 | 60 | // Добавляем оставшиеся элементы в результирующий массив 61 | return _arr 62 | .concat(leftArray.slice(leftIndex)) 63 | .concat(rightArray.slice(rightIndex)) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/algorithms/sorting/quick-sort.js: -------------------------------------------------------------------------------- 1 | import Sort from './sort' 2 | 3 | export default class QuickSort extends Sort { 4 | sort(arr) { 5 | // Копируем оригинальный массив во избежание его модификации 6 | const _arr = [...arr] 7 | 8 | // Если массив пустой или содержит только один элемент, 9 | // возвращаем его, поскольку он уже отсортирован 10 | if (_arr.length <= 1) { 11 | return _arr 12 | } 13 | 14 | const leftArr = [] 15 | const rightArr = [] 16 | 17 | // Берем первый элемент массива в качестве опорного 18 | const pivot = _arr.shift() 19 | const centerArr = [pivot] 20 | 21 | // Распределяем все элементы массива между левым, центральным и правым подмассивами 22 | while (_arr.length) { 23 | const currentItem = _arr.shift() 24 | 25 | this.callbacks.visitingCallback(currentItem) 26 | 27 | if (this.comparator.equal(currentItem, pivot)) { 28 | centerArr.push(currentItem) 29 | } else if (this.comparator.lessThan(currentItem, pivot)) { 30 | leftArr.push(currentItem) 31 | } else { 32 | rightArr.push(currentItem) 33 | } 34 | } 35 | 36 | // Сортируем левый и правый подмассивы 37 | const leftArraySorted = this.sort(leftArr) 38 | const rightArraySorted = this.sort(rightArr) 39 | 40 | // Объединяем массивы слева направо 41 | return leftArraySorted.concat(centerArr, rightArraySorted) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/algorithms/sorting/selection-sort.js: -------------------------------------------------------------------------------- 1 | import Sort from './sort' 2 | 3 | export default class SelectionSort extends Sort { 4 | sort(arr) { 5 | // Копируем оригинальный массив во избежание его модификации 6 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/structuredClone 7 | const _arr = structuredClone(arr) 8 | 9 | // Перебираем все элементы массива 10 | for (let i = 0; i < _arr.length - 1; i++) { 11 | // Индекс минимального элемента 12 | let minIndex = i 13 | 14 | this.callbacks.visitingCallback(_arr[i]) 15 | 16 | // Обратите внимание, что здесь мы двигаемся от `i + 1` 17 | for (let j = i + 1; j < _arr.length; j++) { 18 | this.callbacks.visitingCallback(_arr[j]) 19 | 20 | if (this.comparator.lessThan(_arr[j], _arr[minIndex])) { 21 | minIndex = j 22 | } 23 | } 24 | 25 | // Если обнаружен новый минимальный элемент, 26 | // меняем на него текущий элемент 27 | if (minIndex !== i) { 28 | ;[_arr[i], _arr[minIndex]] = [_arr[minIndex], _arr[i]] 29 | } 30 | } 31 | 32 | return _arr 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/algorithms/sorting/shell-sort.js: -------------------------------------------------------------------------------- 1 | import Sort from './sort' 2 | 3 | export default class ShellSort extends Sort { 4 | sort(arr) { 5 | // Копируем оригинальный массив во избежание его модификации 6 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/structuredClone 7 | const _arr = structuredClone(arr) 8 | 9 | // Определяем шаг - половина массива 10 | let step = Math.floor(_arr.length / 2) 11 | 12 | // До тех пор, пока шаг больше нуля 13 | while (step > 0) { 14 | // Сравниваем все пары элементов 15 | for (let i = 0; i < _arr.length - step; i++) { 16 | let currentIndex = i 17 | let gapShiftedIndex = i + step 18 | 19 | while (currentIndex >= 0) { 20 | this.callbacks.visitingCallback(_arr[currentIndex]) 21 | 22 | // Сравниваем и меняем элементы местами при необходимости 23 | if ( 24 | this.comparator.lessThan(_arr[gapShiftedIndex], _arr[currentIndex]) 25 | ) { 26 | const tmp = _arr[currentIndex] 27 | 28 | _arr[currentIndex] = _arr[gapShiftedIndex] 29 | 30 | _arr[gapShiftedIndex] = tmp 31 | } 32 | 33 | gapShiftedIndex = currentIndex 34 | currentIndex -= step 35 | } 36 | } 37 | 38 | // Уменьшаем шаг в 2 раза 39 | step = Math.floor(step / 2) 40 | } 41 | 42 | return _arr 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/algorithms/sorting/sort.js: -------------------------------------------------------------------------------- 1 | import Comparator from '../../utils/comparator' 2 | 3 | export default class Sort { 4 | constructor(originalCallbacks) { 5 | // Коллбэки сортировки 6 | this.callbacks = Sort.initSortingCallbacks(originalCallbacks) 7 | // Функция сравнения элементов 8 | this.comparator = new Comparator(this.callbacks.compareCallback) 9 | } 10 | 11 | // Коллбэки сортировки 12 | static initSortingCallbacks(originalCallbacks) { 13 | const callbacks = originalCallbacks || {} 14 | const stubCallback = () => {} 15 | 16 | // Вызывается при сравнении элементов 17 | callbacks.compareCallback = callbacks.compareCallback || undefined 18 | // Вызывается при посещении элемента 19 | callbacks.visitingCallback = callbacks.visitingCallback || stubCallback 20 | 21 | return callbacks 22 | } 23 | 24 | // Метод сортировки реализуется подклассом 25 | sort() { 26 | throw new Error('Метод сортировки не реализован') 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/algorithms/statistics/weighted-random.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Возвращает произвольный элемент на основе его веса. 3 | * Элементы с более высоким весом выбираются чаще (с большей вероятностью). 4 | * 5 | * Например: 6 | * - items = ['banana', 'orange', 'apple'] 7 | * - weights = [0, 0.2, 0.8] 8 | * - weightedRandom(items, weights) в 80% случаев будет возвращать 'apple', 9 | * в 20% случаев - 'orange' и никогда - 'banana' (поскольку вероятность его выбора равна 0%) 10 | * 11 | * @param {any[]} items 12 | * @param {number[]} weights 13 | * @returns {{item: any, index: number}} 14 | */ 15 | export default function weightedRandom(items, weights) { 16 | if (!items.length || !weights.length) { 17 | throw new Error('Элементы/веса не должны быть пустыми') 18 | } 19 | if (items.length !== weights.length) { 20 | throw new Error('Массивы элементов и весов должны иметь одинаковую длину') 21 | } 22 | 23 | // Готовим массив совокупных весов. 24 | // Например: 25 | // - weights = [1, 4, 3] 26 | // - cumulativeWeights = [1, 5, 8] 27 | const cumulativeWeights = [] 28 | for (let i = 0; i < weights.length; i++) { 29 | cumulativeWeights[i] = weights[i] + (cumulativeWeights[i - 1] || 0) 30 | } 31 | 32 | // Получаем произвольное число в диапазоне [0...sum(weights)]. 33 | // Например: 34 | // - weights = [1, 4, 3] 35 | // - maxCumulativeWeight = 8 36 | // - диапазон произвольного числа - [0...8] 37 | const maxCumulativeWeight = cumulativeWeights.at(-1) 38 | const random = Math.random() * maxCumulativeWeight 39 | 40 | // Извлекаем произвольный элемент на основе его веса. 41 | // Элементы с более высоким весом выбираются чаще 42 | const index = cumulativeWeights.findIndex((cumulativeWeight) => { 43 | return cumulativeWeight >= random 44 | }) 45 | const item = items[index] 46 | 47 | return { 48 | item, 49 | index, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/algorithms/strings/__tests__/hamming-distance.test.js: -------------------------------------------------------------------------------- 1 | import hammingDistance from '../hamming-distance' 2 | 3 | describe('hammingDistance', () => { 4 | it('должен выбросить исключение при сравнении строк разной длины', () => { 5 | const compareStringsOfDifferentLength = () => { 6 | hammingDistance('a', 'aa') 7 | } 8 | 9 | expect(compareStringsOfDifferentLength).toThrowError() 10 | }) 11 | 12 | it('должен вычислить расстояния Хэмминга между двумя строками', () => { 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/strings/__tests__/knuth-morris-pratt.test.js: -------------------------------------------------------------------------------- 1 | import knuthMorrisPratt from '../knuth-morris-pratt' 2 | 3 | describe('knuthMorrisPratt', () => { 4 | it('должен найти начальные индексы слов в текстах', () => { 5 | expect(knuthMorrisPratt('', '')).toBe(0) 6 | expect(knuthMorrisPratt('a', '')).toBe(0) 7 | expect(knuthMorrisPratt('a', 'a')).toBe(0) 8 | expect(knuthMorrisPratt('abcbcglx', 'abca')).toBe(-1) 9 | expect(knuthMorrisPratt('abcbcglx', 'bcgl')).toBe(3) 10 | expect(knuthMorrisPratt('abcxabcdabxabcdabcdabcy', 'abcdabcy')).toBe(15) 11 | expect(knuthMorrisPratt('abcxabcdabxabcdabcdabcy', 'abcdabca')).toBe(-1) 12 | expect( 13 | knuthMorrisPratt('abcxabcdabxaabcdabcabcdabcdabcy', 'abcdabca'), 14 | ).toBe(12) 15 | expect( 16 | knuthMorrisPratt('abcxabcdabxaabaabaaaabcdabcdabcy', 'aabaabaaa'), 17 | ).toBe(11) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/algorithms/strings/__tests__/levenshtein-distance.test.js: -------------------------------------------------------------------------------- 1 | import levenshteinDistance from '../levenshtein-distance' 2 | 3 | describe('levenshteinDistance', () => { 4 | it('должен вычислить расстояния Левенштейна между двумя строками', () => { 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 | // Нужно добавить `I` в начало 12 | expect(levenshteinDistance('islander', 'slander')).toBe(1) 13 | 14 | // Нужно заменить `M` на `K`, `T` на `M` и добавить `A` в конец 15 | expect(levenshteinDistance('mart', 'karma')).toBe(3) 16 | 17 | // Нужно заменить `K` на `S`, `E` на `I` и добавить `G` в конец 18 | expect(levenshteinDistance('kitten', 'sitting')).toBe(3) 19 | 20 | // Нужно добавить 4 буквы `FOOT` в начало 21 | expect(levenshteinDistance('ball', 'football')).toBe(4) 22 | 23 | // Нужно удалить 4 буквы `FOOT` в начале 24 | expect(levenshteinDistance('football', 'foot')).toBe(4) 25 | 26 | // Нужно заменить первые 5 букв `INTEN` на `EXECU` 27 | expect(levenshteinDistance('intention', 'execution')).toBe(5) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /src/algorithms/strings/__tests__/longest-common-substring.test.js: -------------------------------------------------------------------------------- 1 | import longestCommonSubstring from '../longest-common-substring' 2 | 3 | describe('longestCommonSubstring', () => { 4 | it('должен найти наибольшие общие подстроки двух строк', () => { 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 | expect(longestCommonSubstring('sea', 'eat')).toBe('ea') 11 | expect(longestCommonSubstring('algorithms', 'rithm')).toBe('rithm') 12 | expect( 13 | longestCommonSubstring( 14 | 'Algorithms and data structures implemented in JavaScript', 15 | 'Here you may find Algorithms and data structures that are implemented in JavaScript', 16 | ), 17 | ).toBe('Algorithms and data structures ') 18 | }) 19 | 20 | it('должен правильно обрабатываться юникод', () => { 21 | expect(longestCommonSubstring('𐌵𐌵**ABC', '𐌵𐌵--ABC')).toBe('ABC') 22 | expect(longestCommonSubstring('𐌵𐌵**A', '𐌵𐌵--A')).toBe('𐌵𐌵') 23 | expect(longestCommonSubstring('A买B时', '买B时GD')).toBe('买B时') 24 | expect( 25 | longestCommonSubstring('After test买时 case', 'another_test买时'), 26 | ).toBe('test买时') 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/algorithms/strings/__tests__/rabin-karp.test.js: -------------------------------------------------------------------------------- 1 | import rabinKarp from '../rabin-karp' 2 | 3 | describe('rabinKarp', () => { 4 | it('должен найти подстроки в строке', () => { 5 | expect(rabinKarp('', '')).toBe(0) 6 | expect(rabinKarp('a', '')).toBe(0) 7 | expect(rabinKarp('a', 'a')).toBe(0) 8 | expect(rabinKarp('ab', 'b')).toBe(1) 9 | expect(rabinKarp('abcbcglx', 'abca')).toBe(-1) 10 | expect(rabinKarp('abcbcglx', 'bcgl')).toBe(3) 11 | expect(rabinKarp('abcxabcdabxabcdabcdabcy', 'abcdabcy')).toBe(15) 12 | expect(rabinKarp('abcxabcdabxabcdabcdabcy', 'abcdabca')).toBe(-1) 13 | expect(rabinKarp('abcxabcdabxaabcdabcabcdabcdabcy', 'abcdabca')).toBe(12) 14 | expect(rabinKarp('abcxabcdabxaabaabaaaabcdabcdabcy', 'aabaabaaa')).toBe(11) 15 | expect(rabinKarp("^ !/'#'pp", " !/'#'pp")).toBe(1) 16 | }) 17 | 18 | it('должен работать с большими текстами', () => { 19 | const text = 20 | 'Lorem Ipsum is simply dummy text of the printing and ' + 21 | "typesetting industry. Lorem Ipsum has been the industry's standard " + 22 | 'dummy text ever since the 1500s, when an unknown printer took a ' + 23 | 'galley of type and scrambled it to make a type specimen book. It ' + 24 | 'has survived not only five centuries, but also the leap into ' + 25 | 'electronic typesetting, remaining essentially unchanged. It was ' + 26 | 'popularised in the 1960s with the release of Letraset sheets ' + 27 | 'containing Lorem Ipsum passages, and more recently with desktop' + 28 | 'publishing software like Aldus PageMaker including versions of Lorem ' + 29 | 'Ipsum.' 30 | 31 | expect(rabinKarp(text, 'Lorem')).toBe(0) 32 | expect(rabinKarp(text, 'versions')).toBe(549) 33 | expect(rabinKarp(text, 'versions of Lorem Ipsum.')).toBe(549) 34 | expect(rabinKarp(text, 'versions of Lorem Ipsum:')).toBe(-1) 35 | expect( 36 | rabinKarp(text, 'Lorem Ipsum passages, and more recently with'), 37 | ).toBe(446) 38 | }) 39 | 40 | it('должен работать с символами UTF', () => { 41 | expect(rabinKarp('a\u{ffff}', '\u{ffff}')).toBe(1) 42 | expect(rabinKarp('\u0000耀\u0000', '耀\u0000')).toBe(1) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /src/algorithms/strings/hamming-distance.js: -------------------------------------------------------------------------------- 1 | export default function hammingDistance(x, y) { 2 | if (x.length !== y.length) { 3 | throw new Error('Строки должны иметь одинаковую длину') 4 | } 5 | 6 | let distance = 0 7 | 8 | for (let i = 0; i < x.length; i++) { 9 | if (x[i] !== y[i]) { 10 | distance++ 11 | } 12 | } 13 | 14 | return distance 15 | } 16 | -------------------------------------------------------------------------------- /src/algorithms/strings/knuth-morris-pratt.js: -------------------------------------------------------------------------------- 1 | function buildPatternTable(word) { 2 | const patternTable = [0] 3 | let prefixIndex = 0 4 | let suffixIndex = 1 5 | 6 | while (suffixIndex < word.length) { 7 | if (word[suffixIndex] === word[prefixIndex]) { 8 | patternTable[suffixIndex] = prefixIndex + 1 9 | prefixIndex += 1 10 | suffixIndex += 1 11 | } else if (prefixIndex === 0) { 12 | patternTable[suffixIndex] = 0 13 | suffixIndex += 1 14 | } else { 15 | prefixIndex = patternTable[prefixIndex - 1] 16 | } 17 | } 18 | 19 | return patternTable 20 | } 21 | 22 | export default function knuthMorrisPratt(text, word) { 23 | if (word.length === 0) return 0 24 | 25 | let textIndex = 0 26 | let wordIndex = 0 27 | 28 | const patternTable = buildPatternTable(word) 29 | 30 | while (textIndex < text.length) { 31 | if (text[textIndex] === word[wordIndex]) { 32 | // Найдено совпадение 33 | if (wordIndex === word.length - 1) { 34 | return textIndex - word.length + 1 35 | } 36 | 37 | textIndex += 1 38 | wordIndex += 1 39 | } else if (wordIndex > 0) { 40 | wordIndex = patternTable[wordIndex - 1] 41 | } else { 42 | textIndex += 1 43 | } 44 | } 45 | 46 | return -1 47 | } 48 | -------------------------------------------------------------------------------- /src/algorithms/strings/levenshtein-distance.js: -------------------------------------------------------------------------------- 1 | export default function levenshteinDistance(a, b) { 2 | // Создаем пустую матрицу редактирования 3 | const matrix = new Array(b.length + 1) 4 | .fill(null) 5 | .map(() => new Array(a.length + 1).fill(null)) 6 | 7 | // Заполняем первую строку матрицы. 8 | // Если это первая строка, тогда мы преобразуем пустую строку в `a`. 9 | // В этом случае количество модификаций равняется размеру подстроки 10 | for (let i = 0; i <= a.length; i++) { 11 | matrix[0][i] = i 12 | } 13 | 14 | // Заполняем первую колонку матрицы. 15 | // Если это первая колонка, тогда мы преобразуем пустую строку в `b`. 16 | // В этом случае количество модификаций равняется размеру подстроки 17 | for (let j = 0; j <= b.length; j++) { 18 | matrix[j][0] = j 19 | } 20 | 21 | // Заполняем матрицу редактирования 22 | for (let j = 1; j <= b.length; j++) { 23 | for (let i = 1; i <= a.length; i++) { 24 | const indicator = b[j - 1] === a[i - 1] ? 0 : 1 25 | matrix[j][i] = Math.min( 26 | matrix[j][i - 1] + 1, // удаление 27 | matrix[j - 1][i] + 1, // вставка 28 | matrix[j - 1][i - 1] + indicator, // замена 29 | ) 30 | } 31 | } 32 | 33 | return matrix[b.length][a.length] 34 | } 35 | -------------------------------------------------------------------------------- /src/algorithms/strings/longest-common-substring.js: -------------------------------------------------------------------------------- 1 | export default function longestCommonSubstring(str1, str2) { 2 | const s1 = [...str1] 3 | const s2 = [...str2] 4 | 5 | const matrix = new Array(s2.length + 1) 6 | .fill(null) 7 | .map(() => new Array(s1.length + 1).fill(null)) 8 | 9 | // Заполняем первую строку и первую колонку `0` 10 | for (let columnIndex = 0; columnIndex <= s1.length; columnIndex++) { 11 | matrix[0][columnIndex] = 0 12 | } 13 | for (let rowIndex = 0; rowIndex <= s2.length; rowIndex++) { 14 | matrix[rowIndex][0] = 0 15 | } 16 | 17 | let longestSubstringLength = 0 18 | let longestSubstringColumn = 0 19 | let longestSubstringRow = 0 20 | 21 | for (let rowIndex = 1; rowIndex <= s2.length; rowIndex++) { 22 | for (let columnIndex = 1; columnIndex <= s1.length; columnIndex++) { 23 | if (s1[columnIndex - 1] === s2[rowIndex - 1]) { 24 | matrix[rowIndex][columnIndex] = 25 | matrix[rowIndex - 1][columnIndex - 1] + 1 26 | } else { 27 | matrix[rowIndex][columnIndex] = 0 28 | } 29 | 30 | // Ищем наибольшую длину 31 | if (matrix[rowIndex][columnIndex] > longestSubstringLength) { 32 | longestSubstringLength = matrix[rowIndex][columnIndex] 33 | longestSubstringColumn = columnIndex 34 | longestSubstringRow = rowIndex 35 | } 36 | } 37 | } 38 | 39 | // Самая длинная подстрока не найдена 40 | if (longestSubstringLength === 0) { 41 | return '' 42 | } 43 | 44 | // Извлекаем самую длинную подстроку из матрицы 45 | // путем конкатенации символов 46 | let longestSubstring = '' 47 | 48 | while (matrix[longestSubstringRow][longestSubstringColumn] > 0) { 49 | longestSubstring = s1[longestSubstringColumn - 1] + longestSubstring 50 | longestSubstringColumn-- 51 | longestSubstringRow-- 52 | } 53 | 54 | return longestSubstring 55 | } 56 | -------------------------------------------------------------------------------- /src/algorithms/strings/rabin-karp.js: -------------------------------------------------------------------------------- 1 | import PolynomialHash from '../cryptography/polynomial-hash' 2 | 3 | export default function rabinKarp(text, word) { 4 | const hasher = new PolynomialHash() 5 | 6 | // Вычисляем хэш слова, который будет использоваться для сравнения с хэшами подстрок 7 | const wordHash = hasher.hash(word) 8 | 9 | let prevFrame = null 10 | let currentFrameHash = null 11 | 12 | // Перебираем все подстроки текста, которые могут совпасть 13 | for (let i = 0; i < text.length - word.length + 1; i++) { 14 | const currentFrame = text.slice(i, i + word.length) 15 | 16 | // Вычисляем хэш текущей подстроки 17 | if (!currentFrameHash) { 18 | currentFrameHash = hasher.hash(currentFrame) 19 | } else { 20 | currentFrameHash = hasher.roll(currentFrameHash, prevFrame, currentFrame) 21 | } 22 | 23 | prevFrame = currentFrame 24 | 25 | // Сравниваем хэш текущей подстроки с искомым паттерном. 26 | // При совпадении хэшей, проверяем равенство подстрок на случай коллизии хэшей 27 | if ( 28 | wordHash === currentFrameHash && 29 | text.slice(i, i + word.length) === word 30 | ) { 31 | return i 32 | } 33 | } 34 | 35 | return -1 36 | } 37 | -------------------------------------------------------------------------------- /src/algorithms/trees/breadth-first-search.js: -------------------------------------------------------------------------------- 1 | import Queue from '../../data-structures/queue' 2 | 3 | // Функция инициализации обработчиков 4 | function initCallbacks(callbacks = {}) { 5 | const initiatedCallbacks = {} 6 | const stubCallback = () => {} 7 | const defaultAllowTraverseCallback = () => true 8 | 9 | // Обработчик определения допустимости обхода 10 | initiatedCallbacks.allowTraverse = 11 | callbacks.allowTraverse || defaultAllowTraverseCallback 12 | // Обработчик вхождения в узел 13 | initiatedCallbacks.enterNode = callbacks.enterNode || stubCallback 14 | // Обработчик выхода из узла 15 | initiatedCallbacks.leaveNode = callbacks.leaveNode || stubCallback 16 | 17 | return initiatedCallbacks 18 | } 19 | 20 | // Функция принимает начальный (корневой) узел и обработчики 21 | export default function breadthFirstSearch(root, callbacks) { 22 | // Инициализируем обработчики 23 | const _callbacks = initCallbacks(callbacks) 24 | // Создаем очередь 25 | const queue = new Queue() 26 | 27 | // Добавляем корневой узел в конец очереди 28 | queue.enqueue(root) 29 | 30 | // Пока в очереди есть элементы 31 | while (!queue.isEmpty()) { 32 | // Берем узел из начала очереди 33 | const node = queue.dequeue() 34 | 35 | // Вызываем обработчик вхождения в узел 36 | _callbacks.enterNode(node) 37 | 38 | // Если имеется левый узел и его обход разрешен 39 | if (node.left && _callbacks.allowTraverse(node, node.left)) { 40 | // Добавляем его в конец очереди 41 | queue.enqueue(node.left) 42 | } 43 | 44 | // Если имеется правый узел и его обход разрешен 45 | if (node.right && _callbacks.allowTraverse(node, node.right)) { 46 | // Добавляем его в конец очереди 47 | queue.enqueue(node.right) 48 | } 49 | 50 | // Вызываем обработчик выхода в узел 51 | _callbacks.leaveNode(node) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/algorithms/trees/depth-first-search.js: -------------------------------------------------------------------------------- 1 | // Функция инициализации обработчиков 2 | function initCallbacks(callbacks = {}) { 3 | const initiatedCallbacks = {} 4 | const stubCallback = () => {} 5 | const defaultAllowTraverseCallback = () => true 6 | 7 | // Обработчик определения допустимости обхода 8 | initiatedCallbacks.allowTraverse = 9 | callbacks.allowTraverse || defaultAllowTraverseCallback 10 | // Обработчик вхождения в узел 11 | initiatedCallbacks.enterNode = callbacks.enterNode || stubCallback 12 | // Обработчик выхода из узла 13 | initiatedCallbacks.leaveNode = callbacks.leaveNode || stubCallback 14 | 15 | return initiatedCallbacks 16 | } 17 | 18 | // Функция принимает узел и обработчики 19 | function depthFirstSearchRecursive(node, callbacks) { 20 | // Вызываем обработчик вхождения в узел 21 | callbacks.enterNode(node) 22 | 23 | // Если имеется левый узел и его обход разрешен 24 | if (node.left && callbacks.allowTraverse(node, node.left)) { 25 | // Обходим левое поддерево 26 | depthFirstSearchRecursive(node.left, callbacks) 27 | } 28 | 29 | // Если имеется правый узел и его обход разрешен 30 | if (node.right && callbacks.allowTraverse(node, node.right)) { 31 | // Обходим правое поддерево 32 | depthFirstSearchRecursive(node.right, callbacks) 33 | } 34 | 35 | // Вызываем обработчик выхода из узла 36 | callbacks.leaveNode(node) 37 | } 38 | 39 | // Функция принимает начальный (корневой) узел и обработчики 40 | export default function depthFirstSearch(root, callbacks) { 41 | // Инициализируем обработчики 42 | const _callbacks = initCallbacks(callbacks) 43 | // Запускаем рекурсию 44 | depthFirstSearchRecursive(root, _callbacks) 45 | } 46 | -------------------------------------------------------------------------------- /src/data-structures/__tests__/bloom-filter.test.js: -------------------------------------------------------------------------------- 1 | import BloomFilter from '../bloom-filter' 2 | 3 | describe('BloomFilter', () => { 4 | let bloomFilter 5 | const people = ['Bruce Wayne', 'Clark Kent', 'Barry Allen'] 6 | 7 | beforeEach(() => { 8 | bloomFilter = new BloomFilter() 9 | }) 10 | 11 | it('должен содержать методы "insert" и "mayContain"', () => { 12 | expect(typeof bloomFilter.insert).toBe('function') 13 | expect(typeof bloomFilter.mayContain).toBe('function') 14 | }) 15 | 16 | it('должен создать хранилище с указанными методами', () => { 17 | const store = bloomFilter.createStore(18) 18 | expect(typeof store.getValue).toBe('function') 19 | expect(typeof store.setValue).toBe('function') 20 | }) 21 | 22 | it('должен стабильно хешировать элементы с помощью трех хеш-функций', () => { 23 | const str1 = 'apple' 24 | 25 | expect(bloomFilter.hash1(str1)).toEqual(bloomFilter.hash1(str1)) 26 | expect(bloomFilter.hash2(str1)).toEqual(bloomFilter.hash2(str1)) 27 | expect(bloomFilter.hash3(str1)).toEqual(bloomFilter.hash3(str1)) 28 | 29 | expect(bloomFilter.hash1(str1)).toBe(14) 30 | expect(bloomFilter.hash2(str1)).toBe(43) 31 | expect(bloomFilter.hash3(str1)).toBe(10) 32 | 33 | const str2 = 'orange' 34 | 35 | expect(bloomFilter.hash1(str2)).toEqual(bloomFilter.hash1(str2)) 36 | expect(bloomFilter.hash2(str2)).toEqual(bloomFilter.hash2(str2)) 37 | expect(bloomFilter.hash3(str2)).toEqual(bloomFilter.hash3(str2)) 38 | 39 | expect(bloomFilter.hash1(str2)).toBe(0) 40 | expect(bloomFilter.hash2(str2)).toBe(61) 41 | expect(bloomFilter.hash3(str2)).toBe(10) 42 | }) 43 | 44 | it('должен создать массив с тремя хешированными значениями', () => { 45 | expect(bloomFilter.getHashValues('abc').length).toBe(3) 46 | expect(bloomFilter.getHashValues('abc')).toEqual([66, 63, 54]) 47 | }) 48 | 49 | it('должен добавить строки и возвращать `true` при проверке их наличия', () => { 50 | people.forEach((person) => bloomFilter.insert(person)) 51 | 52 | // expect(bloomFilter.mayContain('Bruce Wayne')).toBe(true) 53 | // expect(bloomFilter.mayContain('Clark Kent')).toBe(true) 54 | // expect(bloomFilter.mayContain('Barry Allen')).toBe(true) 55 | 56 | expect(bloomFilter.mayContain('Tony Stark')).toBe(false) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /src/data-structures/__tests__/queue.test.js: -------------------------------------------------------------------------------- 1 | import Queue from '../queue' 2 | 3 | describe('Queue', () => { 4 | it('должен создать пустую очередь', () => { 5 | const queue = new Queue() 6 | expect(queue).not.toBeNull() 7 | expect(queue.linkedList).not.toBeNull() 8 | }) 9 | 10 | it('должен добавить значения в очередь', () => { 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('должен добавить/удалить объекты в/из очереди', () => { 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('должен извлечь значения из очереди без удаления и с удалением соответствующих узлов', () => { 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('должен проверить пустоту очереди', () => { 45 | const queue = new Queue() 46 | 47 | expect(queue.isEmpty()).toBe(true) 48 | 49 | queue.enqueue(1) 50 | 51 | expect(queue.isEmpty()).toBe(false) 52 | }) 53 | 54 | it('должен удалять элементы из очереди в порядке FIFO', () => { 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()).toBe(true) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /src/data-structures/__tests__/stack.test.js: -------------------------------------------------------------------------------- 1 | import Stack from '../stack' 2 | 3 | describe('Stack', () => { 4 | it('должен создать пустой стек', () => { 5 | const stack = new Stack() 6 | expect(stack).not.toBeNull() 7 | expect(stack.linkedList).not.toBeNull() 8 | }) 9 | 10 | it('должен добавить значения в стек', () => { 11 | const stack = new Stack() 12 | 13 | stack.push(1) 14 | stack.push(2) 15 | 16 | expect(stack.toString()).toBe('2,1') 17 | }) 18 | 19 | it('должен проверить пустоту стека', () => { 20 | const stack = new Stack() 21 | 22 | expect(stack.isEmpty()).toBe(true) 23 | 24 | stack.push(1) 25 | 26 | expect(stack.isEmpty()).toBe(false) 27 | }) 28 | 29 | it('должен извлечь значения из стека без удаления узлов', () => { 30 | const stack = new Stack() 31 | 32 | expect(stack.peek()).toBeNull() 33 | 34 | stack.push(1) 35 | stack.push(2) 36 | 37 | expect(stack.peek()).toBe(2) 38 | expect(stack.peek()).toBe(2) 39 | }) 40 | 41 | it('должен извлечь значения из стека с удалением узлов', () => { 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()).toBe(true) 51 | }) 52 | 53 | it('должен добавить/удалить объекты в/из стека', () => { 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('key2:test2,key1:test1') 62 | expect(stack.pop().value).toBe('test2') 63 | expect(stack.pop().value).toBe('test1') 64 | }) 65 | 66 | it('должен преобразовать стек в массив', () => { 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/disjoint-set/__tests__/ad-hoc.test.js: -------------------------------------------------------------------------------- 1 | import DisjointSetAdhoc from '../ad-hoc' 2 | 3 | describe('DisjointSetAdhoc', () => { 4 | it('должен создать множества и найти соединенные элементы', () => { 5 | const set = new DisjointSetAdhoc(10) 6 | 7 | // 1-2-5-6-7 3-8-9 4 8 | set.union(1, 2) 9 | set.union(2, 5) 10 | set.union(5, 6) 11 | set.union(6, 7) 12 | 13 | set.union(3, 8) 14 | set.union(8, 9) 15 | 16 | expect(set.connected(1, 5)).toBe(true) 17 | expect(set.connected(5, 7)).toBe(true) 18 | expect(set.connected(3, 8)).toBe(true) 19 | 20 | expect(set.connected(4, 9)).toBe(false) 21 | expect(set.connected(4, 7)).toBe(false) 22 | 23 | // 1-2-5-6-7 3-8-9-4 24 | set.union(9, 4) 25 | 26 | expect(set.connected(4, 9)).toBe(true) 27 | expect(set.connected(4, 3)).toBe(true) 28 | expect(set.connected(8, 4)).toBe(true) 29 | 30 | expect(set.connected(8, 7)).toBe(false) 31 | expect(set.connected(2, 3)).toBe(false) 32 | }) 33 | 34 | it('должен сохранять высоту дерева маленькой', () => { 35 | const set = new DisjointSetAdhoc(10) 36 | 37 | // 1-2-6-7-9 1 3 4 5 38 | set.union(7, 6) 39 | set.union(1, 2) 40 | set.union(2, 6) 41 | set.union(1, 7) 42 | set.union(9, 1) 43 | 44 | expect(set.connected(1, 7)).toBe(true) 45 | expect(set.connected(6, 9)).toBe(true) 46 | expect(set.connected(4, 9)).toBe(false) 47 | 48 | expect(Math.max(...set.heights)).toBe(3) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /src/data-structures/disjoint-set/ad-hoc.js: -------------------------------------------------------------------------------- 1 | export default class DisjointSetAdHoc { 2 | constructor(size) { 3 | this.roots = new Array(size).fill(0).map((_, i) => i) 4 | this.heights = new Array(size).fill(1) 5 | } 6 | 7 | find(a) { 8 | if (this.roots[a] === a) return a 9 | this.roots[a] = this.find(this.roots[a]) 10 | return this.roots[a] 11 | } 12 | 13 | union(a, b) { 14 | const rootA = this.find(a) 15 | const rootB = this.find(b) 16 | if (rootA === rootB) return 17 | 18 | if (this.heights[rootA] < this.heights[rootB]) { 19 | this.roots[rootA] = rootB 20 | } else if (this.heights[rootA] > this.heights[rootB]) { 21 | this.roots[rootB] = rootA 22 | } else { 23 | this.roots[rootB] = rootA 24 | this.heights[rootA]++ 25 | } 26 | } 27 | 28 | connected(a, b) { 29 | return this.find(a) === this.find(b) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/data-structures/disjoint-set/index.js: -------------------------------------------------------------------------------- 1 | import Item from './item' 2 | 3 | export default class DisjointSet { 4 | constructor(cb) { 5 | // Кастомная функция извлечения ключа (значения) узла 6 | this.cb = cb 7 | // Непересекающиеся подмножества 8 | this.items = {} 9 | } 10 | 11 | // Создает подмножество 12 | makeSet(value) { 13 | // Создаем выделенный элемент 14 | const item = new Item(value, this.cb) 15 | 16 | // Добавляем подмножество в список 17 | if (!this.items[item.getKey()]) { 18 | this.items[item.getKey()] = item 19 | } 20 | 21 | return this 22 | } 23 | 24 | // Ищет выделенный элемент 25 | find(value) { 26 | const temp = new Item(value, this.cb) 27 | const item = this.items[temp.getKey()] 28 | return item ? item.getRoot().getKey() : null 29 | } 30 | 31 | // Объединяет подмножества 32 | union(value1, value2) { 33 | const root1 = this.find(value1) 34 | const root2 = this.find(value2) 35 | 36 | if (!root1 || !root2) { 37 | throw new Error('Одно или оба значения отсутствуют') 38 | } 39 | 40 | if (root1 === root2) { 41 | return this 42 | } 43 | 44 | const item1 = this.items[root1] 45 | const item2 = this.items[root2] 46 | 47 | // Определяем, какое подмножество имеет больший ранг. 48 | // Подмножество с меньшим рангом становится потомком подмножества с большим рангом 49 | if (item1.getRank() < item2.getRank()) { 50 | item2.addChild(item1) 51 | return this 52 | } 53 | 54 | item1.addChild(item2) 55 | return this 56 | } 57 | 58 | // Определяет, принадлежат ли значения к одному множеству 59 | isSameSet(value1, value2) { 60 | const root1 = this.find(value1) 61 | const root2 = this.find(value2) 62 | 63 | if (!root1 || !root2) { 64 | throw new Error('Одно или оба значения отсутствуют!') 65 | } 66 | 67 | return root1 === root2 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/data-structures/disjoint-set/item.js: -------------------------------------------------------------------------------- 1 | export default class Item { 2 | constructor(value, keyCb) { 3 | // Значение 4 | this.value = value 5 | // Кастомная функция извлечения ключа 6 | this.keyCb = keyCb 7 | // Родительский узел 8 | this.parent = null 9 | // Дочерние узлы 10 | this.children = {} 11 | } 12 | 13 | // Возвращает ключ (значение) 14 | getKey() { 15 | if (this.keyCb) { 16 | return this.keyCb(this.value) 17 | } 18 | return this.value 19 | } 20 | 21 | // Возвращает корневой узел 22 | getRoot() { 23 | return this.isRoot() ? this : this.parent.getRoot() 24 | } 25 | 26 | // Определяет, является ли узел корневым 27 | isRoot() { 28 | return this.parent === null 29 | } 30 | 31 | // Возвращает ранг (вес) узла 32 | getRank() { 33 | const children = this.getChildren() 34 | 35 | if (children.length === 0) { 36 | return 0 37 | } 38 | 39 | let rank = 0 40 | for (const child of children) { 41 | rank += 1 42 | rank += child.getRank() 43 | } 44 | return rank 45 | } 46 | 47 | // Возвращает потомков 48 | getChildren() { 49 | return Object.values(this.children) 50 | } 51 | 52 | // Устанавливает предка 53 | setParent(parent, forceSettingParentChild = true) { 54 | this.parent = parent 55 | 56 | if (forceSettingParentChild) { 57 | parent.addChild(this) 58 | } 59 | 60 | return this 61 | } 62 | 63 | // Добавляет потомка 64 | addChild(child) { 65 | this.children[child.getKey()] = child 66 | child.setParent(this, false) 67 | 68 | return this 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/data-structures/graph/__tests__/edge.test.js: -------------------------------------------------------------------------------- 1 | import Edge from '../edge' 2 | import Node from '../node' 3 | 4 | describe('Edge', () => { 5 | it('должена создать ребро графа с дефолтным весом', () => { 6 | const from = new Node('A') 7 | const to = new Node('B') 8 | const edge = new Edge(from, to) 9 | 10 | expect(edge.getKey()).toBe('A_B') 11 | expect(edge.toString()).toBe('A_B') 12 | expect(edge.from).toEqual(from) 13 | expect(edge.to).toEqual(to) 14 | expect(edge.weight).toEqual(0) 15 | }) 16 | 17 | it('должена создать граф с указанным весом', () => { 18 | const from = new Node('A') 19 | const to = new Node('B') 20 | const edge = new Edge(from, to, 10) 21 | 22 | expect(edge.from).toEqual(from) 23 | expect(edge.to).toEqual(to) 24 | expect(edge.weight).toEqual(10) 25 | }) 26 | 27 | it('должен инвертировать ребро', () => { 28 | const nodeA = new Node('A') 29 | const nodeB = new Node('B') 30 | const edge = new Edge(nodeA, nodeB, 10) 31 | 32 | expect(edge.from).toEqual(nodeA) 33 | expect(edge.to).toEqual(nodeB) 34 | expect(edge.weight).toEqual(10) 35 | 36 | edge.reverse() 37 | 38 | expect(edge.from).toEqual(nodeB) 39 | expect(edge.to).toEqual(nodeA) 40 | expect(edge.weight).toEqual(10) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /src/data-structures/graph/edge.js: -------------------------------------------------------------------------------- 1 | export default class Edge { 2 | constructor(from, to, weight = 0) { 3 | // Начальный узел 4 | this.from = from 5 | // Конечный узел 6 | this.to = to 7 | // Вес ребра 8 | this.weight = weight 9 | } 10 | 11 | // Возвращает ключ ребра 12 | getKey() { 13 | const fromKey = this.from.getKey() 14 | const toKey = this.to.getKey() 15 | 16 | // Например, `A_B` 17 | return `${fromKey}_${toKey}` 18 | } 19 | 20 | // Инвертирует ребро 21 | reverse() { 22 | const tmp = this.from 23 | this.from = this.to 24 | this.to = tmp 25 | 26 | return this 27 | } 28 | 29 | // Преобразует ребро в строку 30 | toString() { 31 | return this.getKey() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/data-structures/heap/__tests__/heap.test.js: -------------------------------------------------------------------------------- 1 | import Heap from '..' 2 | 3 | describe('Heap', () => { 4 | it('должен выбросить исключение при попытке создания кучи напрямую', () => { 5 | const instantiateHeap = () => { 6 | const heap = new Heap() 7 | heap.add(5) 8 | } 9 | 10 | expect(instantiateHeap).toThrow() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/data-structures/heap/max-heap.js: -------------------------------------------------------------------------------- 1 | import Heap from '.' 2 | 3 | export default class MaxHeap extends Heap { 4 | pairIsInCorrectOrder(firstElement, secondElement) { 5 | // Первый элемент должен быть больше или равен второму 6 | return this.compare.greaterThanOrEqual(firstElement, secondElement) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/data-structures/heap/min-heap.js: -------------------------------------------------------------------------------- 1 | import Heap from '.' 2 | 3 | export default class MinHeap extends Heap { 4 | pairIsInCorrectOrder(firstElement, secondElement) { 5 | // Первый элемент должен быть меньше или равен второму 6 | return this.compare.lessThanOrEqual(firstElement, secondElement) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/data-structures/lru-cache-on-map.js: -------------------------------------------------------------------------------- 1 | export default class LruCacheOnMap { 2 | // Конструктор принимает размер кэша 3 | constructor(size) { 4 | // Размер кэша 5 | this.size = size 6 | // Хранилище (по сути, сам кэш) 7 | this.map = new Map() 8 | } 9 | 10 | // Возвращает значение по ключу 11 | get(key) { 12 | const val = this.map.get(key) 13 | if (!val) return null 14 | // Обновляем "приоритет" элемента 15 | this.map.delete(key) 16 | this.map.set(key, val) 17 | return val 18 | } 19 | 20 | // Добавляет элемент в кэш 21 | set(key, val) { 22 | // Обновляем "приоритет" элемента 23 | this.map.delete(key) 24 | this.map.set(key, val) 25 | // Если кэш переполнен, удаляем первый (самый редко используемый) элемент 26 | if (this.map.size > this.size) { 27 | for (const key of this.map.keys()) { 28 | this.map.delete(key) 29 | break 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/data-structures/priority-queue.js: -------------------------------------------------------------------------------- 1 | // Импортируем конструктор функции сравнения узлов 2 | import Comparator from '../utils/comparator' 3 | // Импортируем конструктор min-кучи 4 | import MinHeap from './heap/min-heap' 5 | 6 | // Очередь с приоритетом. 7 | // Реализация на основе min-кучи 8 | export default class PriorityQueue extends MinHeap { 9 | constructor() { 10 | // Инициализируем min-кучу 11 | super() 12 | // Карта приоритетов 13 | this.priorities = new Map() 14 | // Функция сравнения элементов 15 | this.compare = new Comparator(this.comparePriorities.bind(this)) 16 | } 17 | 18 | // Добавляет элемент в очередь. 19 | // Принимает элемент и приоритет. 20 | // Чем больше приоритет (меньше значение `priority`), 21 | // тем "выше" элемент находится в очереди 22 | add(item, priority = 0) { 23 | // Обновляем приоритеты 24 | this.priorities.set(item, priority) 25 | // Добавляем элемент в кучу 26 | super.add(item) 27 | 28 | return this 29 | } 30 | 31 | // Удаляет элемент из очереди. 32 | // Принимает элемент и кастомную функцию сравнения элементов 33 | remove(item, compare) { 34 | // Удаляем элемент из кучи 35 | super.remove(item, compare) 36 | // Обновляем приоритеты 37 | this.priorities.delete(item) 38 | 39 | return this 40 | } 41 | 42 | // Обновляет приоритет. 43 | // Принимает элемент и новый приоритет 44 | changePriority(item, priority) { 45 | // Удаляем элемент из очереди 46 | this.remove(item, new Comparator(this.compareValues)) 47 | // Добавляем элемент с новым приоритетом 48 | this.add(item, priority) 49 | 50 | return this 51 | } 52 | 53 | // Ищет элемент по значению. 54 | // Возвращает массив индексов 55 | findByValue(item) { 56 | return this.find(item, new Comparator(this.compareValues)) 57 | } 58 | 59 | // Определяет наличие элемента 60 | hasValue(item) { 61 | return this.findByValue(item).length > 0 62 | } 63 | 64 | // Сравнивает приоритеты 65 | comparePriorities(a, b) { 66 | // Вызываем функцию сравнения значений, 67 | // передавая ей приоритеты 68 | return this.compareValues(this.priorities.get(a), this.priorities.get(b)) 69 | } 70 | 71 | // Сравнивает значения 72 | compareValues(a, b) { 73 | if (a === b) { 74 | return 0 75 | } 76 | return a < b ? -1 : 1 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/data-structures/queue.js: -------------------------------------------------------------------------------- 1 | // Импортируем конструктор связного списка 2 | import LinkedList from './linked-list' 3 | 4 | // Очередь 5 | export default class Queue { 6 | constructor() { 7 | // Создаем связный список 8 | this.list = new LinkedList() 9 | } 10 | 11 | // Проверяет, является ли очередь пустой 12 | isEmpty() { 13 | return !this.list.head 14 | } 15 | 16 | // Возвращает значение первого узла без его удаления 17 | peek() { 18 | if (this.isEmpty()) { 19 | return null 20 | } 21 | 22 | return this.list.head.value 23 | } 24 | 25 | // Добавляет элемент в конец очереди 26 | enqueue(value) { 27 | this.list.append(value) 28 | } 29 | 30 | // Удаляет первый узел и возвращает его значение 31 | dequeue() { 32 | const removedHead = this.list.removeHead() 33 | return removedHead?.value || null 34 | } 35 | 36 | // Преобразует очередь в строку. 37 | // Принимает кастомную функцию стрингификации 38 | toString(cb) { 39 | return this.list.toString(cb) 40 | } 41 | 42 | // Преобразует очередь в массив значений 43 | toArray() { 44 | return this.list.toArray().map((node) => node.value) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/data-structures/stack.js: -------------------------------------------------------------------------------- 1 | // Импортируем конструктор связного списка 2 | import LinkedList from './linked-list' 3 | 4 | // Стек 5 | export default class Stack { 6 | constructor() { 7 | // Создаем связный список 8 | this.list = new LinkedList() 9 | } 10 | 11 | // Проверяет, является ли стек пустым 12 | isEmpty() { 13 | return !this.list.head 14 | } 15 | 16 | // Возвращает значение первого узла без его удаления 17 | peek() { 18 | if (this.isEmpty()) { 19 | return null 20 | } 21 | return this.list.head.value 22 | } 23 | 24 | // Добавляет элемент в начало стека 25 | push(value) { 26 | this.list.prepend(value) 27 | } 28 | 29 | // Удаляет первый узел и возвращает его значение 30 | pop() { 31 | const removedHead = this.list.removeHead() 32 | return removedHead?.value || null 33 | } 34 | 35 | // Преобразует стек в строку 36 | toString(cb) { 37 | return this.list.toString(cb) 38 | } 39 | 40 | // Преобразует стек в массив значений 41 | toArray() { 42 | return this.list.toArray().map((node) => node.value) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/data-structures/tree/fenwick-tree.js: -------------------------------------------------------------------------------- 1 | export default class FenwickTree { 2 | // Конструктор создает дерево Фенвика размера `size`, 3 | // однако, размер дерева `n+1`, потому что индексация начинается с `1` 4 | constructor(size) { 5 | this.size = size 6 | // Заполняем массив нулями 7 | this.tree = new Array(size + 1).fill(0) 8 | } 9 | 10 | // Прибавляет значение к существующему на указанной позиции 11 | increase(position, value) { 12 | if (position < 1 || position > this.size) { 13 | throw new Error('Позиция находится за пределами разрешенного диапазона') 14 | } 15 | 16 | // magic :D 17 | for (let i = position; i <= this.size; i += i & -i) { 18 | this.tree[i] += value 19 | } 20 | 21 | return this 22 | } 23 | 24 | // Возвращает сумму от индекса 1 до указанной позиции 25 | query(position) { 26 | if (position < 1 || position > this.size) { 27 | throw new Error('Позиция находится за пределами разрешенного диапазона') 28 | } 29 | 30 | let sum = 0 31 | 32 | // magic :D 33 | for (let i = position; i > 0; i -= i & -i) { 34 | sum += this.tree[i] 35 | } 36 | 37 | return sum 38 | } 39 | 40 | // Возвращает сумму от `leftIndex` до `rightIndex` 41 | queryRange(leftIndex, rightIndex) { 42 | if (leftIndex > rightIndex) { 43 | throw new Error('Левый индекс не может превышать правый') 44 | } 45 | 46 | if (leftIndex === 1) { 47 | return this.query(rightIndex) 48 | } 49 | 50 | return this.query(rightIndex) - this.query(leftIndex - 1) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/data-structures/trie/node.js: -------------------------------------------------------------------------------- 1 | // Импортируем конструктор хэш-таблицы 2 | import HashTable from '../hash-table' 3 | 4 | // Последний (завершающий) символ 5 | export const HEAD_CHARACTER = '*' 6 | 7 | // Узел префиксного дерева 8 | export default class Node { 9 | constructor(char, isCompleteWord = false) { 10 | // Символ 11 | this.char = char 12 | // Индикатор завершающего символа 13 | this.isCompleteWord = isCompleteWord 14 | // Хэш-таблица потомков 15 | this.children = new HashTable() 16 | } 17 | 18 | // Добавляет потомка в дерево 19 | addChild(char, isCompleteWord = false) { 20 | // Добавляем узел при отсутствии 21 | if (!this.hasChild(char)) { 22 | this.children.set(char, new Node(char, isCompleteWord)) 23 | } 24 | 25 | // Извлекаем узел 26 | const node = this.getChild(char) 27 | 28 | // Обновляем флаг `isCompleteWord` при необходимости, 29 | // например, при добавлении слова "car" после слова "carpet", 30 | // букву "r" нужно пометить как завершающую 31 | node.isCompleteWord = node.isCompleteWord || isCompleteWord 32 | 33 | // Возвращаем узел 34 | return node 35 | } 36 | 37 | // Удаляет потомка 38 | removeChild(char) { 39 | // Извлекаем узел 40 | const node = this.getChild(char) 41 | 42 | // Удаляем узел, только если: 43 | // - у него нет потомков 44 | // - node.isCompleteWord === false 45 | if (node && !node.isCompleteWord && !node.hasChildren()) { 46 | this.children.remove(char) 47 | } 48 | 49 | return this 50 | } 51 | 52 | // Возвращает потомка 53 | getChild(char) { 54 | return this.children.get(char) 55 | } 56 | 57 | // Определяет наличие потомка 58 | hasChild(char) { 59 | return this.children.has(char) 60 | } 61 | 62 | // Определяет наличие потомков 63 | hasChildren() { 64 | return this.children.getKeys().length > 0 65 | } 66 | 67 | // Автодополнение (предложение следующих символов) 68 | suggestChildren() { 69 | return [...this.children.getKeys()] 70 | } 71 | 72 | // Преобразует потомков в строку 73 | // с указанием признака завершающего символа 74 | toString() { 75 | let childrenAsString = this.suggestChildren().toString() 76 | childrenAsString = childrenAsString ? `:${childrenAsString}` : '' 77 | const isCompleteString = this.isCompleteWord ? HEAD_CHARACTER : '' 78 | 79 | return `${this.char}${isCompleteString}${childrenAsString}` 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/utils/comparator.js: -------------------------------------------------------------------------------- 1 | export default class Comparator { 2 | constructor(fn) { 3 | this.compare = fn || Comparator.defaultCompare 4 | } 5 | 6 | // Дефолтная функция сравнения узлов 7 | static defaultCompare(a, b) { 8 | if (a === b) { 9 | return 0 10 | } 11 | return a < b ? -1 : 1 12 | } 13 | 14 | // Проверка на равенство 15 | equal(a, b) { 16 | return this.compare(a, b) === 0 17 | } 18 | 19 | // Меньше чем 20 | lessThan(a, b) { 21 | return this.compare(a, b) < 0 22 | } 23 | 24 | // Больше чем 25 | greaterThan(a, b) { 26 | return this.compare(a, b) > 0 27 | } 28 | 29 | // Меньше или равно 30 | lessThanOrEqual(a, b) { 31 | return this.lessThan(a, b) || this.equal(a, b) 32 | } 33 | 34 | // Больше или равно 35 | greaterThanOrEqual(a, b) { 36 | return this.greaterThan(a, b) || this.equal(a, b) 37 | } 38 | 39 | // Инверсия сравнения 40 | reverse() { 41 | const original = this.compare 42 | this.compare = (a, b) => original(b, a) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* Box sizing rules */ 2 | *, 3 | *::before, 4 | *::after { 5 | box-sizing: border-box; 6 | } 7 | 8 | /* Prevent font size inflation */ 9 | html { 10 | -moz-text-size-adjust: none; 11 | -webkit-text-size-adjust: none; 12 | text-size-adjust: none; 13 | } 14 | 15 | /* Remove default margin in favour of better control in authored CSS */ 16 | body, 17 | h1, 18 | h2, 19 | h3, 20 | h4, 21 | p, 22 | figure, 23 | blockquote, 24 | dl, 25 | dd { 26 | margin: 0; 27 | } 28 | 29 | /* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */ 30 | ul[role='list'], 31 | ol[role='list'] { 32 | list-style: none; 33 | } 34 | 35 | /* Set core body defaults */ 36 | body { 37 | min-height: 100vh; 38 | line-height: 1.5; 39 | } 40 | 41 | /* Set shorter line heights on headings and interactive elements */ 42 | h1, 43 | h2, 44 | h3, 45 | h4, 46 | button, 47 | input, 48 | label { 49 | line-height: 1.1; 50 | } 51 | 52 | /* Balance text wrapping on headings */ 53 | h1, 54 | h2, 55 | h3, 56 | h4 { 57 | text-wrap: balance; 58 | } 59 | 60 | /* A elements that don't have a class get default styles */ 61 | a:not([class]) { 62 | text-decoration-skip-ink: auto; 63 | color: currentColor; 64 | } 65 | 66 | /* Make images easier to work with */ 67 | img, 68 | picture { 69 | max-width: 100%; 70 | display: block; 71 | } 72 | 73 | /* Inherit fonts for inputs and buttons */ 74 | input, 75 | button, 76 | textarea, 77 | select { 78 | font: inherit; 79 | } 80 | 81 | /* Make sure textareas without a rows attribute are not tiny */ 82 | textarea:not([rows]) { 83 | min-height: 10em; 84 | } 85 | 86 | /* Anything that has been anchored to should have extra scroll margin */ 87 | :target { 88 | scroll-margin-block: 5ex; 89 | } 90 | --------------------------------------------------------------------------------