diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..e2c4b49ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,41 @@ +name: Bug report +description: 'Create a report to help us improve' +title: '[BUG]: ' +labels: ['bug'] +body: + - type: markdown + attributes: + value: '### Before you open an issue, please verify if a similar one already exists or has been closed before. More details about the process of contributing can be found in [CONTRIBUTING.md](https://github.com/TheAlgorithms/JavaScript/blob/master/CONTRIBUTING.md).' + - type: textarea + id: description + attributes: + label: Description + description: Explain what the problem is. + validations: + required: true + - type: textarea + id: expectedbhv + attributes: + label: Expected Behavior + description: Describe what was the expected behavior. + validations: + required: true + - type: textarea + id: actualbhv + attributes: + label: Actual Behavior + description: Describe what actually happens. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce (if applicable) + description: List steps to reproduce the behavior. + placeholder: | + 1. + 2. + 3. + 4. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..a6ee8bf45 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,31 @@ +name: Feature request +description: 'Suggest features, propose improvements, discuss new ideas' +title: '[FEATURE]: ' +labels: ['enhancement'] +body: + - type: markdown + attributes: + value: | + ## This issue template is not for requesting new algorithms. For new algorithms, PRs should be opened directly. + ## Make sure your issue isn't a duplicate and you follow our [contributing guidelines](https://github.com/TheAlgorithms/JavaScript/blob/master/CONTRIBUTING.md) + - type: textarea + id: description + attributes: + label: Motivation + description: Describe what is the motivation behind this feature. + validations: + required: true + - type: textarea + id: examples + attributes: + label: Examples + description: If possible, provide examples of how this feature can be used. + validations: + required: false + - type: textarea + id: workarounds + attributes: + label: Possible workarounds + description: If possible, describes possible workarounds to this feature. + validations: + required: false diff --git a/.github/workflows/Ci.yml b/.github/workflows/Ci.yml index 8d4120314..a783e0bb9 100644 --- a/.github/workflows/Ci.yml +++ b/.github/workflows/Ci.yml @@ -12,6 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: 16 @@ -20,8 +21,13 @@ jobs: - name: ๐Ÿ“ฆ Install dependencies run: npm ci - - name: ๐Ÿงช Run tests - run: npm test + - name: ๐Ÿงช Run all tests + if: ${{ github.event_name == 'push' }} + run: npm run test + + - name: ๐Ÿงช Run tests for changed files only + if: ${{ github.event_name == 'pull_request' }} + run: npm run test-changed - name: ๐Ÿ’„ Code style run: npm run style diff --git a/.gitpod.yml b/.gitpod.yml index 655da0e58..3f6c645c7 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,3 +1,11 @@ +github: + prebuilds: + addBadge: true + addComment: false + addCheck: false + master: true + branches: true + pullRequestsFromForks: true + tasks: - init: npm install - diff --git a/.husky/pre-commit b/.husky/pre-commit index 35ea7e3c0..9b70cd595 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,4 +2,4 @@ . "$(dirname "$0")/_/husky.sh" npm run style -npm run test +npm run test-changed diff --git a/Backtracking/tests/GeneratePermutations.test.js b/Backtracking/tests/GeneratePermutations.test.js index 718bf2670..5aa74c72a 100644 --- a/Backtracking/tests/GeneratePermutations.test.js +++ b/Backtracking/tests/GeneratePermutations.test.js @@ -1,14 +1,33 @@ +import { factorial } from '../../Recursive/Factorial' import { permutations } from '../GeneratePermutations' describe('Permutations', () => { + it('Permutations of [a]', () => { + const perms = permutations(['a']) + expect(perms).toHaveLength(factorial(1)) + expect(perms).toContainEqual(['a']) + }) + + it('Permutations of [true, false]', () => { + const perms = permutations([true, false]) + expect(perms).toHaveLength(factorial(2)) + expect(perms).toContainEqual([true, false]) + expect(perms).toContainEqual([false, true]) + }) + it('Permutations of [1, 2, 3]', () => { - expect(permutations([1, 2, 3])).toEqual([ - [1, 2, 3], - [1, 3, 2], - [2, 1, 3], - [2, 3, 1], - [3, 1, 2], - [3, 2, 1] - ]) + const perms = permutations([1, 2, 3]) + expect(perms).toHaveLength(factorial(3)) + expect(perms).toContainEqual([1, 2, 3]) + expect(perms).toContainEqual([1, 3, 2]) + expect(perms).toContainEqual([2, 1, 3]) + expect(perms).toContainEqual([2, 3, 1]) + expect(perms).toContainEqual([3, 1, 2]) + expect(perms).toContainEqual([3, 2, 1]) + }) + + it('Permutation counts across larger input arrays', () => { + expect(permutations([1, 2, 3, 4, 5, 6, 7, 8])).toHaveLength(factorial(8)) + expect(permutations([1, 2, 3, 4, 5, 6, 7, 8, 9])).toHaveLength(factorial(9)) }) }) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7386f9dca..3b85427d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,9 @@ ## Before contributing -Welcome to [TheAlgorithms/Javascript](https://github.com/TheAlgorithms/Javascript)! Before sending your pull requests, -make sure that you **read the whole guidelines**. If you have any doubt on the contributing guide, please feel free to -[state it clearly in an issue](https://github.com/TheAlgorithms/Javascript/issues/new). +Welcome to [TheAlgorithms/JavaScript](https://github.com/TheAlgorithms/JavaScript)! Before sending your pull requests, +make sure that you **read the whole guidelines**. If you have any doubts about the contributing guide, please feel free to +[state them clearly in an issue](https://github.com/TheAlgorithms/JavaScript/issues/new) or by joining our [Discord community](https://the-algorithms.com/discord). ## Contributing @@ -15,11 +15,11 @@ referenced and used by learners from around the globe. Being one of our contribu - You did your work - plagiarism is not allowed. - Any plagiarized work will not be merged. -- Your work will be distributed under [GNU License](LICENSE) once your pull request is merged. +- Your work will be distributed under the [GNU GPLv3.0](https://github.com/TheAlgorithms/JavaScript/blob/master/LICENSE) once your pull request is merged. - Your submitted work must fulfill our styles and standards. -**New implementation** is welcome! For example, new solutions to a problem, different representations of a graph data -structure or algorithm designs with different complexity. +**New implementations** are welcome! For example, new solutions to a problem, different representations of a graph data +structure, or algorithm designs with different complexity. **Improving comments** and **writing proper tests** are also highly welcome. @@ -52,18 +52,39 @@ Algorithms should: Algorithms in this repo should not be how-to examples for existing JavaScript packages. Instead, they should perform internal calculations or manipulations to convert input values into different output values. Those calculations or manipulations can use data types, classes, or functions of existing JavaScript packages but each algorithm in this repo -should add unique value. +should add a unique value. + +#### Commit guidelines + +- Follow [**Conventional Commits**](https://www.conventionalcommits.org/en/v1.0.0/) guidelines at all times. +- Use one of the following prefixes (there might be other miscellaneous prefixes, though). + - fix: A bug fix in an algorithm, workflow, configuration/settings, etc.. + - feat: A new feature, such as new algorithms, new workflows, etc.. + - docs: Documentation changes or fixes, like improving the contributing guidelines, fixing a typo, etc.. + - test: Correct existing tests or add new ones. + - chore: Miscellaneous changes that do not match any of the above. + +Examples of best commit messages. + +```txt +fix: fixed error in XYZ algorithm +feat: re-work the CI workflow +docs: improve the contributing guidelines +test: add self-tests for XYZ algorithm +chore: update readme badges +``` #### File Naming Convention -- filenames should use the UpperCamelCase (PascalCase) style. +- Filenames should use the UpperCamelCase (PascalCase) style. - There should be no spaces in filenames. - **Example:** `UserProfile.js` is allowed but `userprofile.js`,`Userprofile.js`,`user-Profile.js`,`userProfile.js` are not. #### Module System -We use the [ES Module](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) system, which bring an official, standardized module system to JavaScript. +We use the [ES Module](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) system, which brings an +official, standardized module system to JavaScript. It roughly means you will need to use `export` and `import` statements instead of `module.exports` and `require()`. @@ -71,7 +92,7 @@ It roughly means you will need to use `export` and `import` statements instead o Be confident that your code works. When was the last time you committed a code change, your build failed, and half of your app stopped working? Mine was last week. Writing tests for our Algorithms will help us ensure the implementations -are air tight even after multiple fixes and code changes. +are airtight even after multiple fixes and code changes. We use [Jest](https://jestjs.io/) to run unit tests on our algorithms. It provides a very readable and expressive way to structure your test code. @@ -82,31 +103,37 @@ and inspect the outcome. Example: [RatInAMaze.test.js](Backtracking/tests/RatInA Please refrain from using `console` in your implementation AND test code. -First you should install all dependencies using: +First, you should install all dependencies using: -```shell +```bash npm install ``` You can (and should!) run all tests locally before committing your changes: -```shell +```bash npm test ``` -If you want save some time and just run a specific test: +If you want to save some time and just run a specific test: -```shell +```bash # This will run any test file where the filename contains "koch" (no need to specify folder path) npm test -- koch ``` You can also start Jest in "watch" mode: -```shell +```bash npm test -- --watchAll ``` +We also prepared a helper script that runs tests only for changed files: + +```bash +npm run test-changed +``` + This will run all tests and watch source and test files for changes. When a change is made, the tests will run again. #### Coding Style @@ -114,9 +141,9 @@ This will run all tests and watch source and test files for changes. When a chan To maximize the readability and correctness of our code, we require that new submissions follow the [JavaScript Standard Style](https://standardjs.com/). -Before committing, please run +Before committing, please run: -```shell +```bash npm run style ``` @@ -127,7 +154,7 @@ A few (but not all) of the things to keep in mind: - Use camelCase with the leading character as lowercase for identifier names (variables and functions). - Names start with a letter. -- Follow code indentation: Always use 2 spaces for indentation of code blocks. +- Follow code indentation: Always use 2 spaces for code-block indentation. ```js function sumOfArray(arrayOfNumbers) { @@ -151,4 +178,4 @@ function sumOfArray(arrayOfNumbers) { - **Be consistent in the use of these guidelines when submitting.** - Happy coding! -Writer [@itsvinayak](https://github.com/itsvinayak), May 2020. +Writer [@itsvinayak](https://github.com/itsvinayak) and contributors, May 2020. diff --git a/Conversions/LitersToImperialGallons.js b/Conversions/LitersToImperialGallons.js new file mode 100644 index 000000000..5e8008ecb --- /dev/null +++ b/Conversions/LitersToImperialGallons.js @@ -0,0 +1,11 @@ +/** + * This function converts liters to imperial gallons + * @constructor + * @param {number} liters - Amount of liters to convert to gallons + * @see https://en.wikipedia.org/wiki/Gallon + */ +const litersToImperialGallons = (liters) => { + return liters / 4.54609 +} + +export default litersToImperialGallons diff --git a/Conversions/LitersToUSGallons.js b/Conversions/LitersToUSGallons.js new file mode 100644 index 000000000..62941a2c6 --- /dev/null +++ b/Conversions/LitersToUSGallons.js @@ -0,0 +1,11 @@ +/** + * This function converts liters to US gallons + * https://en.wikipedia.org/wiki/Gallon + * @constructor + * @param {number} liters - Amount of liters to convert to gallons + */ +const litersToUSGallons = (liters) => { + return liters / 3.785411784 +} + +export default litersToUSGallons diff --git a/Conversions/OuncesToKilograms.js b/Conversions/OuncesToKilograms.js new file mode 100644 index 000000000..d39de8564 --- /dev/null +++ b/Conversions/OuncesToKilograms.js @@ -0,0 +1,11 @@ +/** + * This function converts ounces to kilograms + * https://en.wikipedia.org/wiki/Ounce + * @constructor + * @param {number} oz - Amount of ounces to convert to kilograms + */ +const ouncesToKilograms = (oz) => { + return oz * 28.3498 / 1000 +} + +export default ouncesToKilograms diff --git a/Conversions/test/LitersToImperialGallons.test.js b/Conversions/test/LitersToImperialGallons.test.js new file mode 100644 index 000000000..3a019887d --- /dev/null +++ b/Conversions/test/LitersToImperialGallons.test.js @@ -0,0 +1,5 @@ +import litersToImperialGallons from '../LitersToImperialGallons' + +test('Convert 25 liters to imperial gallons', () => { + expect(parseFloat(litersToImperialGallons(25).toFixed(2))).toBe(5.5) +}) diff --git a/Conversions/test/LitersToUSGallons.test.js b/Conversions/test/LitersToUSGallons.test.js new file mode 100644 index 000000000..a1ad26b75 --- /dev/null +++ b/Conversions/test/LitersToUSGallons.test.js @@ -0,0 +1,5 @@ +import litersToUSGallons from '../LitersToUSGallons' + +test('Convert 50 liters to US gallons', () => { + expect(parseFloat(litersToUSGallons(50).toFixed(2))).toBe(13.21) +}) diff --git a/Conversions/test/OuncesToKilogram.test.js b/Conversions/test/OuncesToKilogram.test.js new file mode 100644 index 000000000..e72e06958 --- /dev/null +++ b/Conversions/test/OuncesToKilogram.test.js @@ -0,0 +1,5 @@ +import ouncesToKilograms from '../OuncesToKilograms' + +test('Convert 60 ounces to kilograms', () => { + expect(parseFloat(ouncesToKilograms(60).toFixed(3))).toBe(1.701) +}) diff --git a/DIRECTORY.md b/DIRECTORY.md index e1bbd7700..ca30d6582 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -42,9 +42,12 @@ * [HexToBinary](Conversions/HexToBinary.js) * [HexToDecimal](Conversions/HexToDecimal.js) * [HexToRGB](Conversions/HexToRGB.js) + * [LitersToImperialGallons](Conversions/LitersToImperialGallons.js) + * [LitersToUSGallons](Conversions/LitersToUSGallons.js) * [LowerCaseConversion](Conversions/LowerCaseConversion.js) * [MeterToFeetConversion](Conversions/MeterToFeetConversion.js) * [OctToDecimal](Conversions/OctToDecimal.js) + * [OuncesToKilograms](Conversions/OuncesToKilograms.js) * [RailwayTimeConversion](Conversions/RailwayTimeConversion.js) * [RgbHsvConversion](Conversions/RgbHsvConversion.js) * [RGBToHex](Conversions/RGBToHex.js) @@ -57,6 +60,7 @@ * [LocalMaximomPoint](Data-Structures/Array/LocalMaximomPoint.js) * [NumberOfLocalMaximumPoints](Data-Structures/Array/NumberOfLocalMaximumPoints.js) * [QuickSelect](Data-Structures/Array/QuickSelect.js) + * [Reverse](Data-Structures/Array/Reverse.js) * **Graph** * [Graph](Data-Structures/Graph/Graph.js) * [Graph2](Data-Structures/Graph/Graph2.js) @@ -69,6 +73,7 @@ * [AddTwoNumbers](Data-Structures/Linked-List/AddTwoNumbers.js) * [CycleDetection](Data-Structures/Linked-List/CycleDetection.js) * [DoublyLinkedList](Data-Structures/Linked-List/DoublyLinkedList.js) + * [ReverseSinglyLinkedList](Data-Structures/Linked-List/ReverseSinglyLinkedList.js) * [SinglyCircularLinkedList](Data-Structures/Linked-List/SinglyCircularLinkedList.js) * [SinglyLinkedList](Data-Structures/Linked-List/SinglyLinkedList.js) * **Queue** @@ -81,6 +86,7 @@ * **Tree** * [AVLTree](Data-Structures/Tree/AVLTree.js) * [BinarySearchTree](Data-Structures/Tree/BinarySearchTree.js) + * [SegmentTree](Data-Structures/Tree/SegmentTree.js) * [Trie](Data-Structures/Tree/Trie.js) * **Vectors** * [Vector2](Data-Structures/Vectors/Vector2.js) @@ -111,11 +117,18 @@ * [SudokuSolver](Dynamic-Programming/SudokuSolver.js) * [TrappingRainWater](Dynamic-Programming/TrappingRainWater.js) * [TribonacciNumber](Dynamic-Programming/TribonacciNumber.js) + * [UniquePaths](Dynamic-Programming/UniquePaths.js) + * [UniquePaths2](Dynamic-Programming/UniquePaths2.js) * [ZeroOneKnapsack](Dynamic-Programming/ZeroOneKnapsack.js) * **Geometry** + * [Circle](Geometry/Circle.js) + * [Cone](Geometry/Cone.js) * [ConvexHullGraham](Geometry/ConvexHullGraham.js) + * [Pyramid](Geometry/Pyramid.js) + * [Sphere](Geometry/Sphere.js) * **Graphs** * [BellmanFord](Graphs/BellmanFord.js) + * [BinaryLifting](Graphs/BinaryLifting.js) * [BreadthFirstSearch](Graphs/BreadthFirstSearch.js) * [BreadthFirstShortestPath](Graphs/BreadthFirstShortestPath.js) * [ConnectedComponents](Graphs/ConnectedComponents.js) @@ -125,7 +138,9 @@ * [Dijkstra](Graphs/Dijkstra.js) * [DijkstraSmallestPath](Graphs/DijkstraSmallestPath.js) * [FloydWarshall](Graphs/FloydWarshall.js) + * [Kosaraju](Graphs/Kosaraju.js) * [KruskalMST](Graphs/KruskalMST.js) + * [LCABinaryLifting](Graphs/LCABinaryLifting.js) * [NodeNeighbors](Graphs/NodeNeighbors.js) * [NumberOfIslands](Graphs/NumberOfIslands.js) * [PrimMST](Graphs/PrimMST.js) @@ -157,6 +172,7 @@ * [EulerMethod](Maths/EulerMethod.js) * [EulersTotient](Maths/EulersTotient.js) * [EulersTotientFunction](Maths/EulersTotientFunction.js) + * [ExponentialFunction](Maths/ExponentialFunction.js) * [ExtendedEuclideanGCD](Maths/ExtendedEuclideanGCD.js) * [Factorial](Maths/Factorial.js) * [Factors](Maths/Factors.js) @@ -169,8 +185,11 @@ * [FindMaxRecursion](Maths/FindMaxRecursion.js) * [FindMin](Maths/FindMin.js) * [FindMinIterator](Maths/FindMinIterator.js) + * [FriendlyNumbers](Maths/FriendlyNumbers.js) * [GetEuclidGCD](Maths/GetEuclidGCD.js) * [GridGet](Maths/GridGet.js) + * [HexagonalNumber](Maths/HexagonalNumber.js) + * [IntToBase](Maths/IntToBase.js) * [IsDivisible](Maths/IsDivisible.js) * [IsEven](Maths/IsEven.js) * [IsOdd](Maths/IsOdd.js) @@ -189,6 +208,7 @@ * [MeanSquareError](Maths/MeanSquareError.js) * [MidpointIntegration](Maths/MidpointIntegration.js) * [MobiusFunction](Maths/MobiusFunction.js) + * [ModularArithmetic](Maths/ModularArithmetic.js) * [ModularBinaryExponentiationRecursive](Maths/ModularBinaryExponentiationRecursive.js) * [NumberOfDigits](Maths/NumberOfDigits.js) * [Palindrome](Maths/Palindrome.js) @@ -208,9 +228,12 @@ * [ReversePolishNotation](Maths/ReversePolishNotation.js) * [ShorsAlgorithm](Maths/ShorsAlgorithm.js) * [SieveOfEratosthenes](Maths/SieveOfEratosthenes.js) + * [SieveOfEratosthenesIntArray](Maths/SieveOfEratosthenesIntArray.js) + * [Signum](Maths/Signum.js) * [SimpsonIntegration](Maths/SimpsonIntegration.js) * [Softmax](Maths/Softmax.js) * [SquareRoot](Maths/SquareRoot.js) + * [SquareRootLogarithmic](Maths/SquareRootLogarithmic.js) * [SumOfDigits](Maths/SumOfDigits.js) * [SumOfGeometricProgression](Maths/SumOfGeometricProgression.js) * [TwinPrime](Maths/TwinPrime.js) @@ -242,6 +265,7 @@ * [Problem023](Project-Euler/Problem023.js) * [Problem025](Project-Euler/Problem025.js) * [Problem028](Project-Euler/Problem028.js) + * [Problem035](Project-Euler/Problem035.js) * [Problem044](Project-Euler/Problem044.js) * **Recursive** * [BinaryEquivalent](Recursive/BinaryEquivalent.js) @@ -251,6 +275,7 @@ * [FibonacciNumberRecursive](Recursive/FibonacciNumberRecursive.js) * [FloodFill](Recursive/FloodFill.js) * [KochSnowflake](Recursive/KochSnowflake.js) + * [LetterCombination](Recursive/LetterCombination.js) * [Palindrome](Recursive/Palindrome.js) * [SubsequenceRecursive](Recursive/SubsequenceRecursive.js) * [TowerOfHanoi](Recursive/TowerOfHanoi.js) @@ -315,6 +340,7 @@ * [CheckRearrangePalindrome](String/CheckRearrangePalindrome.js) * [CheckSnakeCase](String/CheckSnakeCase.js) * [CheckWordOccurrence](String/CheckWordOccurrence.js) + * [CountLetters](String/CountLetters.js) * [CountSubstrings](String/CountSubstrings.js) * [CountVowels](String/CountVowels.js) * [CreatePermutations](String/CreatePermutations.js) @@ -330,6 +356,7 @@ * [MaxCharacter](String/MaxCharacter.js) * [MaxWord](String/MaxWord.js) * [PatternMatching](String/PatternMatching.js) + * [PercentageOfLetters](String/PercentageOfLetters.js) * [PermutateString](String/PermutateString.js) * [ReverseString](String/ReverseString.js) * [ReverseWords](String/ReverseWords.js) @@ -338,6 +365,7 @@ * [ValidateCreditCard](String/ValidateCreditCard.js) * [ValidateEmail](String/ValidateEmail.js) * [ValidateUrl](String/ValidateUrl.js) + * [ZFunction](String/ZFunction.js) * **Timing-Functions** * [GetMonthDays](Timing-Functions/GetMonthDays.js) * [IntervalTimer](Timing-Functions/IntervalTimer.js) diff --git a/Data-Structures/Array/Reverse.js b/Data-Structures/Array/Reverse.js new file mode 100644 index 000000000..79a789c01 --- /dev/null +++ b/Data-Structures/Array/Reverse.js @@ -0,0 +1,17 @@ +/** https://www.geeksforgeeks.org/write-a-program-to-Reverse-an-array-or-string/ + * This function will accept an array and + * Reverse its elements and returns the inverted array + * @param {Array} arr array with elements of any data type + * @returns {Array} array with inverted elements + */ + +const Reverse = (arr) => { + // limit specifies the amount of Reverse actions + for (let i = 0, j = arr.length - 1; i < arr.length / 2; i++, j--) { + const temp = arr[i] + arr[i] = arr[j] + arr[j] = temp + } + return arr +} +export { Reverse } diff --git a/Data-Structures/Array/test/Reverse.test.js b/Data-Structures/Array/test/Reverse.test.js new file mode 100644 index 000000000..bf137fe65 --- /dev/null +++ b/Data-Structures/Array/test/Reverse.test.js @@ -0,0 +1,13 @@ +import { Reverse } from '../Reverse.js' +import each from 'jest-each' + +describe('reverse elements in an array', () => { + each` + array | expected + ${[]} | ${[]} + ${[1]} | ${[1]} + ${[1, 2, 3, 4]} | ${[4, 3, 2, 1]} + `.test('returns $expected when given $array', ({ array, expected }) => { + expect(Reverse(array)).toEqual(expected) + }) +}) diff --git a/Data-Structures/Linked-List/ReverseSinglyLinkedList.js b/Data-Structures/Linked-List/ReverseSinglyLinkedList.js new file mode 100644 index 000000000..db4e7ef6d --- /dev/null +++ b/Data-Structures/Linked-List/ReverseSinglyLinkedList.js @@ -0,0 +1,16 @@ +/** A LinkedList based solution to reverse a number +Problem Statement: Given a number such that each of its digit is stored in a singly linked list. Reverse the linked list and return the head of the linked list Link for the Problem: https://leetcode.com/problems/reverse-linked-list/ */ +class ReverseSinglyLinkedList { + solution (head) { + let prev = null + let next = null + while (head) { + next = head.next + head.next = prev + prev = head + head = next + } + return prev + }; +} +export { ReverseSinglyLinkedList } diff --git a/Data-Structures/Linked-List/SinglyLinkedList.js b/Data-Structures/Linked-List/SinglyLinkedList.js index d55e0b14b..605ede401 100644 --- a/Data-Structures/Linked-List/SinglyLinkedList.js +++ b/Data-Structures/Linked-List/SinglyLinkedList.js @@ -274,6 +274,20 @@ class LinkedList { log () { console.log(JSON.stringify(this.headNode, null, 2)) } + + // Method to reverse the LinkedList + reverse () { + let head = this.headNode + let prev = null + let next = null + while (head) { + next = head.next + head.next = prev + prev = head + head = next + } + this.headNode = prev + }; } export { Node, LinkedList } diff --git a/Data-Structures/Linked-List/test/ReverseSinglyLinkedList.test.js b/Data-Structures/Linked-List/test/ReverseSinglyLinkedList.test.js new file mode 100644 index 000000000..847026db8 --- /dev/null +++ b/Data-Structures/Linked-List/test/ReverseSinglyLinkedList.test.js @@ -0,0 +1,14 @@ +import { ReverseSinglyLinkedList } from '../ReverseSinglyLinkedList' +import { Node } from '../SinglyLinkedList' +describe('ReverseSinglyLinkedList', () => { + it('Reverse a Number Represented as Linked List', () => { + const headNode = new Node(3) + headNode.next = new Node(4) + headNode.next.next = new Node(1) + const expected = new Node(1) + expected.next = new Node(4) + expected.next.next = new Node(3) + const reverseSinglyLinkedList = new ReverseSinglyLinkedList() + expect(reverseSinglyLinkedList.solution(headNode)).toEqual(expected) + }) +}) diff --git a/Data-Structures/Linked-List/test/SinglyLinkedList.test.js b/Data-Structures/Linked-List/test/SinglyLinkedList.test.js index 764cac7aa..a04d06ba1 100644 --- a/Data-Structures/Linked-List/test/SinglyLinkedList.test.js +++ b/Data-Structures/Linked-List/test/SinglyLinkedList.test.js @@ -247,4 +247,10 @@ describe('SinglyLinkedList', () => { headNode.rotateListRight(5) expect(headNode.get()).toEqual([20, 30, 40, 50, 10]) }) + + it('Reverse a Linked List', () => { + const list = new LinkedList([4, 3, 1]) + list.reverse() + expect(list.get()).toEqual([1, 3, 4]) + }) }) diff --git a/Data-Structures/Tree/SegmentTree.js b/Data-Structures/Tree/SegmentTree.js new file mode 100644 index 000000000..3778bb1e3 --- /dev/null +++ b/Data-Structures/Tree/SegmentTree.js @@ -0,0 +1,97 @@ +/** + * Segment Tree + * concept : [Wikipedia](https://en.wikipedia.org/wiki/Segment_tree) + * inspired by : https://www.geeksforgeeks.org/segment-tree-efficient-implementation/ + * + * time complexity + * - init : O(N) + * - update : O(log(N)) + * - query : O(log(N)) + * + * space complexity : O(N) + */ +class SegmentTree { + size + tree + constructor (arr) { + // we define tree like this + // tree[1] : root node of tree + // tree[i] : i'th node + // tree[i * 2] : i'th left child + // tree[i * 2 + 1] : i'th right child + // and we use bit, shift operation for index + this.size = arr.length + this.tree = new Array(2 * arr.length) + this.tree.fill(0) + + this.build(arr) + } + + // function to build the tree + build (arr) { + const { size, tree } = this + // insert leaf nodes in tree + // leaf nodes will start from index N + // in this array and will go up to index (2 * N โ€“ 1) + for (let i = 0; i < size; i++) { + tree[size + i] = arr[i] + } + + // build the tree by calculating parents + // tree's root node will contain all leaf node's sum + for (let i = size - 1; i > 0; --i) { + // current node's value is the sum of left child, right child + tree[i] = tree[i * 2] + tree[i * 2 + 1] + } + } + + update (index, value) { + const { size, tree } = this + + // only update values in the parents of the given node being changed. + // to get the parent move to parent node (index / 2) + + // set value at position index + index += size + // tree[index] is leaf node and index's value of array + tree[index] = value + + // move upward and update parents + for (let i = index; i > 1; i >>= 1) { + // i ^ 1 turns (2 * i) to (2 * i + 1) + // i ^ 1 is second child + tree[i >> 1] = tree[i] + tree[i ^ 1] + } + } + + // interval [L,R) with left index(L) included and right (R) excluded. + query (left, right) { + const { size, tree } = this + // cause R is excluded, increase right for convenient + right++ + let res = 0 + + // loop to find the sum in the range + for (left += size, right += size; left < right; left >>= 1, right >>= 1) { + // L is the left border of an query interval + + // if L is odd it means that it is the right child of its parent and our interval includes only L and not the parent. + // So we will simply include this node to sum and move to the parent of its next node by doing L = (L + 1) / 2. + + // if L is even it is the left child of its parent + // and the interval includes its parent also unless the right borders interfere. + if ((left & 1) > 0) { + res += tree[left++] + } + + // same in R (the right border of an query interval) + if ((right & 1) > 0) { + res += tree[--right] + } + } + + return res + } +} + +export { SegmentTree } diff --git a/Data-Structures/Tree/test/SegmentTree.test.js b/Data-Structures/Tree/test/SegmentTree.test.js new file mode 100644 index 000000000..440fbd828 --- /dev/null +++ b/Data-Structures/Tree/test/SegmentTree.test.js @@ -0,0 +1,16 @@ +import { SegmentTree } from '../SegmentTree' + +describe('SegmentTree sum test', () => { + const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + + const segment = new SegmentTree(a) + + it('init sum check', () => { + expect(segment.query(0, 2)).toBe(6) + }) + + it('init sum check', () => { + segment.update(2, 1) + expect(segment.query(0, 2)).toBe(4) + }) +}) diff --git a/Dynamic-Programming/UniquePaths.js b/Dynamic-Programming/UniquePaths.js new file mode 100644 index 000000000..4b3d67a1a --- /dev/null +++ b/Dynamic-Programming/UniquePaths.js @@ -0,0 +1,41 @@ + +/* + * + * Unique Paths + * + * There is a robot on an `m x n` grid. + * The robot is initially located at the top-left corner. + * The robot tries to move to the bottom-right corner. + * The robot can only move either down or right at any point in time. + * + * Given the two integers `m` and `n`, + * return the number of possible unique paths that the robot can take to reach the bottom-right corner. + * More info: https://leetcode.com/problems/unique-paths/ + */ + +/* + * @param {number} m + * @param {number} n + * @return {number} + */ + +const uniquePaths = (m, n) => { + // only one way to reach end + if (m === 1 || n === 1) return 1 + + // build a linear grid of size m + // base case, position 1 has only 1 move + const paths = new Array(m).fill(1) + + for (let i = 1; i < n; i++) { + for (let j = 1; j < m; j++) { + // paths[j] in RHS represents the cell value stored above the current cell + // paths[j-1] in RHS represents the cell value stored to the left of the current cell + // paths [j] on the LHS represents the number of distinct pathways to the cell (i,j) + paths[j] = paths[j - 1] + paths[j] + } + } + return paths[m - 1] +} + +export { uniquePaths } diff --git a/Dynamic-Programming/UniquePaths2.js b/Dynamic-Programming/UniquePaths2.js new file mode 100644 index 000000000..52bf9d08b --- /dev/null +++ b/Dynamic-Programming/UniquePaths2.js @@ -0,0 +1,76 @@ +/* + * Unique Paths 2 + * + * There is a robot on an `m x n` grid. + * The robot is initially located at the top-left corner + * The robot tries to move to the bottom-right corner. + * The robot can only move either down or right at any point in time. + * + * Given grid with obstacles + * An obstacle and space are marked as 1 or 0 respectively in grid. + * A path that the robot takes cannot include any square that is an obstacle. + * Return the number of possible unique paths that the robot can take to reach the bottom-right corner. + * + * More info: https://leetcode.com/problems/unique-paths-ii/ + */ + +/** + * @description Return 'rows x columns' grid with cells filled by 'filler' + * @param {Number} rows Number of rows in the grid + * @param {Number} columns Number of columns in the grid + * @param {String | Number | Boolean} filler The value to fill cells + * @returns {Array [][]} + */ +const generateMatrix = (rows, columns, filler = 0) => { + const matrix = [] + for (let i = 0; i < rows; i++) { + const submatrix = [] + for (let k = 0; k < columns; k++) { + submatrix[k] = filler + } + matrix[i] = submatrix + } + return matrix +} + +/** + * @description Return number of unique paths + * @param {Array [][]} obstacles Obstacles grid + * @returns {Number} + */ +const uniquePaths2 = (obstacles) => { + if (!Array.isArray(obstacles)) { + throw new Error('Input data must be type of Array') + } + // Create grid for calculating number of unique ways + const rows = obstacles.length + const columns = obstacles[0].length + const grid = generateMatrix(rows, columns) + // Fill the outermost cell with 1 b/c it has + // the only way to reach neighbor + for (let i = 0; i < rows; i++) { + // If robot encounters an obstacle in these cells, + // he cannot continue moving in that direction + if (obstacles[i][0]) { + break + } + grid[i][0] = 1 + } + for (let j = 0; j < columns; j++) { + if (obstacles[0][j]) { + break + } + grid[0][j] = 1 + } + // Fill the rest of grid by dynamic programming + // using following reccurent formula: + // K[i][j] = K[i - 1][j] + K[i][j - 1] + for (let i = 1; i < rows; i++) { + for (let j = 1; j < columns; j++) { + grid[i][j] = obstacles[i][j] ? 0 : grid[i - 1][j] + grid[i][j - 1] + } + } + return grid[rows - 1][columns - 1] +} + +export { uniquePaths2 } diff --git a/Dynamic-Programming/tests/UniquePaths.test.js b/Dynamic-Programming/tests/UniquePaths.test.js new file mode 100644 index 000000000..eb6f8c74d --- /dev/null +++ b/Dynamic-Programming/tests/UniquePaths.test.js @@ -0,0 +1,11 @@ +import { uniquePaths } from '../UniquePaths' + +describe('Unique Paths', () => { + it('should return 28 when m is 3 and n is 7', () => { + expect(uniquePaths(3, 7)).toBe(28) + }) + + it('should return 48620 when m is 10 and n is 10', () => { + expect(uniquePaths(10, 10)).toBe(48620) + }) +}) diff --git a/Dynamic-Programming/tests/UniquePaths2.test.js b/Dynamic-Programming/tests/UniquePaths2.test.js new file mode 100644 index 000000000..e34b9a330 --- /dev/null +++ b/Dynamic-Programming/tests/UniquePaths2.test.js @@ -0,0 +1,19 @@ +import { uniquePaths2 } from '../UniquePaths2' + +describe('Unique Paths2', () => { + // Should return number of ways, taken into account the obstacles + test('There are obstacles in the way', () => { + expect(uniquePaths2([[0, 0, 0], [0, 1, 0], [0, 0, 0]])).toEqual(2) + expect(uniquePaths2([[0, 0, 0], [0, 1, 0], [0, 0, 0], [1, 0, 0]])).toEqual(3) + }) + // Should return number of all possible ways to reach right-bottom corner + test('There are no obstacles in the way', () => { + expect(uniquePaths2([[0, 0, 0], [0, 0, 0], [0, 0, 0]])).toEqual(6) + expect(uniquePaths2([[0, 0, 0], [0, 0, 0]])).toEqual(3) + }) + // Should throw an exception b/c input data has wrong type + test('There are wrong type of input data', () => { + expect(() => uniquePaths2('wrong input')).toThrow() + expect(() => uniquePaths2(100)).toThrow() + }) +}) diff --git a/Geometry/Circle.js b/Geometry/Circle.js new file mode 100644 index 000000000..4b4f8e3b9 --- /dev/null +++ b/Geometry/Circle.js @@ -0,0 +1,19 @@ +/** + * This class represents a circle and can calculate it's perimeter and area + * https://en.wikipedia.org/wiki/Circle + * @constructor + * @param {number} radius - The radius of the circule. + */ +export default class Circle { + constructor (radius) { + this.radius = radius + } + + perimeter = () => { + return this.radius * 2 * Math.PI + } + + area = () => { + return Math.pow(this.radius, 2) * Math.PI + } +} diff --git a/Geometry/Cone.js b/Geometry/Cone.js new file mode 100644 index 000000000..742a527f4 --- /dev/null +++ b/Geometry/Cone.js @@ -0,0 +1,25 @@ +/** + * This class represents a circular cone and can calculate its volume and surface area + * https://en.wikipedia.org/wiki/Cone + * @constructor + * @param {number} baseRadius - The radius of the base of the cone. + * @param {number} height - The height of the cone + */ +export default class Cone { + constructor (baseRadius, height) { + this.baseRadius = baseRadius + this.height = height + } + + baseArea = () => { + return Math.pow(this.baseRadius, 2) * Math.PI + } + + volume = () => { + return this.baseArea() * this.height * 1 / 3 + } + + surfaceArea = () => { + return this.baseArea() + Math.PI * this.baseRadius * Math.sqrt(Math.pow(this.baseRadius, 2) + Math.pow(this.height, 2)) + } +} diff --git a/Geometry/Pyramid.js b/Geometry/Pyramid.js new file mode 100644 index 000000000..759b7376e --- /dev/null +++ b/Geometry/Pyramid.js @@ -0,0 +1,25 @@ +/** + * This class represents a regular pyramid and can calculate its volume and surface area + * https://en.wikipedia.org/wiki/Pyramid_(geometry) + * @constructor + * @param {number} bsl - The side length of the base of the pyramid. + * @param {number} height - The height of the pyramid + */ +export default class Pyramid { + constructor (bsl, height) { + this.bsl = bsl + this.height = height + } + + baseArea = () => { + return Math.pow(this.bsl, 2) + } + + volume = () => { + return this.baseArea() * this.height / 3 + } + + surfaceArea = () => { + return this.baseArea() + this.bsl * 4 / 2 * Math.sqrt(Math.pow(this.bsl / 2, 2) + Math.pow(this.height, 2)) + } +} diff --git a/Geometry/Sphere.js b/Geometry/Sphere.js new file mode 100644 index 000000000..82f539b91 --- /dev/null +++ b/Geometry/Sphere.js @@ -0,0 +1,19 @@ +/** + * This class represents a sphere and can calculate its volume and surface area + * @constructor + * @param {number} radius - The radius of the sphere + * @see https://en.wikipedia.org/wiki/Sphere + */ +export default class Sphere { + constructor (radius) { + this.radius = radius + } + + volume = () => { + return Math.pow(this.radius, 3) * Math.PI * 4 / 3 + } + + surfaceArea = () => { + return Math.pow(this.radius, 2) * Math.PI * 4 + } +} diff --git a/Geometry/Test/Circle.test.js b/Geometry/Test/Circle.test.js new file mode 100644 index 000000000..b2332df43 --- /dev/null +++ b/Geometry/Test/Circle.test.js @@ -0,0 +1,11 @@ +import Circle from '../Circle' + +const circle = new Circle(3) + +test('The area of a circle with radius equal to 3', () => { + expect(parseFloat(circle.area().toFixed(2))).toEqual(28.27) +}) + +test('The perimeter of a circle with radius equal to 3', () => { + expect(parseFloat(circle.perimeter().toFixed(2))).toEqual(18.85) +}) diff --git a/Geometry/Test/Cone.test.js b/Geometry/Test/Cone.test.js new file mode 100644 index 000000000..3ab001039 --- /dev/null +++ b/Geometry/Test/Cone.test.js @@ -0,0 +1,11 @@ +import Cone from '../Cone' + +const cone = new Cone(3, 5) + +test('The Volume of a cone with base radius equal to 3 and height equal to 5', () => { + expect(parseFloat(cone.volume().toFixed(2))).toEqual(47.12) +}) + +test('The Surface Area of a cone with base radius equal to 3 and height equal to 5', () => { + expect(parseFloat(cone.surfaceArea().toFixed(2))).toEqual(83.23) +}) diff --git a/Geometry/Test/Pyramid.test.js b/Geometry/Test/Pyramid.test.js new file mode 100644 index 000000000..a6d2b16d1 --- /dev/null +++ b/Geometry/Test/Pyramid.test.js @@ -0,0 +1,11 @@ +import Pyramid from '../Pyramid' + +const pyramid = new Pyramid(3, 5) + +test('The Volume of a cone with base radius equal to 3 and height equal to 5', () => { + expect(parseFloat(pyramid.volume().toFixed(2))).toEqual(15) +}) + +test('The Surface Area of a cone with base radius equal to 3 and height equal to 5', () => { + expect(parseFloat(pyramid.surfaceArea().toFixed(2))).toEqual(40.32) +}) diff --git a/Geometry/Test/Sphere.test.js b/Geometry/Test/Sphere.test.js new file mode 100644 index 000000000..18f8333a7 --- /dev/null +++ b/Geometry/Test/Sphere.test.js @@ -0,0 +1,11 @@ +import Sphere from '../Sphere' + +const sphere = new Sphere(3) + +test('The Volume of a sphere with base radius equal to 3 and height equal to 5', () => { + expect(parseFloat(sphere.volume().toFixed(2))).toEqual(113.1) +}) + +test('The Surface Area of a sphere with base radius equal to 3 and height equal to 5', () => { + expect(parseFloat(sphere.surfaceArea().toFixed(2))).toEqual(113.1) +}) diff --git a/Graphs/BinaryLifting.js b/Graphs/BinaryLifting.js new file mode 100644 index 000000000..40d664ba3 --- /dev/null +++ b/Graphs/BinaryLifting.js @@ -0,0 +1,82 @@ +/** + * Author: Adrito Mukherjee + * Binary Lifting implementation in Javascript + * Binary Lifting is a technique that is used to find the kth ancestor of a node in a rooted tree with N nodes + * The technique requires preprocessing the tree in O(N log N) using dynamic programming + * The techniqe can answer Q queries about kth ancestor of any node in O(Q log N) + * It is faster than the naive algorithm that answers Q queries with complexity O(Q K) + * It can be used to find Lowest Common Ancestor of two nodes in O(log N) + * Tutorial on Binary Lifting: https://codeforces.com/blog/entry/100826 + */ + +export class BinaryLifting { + constructor (root, tree) { + this.root = root + this.connections = new Map() + this.up = new Map() // up[node][i] stores the 2^i-th parent of node + for (const [i, j] of tree) { + this.addEdge(i, j) + } + this.log = Math.ceil(Math.log2(this.connections.size)) + this.dfs(root, root) + } + + addNode (node) { + // Function to add a node to the tree (connection represented by set) + this.connections.set(node, new Set()) + } + + addEdge (node1, node2) { + // Function to add an edge (adds the node too if they are not present in the tree) + if (!this.connections.has(node1)) { + this.addNode(node1) + } + if (!this.connections.has(node2)) { + this.addNode(node2) + } + this.connections.get(node1).add(node2) + this.connections.get(node2).add(node1) + } + + dfs (node, parent) { + // The dfs function calculates 2^i-th ancestor of all nodes for i ranging from 0 to this.log + // We make use of the fact the two consecutive jumps of length 2^(i-1) make the total jump length 2^i + this.up.set(node, new Map()) + this.up.get(node).set(0, parent) + for (let i = 1; i < this.log; i++) { + this.up + .get(node) + .set(i, this.up.get(this.up.get(node).get(i - 1)).get(i - 1)) + } + for (const child of this.connections.get(node)) { + if (child !== parent) this.dfs(child, node) + } + } + + kthAncestor (node, k) { + // if value of k is more than or equal to the number of total nodes, we return the root of the graph + if (k >= this.connections.size) { + return this.root + } + // if i-th bit is set in the binary representation of k, we jump from a node to its 2^i-th ancestor + // so after checking all bits of k, we will have made jumps of total length k, in just log k steps + for (let i = 0; i < this.log; i++) { + if (k & (1 << i)) { + node = this.up.get(node).get(i) + } + } + return node + } +} + +function binaryLifting (root, tree, queries) { + const graphObject = new BinaryLifting(root, tree) + const ancestors = [] + for (const [node, k] of queries) { + const ancestor = graphObject.kthAncestor(node, k) + ancestors.push(ancestor) + } + return ancestors +} + +export default binaryLifting diff --git a/Graphs/BreadthFirstSearch.js b/Graphs/BreadthFirstSearch.js index 1bee2e3cd..abac118ac 100644 --- a/Graphs/BreadthFirstSearch.js +++ b/Graphs/BreadthFirstSearch.js @@ -1,3 +1,5 @@ +import Queue from '../Data-Structures/Queue/Queue' + /** * Breadth-first search is an algorithm for traversing a graph. * @@ -12,11 +14,12 @@ export function breadthFirstSearch (graph, startingNode) { const visited = new Set() // queue contains the nodes to be explored in the future - const queue = [startingNode] + const queue = new Queue() + queue.enqueue(startingNode) - while (queue.length > 0) { + while (!queue.isEmpty()) { // start with the queue's first node - const node = queue.shift() + const node = queue.dequeue() if (!visited.has(node)) { // mark the node as visited @@ -25,7 +28,7 @@ export function breadthFirstSearch (graph, startingNode) { // put all its neighbors into the queue for (let i = 0; i < neighbors.length; i++) { - queue.push(neighbors[i]) + queue.enqueue(neighbors[i]) } } } diff --git a/Graphs/BreadthFirstShortestPath.js b/Graphs/BreadthFirstShortestPath.js index 39a23c75b..0e29b6440 100644 --- a/Graphs/BreadthFirstShortestPath.js +++ b/Graphs/BreadthFirstShortestPath.js @@ -1,3 +1,4 @@ +import Queue from '../Data-Structures/Queue/Queue' /** * Breadth-first approach can be applied to determine the shortest path between two nodes in an equi-weighted graph. * @@ -18,11 +19,12 @@ export function breadthFirstShortestPath (graph, startNode, targetNode) { // queue contains the paths to be explored in the future const initialPath = [startNode] - const queue = [initialPath] + const queue = new Queue() + queue.enqueue(initialPath) - while (queue.length > 0) { + while (!queue.isEmpty()) { // start with the queue's first path - const path = queue.shift() + const path = queue.dequeue() const node = path[path.length - 1] // explore this node if it hasn't been visited yet @@ -42,7 +44,7 @@ export function breadthFirstShortestPath (graph, startNode, targetNode) { } // queue the new path - queue.push(newPath) + queue.enqueue(newPath) } } } diff --git a/Graphs/Kosaraju.js b/Graphs/Kosaraju.js new file mode 100644 index 000000000..a0b4266b0 --- /dev/null +++ b/Graphs/Kosaraju.js @@ -0,0 +1,100 @@ +/** + * Author: Adrito Mukherjee + * Kosaraju's Algorithm implementation in Javascript + * Kosaraju's Algorithm finds all the connected components in a Directed Acyclic Graph (DAG) + * It uses Stack data structure to store the Topological Sorted Order of vertices and also Graph data structure + * + * Wikipedia: https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm + * + */ + +class Kosaraju { + constructor (graph) { + this.connections = {} + this.reverseConnections = {} + this.stronglyConnectedComponents = [] + for (const [i, j] of graph) { + this.addEdge(i, j) + } + this.topoSort() + return this.kosaraju() + } + + addNode (node) { + // Function to add a node to the graph (connection represented by set) + this.connections[node] = new Set() + this.reverseConnections[node] = new Set() + this.topoSorted = [] + } + + addEdge (node1, node2) { + // Function to add an edge (adds the node too if they are not present in the graph) + if (!(node1 in this.connections) || !(node1 in this.reverseConnections)) { + this.addNode(node1) + } + if (!(node2 in this.connections) || !(node2 in this.reverseConnections)) { + this.addNode(node2) + } + this.connections[node1].add(node2) + this.reverseConnections[node2].add(node1) + } + + dfsTopoSort (node, visited) { + visited.add(node) + for (const child of this.connections[node]) { + if (!visited.has(child)) this.dfsTopoSort(child, visited) + } + this.topoSorted.push(node) + } + + topoSort () { + // Function to perform topological sorting + const visited = new Set() + const nodes = Object.keys(this.connections).map((key) => Number(key)) + for (const node of nodes) { + if (!visited.has(node)) this.dfsTopoSort(node, visited) + } + } + + dfsKosaraju (node, visited) { + visited.add(node) + this.stronglyConnectedComponents[ + this.stronglyConnectedComponents.length - 1 + ].push(node) + for (const child of this.reverseConnections[node]) { + if (!visited.has(child)) this.dfsKosaraju(child, visited) + } + } + + kosaraju () { + // Function to perform Kosaraju Algorithm + const visited = new Set() + while (this.topoSorted.length > 0) { + const node = this.topoSorted.pop() + if (!visited.has(node)) { + this.stronglyConnectedComponents.push([]) + this.dfsKosaraju(node, visited) + } + } + return this.stronglyConnectedComponents + } +} + +function kosaraju (graph) { + const stronglyConnectedComponents = new Kosaraju(graph) + return stronglyConnectedComponents +} + +export { kosaraju } + +// kosaraju([ +// [1, 2], +// [2, 3], +// [3, 1], +// [2, 4], +// [4, 5], +// [5, 6], +// [6, 4], +// ]) + +// [ [ 1, 3, 2 ], [ 4, 6, 5 ] ] diff --git a/Graphs/LCABinaryLifting.js b/Graphs/LCABinaryLifting.js new file mode 100644 index 000000000..211f22f24 --- /dev/null +++ b/Graphs/LCABinaryLifting.js @@ -0,0 +1,61 @@ +/** + * Author: Adrito Mukherjee + * Findind Lowest Common Ancestor By Binary Lifting implementation in JavaScript + * The technique requires preprocessing the tree in O(N log N) using dynamic programming) + * It can be used to find Lowest Common Ancestor of two nodes in O(log N) + * Tutorial on Lowest Common Ancestor: https://www.geeksforgeeks.org/lca-in-a-tree-using-binary-lifting-technique + */ + +import { BinaryLifting } from './BinaryLifting' + +class LCABinaryLifting extends BinaryLifting { + constructor (root, tree) { + super(root, tree) + this.depth = new Map() // depth[node] stores the depth of node from root + this.depth.set(root, 1) + this.dfsDepth(root, root) + } + + dfsDepth (node, parent) { + // DFS to find depth of every node in the tree + for (const child of this.connections.get(node)) { + if (child !== parent) { + this.depth.set(child, this.depth.get(node) + 1) + this.dfsDepth(child, node) + } + } + } + + getLCA (node1, node2) { + // We make sure that node1 is the deeper node among node1 and node2 + if (this.depth.get(node1) < this.depth.get(node2)) { + [node1, node2] = [node2, node1] + } + // We check if node1 is the ancestor of node2, and if so, then return node1 + const k = this.depth.get(node1) - this.depth.get(node2) + node1 = this.kthAncestor(node1, k) + if (node1 === node2) { + return node1 + } + + for (let i = this.log - 1; i >= 0; i--) { + if (this.up.get(node1).get(i) !== this.up.get(node2).get(i)) { + node1 = this.up.get(node1).get(i) + node2 = this.up.get(node2).get(i) + } + } + return this.up.get(node1).get(0) + } +} + +function lcaBinaryLifting (root, tree, queries) { + const graphObject = new LCABinaryLifting(root, tree) + const lowestCommonAncestors = [] + for (const [node1, node2] of queries) { + const lca = graphObject.getLCA(node1, node2) + lowestCommonAncestors.push(lca) + } + return lowestCommonAncestors +} + +export default lcaBinaryLifting diff --git a/Graphs/test/BinaryLifting.test.js b/Graphs/test/BinaryLifting.test.js new file mode 100644 index 000000000..769b877e0 --- /dev/null +++ b/Graphs/test/BinaryLifting.test.js @@ -0,0 +1,82 @@ +import binaryLifting from '../BinaryLifting' + +// The graph for Test Case 1 looks like this: +// +// 0 +// /|\ +// / | \ +// 1 3 5 +// / \ \ +// 2 4 6 +// \ +// 7 +// / \ +// 11 8 +// \ +// 9 +// \ +// 10 + +test('Test case 1', () => { + const root = 0 + const graph = [ + [0, 1], + [0, 3], + [0, 5], + [5, 6], + [1, 2], + [1, 4], + [4, 7], + [7, 11], + [7, 8], + [8, 9], + [9, 10] + ] + const queries = [ + [2, 1], + [6, 1], + [7, 2], + [8, 2], + [10, 2], + [10, 3], + [10, 5], + [11, 3] + ] + const kthAncestors = binaryLifting(root, graph, queries) + expect(kthAncestors).toEqual([1, 5, 1, 4, 8, 7, 1, 1]) +}) + +// The graph for Test Case 2 looks like this: +// +// 0 +// / \ +// 1 2 +// / \ \ +// 3 4 5 +// / / \ +// 6 7 8 + +test('Test case 2', () => { + const root = 0 + const graph = [ + [0, 1], + [0, 2], + [1, 3], + [1, 4], + [2, 5], + [3, 6], + [5, 7], + [5, 8] + ] + const queries = [ + [2, 1], + [3, 1], + [3, 2], + [6, 2], + [7, 3], + [8, 2], + [8, 3] + ] + const kthAncestors = binaryLifting(root, graph, queries) + expect(kthAncestors).toEqual([0, 1, 0, 1, 0, 2, 0]) +}) diff --git a/Graphs/test/Kosaraju.test.js b/Graphs/test/Kosaraju.test.js new file mode 100644 index 000000000..a2da394db --- /dev/null +++ b/Graphs/test/Kosaraju.test.js @@ -0,0 +1,30 @@ +import { kosaraju } from '../Kosaraju.js' + +test('Test Case 1', () => { + const graph = [ + [1, 2], + [2, 3], + [3, 1], + [2, 4], + [4, 5], + [5, 6], + [6, 4] + ] + const stronglyConnectedComponents = kosaraju(graph) + expect(stronglyConnectedComponents).toStrictEqual([ + [1, 3, 2], + [4, 6, 5] + ]) +}) + +test('Test Case 2', () => { + const graph = [ + [1, 2], + [2, 3], + [3, 1], + [2, 4], + [4, 5] + ] + const stronglyConnectedComponents = kosaraju(graph) + expect(stronglyConnectedComponents).toStrictEqual([[1, 3, 2], [4], [5]]) +}) diff --git a/Graphs/test/LCABinaryLifting.test.js b/Graphs/test/LCABinaryLifting.test.js new file mode 100644 index 000000000..db77c9135 --- /dev/null +++ b/Graphs/test/LCABinaryLifting.test.js @@ -0,0 +1,80 @@ +import lcaBinaryLifting from '../LCABinaryLifting' + +// The graph for Test Case 1 looks like this: +// +// 0 +// /|\ +// / | \ +// 1 3 5 +// / \ \ +// 2 4 6 +// \ +// 7 +// / \ +// 11 8 +// \ +// 9 +// \ +// 10 + +test('Test case 1', () => { + const root = 0 + const graph = [ + [0, 1], + [0, 3], + [0, 5], + [5, 6], + [1, 2], + [1, 4], + [4, 7], + [7, 11], + [7, 8], + [8, 9], + [9, 10] + ] + const queries = [ + [1, 3], + [6, 5], + [3, 6], + [7, 10], + [8, 10], + [11, 2], + [11, 10] + ] + const lowestCommonAncestors = lcaBinaryLifting(root, graph, queries) + expect(lowestCommonAncestors).toEqual([0, 5, 0, 7, 8, 1, 7]) +}) + +// The graph for Test Case 2 looks like this: +// +// 0 +// / \ +// 1 2 +// / \ \ +// 3 4 5 +// / / \ +// 6 7 8 + +test('Test case 2', () => { + const root = 0 + const graph = [ + [0, 1], + [0, 2], + [1, 3], + [1, 4], + [2, 5], + [3, 6], + [5, 7], + [5, 8] + ] + const queries = [ + [1, 2], + [3, 4], + [5, 4], + [6, 7], + [6, 8], + [7, 8] + ] + const lowestCommonAncestors = lcaBinaryLifting(root, graph, queries) + expect(lowestCommonAncestors).toEqual([0, 1, 0, 0, 0, 5]) +}) diff --git a/Maths/ExponentialFunction.js b/Maths/ExponentialFunction.js new file mode 100644 index 000000000..421269381 --- /dev/null +++ b/Maths/ExponentialFunction.js @@ -0,0 +1,25 @@ +/** + * @function exponentialFunction + * @description Calculates the n+1 th order Taylor series approximation of exponential function e^x given n + * @param {Integer} power + * @param {Integer} order - 1 + * @returns exponentialFunction(2,20) = 7.3890560989301735 + * @url https://en.wikipedia.org/wiki/Exponential_function + */ +function exponentialFunction (power, n) { + let output = 0 + let fac = 1 + if (isNaN(power) || isNaN(n) || n < 0) { + throw new TypeError('Invalid Input') + } + if (n === 0) { return 1 } + for (let i = 0; i < n; i++) { + output += (power ** i) / fac + fac *= (i + 1) + } + return output +} + +export { + exponentialFunction +} diff --git a/Maths/FindLcm.js b/Maths/FindLcm.js index 101da8565..e75a89fd7 100644 --- a/Maths/FindLcm.js +++ b/Maths/FindLcm.js @@ -11,16 +11,18 @@ 'use strict' +import { findHCF } from './FindHcf' + // Find the LCM of two numbers. const findLcm = (num1, num2) => { // If the input numbers are less than 1 return an error message. if (num1 < 1 || num2 < 1) { - return 'Please enter values greater than zero.' + throw Error('Numbers must be positive.') } // If the input numbers are not integers return an error message. if (num1 !== Math.round(num1) || num2 !== Math.round(num2)) { - return 'Please enter whole numbers.' + throw Error('Numbers must be whole.') } // Get the larger number between the two @@ -33,4 +35,19 @@ const findLcm = (num1, num2) => { } } -export { findLcm } +// Typically, but not always, more efficient +const findLcmWithHcf = (num1, num2) => { + // If the input numbers are less than 1 return an error message. + if (num1 < 1 || num2 < 1) { + throw Error('Numbers must be positive.') + } + + // If the input numbers are not integers return an error message. + if (num1 !== Math.round(num1) || num2 !== Math.round(num2)) { + throw Error('Numbers must be whole.') + } + + return num1 * num2 / findHCF(num1, num2) +} + +export { findLcm, findLcmWithHcf } diff --git a/Maths/FriendlyNumbers.js b/Maths/FriendlyNumbers.js new file mode 100644 index 000000000..a9b6b37ec --- /dev/null +++ b/Maths/FriendlyNumbers.js @@ -0,0 +1,32 @@ +/* + 'In number theory, friendly numbers are two or more natural numbers with a common abundancy index, the + ratio between the sum of divisors of a number and the number itself.' + Source: https://en.wikipedia.org/wiki/Friendly_number + See also: https://mathworld.wolfram.com/FriendlyNumber.html#:~:text=The%20numbers%20known%20to%20be,numbers%20have%20a%20positive%20density. +*/ + +export const FriendlyNumbers = (firstNumber, secondNumber) => { + // input: two integers + // output: true if the two integers are friendly numbers, false if they are not friendly numbers + + // First, check that the parameters are valid + if (!Number.isInteger(firstNumber) || !Number.isInteger(secondNumber) || firstNumber === 0 || secondNumber === 0 || firstNumber === secondNumber) { + throw new Error('The two parameters must be distinct, non-null integers') + } + + return abundancyIndex(firstNumber) === abundancyIndex(secondNumber) +} + +function abundancyIndex (number) { + return sumDivisors(number) / number +} + +function sumDivisors (number) { + let runningSumDivisors = number + for (let i = 0; i < number / 2; i++) { + if (Number.isInteger(number / i)) { + runningSumDivisors += i + } + } + return runningSumDivisors +} diff --git a/Maths/HexagonalNumber.js b/Maths/HexagonalNumber.js new file mode 100644 index 000000000..31fac7ea7 --- /dev/null +++ b/Maths/HexagonalNumber.js @@ -0,0 +1,21 @@ +/* + * Author: Akshay Dubey (https://github.com/itsAkshayDubey) + * Hexagonal Number: https://en.wikipedia.org/wiki/Hexagonal_number + * The nth hexagonal number hn is the number of distinct dots in a pattern of dots + * consisting of the outlines of regular hexagons with sides up to n dots, when the + * hexagons are overlaid so that they share one vertex. + */ + +/** + * @function hexagonalNumber + * @description -> returns nth hexagonal number + * @param {Integer} number + * @returns {Integer} nth hexagonal number + */ + +export const hexagonalNumber = (number) => { + if (number <= 0) { + throw new Error('Number must be greater than zero.') + } + return number * (2 * number - 1) +} diff --git a/Maths/IntToBase.js b/Maths/IntToBase.js new file mode 100644 index 000000000..6ec8a0bab --- /dev/null +++ b/Maths/IntToBase.js @@ -0,0 +1,40 @@ +/** + * @function intToBase + * @description Convert a number from decimal system to another (till decimal) + * @param {Number} number Number to be converted + * @param {Number} base Base of new number system + * @returns {String} Converted Number + * @see [HornerMethod](https://en.wikipedia.org/wiki/Horner%27s_method) + * @example + * const num1 = 125 // Needs to be converted to the binary number system + * gornerScheme(num, 2); // ===> 1111101 + * @example + * const num2 = 125 // Needs to be converted to the octal number system + * gornerScheme(num, 8); // ===> 175 + */ +const intToBase = (number, base) => { + if (typeof number !== 'number' || typeof base !== 'number') { + throw new Error('Input data must be numbers') + } + // Zero in any number system is zero + if (number === 0) { + return '0' + } + let absoluteValue = Math.abs(number) + let convertedNumber = '' + while (absoluteValue > 0) { + // Every iteration last digit is taken away + // and added to the previous one + const lastDigit = absoluteValue % base + convertedNumber = lastDigit + convertedNumber + absoluteValue = Math.trunc(absoluteValue / base) + } + // Result is whether negative or positive, + // depending on the original value + if (number < 0) { + convertedNumber = '-' + convertedNumber + } + return convertedNumber +} + +export { intToBase } diff --git a/Maths/ModularArithmetic.js b/Maths/ModularArithmetic.js new file mode 100644 index 000000000..26c54ebbb --- /dev/null +++ b/Maths/ModularArithmetic.js @@ -0,0 +1,56 @@ +import { extendedEuclideanGCD } from './ExtendedEuclideanGCD' + +/** + * https://brilliant.org/wiki/modular-arithmetic/ + * @param {Number} arg1 first argument + * @param {Number} arg2 second argument + * @returns {Number} + */ + +export class ModRing { + constructor (MOD) { + this.MOD = MOD + } + + isInputValid = (arg1, arg2) => { + if (!this.MOD) { + throw new Error('Modulus must be initialized in the object constructor') + } + if (typeof arg1 !== 'number' || typeof arg2 !== 'number') { + throw new TypeError('Input must be Numbers') + } + } + /** + * Modulus is Distributive property, + * As a result, we separate it into numbers in order to keep it within MOD's range + */ + + add = (arg1, arg2) => { + this.isInputValid(arg1, arg2) + return ((arg1 % this.MOD) + (arg2 % this.MOD)) % this.MOD + } + + subtract = (arg1, arg2) => { + this.isInputValid(arg1, arg2) + // An extra MOD is added to check negative results + return ((arg1 % this.MOD) - (arg2 % this.MOD) + this.MOD) % this.MOD + } + + multiply = (arg1, arg2) => { + this.isInputValid(arg1, arg2) + return ((arg1 % this.MOD) * (arg2 % this.MOD)) % this.MOD + } + + /** + * + * It is not Possible to find Division directly like the above methods, + * So we have to use the Extended Euclidean Theorem for finding Multiplicative Inverse + * https://github.com/TheAlgorithms/JavaScript/blob/master/Maths/ExtendedEuclideanGCD.js + */ + + divide = (arg1, arg2) => { + // 1st Index contains the required result + // The theorem may have return Negative value, we need to add MOD to make it Positive + return (extendedEuclideanGCD(arg1, arg2)[1] + this.MOD) % this.MOD + } +} diff --git a/Maths/SieveOfEratosthenesIntArray.js b/Maths/SieveOfEratosthenesIntArray.js new file mode 100644 index 000000000..7de49ffb7 --- /dev/null +++ b/Maths/SieveOfEratosthenesIntArray.js @@ -0,0 +1,22 @@ +/** + * Function to get all prime numbers below a given number + * This function returns an array of prime numbers + * @see {@link https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes} + */ + +function sieveOfEratosthenes (max) { + const sieve = [] + const primes = [] + + for (let i = 2; i <= max; ++i) { + if (!sieve[i]) { // If i has not been marked then it is prime + primes.push(i) + for (let j = i << 1; j <= max; j += i) { // Mark all multiples of i as non-prime + sieve[j] = true + } + } + } + return primes +} + +export { sieveOfEratosthenes } diff --git a/Maths/Signum.js b/Maths/Signum.js new file mode 100644 index 000000000..35047cac1 --- /dev/null +++ b/Maths/Signum.js @@ -0,0 +1,25 @@ +/* + A program to demonstrate the implementation of the signum function, + also known as the sign function, in JavaScript. + + The signum function is an odd mathematical function, which returns the + sign of the provided real number. + It can return 3 values: 1 for values greater than zero, 0 for zero itself, + and -1 for values less than zero + + Wikipedia: https://en.wikipedia.org/wiki/Sign_function +*/ + +/** + * @param {Number} input + * @returns {-1 | 0 | 1 | NaN} sign of input (and NaN if the input is not a number) + */ +function signum (input) { + if (input === 0) return 0 + if (input > 0) return 1 + if (input < 0) return -1 + + return NaN +} + +export { signum } diff --git a/Maths/SquareRootLogarithmic.js b/Maths/SquareRootLogarithmic.js new file mode 100644 index 000000000..e9b54aed3 --- /dev/null +++ b/Maths/SquareRootLogarithmic.js @@ -0,0 +1,41 @@ +/** + * @function squareRootLogarithmic + * @description + * Return the square root of 'num' rounded down + * to the nearest integer. + * More info: https://leetcode.com/problems/sqrtx/ + * @param {Number} num Number whose square of root is to be found + * @returns {Number} Square root + * @see [BinarySearch](https://en.wikipedia.org/wiki/Binary_search_algorithm) + * @example + * const num1 = 4 + * logarithmicSquareRoot(num1) // ====> 2 + * @example + * const num2 = 8 + * logarithmicSquareRoot(num1) // ====> 2 + * + */ +const squareRootLogarithmic = (num) => { + if (typeof num !== 'number') { + throw new Error('Input data must be numbers') + } + let answer = 0 + let sqrt = 0 + let edge = num + + while (sqrt <= edge) { + const mid = Math.trunc((sqrt + edge) / 2) + if (mid * mid === num) { + return mid + } else if (mid * mid < num) { + sqrt = mid + 1 + answer = mid + } else { + edge = mid - 1 + } + } + + return answer +} + +export { squareRootLogarithmic } diff --git a/Maths/test/ExponentialFunction.test.js b/Maths/test/ExponentialFunction.test.js new file mode 100644 index 000000000..d1eed4895 --- /dev/null +++ b/Maths/test/ExponentialFunction.test.js @@ -0,0 +1,16 @@ +import { exponentialFunction } from '../ExponentialFunction' + +describe('Tests for exponential function', () => { + it('should be a function', () => { + expect(typeof exponentialFunction).toEqual('function') + }) + + it('should throw error for invalid input', () => { + expect(() => exponentialFunction(2, -34)).toThrow() + }) + + it('should return the exponential function of power of 5 and order of 21', () => { + const ex = exponentialFunction(5, 20) + expect(ex).toBe(148.4131078683383) + }) +}) diff --git a/Maths/test/FindLcm.test.js b/Maths/test/FindLcm.test.js index 1e5c0905c..0a744cf5a 100644 --- a/Maths/test/FindLcm.test.js +++ b/Maths/test/FindLcm.test.js @@ -1,20 +1,39 @@ -import { findLcm } from '../FindLcm' +import { findLcm, findLcmWithHcf } from '../FindLcm' describe('findLcm', () => { it('should throw a statement for values less than 1', () => { - expect(findLcm(0, 0)).toBe('Please enter values greater than zero.') + expect(() => { findLcm(0, 0) }).toThrow(Error) }) it('should throw a statement for one value less than 1', () => { - expect(findLcm(1, 0)).toBe('Please enter values greater than zero.') - expect(findLcm(0, 1)).toBe('Please enter values greater than zero.') + expect(() => { findLcm(1, 0) }).toThrow(Error) + expect(() => { findLcm(0, 1) }).toThrow(Error) }) it('should return an error for values non-integer values', () => { - expect(findLcm(4.564, 7.39)).toBe('Please enter whole numbers.') + expect(() => { findLcm(4.564, 7.39) }).toThrow(Error) }) it('should return the LCM of two given integers', () => { expect(findLcm(27, 36)).toBe(108) }) }) + +describe('findLcmWithHcf', () => { + it('should throw a statement for values less than 1', () => { + expect(() => { findLcmWithHcf(0, 0) }).toThrow(Error) + }) + + it('should throw a statement for one value less than 1', () => { + expect(() => { findLcmWithHcf(1, 0) }).toThrow(Error) + expect(() => { findLcmWithHcf(0, 1) }).toThrow(Error) + }) + + it('should return an error for values non-integer values', () => { + expect(() => { findLcmWithHcf(4.564, 7.39) }).toThrow(Error) + }) + + it('should return the LCM of two given integers', () => { + expect(findLcmWithHcf(27, 36)).toBe(108) + }) +}) diff --git a/Maths/test/HexagonalNumber.test.js b/Maths/test/HexagonalNumber.test.js new file mode 100644 index 000000000..ebfc1cc73 --- /dev/null +++ b/Maths/test/HexagonalNumber.test.js @@ -0,0 +1,19 @@ +import { hexagonalNumber } from '../HexagonalNumber' + +const expectedValuesArray = [1, 6, 15, 28, 45, 66, 91, 120, 153, 190, 231, 276, 325, 378, 435, 496, 561, 630, 703, 780, 861, 946] + +describe('Testing hexagonalNumber', () => { + for (let i = 1; i <= 22; i++) { + it('Testing for number = ' + i + ', should return ' + expectedValuesArray[i], () => { + expect(hexagonalNumber(i)).toBe(expectedValuesArray[i - 1]) + }) + } + + it('should throw error when supplied negative numbers', () => { + expect(() => { hexagonalNumber(-1) }).toThrow(Error) + }) + + it('should throw error when supplied zero', () => { + expect(() => { hexagonalNumber(0) }).toThrow(Error) + }) +}) diff --git a/Maths/test/IntToBase.test.js b/Maths/test/IntToBase.test.js new file mode 100644 index 000000000..9ebcbbf48 --- /dev/null +++ b/Maths/test/IntToBase.test.js @@ -0,0 +1,25 @@ +import { intToBase } from '../IntToBase' + +describe('Int to Base', () => { + test('Conversion to the binary system', () => { + expect(intToBase(210, 2)).toEqual('11010010') + expect(intToBase(-210, 2)).toEqual('-11010010') + }) + test('Conversion to the system with base 5', () => { + expect(intToBase(210, 5)).toEqual('1320') + expect(intToBase(-210, 5)).toEqual('-1320') + }) + test('Conversion to the octal system', () => { + expect(intToBase(210, 8)).toEqual('322') + expect(intToBase(-210, 8)).toEqual('-322') + }) + test('Output is 0', () => { + expect(intToBase(0, 8)).toEqual('0') + expect(intToBase(0, 8)).toEqual('0') + }) + test('Throwing an exception', () => { + expect(() => intToBase('string', 2)).toThrow() + expect(() => intToBase(10, 'base')).toThrow() + expect(() => intToBase(true, false)).toThrow() + }) +}) diff --git a/Maths/test/ModularArithmetic.test.js b/Maths/test/ModularArithmetic.test.js new file mode 100644 index 000000000..af42e243d --- /dev/null +++ b/Maths/test/ModularArithmetic.test.js @@ -0,0 +1,45 @@ +import { ModRing } from '../ModularArithmetic' + +describe('Modular Arithmetic', () => { + const MOD = 10000007 + let ring + beforeEach(() => { + ring = new ModRing(MOD) + }) + + describe('add', () => { + it('Should return 9999993 for 10000000 and 10000000', () => { + expect(ring.add(10000000, 10000000)).toBe(9999993) + }) + it('Should return 9999986 for 10000000 and 20000000', () => { + expect(ring.add(10000000, 20000000)).toBe(9999986) + }) + }) + + describe('subtract', () => { + it('Should return 1000000 for 10000000 and 9000000', () => { + expect(ring.subtract(10000000, 9000000)).toBe(1000000) + }) + it('Should return 7 for 10000000 and 20000000', () => { + expect(ring.subtract(10000000, 20000000)).toBe(7) + }) + }) + + describe('multiply', () => { + it('Should return 1000000 for 100000 and 10000', () => { + expect(ring.multiply(100000, 10000)).toBe(9999307) + }) + it('Should return 7 for 100000 and 10000100', () => { + expect(ring.multiply(10000000, 20000000)).toBe(98) + }) + }) + + describe('divide', () => { + it('Should return 4 for 3 and 11', () => { + expect(ring.divide(3, 11)).toBe(4) + }) + it('Should return 2 for 18 and 7', () => { + expect(ring.divide(18, 7)).toBe(2) + }) + }) +}) diff --git a/Maths/test/SieveOfEratosthenesIntArray.test.js b/Maths/test/SieveOfEratosthenesIntArray.test.js new file mode 100644 index 000000000..eeb6dd9d8 --- /dev/null +++ b/Maths/test/SieveOfEratosthenesIntArray.test.js @@ -0,0 +1,12 @@ +import { sieveOfEratosthenes } from '../SieveOfEratosthenesIntArray' +import { PrimeCheck } from '../PrimeCheck' + +describe('should return an array of prime numbers', () => { + it('should have each element in the array as a prime numbers', () => { + const n = 100 + const primes = sieveOfEratosthenes(n) + primes.forEach(prime => { + expect(PrimeCheck(prime)).toBeTruthy() + }) + }) +}) diff --git a/Maths/test/Signum.test.js b/Maths/test/Signum.test.js new file mode 100644 index 000000000..ac00676a0 --- /dev/null +++ b/Maths/test/Signum.test.js @@ -0,0 +1,19 @@ +import { signum } from '../Signum' + +describe('The sign of a number', () => { + it('Sign of 10', () => { + expect(signum(10)).toBe(1) + }) + + it('Sign of 0', () => { + expect(signum(0)).toBe(0) + }) + + it('Sign of -420', () => { + expect(signum(-420)).toBe(-1) + }) + + it('Sign of NaN', () => { + expect(signum(NaN)).toBe(NaN) + }) +}) diff --git a/Maths/test/SquareRootLogarithmic.test.js b/Maths/test/SquareRootLogarithmic.test.js new file mode 100644 index 000000000..6eec49d23 --- /dev/null +++ b/Maths/test/SquareRootLogarithmic.test.js @@ -0,0 +1,13 @@ +import { squareRootLogarithmic } from '../SquareRootLogarithmic' + +describe('SquareRootLogarithmic', () => { + test('Finding the square root of a positive integer', () => { + expect(squareRootLogarithmic(4)).toEqual(2) + expect(squareRootLogarithmic(16)).toEqual(4) + expect(squareRootLogarithmic(8)).toEqual(2) + }) + test('Throwing an exception', () => { + expect(() => squareRootLogarithmic('not a number')).toThrow() + expect(() => squareRootLogarithmic(true)).toThrow() + }) +}) diff --git a/Project-Euler/Problem035.js b/Project-Euler/Problem035.js new file mode 100644 index 000000000..f422defcd --- /dev/null +++ b/Project-Euler/Problem035.js @@ -0,0 +1,35 @@ +/** + * Problem 35 - Circular primes + * + * @see {@link https://projecteuler.net/problem=35} + * + * The number, 197, is called a circular prime because all rotations of the digits: 197, 971, and 719, are themselves prime. + * There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, and 97. + * How many circular primes are there below one million? + * + * @author ddaniel27 + */ +import { sieveOfEratosthenes } from '../Maths/SieveOfEratosthenesIntArray' + +function problem35 (n) { + if (n < 2) { + throw new Error('Invalid input') + } + // Get a list of primes without 0, 2, 4, 5, 6, 8; this discards the circular primes 2 & 5 + const list = sieveOfEratosthenes(n).filter(prime => !prime.toString().match(/[024568]/)) + + const result = list.filter((number, _idx, arr) => { + const str = String(number) + for (let i = 0; i < str.length; i++) { // Get all rotations of the number + const rotation = str.slice(i) + str.slice(0, i) + if (!arr.includes(Number(rotation))) { // Check if the rotation is prime + return false + } + } + return true // If all rotations are prime, then the number is circular prime + }) + + return result.length + 2 // Add 2 to the result because the circular primes 2 & 5 were discarded +} + +export { problem35 } diff --git a/Project-Euler/test/Problem003.test.js b/Project-Euler/test/Problem003.test.js new file mode 100644 index 000000000..0f6cf4e6d --- /dev/null +++ b/Project-Euler/test/Problem003.test.js @@ -0,0 +1,12 @@ +import { largestPrime } from '../Problem003.js' + +describe('Largest prime factor', () => { + test('if the number is 13195', () => { + expect(largestPrime(13195)).toBe(29) + }) + // Project Euler Condition Check + test('if the number is 600851475143', () => { + // Default value is same as the tested value + expect(largestPrime()).toBe(6857) + }) +}) diff --git a/Project-Euler/test/Problem035.test.js b/Project-Euler/test/Problem035.test.js new file mode 100644 index 000000000..ebaa4ac46 --- /dev/null +++ b/Project-Euler/test/Problem035.test.js @@ -0,0 +1,18 @@ +import { problem35 } from '../Problem035.js' + +describe('checking circular primes', () => { + it('should be invalid input if number is negative', () => { + expect(() => problem35(-3)).toThrowError('Invalid input') + }) + it('should be invalid input if number is 0', () => { + expect(() => problem35(0)).toThrowError('Invalid input') + }) + // Project Euler Condition Check + test('if the number is equal to 100 result should be 13', () => { + expect(problem35(100)).toBe(13) + }) + // Project Euler Challenge Check + test('if the number is equal to one million result should be 55', () => { + expect(problem35(1000000)).toBe(55) + }) +}) diff --git a/Project-Euler/test/Problem044.test.js b/Project-Euler/test/Problem044.test.js index bf6cccd60..b542b3db6 100644 --- a/Project-Euler/test/Problem044.test.js +++ b/Project-Euler/test/Problem044.test.js @@ -1,10 +1,10 @@ import { problem44 } from '../Problem044.js' describe('checking nth prime number', () => { - it('should be invalid input if number is negative', () => { + test('should be invalid input if number is negative', () => { expect(() => problem44(-3)).toThrowError('Invalid Input') }) - it('should be invalid input if number is 0', () => { + test('should be invalid input if number is 0', () => { expect(() => problem44(0)).toThrowError('Invalid Input') }) // Project Euler Condition Check @@ -12,6 +12,7 @@ describe('checking nth prime number', () => { expect(problem44(1)).toBe(5482660) }) // Project Euler Second Value for Condition Check + // FIXME skip this test for now because it runs very long and clogs up the CI & pre-commit hook test('if the number is greater or equal to 2167', () => { expect(problem44(2167)).toBe(8476206790) }) diff --git a/README.md b/README.md index 25e23a695..758bb5a04 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ JavaScript Repository of TheAlgorithms, which implements various algorithms and [![JavaScript Banner][banner]](DIRECTORY.md) - [![Language grade: JavaScript][grade]][lgtm] [![Checks][checks]][actions] [![Contributions Welcome][welcome]](CONTRIBUTING.md) [![standard.js][standard-logo]][standard-js] @@ -32,7 +31,7 @@ JavaScript Repository of TheAlgorithms, which implements various algorithms and Before contributing to this repository, make sure to read our [Contribution Guidelines](CONTRIBUTING.md). You can look at other [TheAlgorithms Repositories][repositories] or the [issues with a "help wanted" label][help-wanted] for inspiration regarding what to implement. Our maintainers will guide you through how to make your contribution properly -if you make any mistakes. The names of the maintainers of this repository is listed in the +if you make any mistakes. The names of the maintainers of this repository are listed in the [CODEOWNERS file](.github/CODEOWNERS). You can find a list of the algorithms currently in the repository in the [directory](DIRECTORY.md). Explanations of @@ -47,8 +46,8 @@ many of the algorithms can be found in the [wiki][explanation]. [standard-logo]: https://img.shields.io/badge/code%20style-standardjs-%23f3df49 [chat]: https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA [welcome]: https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3 -[checks]: https://img.shields.io/github/workflow/status/TheAlgorithms/JavaScript/Node%20CI?label=checks -[grade]: https://img.shields.io/lgtm/grade/javascript/g/TheAlgorithms/Javascript.svg?logo=lgtm&logoWidth=18 +[checks]: https://img.shields.io/github/actions/workflow/status/TheAlgorithms/JavaScript/Ci.yml?branch=master&label=checks +[grade]: https://img.shields.io/lgtm/grade/javascript/g/TheAlgorithms/JavaScript.svg?logo=lgtm&logoWidth=18 [standard-js]: https://standardjs.com/ @@ -56,6 +55,4 @@ many of the algorithms can be found in the [wiki][explanation]. [actions]: https://github.com/TheAlgorithms/JavaScript/actions [explanation]: https://github.com/TheAlgorithms/JavaScript/wiki [repositories]: https://github.com/orgs/TheAlgorithms/repositories -[lgtm]: https://lgtm.com/projects/g/TheAlgorithms/Javascript/context:javascript [help-wanted]: https://github.com/TheAlgorithms/JavaScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22 - diff --git a/Recursive/Factorial.js b/Recursive/Factorial.js index 5d882e5ea..5a0d56051 100644 --- a/Recursive/Factorial.js +++ b/Recursive/Factorial.js @@ -9,6 +9,14 @@ */ const factorial = (n) => { + if (!Number.isInteger(n)) { + throw new RangeError('Not a Whole Number') + } + + if (n < 0) { + throw new RangeError('Not a Positive Number') + } + if (n === 0) { return 1 } diff --git a/Recursive/LetterCombination.js b/Recursive/LetterCombination.js new file mode 100644 index 000000000..5131ebd85 --- /dev/null +++ b/Recursive/LetterCombination.js @@ -0,0 +1,53 @@ +/* + * + * Letter Combinations of a Phone Number + * + * Given a string containing digits from 2-9 inclusive, + * return all possible letter combinations that the number could represent. + * Return the answer in any order. + + * A mapping of digits to letters (just like on the telephone buttons) is given below. + * Note that 1 does not map to any letters. + * More info: https://leetcode.com/problems/letter-combinations-of-a-phone-number/ + */ + +/* + * @param {string} digits + * @returns {string[]} all the possible combinations + */ + +const letterCombinations = (digits) => { + const length = digits?.length + const result = [] + if (!length) { + return result + } + const digitMap = { + 2: 'abc', + 3: 'def', + 4: 'ghi', + 5: 'jkl', + 6: 'mno', + 7: 'pqrs', + 8: 'tuv', + 9: 'wxyz' + } + + const combinations = (index, combination) => { + let letter + let letterIndex + if (index >= length) { + result.push(combination) + return + } + const digit = digitMap[digits[index]] + letterIndex = 0 + while ((letter = digit[letterIndex++])) { + combinations(index + 1, combination + letter) + } + } + combinations(0, '') + return result +} + +export { letterCombinations } diff --git a/Recursive/test/Factorial.test.js b/Recursive/test/Factorial.test.js index b124efea5..5f32a4433 100644 --- a/Recursive/test/Factorial.test.js +++ b/Recursive/test/Factorial.test.js @@ -8,4 +8,12 @@ describe('Factorial', () => { it('should return factorial 120 for value "5"', () => { expect(factorial(5)).toBe(120) }) + + it('Throw Error for Invalid Input', () => { + expect(() => factorial('-')).toThrow('Not a Whole Number') + expect(() => factorial(null)).toThrow('Not a Whole Number') + expect(() => factorial(undefined)).toThrow('Not a Whole Number') + expect(() => factorial(3.142)).toThrow('Not a Whole Number') + expect(() => factorial(-1)).toThrow('Not a Positive Number') + }) }) diff --git a/Recursive/test/LetterCombination.test.js b/Recursive/test/LetterCombination.test.js new file mode 100644 index 000000000..29631b8f4 --- /dev/null +++ b/Recursive/test/LetterCombination.test.js @@ -0,0 +1,48 @@ +import { letterCombinations } from '../LetterCombination' + +describe('Letter Combinations', () => { + it('should return empty array if provided string is not valid', () => { + const result = letterCombinations('') + expect(Array.isArray(result)).toBe(true) + expect(result.length).toBe(0) + }) + + it('should return empty array if provided string is empty', () => { + const result = letterCombinations(null) + expect(Array.isArray(result)).toBe(true) + expect(result.length).toBe(0) + }) + + it('should return letter combination of 234', () => { + const result = letterCombinations('234') + expect(result).toEqual([ + 'adg', + 'adh', + 'adi', + 'aeg', + 'aeh', + 'aei', + 'afg', + 'afh', + 'afi', + 'bdg', + 'bdh', + 'bdi', + 'beg', + 'beh', + 'bei', + 'bfg', + 'bfh', + 'bfi', + 'cdg', + 'cdh', + 'cdi', + 'ceg', + 'ceh', + 'cei', + 'cfg', + 'cfh', + 'cfi' + ]) + }) +}) diff --git a/String/CountLetters.js b/String/CountLetters.js new file mode 100644 index 000000000..6b6400150 --- /dev/null +++ b/String/CountLetters.js @@ -0,0 +1,33 @@ +/** + * @function countLetters + * @description Given a string, count the number of each letter. + * @param {String} str - The input string + * @return {Object} - Object with letters and number of times + * @example countLetters("hello") => {h: 1, e: 1, l: 2, o: 1} + */ + +const countLetters = (str) => { + const specialChars = /\W/g + + if (typeof str !== 'string') { + throw new TypeError('Input should be a string') + } + + if (specialChars.test(str)) { + throw new TypeError('Input must not contain special characters') + } + + if (/\d/.test(str)) { + throw new TypeError('Input must not contain numbers') + } + + const obj = {} + for (let i = 0; i < str.toLowerCase().length; i++) { + const char = str.toLowerCase().charAt(i) + obj[char] = (obj[char] || 0) + 1 + } + + return obj +} + +export { countLetters } diff --git a/String/PercentageOfLetters.js b/String/PercentageOfLetters.js new file mode 100644 index 000000000..fbd6a03be --- /dev/null +++ b/String/PercentageOfLetters.js @@ -0,0 +1,27 @@ +/** + * @function percentageOfLetter + * @description Return the percentage of characters in 'str' + * that equal 'letter' rounded down to the nearest whole percent. + * More info: https://leetcode.com/problems/percentage-of-letter-in-string/ + * @param {String} str + * @param {String} letter + * @returns {Number} + * @example + * const str = 'foobar', const letter = 'o' + * percentageOfLetter(str, letter) // ===> 33 + */ +const percentageOfLetter = (str, letter) => { + if (typeof str !== 'string' || typeof letter !== 'string') { + throw new Error('Input data must be strings') + } + let letterCount = 0 + // Iterate through the whole given text + for (let i = 0; i < str.length; i++) { + // Count how often the letter appears in the word + letterCount += str[i].toLowerCase() === letter.toLowerCase() ? 1 : 0 + } + const percentage = Math.floor((100 * letterCount) / str.length) + return percentage +} + +export { percentageOfLetter } diff --git a/String/ZFunction.js b/String/ZFunction.js new file mode 100644 index 000000000..0d1ff2b12 --- /dev/null +++ b/String/ZFunction.js @@ -0,0 +1,41 @@ +/** + * @author: Adrito Mukherjee + * Implementation of ZFunction in JavaScript + * ZFunction at an index i gives the length of the longest substring starting at i, that is also a prefix of the whole string + * ZFunction for all indices in a string can be calculated in O(N) + * @see https://cp-algorithms.com/string/z-function.html + * @param {String} text The string whose Z Function is to be calculated + * @return {Array} Returns an array whose i-th index is the value of Z Function for text at index i + */ + +function zFunction (text) { + const length = text.length + const zArray = Array(length).fill(0) + // Initializing left and right variable to zero + let left = 0 + let right = 0 + for (let index = 0; index < length; index++) { + // If index is less than or equal to right, we reuse the values of zFunction at index right-index+1 + // It is made sure that value of zFunction at index is not greater than maximum possible value at index + if (index <= right) { + zArray[index] = Math.min(right - index + 1, zArray[index - left]) + } + + // After zArray[index] is initialized, we see if we can increase its value by trivially comparing character by character + while ( + index + zArray[index] < length && + text[zArray[index]] === text[index + zArray[index]] + ) { + zArray[index]++ + } + + // If index + zArray[index] - 1 is greater than right, we update values of variables left and right + if (index + zArray[index] - 1 > right) { + left = index + right = index + zArray[index] - 1 + } + } + return zArray +} + +export default zFunction diff --git a/String/test/CountLetters.test.js b/String/test/CountLetters.test.js new file mode 100644 index 000000000..424dcfa31 --- /dev/null +++ b/String/test/CountLetters.test.js @@ -0,0 +1,33 @@ +import { countLetters } from '../CountLetters' + +describe('CountLetters', () => { + it('expect throws on use wrong param', () => { + expect(() => countLetters(0)).toThrow() + }) + + it('expect throws when using a number in the string', () => { + expect(() => countLetters('h3llo')).toThrow() + }) + + it('expect throws when using a special characters in the string', () => { + expect(() => countLetters('hello!')).toThrow() + }) + + it('count the letters in a string. Allows lower case', () => { + const value = 'hello' + const count = countLetters(value) + expect(count).toEqual({ h: 1, e: 1, l: 2, o: 1 }) + }) + + it('count the letters in a string. Allows upper case', () => { + const value = 'HELLO' + const count = countLetters(value) + expect(count).toEqual({ h: 1, e: 1, l: 2, o: 1 }) + }) + + it('count the letters in a string. Allows upper and lower case', () => { + const value = 'HelLo' + const count = countLetters(value) + expect(count).toEqual({ h: 1, e: 1, l: 2, o: 1 }) + }) +}) diff --git a/String/test/PercentageOfLetters.test.js b/String/test/PercentageOfLetters.test.js new file mode 100644 index 000000000..7a58dd0ec --- /dev/null +++ b/String/test/PercentageOfLetters.test.js @@ -0,0 +1,16 @@ +import { percentageOfLetter } from '../PercentageOfLetters' + +describe('Percentage of Letters in a String', () => { + test('Calculate percent for lower case', () => { + expect(percentageOfLetter('foobar', 'o')).toEqual(33) + expect(percentageOfLetter('aaabcd', 'a')).toEqual(50) + }) + test('Calculate percent for upper case', () => { + expect(percentageOfLetter('foobar', 'o')).toEqual(33) + expect(percentageOfLetter('aAabcd', 'a')).toEqual(50) + }) + test('Throwing an exception', () => { + expect(() => percentageOfLetter(100, 'string')).toThrow() + expect(() => percentageOfLetter('string', true)).toThrow() + }) +}) diff --git a/String/test/ZFunction.test.js b/String/test/ZFunction.test.js new file mode 100644 index 000000000..a945f2804 --- /dev/null +++ b/String/test/ZFunction.test.js @@ -0,0 +1,8 @@ +import zFunction from '../ZFunction' + +test('Testing zFunction', () => { + expect(zFunction('aabxaayaab')).toEqual([10, 1, 0, 0, 2, 1, 0, 3, 1, 0]) + expect(zFunction('aabxaabxcaabxaabxay')).toEqual([ + 19, 1, 0, 0, 4, 1, 0, 0, 0, 8, 1, 0, 0, 5, 1, 0, 0, 1, 0 + ]) +}) diff --git a/Trees/FenwickTree.js b/Trees/FenwickTree.js index df9406700..f3c65f56d 100644 --- a/Trees/FenwickTree.js +++ b/Trees/FenwickTree.js @@ -1,7 +1,7 @@ /* * Author: Mohit Kumar - * Fedwick Tree Implementation in JavaScript - * Fedwick Tree Implementation for finding prefix sum. + * Fenwick Tree Implementation in JavaScript + * Fenwick Tree Implementation for finding prefix sum. */ class FenwickTree { diff --git a/package-lock.json b/package-lock.json index aee3c0188..53a83cf06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "standard": "^17.0.0" }, "engines": { - "node": ">=18" + "node": ">=16.6.0" } }, "node_modules/@ampproject/remapping": { diff --git a/package.json b/package.json index 2fdd75f06..6ab94ac09 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "type": "module", "description": "A repository for All algorithms implemented in Javascript (for educational purposes only)", "scripts": { - "test": "jest --no-cache", + "test": "jest", + "test-changed": "jest --onlyChanged", "style": "standard", "prepare": "husky install" },