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