Merge branch 'master' into improve_pr_template

This commit is contained in:
David Leal
2022-12-18 11:56:49 -06:00
committed by GitHub
77 changed files with 1966 additions and 60 deletions

41
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,3 +1,11 @@
github:
prebuilds:
addBadge: true
addComment: false
addCheck: false
master: true
branches: true
pullRequestsFromForks: true
tasks:
- init: npm install

View File

@ -2,4 +2,4 @@
. "$(dirname "$0")/_/husky.sh"
npm run style
npm run test
npm run test-changed

View File

@ -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))
})
})

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
import litersToImperialGallons from '../LitersToImperialGallons'
test('Convert 25 liters to imperial gallons', () => {
expect(parseFloat(litersToImperialGallons(25).toFixed(2))).toBe(5.5)
})

View File

@ -0,0 +1,5 @@
import litersToUSGallons from '../LitersToUSGallons'
test('Convert 50 liters to US gallons', () => {
expect(parseFloat(litersToUSGallons(50).toFixed(2))).toBe(13.21)
})

View File

@ -0,0 +1,5 @@
import ouncesToKilograms from '../OuncesToKilograms'
test('Convert 60 ounces to kilograms', () => {
expect(parseFloat(ouncesToKilograms(60).toFixed(3))).toBe(1.701)
})

View File

@ -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)

View File

@ -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 }

View File

@ -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)
})
})

View File

@ -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 }

View File

@ -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 }

View File

@ -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)
})
})

View File

@ -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])
})
})

View File

@ -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 }

View File

@ -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)
})
})

View File

@ -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 }

View File

@ -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 }

View File

@ -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)
})
})

View File

@ -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()
})
})

19
Geometry/Circle.js Normal file
View File

@ -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
}
}

25
Geometry/Cone.js Normal file
View File

@ -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))
}
}

25
Geometry/Pyramid.js Normal file
View File

@ -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))
}
}

19
Geometry/Sphere.js Normal file
View File

@ -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
}
}

View File

@ -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)
})

View File

@ -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)
})

View File

@ -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)
})

View File

@ -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)
})

82
Graphs/BinaryLifting.js Normal file
View File

@ -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

View File

@ -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])
}
}
}

View File

@ -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)
}
}
}

100
Graphs/Kosaraju.js Normal file
View File

@ -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 ] ]

View File

@ -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

View File

@ -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])
})

View File

@ -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]])
})

View File

@ -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])
})

View File

@ -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
}

View File

@ -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 }

32
Maths/FriendlyNumbers.js Normal file
View File

@ -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
}

21
Maths/HexagonalNumber.js Normal file
View File

@ -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)
}

40
Maths/IntToBase.js Normal file
View File

@ -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 }

View File

@ -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
}
}

View File

@ -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 }

25
Maths/Signum.js Normal file
View File

@ -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 }

View File

@ -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 }

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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()
})
})

View File

@ -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)
})
})
})

View File

@ -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()
})
})
})

19
Maths/test/Signum.test.js Normal file
View File

@ -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)
})
})

View File

@ -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()
})
})

View File

@ -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 }

View File

@ -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)
})
})

View File

@ -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)
})
})

View File

@ -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)
})

View File

@ -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
<!-- External Links -->
[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

View File

@ -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
}

View File

@ -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 }

View File

@ -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')
})
})

View File

@ -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'
])
})
})

33
String/CountLetters.js Normal file
View File

@ -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 }

View File

@ -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 }

41
String/ZFunction.js Normal file
View File

@ -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

View File

@ -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 })
})
})

View File

@ -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()
})
})

View File

@ -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
])
})

View File

@ -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 {

2
package-lock.json generated
View File

@ -23,7 +23,7 @@
"standard": "^17.0.0"
},
"engines": {
"node": ">=18"
"node": ">=16.6.0"
}
},
"node_modules/@ampproject/remapping": {

View File

@ -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"
},