diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..41d34b9a9f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @raklaptudirm @appgurueu diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..6a4de4e591 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,48 @@ +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 + - type: textarea + id: extra_information + attributes: + label: Additional information + description: Is there anything else we should know about this bug report? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..fe511fee04 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discord community + url: https://the-algorithms.com/discord/ + about: Have any questions or found any bugs? Contact us via our Discord community. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..f785578cb6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,38 @@ +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 + - type: textarea + id: extra_information + attributes: + label: Additional information + description: Is there anything else we should know about this feature request? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/other.yml b/.github/ISSUE_TEMPLATE/other.yml new file mode 100644 index 0000000000..0ae3fb4534 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other.yml @@ -0,0 +1,19 @@ +name: Other issue +description: Use this for any other issues. Do NOT create blank issues +title: "[OTHER]: " +labels: ["awaiting triage"] +body: + - type: textarea + id: description + attributes: + label: What would you like to share? + description: Provide a clear and concise explanation of your issue. + validations: + required: true + - type: textarea + id: extrainfo + attributes: + label: Additional information + description: Is there anything else we should know about this issue? + validations: + required: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..15e494ec86 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +# Keep GitHub Actions up to date with Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3d4034e762..bcb4e6bbaa 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,19 +1,19 @@ -# Welcome to JavaScript community +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/) [know more](https://www.gitpod.io/docs/pull-requests/) -### **Describe your change:** +### Describe your change: -* [ ] Add an algorithm? -* [ ] Fix a bug or typo in an existing algorithm? -* [ ] Documentation change? +- [ ] Add an algorithm? +- [ ] Fix a bug or typo in an existing algorithm? +- [ ] Documentation change? +### Checklist: -### **Checklist:** -* [ ] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Javascript/blob/master/CONTRIBUTING.md). -* [ ] This pull request is all my own work -- I have not plagiarized. -* [ ] I know that pull requests will not be merged if they fail the automated tests. -* [ ] This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms. -* [ ] All new JavaScript files are placed inside an existing directory. -* [ ] All 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 -* [ ] All new algorithms have a URL in its comments that points to Wikipedia or other similar explanation. -* [ ] If this pull request resolves one or more open issues then the commit message contains `Fixes: #{$ISSUE_NO}`. +- [ ] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Javascript/blob/master/CONTRIBUTING.md). +- [ ] This pull request is all my own work -- I have not plagiarized. +- [ ] I know that pull requests will not be merged if they fail the automated tests. +- [ ] This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms. +- [ ] All new JavaScript files are placed inside an existing directory. +- [ ] All 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 +- [ ] All new algorithms have a URL in their comments that points to Wikipedia or another similar explanation. +- [ ] If this pull request resolves one or more open issues then the commit message contains `Fixes: #{$ISSUE_NO}`. diff --git a/.github/stale.yml b/.github/stale.yml index fe51e49e47..9692a0a677 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,6 +1,6 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 30 -# Number of days of inactivity before a stale issue is closed +# Number of days of inactivity before an issue becomes stale (2 weeks) +daysUntilStale: 14 +# Number of days of inactivity before a stale issue is closed (a week) daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: @@ -8,7 +8,7 @@ exemptLabels: - help wanted - OK to merge # Label to use when marking an issue as stale -staleLabel: wontfix +staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had @@ -16,5 +16,5 @@ markComment: > for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: > - Please reopen this issue once you commit the changes requested or + Please reopen this issue once you commit the changes requested or make improvements on the code. Thank you for your contributions. diff --git a/.github/workflows/Ci.yml b/.github/workflows/Ci.yml new file mode 100644 index 0000000000..99e8f7831f --- /dev/null +++ b/.github/workflows/Ci.yml @@ -0,0 +1,39 @@ +name: Continuous Integration + +on: + push: + branches: + - master + pull_request: + +jobs: + build: + name: Code style and tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: ๐Ÿ“ฆ Install dependencies + run: npm ci + + - name: ๐Ÿงช Run all tests + run: npm run test + + - name: ๐Ÿ’„ Code style + run: npm run check-style + + codespell: + name: Check for spelling errors + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: codespell-project/actions-codespell@master + with: + # file types to ignore + skip: "*.json,*.yml,DIRECTORY.md" + ignore_words_list: "ba,esy,yse,falsy" diff --git a/.github/workflows/UpdateDirectory.js b/.github/workflows/UpdateDirectory.js index 6ab4e57964..925b92b781 100644 --- a/.github/workflows/UpdateDirectory.js +++ b/.github/workflows/UpdateDirectory.js @@ -1,108 +1,71 @@ -// requiring path and fs modules -const path = require('path'); -const fs = require('fs'); +import path from 'path' +import fs from 'fs' +import { globby } from 'globby' -let URL_BASE = "https://github.com/TheAlgorithms/Javascript/blob/master"; -let g_output = []; - -let filepaths = []; -function good_filepaths(top_dir = ".") { - fs.readdir(top_dir, function(err, list) { - if (err) { - console.log(err); - return; - } - list.forEach(function(file) { - let path = top_dir + "/" + file; - if (!file.startsWith(".")) { - fs.stat(path, function(err, stat) { - if (stat && stat.isDirectory()) { - good_filepaths(path); - } else { - if (file.toLowerCase().endsWith(".js")) { - filepaths.push(path.slice(2)); - } - } - }); - } - }); - }) +function pathPrefix (i) { + const res = ' '.repeat(i) + return res + '*' } -function md_prefix(i) { - if (i) { - let res = ' '.repeat(i); - return res + "*"; - } else { - return "\n##" - } -} +function printPath (oldPath, newPath, output) { + const oldParts = oldPath.split(path.sep) + const newParts = newPath.split(path.sep) -function print_path(old_path, new_path) { - let old_parts = old_path.split(path.sep); - let new_parts = new_path.split(path.sep); - for (let i = 0; i < new_parts.length; ++i) { - let new_part = new_parts[i]; - if (i + 1 > old_parts.len || old_parts[i] != new_part) { - if (new_part) { - g_output.push(`${md_prefix(i)} ${new_part.replace('_', ' ')}`); + for (let i = 0; i < newParts.length; ++i) { + const newPart = newParts[i] + if (i + 1 > oldParts.length || oldParts[i] !== newPart) { + if (newPart) { + output.push(`${pathPrefix(i)} **${newPart.replace('_', ' ')}**`) } } } - return new_path; + + return newPath } -function build_directory_md(top_dir = ".") { - old_path = ""; - filepaths.sort(function(a, b) { - if (a.toLowerCase() < b.toLowerCase()) return -1; - if (a.toLowerCase() > b.toLowerCase()) return 1; - return 0; - }); - for (let filepath of filepaths) { - file = filepath.split(path.sep); - if (file.length == 1) { - filepath = ""; - filename = file[0]; - } else { - let total = file.length; - filename = file[total - 1]; - filepath = file.splice(0, total - 1).join(path.sep); - } - if (filepath != old_path) { - old_path = print_path(old_path, filepath); - } - let indent = 0; - for (let i = 0; i < filepath.length; ++i) { - if (filepath[i] == path.sep) { - ++indent; - } - } - if (filepath) { - ++indent; +function pathsToMarkdown (filePaths) { + const output = [] + + let oldPath = '' + filePaths.sort(function (a, b) { + if (a.toLowerCase() < b.toLowerCase()) return -1 + if (a.toLowerCase() > b.toLowerCase()) return 1 + return 0 + }) + + for (let filepath of filePaths) { + let filename = path.basename(filepath) + filepath = path.dirname(filepath) + + if (filepath !== oldPath) { + oldPath = printPath(oldPath, filepath, output) } - let urls = [URL_BASE, filepath, filename]; - let url = urls.join("/").replace(" ", "%20"); + + let indent = filepath.split(path.sep).length + + // prepare the markdown-esque prefix to the file's line + const prefix = pathPrefix(indent) + // remove extension from filename - filename = filename.split(".")[0]; - g_output.push(`${md_prefix(indent)} [${filename}](${url})`); + const name = path.basename(filename, ".js") + const url = path.join(filepath, filename) + + output.push(`${prefix} [${name}](${url})`) } - g_output = g_output.join('\n'); - return g_output; + + return output.join('\n') } -good_filepaths(); -setTimeout(() => { - // once the filepaths have been computed - build_directory_md(); - // console.log(filepaths); -}, 1000); -setTimeout(() => { - // once the g_output has been constructed, write to the file - fs.writeFile('DIRECTORY.md', g_output + '\n', (err) => { - if (err) { - console.log(err); - } - }) - // console.log(g_output); -}, 1000); +// get paths of all .js files - excluding node_modules, the .github folder, tests and config stuff +globby([ + '**/*.js', + '!(node_modules|.github)/**/*', + "!**/test/**/*", + '!**/*.test.js', + '!**/*.manual-test.js', + '!vitest.config.ts' +]) + // create markdown content + .then(pathsToMarkdown) + // write markdown to file + .then(markdown => fs.writeFileSync('DIRECTORY.md', markdown + '\n', { encoding: 'utf8' })) diff --git a/.github/workflows/UpdateDirectory.yml b/.github/workflows/UpdateDirectory.yml new file mode 100644 index 0000000000..437ab55b91 --- /dev/null +++ b/.github/workflows/UpdateDirectory.yml @@ -0,0 +1,38 @@ +# This GitHub Action updates the DIRECTORY.md file (if needed) when doing a git push +name: Update Directory + +on: + push: + branches-ignore: + "master" + +jobs: + updateDirectory: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: ๐Ÿ“ฆ Install dependencies + run: npm ci + + - name: ๐Ÿ—„๏ธ Create Directory from JS files + run: node .github/workflows/UpdateDirectory.js + + - name: Configure Github Action + run: | + git config --global user.name "$GITHUB_ACTOR" + git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: ๐Ÿค“ Commit & push new Directory (if needed) + run: | + if [[ `git status --porcelain` ]]; then + git commit -am "Updated Documentation in README.md" + git push + else + echo "NO CHANGES DETECTED" + fi diff --git a/.github/workflows/UploadCoverageReport.yml b/.github/workflows/UploadCoverageReport.yml new file mode 100644 index 0000000000..3f8fee4256 --- /dev/null +++ b/.github/workflows/UploadCoverageReport.yml @@ -0,0 +1,45 @@ +--- +name: UploadCoverageReport + +'on': + workflow_dispatch: + push: + branches: + - master + pull_request: + +env: + REPORT_PATH: "coverage/coverage-final.json" + +jobs: + UploadCoverageReport: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Generate coverage report + run: npm test -- --coverage + + - name: Upload coverage to codecov (tokenless) + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository + uses: codecov/codecov-action@v4 + with: + files: "${{ env.REPORT_PATH }}" + fail_ci_if_error: true + + - name: Upload coverage to codecov (with token) + if: "! github.event.pull_request.head.repo.fork " + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: "${{ env.REPORT_PATH }}" + fail_ci_if_error: true +... diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml deleted file mode 100644 index 845caee7bc..0000000000 --- a/.github/workflows/nodejs.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Node CI -on: [push, pull_request] -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: npm install, build, and test - run: | - npm install doctest standard --save-dev - npx doctest Sorts/BogoSort.js Sorts/BucketSort.js - npx standard - cd Linear-Algebra - npm ci - npm run build --if-present - npm test - env: - CI: true diff --git a/.github/workflows/update_directory_md.yml b/.github/workflows/update_directory_md.yml deleted file mode 100644 index 3c6c333cb4..0000000000 --- a/.github/workflows/update_directory_md.yml +++ /dev/null @@ -1,18 +0,0 @@ -# This GitHub Action updates the DIRECTORY.md file (if needed) when doing a git push -name: update_directory_md -on: [push] -jobs: - update_directory_md: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions/setup-node@v1 - - run: | - node .github/workflows/UpdateDirectory.js - cat DIRECTORY.md - git config --global user.name github-actions - git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY - git add DIRECTORY.md - git commit -am "updating DIRECTORY.md" || true - git push --force origin HEAD:$GITHUB_REF || true diff --git a/.gitignore b/.gitignore index 0bd5fa7e9a..1cc076e7bb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,8 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +# intelliJ workspace folder +.idea + +/coverage diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000000..3f6c645c7c --- /dev/null +++ b/.gitpod.yml @@ -0,0 +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 new file mode 100755 index 0000000000..037dbe870a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,5 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run check-style +npm run test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..d66f37a719 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +.github +DIRECTORY.md diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..a85cb7eb8e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "endOfLine": "lf", + "insertPragma": false, + "printWidth": 80, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false +} diff --git a/Backtracking/AllCombinationsOfSizeK.js b/Backtracking/AllCombinationsOfSizeK.js new file mode 100644 index 0000000000..bbe4e7fd5b --- /dev/null +++ b/Backtracking/AllCombinationsOfSizeK.js @@ -0,0 +1,28 @@ +function generateCombinations(n, k) { + let currentCombination = [] + let allCombinations = [] // will be used for storing all combinations + let currentValue = 1 + + function findCombinations() { + if (currentCombination.length === k) { + // Add the array of size k to the allCombinations array + allCombinations.push([...currentCombination]) + return + } + if (currentValue > n) { + // Check for exceeding the range + return + } + currentCombination.push(currentValue++) + findCombinations() + currentCombination.pop() + findCombinations() + currentValue-- + } + + findCombinations() + + return allCombinations +} + +export { generateCombinations } diff --git a/Backtracking/GeneratePermutations.js b/Backtracking/GeneratePermutations.js new file mode 100644 index 0000000000..54fa6bb518 --- /dev/null +++ b/Backtracking/GeneratePermutations.js @@ -0,0 +1,36 @@ +/* + * Problem Statement: Generate all distinct permutations of an array (all permutations should be in sorted order); + * + * What is permutations? + * - Permutation means possible arrangements in a set (here it is an array); + * + * Reference to know more about permutations: + * - https://www.britannica.com/science/permutation + * + */ + +const swap = (arr, i, j) => { + const newArray = [...arr] + + ;[newArray[i], newArray[j]] = [newArray[j], newArray[i]] // Swapping elements ES6 way + + return newArray +} + +const permutations = (arr) => { + const P = [] + const permute = (arr, low, high) => { + if (low === high) { + P.push([...arr]) + return P + } + for (let i = low; i <= high; i++) { + arr = swap(arr, low, i) + permute(arr, low + 1, high) + } + return P + } + return permute(arr, 0, arr.length - 1) +} + +export { permutations } diff --git a/back-tracking/KnightTour.js b/Backtracking/KnightTour.js similarity index 53% rename from back-tracking/KnightTour.js rename to Backtracking/KnightTour.js index 5c7637a9e3..5f81ca345a 100644 --- a/back-tracking/KnightTour.js +++ b/Backtracking/KnightTour.js @@ -1,13 +1,14 @@ // Wikipedia: https://en.wikipedia.org/wiki/Knight%27s_tour class OpenKnightTour { - constructor (size) { + constructor(size) { + // Constructor to initialize the chessboard and size this.board = new Array(size).fill(0).map(() => new Array(size).fill(0)) this.size = size } - getMoves ([i, j]) { - // helper function to get the valid moves of the knight from the current position + getMoves([i, j]) { + // Helper function to get the valid moves of the knight from the current position const moves = [ [i + 2, j - 1], [i + 2, j + 1], @@ -19,16 +20,19 @@ class OpenKnightTour { [i - 1, j + 2] ] - return moves.filter(([y, x]) => y >= 0 && y < this.size && x >= 0 && x < this.size) + // Filter out moves that are within the board boundaries + return moves.filter( + ([y, x]) => y >= 0 && y < this.size && x >= 0 && x < this.size + ) } - isComplete () { - // helper function to check if the board is complete - return !this.board.map(row => row.includes(0)).includes(true) + isComplete() { + // Helper function to check if the board is complete + return !this.board.map((row) => row.includes(0)).includes(true) } - solve () { - // function to find the solution for the given board + solve() { + // Function to find the solution for the given board for (let i = 0; i < this.size; i++) { for (let j = 0; j < this.size; j++) { if (this.solveHelper([i, j], 0)) return true @@ -37,42 +41,32 @@ class OpenKnightTour { return false } - solveHelper ([i, j], curr) { - // helper function for the main computation + solveHelper([i, j], curr) { + // Helper function for the main computation if (this.isComplete()) return true + // Iterate through possible moves and attempt to fill the board for (const [y, x] of this.getMoves([i, j])) { if (this.board[y][x] === 0) { this.board[y][x] = curr + 1 if (this.solveHelper([y, x], curr + 1)) return true - // backtracking + // Backtracking: If the solution is not found, reset the cell to 0 this.board[y][x] = 0 } } return false } - printBoard () { - // utility function to display the board + printBoard(output = (value) => console.log(value)) { + // Utility function to display the board for (const row of this.board) { let string = '' for (const elem of row) { string += elem + '\t' } - console.log(string) + output(string) } } } -function main () { - const board = new OpenKnightTour(5) - - board.printBoard() - console.log('\n') - - board.solve() - - board.printBoard() -} - -main() +export { OpenKnightTour } diff --git a/Backtracking/MColoringProblem.js b/Backtracking/MColoringProblem.js new file mode 100644 index 0000000000..d625b34a45 --- /dev/null +++ b/Backtracking/MColoringProblem.js @@ -0,0 +1,49 @@ +/** + * Colors a graph using up to m colors such that no two adjacent vertices share the same color. + * @param {number[][]} graph - Adjacency matrix of the graph, using 0 for no edge. + * @param {number} m - The number of colors to use. + * @returns {?Array.} A valid M-coloring of the graph using colors 1 to m, or null if none exists. + * @see https://en.wikipedia.org/wiki/Graph_coloring + */ +function mColoring(graph, m) { + const colors = new Array(graph.length).fill(0) + + // Check if it's safe to color a vertex with a given color. + function isSafe(vertex, color) { + for (let i = 0; i < graph.length; i++) { + if (graph[vertex][i] && colors[i] === color) { + return false + } + } + return true + } + + // Use backtracking to try and color the graph. + function solveColoring(vertex = 0) { + if (vertex === graph.length) { + return true + } + + for (let color = 1; color <= m; color++) { + if (isSafe(vertex, color)) { + colors[vertex] = color + + if (solveColoring(vertex + 1)) { + return true + } + + // If no solution, backtrack. + colors[vertex] = 0 + } + } + return false + } + + // If coloring is possible, return the colors. + if (solveColoring()) { + return colors + } + return null +} + +export { mColoring } diff --git a/back-tracking/NQueen.js b/Backtracking/NQueens.js similarity index 57% rename from back-tracking/NQueen.js rename to Backtracking/NQueens.js index dfd3dfa9fb..307c66dc04 100644 --- a/back-tracking/NQueen.js +++ b/Backtracking/NQueens.js @@ -1,10 +1,14 @@ -class NQueen { - constructor (size) { +class NQueens { + constructor(size) { + if (size < 0) { + throw RangeError('Invalid board size') + } this.board = new Array(size).fill('.').map(() => new Array(size).fill('.')) this.size = size + this.solutionCount = 0 } - isValid ([row, col]) { + isValid([row, col]) { // function to check if the placement of the queen in the given location is valid // checking the left of the current row @@ -25,41 +29,39 @@ class NQueen { return true } - solve (col = 0) { - // function to solve the board - if (col >= this.size) { return true } + placeQueen(row, col) { + this.board[row][col] = 'Q' + } - for (let i = 0; i < this.size; i++) { - if (this.isValid([i, col])) { - this.board[i][col] = 'Q' + removeQueen(row, col) { + this.board[row][col] = '.' + } - if (this.solve(col + 1)) { return true } + solve(col = 0) { + if (col >= this.size) { + this.solutionCount++ + return true + } - // backtracking - this.board[i][col] = '.' + for (let i = 0; i < this.size; i++) { + if (this.isValid([i, col])) { + this.placeQueen(i, col) + this.solve(col + 1) + this.removeQueen(i, col) } } return false } - printBoard () { - // utility function to display the board + printBoard(output = (value) => console.log(value)) { + if (!output._isMockFunction) { + output('\n') + } for (const row of this.board) { - console.log(...row) + output(row) } } } -function main () { - const board = new NQueen(8) - - board.printBoard() - console.log('\n') - - board.solve() - - board.printBoard() -} - -main() +export { NQueens } diff --git a/Backtracking/RatInAMaze.js b/Backtracking/RatInAMaze.js new file mode 100644 index 0000000000..6522a87244 --- /dev/null +++ b/Backtracking/RatInAMaze.js @@ -0,0 +1,130 @@ +/* + * Problem Statement: + * - Given a NxN grid, find whether rat in cell [0, 0] can reach the target in cell [N-1, N-1] + * - The grid is represented as an array of rows. Each row is represented as an array of 0 or 1 values. + * - A cell with value 0 can not be moved through. Value 1 means the rat can move here. + * - The rat can not move diagonally. + * + * Reference for this problem: https://www.geeksforgeeks.org/rat-in-a-maze-backtracking-2/ + * + * Based on the original implementation contributed by Chiranjeev Thapliyal (https://github.com/chiranjeev-thapliyal). + */ + +/** + * Checks if the given grid is valid. + * + * A grid needs to satisfy these conditions: + * - must not be empty + * - must be a square + * - must not contain values other than {@code 0} and {@code 1} + * + * @param grid The grid to check. + * @throws TypeError When the given grid is invalid. + */ +function validateGrid(grid) { + if (!Array.isArray(grid) || grid.length === 0) + throw new TypeError('Grid must be a non-empty array') + + const allRowsHaveCorrectLength = grid.every( + (row) => row.length === grid.length + ) + if (!allRowsHaveCorrectLength) throw new TypeError('Grid must be a square') + + const allCellsHaveValidValues = grid.every((row) => { + return row.every((cell) => cell === 0 || cell === 1) + }) + if (!allCellsHaveValidValues) + throw new TypeError('Grid must only contain 0s and 1s') +} + +function isSafe(grid, x, y) { + const n = grid.length + return x >= 0 && x < n && y >= 0 && y < n && grid[y][x] === 1 +} + +/** + * Attempts to calculate the remaining path to the target. + * + * @param grid The full grid. + * @param x The current X coordinate. + * @param y The current Y coordinate. + * @param solution The current solution matrix. + * @param path The path we took to get from the source cell to the current location. + * @returns {string|boolean} Either the path to the target cell or false. + */ +function getPathPart(grid, x, y, solution, path) { + const n = grid.length + + // are we there yet? + if (x === n - 1 && y === n - 1 && grid[y][x] === 1) { + solution[y][x] = 1 + return path + } + + // did we step on a 0 cell or outside the grid? + if (!isSafe(grid, x, y)) return false + + // are we walking onto an already-marked solution coordinate? + if (solution[y][x] === 1) return false + + // none of the above? let's dig deeper! + + // mark the current coordinates on the solution matrix + solution[y][x] = 1 + + // attempt to move right + const right = getPathPart(grid, x + 1, y, solution, path + 'R') + if (right) return right + + // right didn't work: attempt to move down + const down = getPathPart(grid, x, y + 1, solution, path + 'D') + if (down) return down + + // down didn't work: attempt to move up + const up = getPathPart(grid, x, y - 1, solution, path + 'U') + if (up) return up + + // up didn't work: attempt to move left + const left = getPathPart(grid, x - 1, y, solution, path + 'L') + if (left) return left + + // no direction was successful: remove this cell from the solution matrix and backtrack + solution[y][x] = 0 + return false +} + +function getPath(grid) { + // grid dimensions + const n = grid.length + + // prepare solution matrix + const solution = [] + for (let i = 0; i < n; i++) { + const row = Array(n) + row.fill(0) + solution[i] = row + } + + return getPathPart(grid, 0, 0, solution, '') +} + +/** + * Creates an instance of the "rat in a maze" based on a given grid (maze). + */ +export class RatInAMaze { + constructor(grid) { + // first, let's do some error checking on the input + validateGrid(grid) + + // attempt to solve the maze now - all public methods only query the result state later + const solution = getPath(grid) + + if (solution !== false) { + this.path = solution + this.solved = true + } else { + this.path = '' + this.solved = false + } + } +} diff --git a/back-tracking/Sudoku.js b/Backtracking/Sudoku.js similarity index 61% rename from back-tracking/Sudoku.js rename to Backtracking/Sudoku.js index ae360bbe2e..6d529cc9dd 100644 --- a/back-tracking/Sudoku.js +++ b/Backtracking/Sudoku.js @@ -1,10 +1,10 @@ class Sudoku { // Sudoku Class to hold the board and related functions - constructor (board) { + constructor(board) { this.board = board } - findEmptyCell () { + findEmptyCell() { // Find a empty cell in the board (returns [-1, -1] if all cells are filled) for (let i = 0; i < 9; i++) { for (let j = 0; j < 9; j++) { @@ -14,7 +14,7 @@ class Sudoku { return [-1, -1] } - check ([y, x], value) { + check([y, x], value) { // checks if the value to be added in the board is an acceptable value for the cell // checking through the row @@ -29,8 +29,8 @@ class Sudoku { // checking through the 3x3 block of the cell const secRow = Math.floor(y / 3) const secCol = Math.floor(x / 3) - for (let i = (secRow * 3); i < ((secRow * 3) + 3); i++) { - for (let j = (secCol * 3); j < ((secCol * 3) + 3); j++) { + for (let i = secRow * 3; i < secRow * 3 + 3; i++) { + for (let j = secCol * 3; j < secCol * 3 + 3; j++) { if (y !== i && x !== j && this.board[i][j] === value) return false } } @@ -38,7 +38,7 @@ class Sudoku { return true } - solve () { + solve() { const [y, x] = this.findEmptyCell() // checking if the board is complete @@ -56,42 +56,25 @@ class Sudoku { return false } - getSection (row, [start, end]) { + getSection(row, [start, end]) { return this.board[row].slice(start, end) } - printBoard () { + printBoard(output = (...v) => console.log(...v)) { // helper function to display board for (let i = 0; i < 9; i++) { - if (i % 3 === 0 && i !== 0) console.log('- - - - - - - - - - - -') - console.log( - ...this.getSection(i, [0, 3]), ' | ', - ...this.getSection(i, [3, 6]), ' | ', - ...this.getSection(i, [6, 9])) + if (i % 3 === 0 && i !== 0) { + output('- - - - - - - - - - - -') + } + output( + ...this.getSection(i, [0, 3]), + ' | ', + ...this.getSection(i, [3, 6]), + ' | ', + ...this.getSection(i, [6, 9]) + ) } } } -function main () { - // main function with an example - const sudokuBoard = new Sudoku([ - [3, 0, 6, 5, 0, 8, 4, 0, 0], - [5, 2, 0, 0, 0, 0, 0, 0, 0], - [0, 8, 7, 0, 0, 0, 0, 3, 1], - [0, 0, 3, 0, 1, 0, 0, 8, 0], - [9, 0, 0, 8, 6, 3, 0, 0, 5], - [0, 5, 0, 0, 9, 0, 6, 0, 0], - [1, 3, 0, 0, 0, 0, 2, 5, 0], - [0, 0, 0, 0, 0, 0, 0, 7, 4], - [0, 0, 5, 2, 0, 6, 3, 0, 0] - ]) - - sudokuBoard.printBoard() - - console.log('\n') - sudokuBoard.solve() - - sudokuBoard.printBoard() -} - -main() +export { Sudoku } diff --git a/Backtracking/SumOfSubset.js b/Backtracking/SumOfSubset.js new file mode 100644 index 0000000000..5676569955 --- /dev/null +++ b/Backtracking/SumOfSubset.js @@ -0,0 +1,64 @@ +/* + * + * Sum of Subset problem + * + * Given an ordered set W of non-negative integers and a value K, + * determine all possible subsets from the given set W whose sum + * of its elements equals to the given value K. + * + * More info: https://www.geeksforgeeks.org/subset-sum-backtracking-4/ + */ + +/* + * @param {number[]} set Original set of numbers + * @param {number[]} subset Subset being evaluated + * @param {number} setIndex Index from set of last element in subset + * @param {number} Sum of elements from subset + * @param {targetSum} The target sum on which the subset sum is compared to + * @returns {number[][]} Subsets whose elements add up to targetSum + */ +const sumOfSubset = (set, subset, setindex, sum, targetSum) => { + // Base case where the subset sum is equal to target sum + // Evaluation of following subsets on this path will always add up to + // greater than targetSum, so no need to continue + if (sum === targetSum) return [subset] + + // This and following subsets on this path will always add up to + // greater than targetSum, so no need to continue + if (sum > targetSum) return [] + + // Initialize results array. Will contain only valid subsets + let results = [] + + // Slice gets from the set all the elements at the right of the last element + // to be evaluated (last element of subset) + // forEach iterated on the resulting array + set.slice(setindex).forEach((num, index) => { + // The next subset to be evaluated, current subset plus next element + const nextSubset = [...subset, num] + + // Next index from the set. Current set index plus iteration index + // index starts at 0, so a + 1 is required + const nextSetIndex = setindex + index + 1 + + // Sum of elements from the next subset to be evaluated + const nextSum = sum + num + + // Call recursively the sumOfSubset for the nextSubset + const subsetResult = sumOfSubset( + set, + nextSubset, + nextSetIndex, + nextSum, + targetSum + ) + + // Concat the recursive result with current result array + results = [...results, ...subsetResult] + }) + + // Return results + return results +} + +export { sumOfSubset } diff --git a/Backtracking/generateParentheses.js b/Backtracking/generateParentheses.js new file mode 100644 index 0000000000..8414c95b08 --- /dev/null +++ b/Backtracking/generateParentheses.js @@ -0,0 +1,31 @@ +/** + * Problem Statement: Given a number n pairs of parentheses, try to Generate all combinations of valid parentheses; + * @param {number} n - number of given parentheses + * @return {string[]} res - array that contains all valid parentheses + * @see https://leetcode.com/problems/generate-parentheses/ + */ + +const generateParentheses = (n) => { + const res = [] + + const solve = (chres, openParenthese, closedParenthese) => { + if (openParenthese === n && closedParenthese === n) { + res.push(chres) + return + } + + if (openParenthese <= n) { + solve(chres + '(', openParenthese + 1, closedParenthese) + } + + if (closedParenthese < openParenthese) { + solve(chres + ')', openParenthese, closedParenthese + 1) + } + } + + solve('', 0, 0) + + return res +} + +export { generateParentheses } diff --git a/Backtracking/tests/AllCombinationsOfSizeK.test.js b/Backtracking/tests/AllCombinationsOfSizeK.test.js new file mode 100644 index 0000000000..29b656a2c4 --- /dev/null +++ b/Backtracking/tests/AllCombinationsOfSizeK.test.js @@ -0,0 +1,24 @@ +import { generateCombinations } from '../AllCombinationsOfSizeK' + +describe('AllCombinationsOfSizeK', () => { + it('should return 3x2 matrix solution for n = 3 and k = 2', () => { + const res = generateCombinations(3, 2) + expect(res).toEqual([ + [1, 2], + [1, 3], + [2, 3] + ]) + }) + + it('should return 6x2 matrix solution for n = 4 and k = 2', () => { + const res = generateCombinations(4, 2) + expect(res).toEqual([ + [1, 2], + [1, 3], + [1, 4], + [2, 3], + [2, 4], + [3, 4] + ]) + }) +}) diff --git a/Backtracking/tests/GenerateParentheses.test.js b/Backtracking/tests/GenerateParentheses.test.js new file mode 100644 index 0000000000..369ae8ee44 --- /dev/null +++ b/Backtracking/tests/GenerateParentheses.test.js @@ -0,0 +1,11 @@ +import { generateParentheses } from '../generateParentheses' + +test('generate all valid parentheses of input 3', () => { + expect(generateParentheses(3)).toStrictEqual([ + '((()))', + '(()())', + '(())()', + '()(())', + '()()()' + ]) +}) diff --git a/Backtracking/tests/GeneratePermutations.test.js b/Backtracking/tests/GeneratePermutations.test.js new file mode 100644 index 0000000000..5aa74c72af --- /dev/null +++ b/Backtracking/tests/GeneratePermutations.test.js @@ -0,0 +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]', () => { + 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/Backtracking/tests/KnightTour.test.js b/Backtracking/tests/KnightTour.test.js new file mode 100644 index 0000000000..f7dc511df9 --- /dev/null +++ b/Backtracking/tests/KnightTour.test.js @@ -0,0 +1,37 @@ +import { OpenKnightTour } from '../KnightTour' + +describe('OpenKnightTour', () => { + it('OpenKnightTour(5)', () => { + const KT = new OpenKnightTour(5) + expect(KT.board).toEqual([ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0] + ]) + + expect(KT.solve()).toBe(true) + expect(KT.board).toEqual([ + [19, 4, 15, 10, 25], + [14, 9, 18, 5, 16], + [1, 20, 3, 24, 11], + [8, 13, 22, 17, 6], + [21, 2, 7, 12, 23] + ]) + }) + + it('OpenKnightTour(2)', () => { + const KT = new OpenKnightTour(2) + expect(KT.board).toEqual([ + [0, 0], + [0, 0] + ]) + + expect(KT.solve()).toBe(false) + expect(KT.board).toEqual([ + [0, 0], + [0, 0] + ]) + }) +}) diff --git a/Backtracking/tests/MColoringProblem.test.js b/Backtracking/tests/MColoringProblem.test.js new file mode 100644 index 0000000000..759a328aae --- /dev/null +++ b/Backtracking/tests/MColoringProblem.test.js @@ -0,0 +1,23 @@ +import { mColoring } from '../MColoringProblem' + +describe('MColoringProblem', () => { + it('should color a triangle with 3 colors', () => { + const graph = [ + [0, 1, 1], + [1, 0, 1], + [1, 1, 0] + ] + const solution = mColoring(graph, 3) + expect(solution).not.toBeNull() + }) + + it('should not color a triangle with 2 colors', () => { + const graph = [ + [0, 1, 1], + [1, 0, 1], + [1, 1, 0] + ] + const solution = mColoring(graph, 2) + expect(solution).toBeNull() + }) +}) diff --git a/Backtracking/tests/NQueens.test.js b/Backtracking/tests/NQueens.test.js new file mode 100644 index 0000000000..681307a35a --- /dev/null +++ b/Backtracking/tests/NQueens.test.js @@ -0,0 +1,21 @@ +import { NQueens } from '../NQueens' + +describe('NQueens', () => { + it('should return 2 solutions for 4x4 size board', () => { + const _4Queens = new NQueens(4) + _4Queens.solve() + expect(_4Queens.solutionCount).toEqual(2) + }) + + it('should return 92 solutions for 8x8 size board', () => { + const _8Queens = new NQueens(8) + _8Queens.solve() + expect(_8Queens.solutionCount).toEqual(92) + }) + + it('should throw RangeError for negative size board', () => { + expect(() => { + return new NQueens(-1) + }).toThrow(RangeError) + }) +}) diff --git a/Backtracking/tests/RatInAMaze.test.js b/Backtracking/tests/RatInAMaze.test.js new file mode 100644 index 0000000000..52f620027b --- /dev/null +++ b/Backtracking/tests/RatInAMaze.test.js @@ -0,0 +1,103 @@ +import { RatInAMaze } from '../RatInAMaze' + +describe('RatInAMaze', () => { + it('should fail for non-arrays', () => { + const values = [undefined, null, {}, 42, 'hello, world'] + + for (const value of values) { + // we deliberately want to check whether this constructor call fails or not + expect(() => { + new RatInAMaze(value) + }).toThrow() + } + }) + + it('should fail for an empty array', () => { + // we deliberately want to check whether this constructor call fails or not + expect(() => { + new RatInAMaze([]) + }).toThrow() + }) + + it('should fail for a non-square array', () => { + const array = [ + [0, 0, 0], + [0, 0] + ] + + // we deliberately want to check whether this constructor call fails or not + expect(() => { + new RatInAMaze(array) + }).toThrow() + }) + + it('should fail for arrays containing invalid values', () => { + const values = [[[2]], [['a']]] + + for (const value of values) { + // we deliberately want to check whether this constructor call fails or not + expect(() => { + new RatInAMaze(value) + }).toThrow() + } + }) + + it('should work for a single-cell maze', () => { + const maze = new RatInAMaze([[1]]) + expect(maze.solved).toBe(true) + expect(maze.path).toBe('') + }) + + it('should work for a single-cell maze that can not be solved', () => { + const maze = new RatInAMaze([[0]]) + expect(maze.solved).toBe(false) + expect(maze.path).toBe('') + }) + + it('should work for a simple 3x3 maze', () => { + const maze = new RatInAMaze([ + [1, 1, 0], + [0, 1, 0], + [0, 1, 1] + ]) + expect(maze.solved).toBe(true) + expect(maze.path).toBe('RDDR') + }) + + it('should work for a simple 2x2 that can not be solved', () => { + const maze = new RatInAMaze([ + [1, 0], + [0, 1] + ]) + expect(maze.solved).toBe(false) + expect(maze.path).toBe('') + }) + + it('should work for a more complex maze', () => { + const maze = new RatInAMaze([ + [1, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 1, 0, 0], + [1, 1, 1, 0, 1, 0, 0], + [1, 0, 1, 0, 1, 0, 0], + [1, 0, 1, 1, 1, 0, 0], + [1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1] + ]) + expect(maze.solved).toBe(true) + expect(maze.path).toBe('RRRRDDDDLLUULLDDDDRRRRRR') + }) + + it('should work for a more complex maze that can not be solved', () => { + const maze = new RatInAMaze([ + [1, 1, 1, 1, 1, 0, 1], + [0, 0, 0, 0, 1, 0, 1], + [1, 1, 1, 0, 1, 0, 1], + [1, 0, 1, 0, 1, 0, 1], + [1, 0, 1, 0, 1, 1, 1], + [1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1] + ]) + expect(maze.solved).toBe(false) + expect(maze.path).toBe('') + }) +}) diff --git a/Backtracking/tests/Sudoku.test.js b/Backtracking/tests/Sudoku.test.js new file mode 100644 index 0000000000..c70f7de67a --- /dev/null +++ b/Backtracking/tests/Sudoku.test.js @@ -0,0 +1,55 @@ +import { Sudoku } from '../Sudoku' + +const data = [ + [3, 0, 6, 5, 0, 8, 4, 0, 0], + [5, 2, 0, 0, 0, 0, 0, 0, 0], + [0, 8, 7, 0, 0, 0, 0, 3, 1], + [0, 0, 3, 0, 1, 0, 0, 8, 0], + [9, 0, 0, 8, 6, 3, 0, 0, 5], + [0, 5, 0, 0, 9, 0, 6, 0, 0], + [1, 3, 0, 0, 0, 0, 2, 5, 0], + [0, 0, 0, 0, 0, 0, 0, 7, 4], + [0, 0, 5, 2, 0, 6, 3, 0, 0] +] + +const solved = [ + [3, 1, 6, 5, 7, 8, 4, 9, 2], + [5, 2, 9, 1, 3, 4, 7, 6, 8], + [4, 8, 7, 6, 2, 9, 5, 3, 1], + [2, 6, 3, 4, 1, 5, 9, 8, 7], + [9, 7, 4, 8, 6, 3, 1, 2, 5], + [8, 5, 1, 7, 9, 2, 6, 4, 3], + [1, 3, 8, 9, 4, 7, 2, 5, 6], + [6, 9, 2, 3, 5, 1, 8, 7, 4], + [7, 4, 5, 2, 8, 6, 3, 1, 9] +] + +describe('Sudoku', () => { + it('should create a valid board successfully', () => { + // we deliberately want to check whether this constructor call fails or not + expect(() => { + new Sudoku(data) + }).not.toThrow() + }) + + it('should find an empty cell', () => { + const board = new Sudoku(data) + const emptyCell = board.findEmptyCell() + expect(emptyCell).not.toEqual([-1, -1]) + }) + + it('should solve the board successfully', () => { + const board = new Sudoku(data) + board.solve() + + // should not have empty cells anymore + const emptyCell = board.findEmptyCell() + expect(emptyCell).toEqual([-1, -1]) + + // solved board should match our expectation + for (let i = 0; i < 9; i++) { + const section = board.getSection(i, [0, 9]) + expect(section).toEqual(solved[i]) + } + }) +}) diff --git a/Backtracking/tests/SumOfSubset.test.js b/Backtracking/tests/SumOfSubset.test.js new file mode 100644 index 0000000000..07a70c704b --- /dev/null +++ b/Backtracking/tests/SumOfSubset.test.js @@ -0,0 +1,18 @@ +import { sumOfSubset } from '../SumOfSubset' + +describe('SumOfSubset', () => { + it('should return the subsets that add up to the given number', () => { + // W = [2, 5, 7, 8, 12, 16, 23, 40] + // K = 25 + + const nums = [2, 5, 7, 8, 12, 16, 23, 40] + + const subsets = sumOfSubset(nums, [], 0, 0, 25) + + expect(subsets).toEqual([ + [2, 7, 16], + [2, 23], + [5, 8, 12] + ]) + }) +}) diff --git a/Bit-Manipulation/BinaryCountSetBits.js b/Bit-Manipulation/BinaryCountSetBits.js new file mode 100644 index 0000000000..b959caf062 --- /dev/null +++ b/Bit-Manipulation/BinaryCountSetBits.js @@ -0,0 +1,26 @@ +/* + author: vivek9patel + license: GPL-3.0 or later + + This script will find number of 1's + in binary representation of given number + +*/ + +function BinaryCountSetBits(a) { + 'use strict' + + // check whether input is an integer, some non-integer number like, 21.1 have non-terminating binary expansions and hence their binary expansion will contain infinite ones, thus the handling of non-integers (including strings,objects etc. as it is meaningless) has been omitted + + if (!Number.isInteger(a)) throw new TypeError('Argument not an Integer') + + let count = 0 + while (a) { + a &= a - 1 + count++ + } + + return count +} + +export { BinaryCountSetBits } diff --git a/Bit-Manipulation/GenerateSubSets.js b/Bit-Manipulation/GenerateSubSets.js new file mode 100644 index 0000000000..9ee131d757 --- /dev/null +++ b/Bit-Manipulation/GenerateSubSets.js @@ -0,0 +1,34 @@ +/** + * @function generateSubSets + * @param {Array} inputArray + * @returns {Array} + * @example [1,2] -> [[],[1],[2],[1,2]] + */ + +// The time complexity of this algorithm is BigO(2^n) where n is the length of array +function generateSubSets(inputArray) { + if (!Array.isArray(inputArray)) { + throw new Error('Provided input is not an array') + } + if (inputArray.length > 32) { + throw new RangeError('Error size should be less than equal to 32') + } + let arrayLength = inputArray.length + let subSets = [] + // loop till (2^n) - 1 + for (let i = 0; i < 1 << arrayLength; i++) { + let subSet = [] + for (let j = 0; j < arrayLength; j++) { + // 1 << j it shifts binary digit 1 by j positions and then we perform + // and by AND operation we are checking whetheer jth bit + // in i is set to 1 if result is non zero just add into set + if (i & (1 << j)) { + subSet.push(inputArray[j]) + } + } + subSets.push(subSet) + } + return subSets +} + +export { generateSubSets } diff --git a/Bit-Manipulation/GrayCodes.js b/Bit-Manipulation/GrayCodes.js new file mode 100644 index 0000000000..c9ce8ecab9 --- /dev/null +++ b/Bit-Manipulation/GrayCodes.js @@ -0,0 +1,42 @@ +/** + * Generates a Gray code sequence for the given number of bits. + * @param {number} n - The number of bits in the Gray code sequence. + * @returns {number[]} - An array of Gray codes in binary format. + * @description + * Gray codes are binary sequences in which two successive values differ in only one bit. + * This function generates a Gray code sequence of length 2^n for the given number of bits. + * + * The algorithm follows these steps: + * + * 1. Initialize an array `grayCodes` to store the Gray codes. Start with [0, 1] for n = 1. + * 2. Iterate from 1 to n: + * a. Calculate `highestBit` as 2^i, where `i` is the current iteration index. + * b. Iterate in reverse order through the existing Gray codes: + * - For each Gray code `code`, add `highestBit | code` to `grayCodes`. + * - This operation flips a single bit in each existing code, creating new codes. + * 3. Return the `grayCodes` array containing the Gray codes in decimal representation. + * + *resources: [GFG](https://www.geeksforgeeks.org/generate-n-bit-gray-codes/) + * @example + * const n = 3; + * const grayCodes = generateGrayCodes(n); + * // grayCodes will be [0, 1, 3, 2, 6, 7, 5, 4] for n=3. + */ +function generateGrayCodes(n) { + if (n <= 0) { + return [0] + } + + const grayCodes = [0, 1] + + for (let i = 1; i < n; i++) { + const highestBit = 1 << i + for (let j = grayCodes.length - 1; j >= 0; j--) { + grayCodes.push(highestBit | grayCodes[j]) + } + } + + return grayCodes +} + +export { generateGrayCodes } diff --git a/Bit-Manipulation/IsPowerOfTwo.js b/Bit-Manipulation/IsPowerOfTwo.js new file mode 100644 index 0000000000..ee466519ea --- /dev/null +++ b/Bit-Manipulation/IsPowerOfTwo.js @@ -0,0 +1,27 @@ +/* + author: @Aayushi-Mittal + + This script will check whether the given + number is a power of two or not. + + A number will be a power of two if only one bit is set and rest are unset. + This is true for all the cases except 01 because (2^0 = 1) which is not a power of 2. + For eg: 10 (2^1 = 2), 100 (2^2 = 4), 10000 (2^4 = 16) + + Reference Link: https://www.hackerearth.com/practice/notes/round-a-number-to-the-next-power-of-2/ + + If we will subtract 1 from a number that is a power of 2 we will get it's 1's complement. + And we know that 1's complement is just opp. of that number. + So, (n & (n-1)) will be 0. + + For eg: (1000 & (1000-1)) + 1 0 0 0 // Original Number (8) + 0 1 1 1 // After Subtracting 1 (8-1 = 7) + _______ + 0 0 0 0 // will become 0 + +*/ + +export const IsPowerOfTwo = (n) => { + return n > 0 && (n & (n - 1)) === 0 +} diff --git a/Bit-Manipulation/IsPowerofFour.js b/Bit-Manipulation/IsPowerofFour.js new file mode 100644 index 0000000000..f0e57f339f --- /dev/null +++ b/Bit-Manipulation/IsPowerofFour.js @@ -0,0 +1,17 @@ +/** + * @author : dev-madhurendra + * Checks whether the given number is a power of four or not. + * + * A number is considered a power of four if and only if there is a single '1' bit in its binary representation, + * and that '1' bit is at the first position, followed by an even number of '0' bits. + * + * @param {number} n - The input number to check. + * @returns {boolean} True if the number is a power of four, false otherwise. + * + * @example + * const result = isPowerOfFour(16); // Returns true (16 is 4^2) + * const result2 = isPowerOfFour(5); // Returns false (5 is not a power of four) + */ +const isPowerOfFour = (n) => n > 0 && (n & (n - 1)) === 0 && n % 3 === 1 + +export { isPowerOfFour } diff --git a/Bit-Manipulation/LogTwo.js b/Bit-Manipulation/LogTwo.js new file mode 100644 index 0000000000..844a7ac5e5 --- /dev/null +++ b/Bit-Manipulation/LogTwo.js @@ -0,0 +1,14 @@ +/** + * https://handwiki.org/wiki/Binary_logarithm + * Approximate log2 using only bitwise operators + * @param {number} n + * @returns {number} Log2 approximation equal to floor(log2(n)) + */ +export const logTwo = (n) => { + let result = 0 + while (n >> 1) { + n >>= 1 + result++ + } + return result +} diff --git a/Bit-Manipulation/NextPowerOfTwo.js b/Bit-Manipulation/NextPowerOfTwo.js new file mode 100644 index 0000000000..623469e4dc --- /dev/null +++ b/Bit-Manipulation/NextPowerOfTwo.js @@ -0,0 +1,18 @@ +/** + * + * This script will find next power of two + * of given number. + * More about it: + * https://www.techiedelight.com/round-next-highest-power-2/ + * + */ + +export const nextPowerOfTwo = (n) => { + if (n > 0 && (n & (n - 1)) === 0) return n + let result = 1 + while (n > 0) { + result = result << 1 + n = n >> 1 + } + return result +} diff --git a/Bit-Manipulation/SetBit.js b/Bit-Manipulation/SetBit.js new file mode 100644 index 0000000000..f7774dd54f --- /dev/null +++ b/Bit-Manipulation/SetBit.js @@ -0,0 +1,31 @@ +/* + * Setting Bit: https://www.geeksforgeeks.org/set-k-th-bit-given-number/ + * + * To set any bit we use bitwise OR (|) operator. + * + * Bitwise OR (|) compares the bits of the 32 + * bit binary representations of the number and + * returns a number after comparing each bit. + * + * 0 | 0 -> 0 + * 0 | 1 -> 1 + * 1 | 0 -> 1 + * 1 | 1 -> 1 + * + * In-order to set kth bit of a number (where k is the position where bit is to be changed) + * we need to shift 1 k times to its left and then perform bitwise OR operation with the + * number and result of left shift performed just before. + * + * References: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR + */ + +/** + * @param {number} number + * @param {number} bitPosition - zero based. + * @return {number} + */ + +export const setBit = (number, bitPosition) => { + return number | (1 << bitPosition) +} diff --git a/Bit-Manipulation/UniqueElementInAnArray.js b/Bit-Manipulation/UniqueElementInAnArray.js new file mode 100644 index 0000000000..74b5fe95a9 --- /dev/null +++ b/Bit-Manipulation/UniqueElementInAnArray.js @@ -0,0 +1,13 @@ +/** + * Finds the unique element in an array where all other elements are repeated twice. + * + * @param {number[]} arr - The input array of integers. + * @returns {number} The unique element. + * + * @example + * const arr = [1, 2, 1, 2, 3]; + * const uniqueElement = findUniqueElement(arr); // Returns 3 + */ +const findUniqueElement = (arr) => arr.reduce((acc, val) => acc ^ val, 0) + +export { findUniqueElement } diff --git a/Bit-Manipulation/test/BinaryCountSetBits.test.js b/Bit-Manipulation/test/BinaryCountSetBits.test.js new file mode 100644 index 0000000000..29c46e8581 --- /dev/null +++ b/Bit-Manipulation/test/BinaryCountSetBits.test.js @@ -0,0 +1,32 @@ +import { BinaryCountSetBits } from '../BinaryCountSetBits' + +test('check BinaryCountSetBits of 25 is 3', () => { + const res = BinaryCountSetBits(25) + expect(res).toBe(3) +}) +test('check BinaryCountSetBits of 36 is 2', () => { + const res = BinaryCountSetBits(36) + expect(res).toBe(2) +}) +test('check BinaryCountSetBits of 16 is 1', () => { + const res = BinaryCountSetBits(16) + expect(res).toBe(1) +}) +test('check BinaryCountSetBits of 58 is 4', () => { + const res = BinaryCountSetBits(58) + expect(res).toBe(4) +}) +test('check BinaryCountSetBits of 4294967295 is 32', () => { + const res = BinaryCountSetBits(4294967295) + expect(res).toBe(32) +}) +test('check BinaryCountSetBits of 0 is 0', () => { + const res = BinaryCountSetBits(0) + expect(res).toBe(0) +}) +test('check BinaryCountSetBits of 21.1 throws error', () => { + expect(() => BinaryCountSetBits(21.1)).toThrow() +}) +test('check BinaryCountSetBits of {} throws error', () => { + expect(() => BinaryCountSetBits({})).toThrow() +}) diff --git a/Bit-Manipulation/test/GenerateSubSets.test.js b/Bit-Manipulation/test/GenerateSubSets.test.js new file mode 100644 index 0000000000..2e3b90ba71 --- /dev/null +++ b/Bit-Manipulation/test/GenerateSubSets.test.js @@ -0,0 +1,36 @@ +import { generateSubSets } from '../GenerateSubSets' + +describe('subSets', () => { + it('find the subsets', () => { + expect(generateSubSets([1, 2, 3])).toEqual([ + [], + [1], + [2], + [1, 2], + [3], + [1, 3], + [2, 3], + [1, 2, 3] + ]) + expect(generateSubSets([1, 2])).toEqual([[], [1], [2], [1, 2]]) + expect(generateSubSets([1, 2, 3])).toEqual([ + [], + [1], + [2], + [1, 2], + [3], + [1, 3], + [2, 3], + [1, 2, 3] + ]) + expect(() => generateSubSets('invalid')).toThrow( + 'Provided input is not an array' + ) + expect(() => + generateSubSets([ + 1, 2, 2, 1, 2, 3, 4, 3, 2, 3, 4, 3, 2, 2, 2, 3, 12, 11, 4, 2, 2, 2, 2, + 1, 2, 3, 5, 6, 7, 7, 8, 6, 5, 6, 7, 8, 9, 8, 0, 6 + ]) + ).toThrow('Error size should be less than equal to 32') + }) +}) diff --git a/Bit-Manipulation/test/GrayCodes.test.js b/Bit-Manipulation/test/GrayCodes.test.js new file mode 100644 index 0000000000..2e8c46cb8e --- /dev/null +++ b/Bit-Manipulation/test/GrayCodes.test.js @@ -0,0 +1,19 @@ +import { generateGrayCodes } from '../GrayCodes.js' + +describe('Gray codes', () => { + test.each([ + [0, [0b0]], + [1, [0b0, 0b1]], + [2, [0b00, 0b01, 0b11, 0b10]], + [3, [0b000, 0b001, 0b011, 0b010, 0b110, 0b111, 0b101, 0b100]], + [ + 4, + [ + 0b0000, 0b0001, 0b0011, 0b0010, 0b0110, 0b0111, 0b0101, 0b0100, 0b1100, + 0b1101, 0b1111, 0b1110, 0b1010, 0b1011, 0b1001, 0b1000 + ] + ] + ])('n = %i -> %j', (n, expected) => { + expect(generateGrayCodes(n)).toEqual(expected) + }) +}) diff --git a/Bit-Manipulation/test/IsPowerOfFour.test.js b/Bit-Manipulation/test/IsPowerOfFour.test.js new file mode 100644 index 0000000000..5bd666fadd --- /dev/null +++ b/Bit-Manipulation/test/IsPowerOfFour.test.js @@ -0,0 +1,14 @@ +import { isPowerOfFour } from '../IsPowerofFour' + +describe('IsPowerOfFour', () => { + it.each([ + [0, false], + [4, true], + [16, true], + [12, false], + [64, true], + [-64, false] + ])('should return the number is power of four or not', (n, expected) => { + expect(isPowerOfFour(n)).toBe(expected) + }) +}) diff --git a/Bit-Manipulation/test/IsPowerOfTwo.test.js b/Bit-Manipulation/test/IsPowerOfTwo.test.js new file mode 100644 index 0000000000..30539f5e58 --- /dev/null +++ b/Bit-Manipulation/test/IsPowerOfTwo.test.js @@ -0,0 +1,26 @@ +import { IsPowerOfTwo } from '../IsPowerOfTwo' + +test('Check if 0 is a power of 2 or not:', () => { + const res = IsPowerOfTwo(0) + expect(res).toBe(false) +}) + +test('Check if 1 is a power of 2 or not:', () => { + const res = IsPowerOfTwo(1) + expect(res).toBe(true) +}) + +test('Check if 4 is a power of 2 or not:', () => { + const res = IsPowerOfTwo(4) + expect(res).toBe(true) +}) + +test('Check if 1024 is a power of 2 or not:', () => { + const res = IsPowerOfTwo(1024) + expect(res).toBe(true) +}) + +test('Check if 1025 is a power of 2 or not:', () => { + const res = IsPowerOfTwo(1025) + expect(res).toBe(false) +}) diff --git a/Bit-Manipulation/test/LogTwo.test.js b/Bit-Manipulation/test/LogTwo.test.js new file mode 100644 index 0000000000..e811f29b23 --- /dev/null +++ b/Bit-Manipulation/test/LogTwo.test.js @@ -0,0 +1,7 @@ +import { logTwo } from '../LogTwo' + +for (let i = 1; i < 100; i++) { + test('log2(' + i + ')', () => { + expect(logTwo(i)).toBe(Math.floor(Math.log2(i))) + }) +} diff --git a/Bit-Manipulation/test/NextPowerOfTwo.test.js b/Bit-Manipulation/test/NextPowerOfTwo.test.js new file mode 100644 index 0000000000..0732240463 --- /dev/null +++ b/Bit-Manipulation/test/NextPowerOfTwo.test.js @@ -0,0 +1,18 @@ +import { nextPowerOfTwo } from '../NextPowerOfTwo' + +describe('NextPowerOfTwo', () => { + it.each` + input | result + ${0} | ${1} + ${1} | ${1} + ${2} | ${2} + ${3} | ${4} + ${5} | ${8} + ${125} | ${128} + ${1024} | ${1024} + ${10000} | ${16384} + `('returns $result when is given $input', ({ input, result }) => { + const res = nextPowerOfTwo(input) + expect(res).toBe(result) + }) +}) diff --git a/Bit-Manipulation/test/SetBit.test.js b/Bit-Manipulation/test/SetBit.test.js new file mode 100644 index 0000000000..0fc0a1d4b8 --- /dev/null +++ b/Bit-Manipulation/test/SetBit.test.js @@ -0,0 +1,21 @@ +import { setBit } from '../SetBit' + +test('Set bit number 0 in 1:', () => { + const setBitPos = setBit(1, 0) + expect(setBitPos).toBe(1) +}) + +test('Set bit number 0 in 2:', () => { + const setBitPos = setBit(2, 0) + expect(setBitPos).toBe(3) +}) + +test('Set bit number 1 in 10:', () => { + const setBitPos = setBit(10, 1) + expect(setBitPos).toBe(10) +}) + +test('Set bit number 2 in 10:', () => { + const setBitPos = setBit(10, 2) + expect(setBitPos).toBe(14) +}) diff --git a/Bit-Manipulation/test/UniqueElementInAnArray.test.js b/Bit-Manipulation/test/UniqueElementInAnArray.test.js new file mode 100644 index 0000000000..4aa66bc8b7 --- /dev/null +++ b/Bit-Manipulation/test/UniqueElementInAnArray.test.js @@ -0,0 +1,10 @@ +import { findUniqueElement } from '../UniqueElementInAnArray' + +describe('UniqueElementInAnArray', () => { + it.each([ + [[1, 2, 1, 3, 3], 2], + [[1, 2, 3, 4, 5, 4, 3, 2, 1], 5] + ])('should return an unique element from an array', (arr, expected) => { + expect(findUniqueElement(arr)).toBe(expected) + }) +}) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8efea899d2..83f0624254 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,89 +2,169 @@ ## 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 ### Contributor -We are very happy that you consider implementing algorithms and data structures for others! This repository is referenced and used by learners from around the globe. Being one of our contributors, you agree and confirm that: +We are very happy that you consider implementing algorithms and data structures for others! This repository is +referenced and used by learners from around the globe. Being one of our contributors, you agree and confirm that: - 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 submitted work must fulfill our styles and standards +- 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. ### Contribution -We appreciate any contribution, from fixing grammar mistakes to implementing complex algorithms. Please read this section if you are contributing to your work. +We appreciate any contribution, from fixing grammar mistakes to implementing complex algorithms. Please read this +section if you are contributing to your work. - -If you submit a pull request that resolves an open issue, please help us to keep our issue list small by adding `fixes: #{$ISSUE_NO}` to your commit message. GitHub will use this tag to auto-close the issue if your PR is merged. +If you submit a pull request that resolves an open issue, please help us to keep our issue list small by adding +`fixes: #{$ISSUE_NO}` to your commit message. GitHub will use this tag to auto-close the issue if your PR is merged. #### What is an Algorithm? An Algorithm is one or more functions (or classes) that: -* take one or more inputs, -* perform some internal calculations or data manipulations, -* return one or more outputs, -* have minimal side effects. + +- take one or more inputs. +- perform some internal calculations or data manipulations. +- return one or more outputs. +- have minimal side effects. Algorithms should be packaged in a way that would make it easy for readers to put them into larger programs. Algorithms should: -* have intuitive class and function names that make their purpose clear to readers -* use JavaScript naming conventions and intuitive variable names to ease comprehension -* be flexible to take different input values -* raise JavaScript exceptions (RangeError, etc.) on erroneous input values -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. +- have intuitive class and function names that make their purpose clear to readers. +- use JavaScript naming conventions and intuitive variable names to ease comprehension. +- be flexible to take different input values. +- raise JavaScript exceptions (RangeError, etc.) on erroneous input values. + +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 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. - - There should be no spaces in filenames. - **Example:**`UserProfile.js` is allowed but `userprofile.js`,`Userprofile.js`,`user-Profile.js`,`userProfile.js` are not + +- 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 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()`. + +#### Testing + +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 airtight even after multiple fixes and code changes. + +We use [Vitest](https://vitest.dev/) to run unit tests on our algorithms. It provides a very readable and expressive +way to structure your test code. + +It is advised that the algorithm file (module) does not contain any "live" code but rather just exports the function(s) +needed to execute the algorithm. Your test code can import those function(s), call them with the appropriate parameters +and inspect the outcome. Example: [RatInAMaze.test.js](Backtracking/tests/RatInAMaze.test.js). + +Please refrain from using `console` in your implementation AND test code. + +First, you should install all dependencies using: + +```bash +npm install +``` + +You can (and should!) run all tests locally before committing your changes: + +```bash +npm test +``` + +If you want to save some time and just run a specific test: + +```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 Vitest in "watch" mode: + +```bash +npm run test-watch +``` + +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 -To maximize the readability and correctness of our code, we require that new submissions follow [JavaScript Standard Style](https://standardjs.com/) - - Command to install JavaScript Standard Style - ``` - $ npm install standard --save-dev - ``` - - Usage - ``` - $ standard MyFile.js // if that fails, try: npx standard MyFile.js - ``` - -- Use camelCase for with leading character lowercase for identifier names (variables and functions) -- Names start with a letter -- follow code indentation - - Always use 2 spaces for indentation of code blocks - ``` - function sumOfArray (arrayOfNumbers) { - let sum = 0 - for (let i = 0; i < arrayOfNumbers.length; i++) { - sum += arrayOfNumbers[i] - } - return (sum) - } - - ``` -- Avoid using global variables and avoid '==' -- Please use 'let' over 'var' -- Please use 'console.log()' -- We strongly recommend the use of ECMAScript 6 -- Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. +For consistency and readability, we require that new submissions follow the [Prettier Style](https://prettier.io/). +Before committing, please format your code automatically using Prettier by running the following command: + +```bash +npm run style +``` + +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 code-block indentation. +```js +function sumOfArray(arrayOfNumbers) { + let sum = 0 -- Most importantly, + for (let i = 0; i < arrayOfNumbers.length; i++) { + sum += arrayOfNumbers[i] + } + + return sum +} +``` + +- Avoid using global variables and avoid `==`. +- Please use `let` over `var`. +- Please refrain from using `console.log` or any other console methods. +- **Absolutely** don't use `alert`. +- We strongly recommend the use of ECMAScript 6. +- Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. +- Most importantly: - **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/Cache/LFUCache.js b/Cache/LFUCache.js index 7238b9cb70..d8774fb506 100644 --- a/Cache/LFUCache.js +++ b/Cache/LFUCache.js @@ -1,143 +1,258 @@ -class DoubleLinkedListNode { - // Double Linked List Node built specifically for LFU Cache - constructor (key, val) { +class CacheNode { + constructor(key, value, frequency) { this.key = key - this.val = val - this.freq = 0 - this.next = null - this.prev = null + this.value = value + this.frequency = frequency + + return Object.seal(this) } } -class DoubleLinkedList { - // Double Linked List built specifically for LFU Cache - constructor () { - this.head = new DoubleLinkedListNode(null, null) - this.rear = new DoubleLinkedListNode(null, null) - this.head.next = this.rear - this.rear.prev = this.head - } - - _positionNode (node) { - // Helper function to position a node based on the frequency of the key - while (node.prev.key && node.prev.freq > node.freq) { - const node1 = node - const node2 = node.prev - node1.prev = node2.prev - node2.next = node1.prev - node1.next = node2 - node2.prev = node1 - } +// This frequency map class will act like javascript Map DS with more two custom method refresh & insert +class FrequencyMap extends Map { + static get [Symbol.species]() { + return Map + } // for using Symbol.species we can access Map constructor @see -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/@@species + get [Symbol.toStringTag]() { + return '' } - add (node) { - // Adds the given node to the end of the list (before rear) and positions it based on frequency - const temp = this.rear.prev - temp.next = node - node.prev = temp - this.rear.prev = node - node.next = this.rear - this._positionNode(node) + /** + * @method refresh + * @description - It's revive a CacheNode, increment of this nodes frequency and refresh the frequencyMap via new incremented nodes frequency + * @param {CacheNode} node + */ + refresh(node) { + const { frequency } = node + const freqSet = this.get(frequency) + freqSet.delete(node) + + node.frequency++ + + this.insert(node) } - remove (node) { - // Removes and returns the given node from the list - const tempLast = node.prev - const tempNext = node.next - node.prev = null - node.next = null - tempLast.next = tempNext - tempNext.prev = tempLast + /** + * @method insert + * @description - Add new CacheNode into HashSet by the frequency + * @param {CacheNode} node + */ + insert(node) { + const { frequency } = node - return node + if (!this.has(frequency)) { + this.set(frequency, new Set()) + } + + this.get(frequency).add(node) } } class LFUCache { - // LFU Cache to store a given capacity of data - // The Double Linked List is used to store the order of deletion from the cache - // The rear.prev holds the most frequently used key and the head.next holds the least used key - // When the number of elements reaches the capacity, the least frequently used item is removed before adding the next key - constructor (capacity) { - this.list = new DoubleLinkedList() - this.capacity = capacity - this.numKeys = 0 + #capacity + #frequencyMap + + /** + * @param {number} capacity - The range of LFUCache + * @returns {LFUCache} - sealed + */ + constructor(capacity) { + this.#capacity = capacity + this.#frequencyMap = new FrequencyMap() + this.misses = 0 this.hits = 0 - this.miss = 0 - this.cache = {} + this.cache = new Map() + + return Object.seal(this) + } + + /** + * Get the capacity of the LFUCache + * @returns {number} + */ + get capacity() { + return this.#capacity } - cacheInfo () { - // Return the details for the cache instance [hits, misses, capacity, current_size] - return `CacheInfo(hits=${this.hits}, misses=${this.miss}, capacity=${this.capacity}, current size=${this.numKeys})` + /** + * Get the current size of LFUCache + * @returns {number} + */ + get size() { + return this.cache.size } - set (key, value) { - // Sets the value for the input key and updates the Double Linked List - if (!(key in this.cache)) { - if (this.numKeys >= this.capacity) { - const keyToDelete = this.list.head.next.key - this.list.remove(this.cache[keyToDelete]) - delete this.cache[keyToDelete] - this.numKeys -= 1 + /** + * Set the capacity of the LFUCache if you decrease the capacity its removed CacheNodes following the LFU - least frequency used + */ + set capacity(newCapacity) { + if (this.#capacity > newCapacity) { + let diff = this.#capacity - newCapacity // get the decrement number of capacity + + while (diff--) { + this.#removeCacheNode() } - this.cache[key] = new DoubleLinkedListNode(key, value) - this.list.add(this.cache[key]) - this.numKeys += 1 - } else { - const node = this.list.remove(this.cache[key]) - node.val = value - this.list.add(node) + + this.cache.size === 0 && this.#frequencyMap.clear() } + + this.#capacity = newCapacity + } + + get info() { + return Object.freeze({ + misses: this.misses, + hits: this.hits, + capacity: this.capacity, + currentSize: this.size, + leastFrequency: this.leastFrequency + }) + } + + get leastFrequency() { + const freqCacheIterator = this.#frequencyMap.keys() + let leastFrequency = freqCacheIterator.next().value || null + + // select the non-empty frequency Set + while (this.#frequencyMap.get(leastFrequency)?.size === 0) { + leastFrequency = freqCacheIterator.next().value + } + + return leastFrequency + } + + #removeCacheNode() { + const leastFreqSet = this.#frequencyMap.get(this.leastFrequency) + // Select the least recently used node from the least Frequency set + const LFUNode = leastFreqSet.values().next().value + + leastFreqSet.delete(LFUNode) + this.cache.delete(LFUNode.key) + } + + /** + * if key exist then return true otherwise false + * @param {any} key + * @returns {boolean} + */ + has(key) { + key = String(key) // converted to string + + return this.cache.has(key) } - get (key) { - // Returns the value for the input key and updates the Double Linked List. Returns null if key is not present in cache - if (key in this.cache) { - this.hits += 1 - this.list.add(this.list.remove(this.cache[key])) - return this.cache[key].val + /** + * @method get + * @description - This method return the value of key & refresh the frequencyMap by the oldNode + * @param {string} key + * @returns {any} + */ + get(key) { + key = String(key) // converted to string + + if (this.cache.has(key)) { + const oldNode = this.cache.get(key) + this.#frequencyMap.refresh(oldNode) + + this.hits++ + + return oldNode.value } - this.miss += 1 + + this.misses++ return null } -} -function main () { - // Example 1 (Small Cache) - const cache = new LFUCache(2) - cache.set(1, 1) - cache.set(2, 2) + /** + * @method set + * @description - This method stored the value by key & add frequency if it doesn't exist + * @param {string} key + * @param {any} value + * @param {number} frequency + * @returns {LFUCache} + */ + set(key, value, frequency = 1) { + key = String(key) // converted to string - console.log(cache.get(1)) + if (this.#capacity === 0) { + throw new RangeError('LFUCache ERROR: The Capacity is 0') + } - cache.set(3, 3) + if (this.cache.has(key)) { + const node = this.cache.get(key) + node.value = value - console.log(cache.get(2)) // cache miss + this.#frequencyMap.refresh(node) - cache.set(4, 4) + return this + } + + // if the cache size is full, then it's delete the Least Frequency Used node + if (this.#capacity === this.cache.size) { + this.#removeCacheNode() + } - console.log(cache.get(1)) // cache miss - console.log(cache.get(3)) - console.log(cache.get(4)) + const newNode = new CacheNode(key, value, frequency) + + this.cache.set(key, newNode) + this.#frequencyMap.insert(newNode) + + return this + } - console.log('Example Cache: ', cache.cacheInfo(), '\n') + /** + * @method parse + * @description - This method receive a valid LFUCache JSON & run JSON.prase() method and merge with existing LFUCache + * @param {JSON} json + * @returns {LFUCache} - merged + */ + parse(json) { + const { misses, hits, cache } = JSON.parse(json) - // Example 2 (Computing Fibonacci Series - 100 terms) - function fib (num, cache = null) { - if (cache) { - const value = cache.get(num) - if (value) { return value } + this.misses += misses ?? 0 + this.hits += hits ?? 0 + + for (const key in cache) { + const { value, frequency } = cache[key] + this.set(key, value, frequency) } - if (num === 1 || num === 2) { return 1 } - const result = fib(num - 1, cache) + fib(num - 2, cache) - if (cache) { cache.set(num, result) } - return result + + return this + } + + /** + * @method clear + * @description - This method cleared the whole LFUCache + * @returns {LFUCache} + */ + clear() { + this.cache.clear() + this.#frequencyMap.clear() + + return this } - const fibCache = new LFUCache(100) - for (let i = 1; i <= 100; i++) { fib(i, fibCache) } - console.log('Fibonacci Series Cache: ', fibCache.cacheInfo(), '\n') + /** + * @method toString + * @description - This method generate a JSON format of LFUCache & return it. + * @param {number} indent + * @returns {string} - JSON + */ + toString(indent) { + const replacer = (_, value) => { + if (value instanceof Set) { + return [...value] + } + + if (value instanceof Map) { + return Object.fromEntries(value) + } + + return value + } + + return JSON.stringify(this, replacer, indent) + } } -main() +export default LFUCache diff --git a/Cache/LRUCache.js b/Cache/LRUCache.js index 5106331df1..43848b52aa 100644 --- a/Cache/LRUCache.js +++ b/Cache/LRUCache.js @@ -1,126 +1,146 @@ -class DoubleLinkedListNode { - // Double Linked List Node built specifically for LRU Cache - constructor (key, val) { - this.key = key - this.val = val - this.next = null - this.prev = null +class LRUCache { + // LRU Cache to store a given capacity of data + #capacity + + /** + * @param {number} capacity - the capacity of LRUCache + * @returns {LRUCache} - sealed + */ + constructor(capacity) { + if (!Number.isInteger(capacity) || capacity < 0) { + throw new TypeError('Invalid capacity') + } + + this.#capacity = ~~capacity + this.misses = 0 + this.hits = 0 + this.cache = new Map() + + return Object.seal(this) } -} -class DoubleLinkedList { - // Double Linked List built specifically for LRU Cache - constructor () { - this.head = new DoubleLinkedListNode(null, null) - this.rear = new DoubleLinkedListNode(null, null) - this.head.next = this.rear - this.rear.prev = this.head + get info() { + return Object.freeze({ + misses: this.misses, + hits: this.hits, + capacity: this.capacity, + size: this.size + }) } - add (node) { - // Adds the given node to the end of the list (before rear) - const temp = this.rear.prev - temp.next = node - node.prev = temp - this.rear.prev = node - node.next = this.rear + get size() { + return this.cache.size } - remove (node) { - // Removes and returns the given node from the list - const tempLast = node.prev - const tempNext = node.next - node.prev = null - node.next = null - tempLast.next = tempNext - tempNext.prev = tempLast + get capacity() { + return this.#capacity + } - return node + set capacity(newCapacity) { + if (newCapacity < 0) { + throw new RangeError('Capacity should be greater than 0') + } + + if (newCapacity < this.capacity) { + let diff = this.capacity - newCapacity + + while (diff--) { + this.#removeLeastRecentlyUsed() + } + } + + this.#capacity = newCapacity } -} -class LRUCache { - // LRU Cache to store a given capacity of data - constructor (capacity) { - this.list = new DoubleLinkedList() - this.capacity = capacity - this.numKeys = 0 - this.hits = 0 - this.miss = 0 - this.cache = {} + /** + * delete oldest key existing in map by the help of iterator + */ + #removeLeastRecentlyUsed() { + this.cache.delete(this.cache.keys().next().value) } - cacheInfo () { - // Return the details for the cache instance [hits, misses, capacity, current_size] - return `CacheInfo(hits=${this.hits}, misses=${this.miss}, capacity=${this.capacity}, current size=${this.numKeys})` + /** + * @param {string} key + * @returns {*} + */ + has(key) { + key = String(key) + + return this.cache.has(key) } - set (key, value) { - // Sets the value for the input key and updates the Double Linked List - if (!(key in this.cache)) { - if (this.numKeys >= this.capacity) { - const keyToDelete = this.list.head.next.key - this.list.remove(this.cache[keyToDelete]) - delete this.cache[keyToDelete] - this.numKeys -= 1 - } - this.cache[key] = new DoubleLinkedListNode(key, value) - this.list.add(this.cache[key]) - this.numKeys += 1 - } else { - const node = this.list.remove(this.cache[key]) - node.val = value - this.list.add(node) + /** + * @param {string} key + * @param {*} value + */ + set(key, value) { + key = String(key) + // Sets the value for the input key and if the key exists it updates the existing key + if (this.size === this.capacity) { + this.#removeLeastRecentlyUsed() } + + this.cache.set(key, value) } - get (key) { - // Returns the value for the input key and updates the Double Linked List. Returns null if key is not present in cache - if (key in this.cache) { - this.hits += 1 - this.list.add(this.list.remove(this.cache[key])) - return this.cache[key].val + /** + * @param {string} key + * @returns {*} + */ + get(key) { + key = String(key) + // Returns the value for the input key. Returns null if key is not present in cache + if (this.cache.has(key)) { + const value = this.cache.get(key) + + // refresh the cache to update the order of key + this.cache.delete(key) + this.cache.set(key, value) + + this.hits++ + return value } - this.miss += 1 + + this.misses++ return null } -} -function main () { - // Example 1 (Small Cache) - const cache = new LRUCache(2) - cache.set(1, 1) - cache.set(2, 2) + /** + * @param {JSON} json + * @returns {LRUCache} + */ + parse(json) { + const { misses, hits, cache } = JSON.parse(json) - console.log(cache.get(1)) + this.misses += misses ?? 0 + this.hits += hits ?? 0 - cache.set(3, 3) - - console.log(cache.get(2)) // cache miss + for (const key in cache) { + this.set(key, cache[key]) + } - cache.set(4, 4) + return this + } - console.log(cache.get(1)) // cache miss - console.log(cache.get(3)) - console.log(cache.get(4)) + /** + * @param {number} indent + * @returns {JSON} - string + */ + toString(indent) { + const replacer = (_, value) => { + if (value instanceof Set) { + return [...value] + } - console.log('Example Cache: ', cache.cacheInfo(), '\n') + if (value instanceof Map) { + return Object.fromEntries(value) + } - // Example 2 (Computing Fibonacci Series - 100 terms) - function fib (num, cache = null) { - if (cache) { - const value = cache.get(num) - if (value) { return value } + return value } - if (num === 1 || num === 2) { return 1 } - const result = fib(num - 1, cache) + fib(num - 2, cache) - if (cache) { cache.set(num, result) } - return result - } - const fibCache = new LRUCache(100) - for (let i = 1; i <= 100; i++) { fib(i, fibCache) } - console.log('Fibonacci Series Cache: ', fibCache.cacheInfo(), '\n') + return JSON.stringify(this, replacer, indent) + } } -main() +export default LRUCache diff --git a/Cache/Memoize.js b/Cache/Memoize.js new file mode 100644 index 0000000000..b6ff37b9e5 --- /dev/null +++ b/Cache/Memoize.js @@ -0,0 +1,60 @@ +/** + * @function memoize + * @description -> + * From [Wikipedia](https://en.wikipedia.org/wiki/Memoization), + * memoization is an optimization technique + * used primarily to speed up computer programs, + * by storing the results of expensive function calls + * and returning the cached result when the same inputs occur again + * This function is a first class objects, + * which lets us use it as [Higher-Order Function](https://eloquentjavascript.net/05_higher_order.html) + * and return another function + * @param {Function} func Original function + * @param {Map} cache - it's receive any cache DS which have get, set & has method + * @returns {Function} Memoized function + */ +const memoize = (func, cache = new Map()) => { + const jsonReplacer = (_, value) => { + if (value instanceof Set) { + // if the value is Set it's converted to Array cause JSON.stringify can't convert Set + return [...value] + } + + if (value instanceof Map) { + // if the value is Map it's converted to Object cause JSON.stringify can't convert Map + return Object.fromEntries(value) + } + + return value + } + + return (...args) => { + /** + * Arguments converted to JSON string for use as a key of Map - it's easy to detect collections like -> Object and Array + * If the args input is -> [new Set([1, 2, 3, 4]), {name: 'myName', age: 23}] + * Then the argsKey generate to -> '[[1,2,3,4],{"name":"myName","age":23}]' which is JSON mean string + * Now it's ready to be a perfect key for Map + */ + const argsKey = JSON.stringify(args, jsonReplacer) + + /** + * Checks if the argument is already present in the cache, + * then return the associated value / result + */ + if (cache.has(argsKey)) { + return cache.get(argsKey) + } + + /** + * If the argument is not yet present in the cache, + * execute original function and save its value / result in cache, + * finally return it + */ + const result = func(...args) // spread all args + cache.set(argsKey, result) + + return result + } +} + +export { memoize } diff --git a/Cache/test/LFUCache.test.js b/Cache/test/LFUCache.test.js new file mode 100644 index 0000000000..a7f30a0274 --- /dev/null +++ b/Cache/test/LFUCache.test.js @@ -0,0 +1,77 @@ +import LFUCache from '../LFUCache' +import { fibonacciCache } from './cacheTest' + +describe('Testing LFUCache class', () => { + it('Example 1 (Small Cache, size = 2)', () => { + const cache = new LFUCache(1) // initially capacity 1 + + cache.capacity = 2 // increase the capacity + + expect(cache.capacity).toBe(2) + + cache.set(1, 1) // frequency = 1 + cache.set(2, 2) // frequency = 1 + + expect(cache.get(1)).toBe(1) // frequency = 2 + expect(cache.get(2)).toBe(2) // frequency = 2 + + // Additional entries triggers cache rotate + cache.set(3, 3) // frequency = 1 & key 1 removed from the cached, cause now it's tie and followed the LRU system + + expect(cache.get(1)).toBe(null) // misses = 1 + expect(cache.get(2)).toBe(2) // frequency = 3 + expect(cache.get(3)).toBe(3) // frequency = 2 + + cache.set(4, 4) // frequency = 1 & key 3 removed cause the frequency of 3 is 2 which is least frequency + expect(cache.get(1)).toBe(null) // misses = 2 + expect(cache.get(2)).toBe(2) // frequency = 4 + expect(cache.get(3)).toBe(null) // misses = 3 + expect(cache.get(4)).toBe(4) // frequency = 2 which is least + + expect(cache.info).toEqual({ + misses: 3, + hits: 6, + capacity: 2, + currentSize: 2, + leastFrequency: 2 + }) + + const json = + '{"misses":3,"hits":6,"cache":{"2":{"key":"2","value":2,"frequency":4},"4":{"key":"4","value":4,"frequency":2}}}' + expect(cache.toString()).toBe(json) + + const cacheInstance = cache.parse(json) // again merge the json + + expect(cacheInstance).toBe(cache) // return the same cache + + cache.capacity = 1 // decrease the capacity + + expect(cache.info).toEqual({ + // after merging the info + misses: 6, + hits: 12, + capacity: 1, + currentSize: 1, + leastFrequency: 5 + }) + + const clearedCache = cache.clear() // clear the cache + expect(clearedCache.size).toBe(0) + }) + + it('Example 2 (Computing Fibonacci Series, size = 100)', () => { + const cache = new LFUCache(100) + + for (let i = 1; i <= 100; i++) { + fibonacciCache(i, cache) + } + + expect(cache.info).toEqual({ + misses: 103, + hits: 193, + capacity: 100, + currentSize: 98, + leastFrequency: 1 + }) + }) +}) diff --git a/Cache/test/LRUCache.test.js b/Cache/test/LRUCache.test.js new file mode 100644 index 0000000000..c9acc3dadf --- /dev/null +++ b/Cache/test/LRUCache.test.js @@ -0,0 +1,74 @@ +import LRUCache from '../LRUCache' +import { fibonacciCache } from './cacheTest' + +describe('Testing LRUCache', () => { + it('Testing with invalid capacity', () => { + expect(() => new LRUCache()).toThrow() + expect(() => new LRUCache('Invalid')).toThrow() + expect(() => new LRUCache(-1)).toThrow() + expect(() => new LRUCache(Infinity)).toThrow() + }) + + it('Example 1 (Small Cache, size = 2)', () => { + const cache = new LRUCache(1) // initially capacity + + cache.capacity++ // now the capacity is increasing by one + + cache.set(1, 1) + cache.set(2, 2) + + expect(cache.get(1)).toBe(1) + expect(cache.get(2)).toBe(2) + + // Additional entries triggers cache rotate + cache.set(3, 3) + + // Then we should have a cache miss for the first entry added + expect(cache.get(1)).toBe(null) + expect(cache.get(2)).toBe(2) + expect(cache.get(3)).toBe(3) + + cache.set(4, 4) + expect(cache.get(1)).toBe(null) // cache miss + expect(cache.get(2)).toBe(null) // cache miss + expect(cache.get(3)).toBe(3) + expect(cache.get(4)).toBe(4) + + expect(cache.info).toEqual({ + misses: 3, + hits: 6, + capacity: 2, + size: 2 + }) + + const json = '{"misses":3,"hits":6,"cache":{"3":3,"4":4}}' + expect(cache.toString()).toBe(json) + + // merge with json + cache.parse(json) + + cache.capacity-- // now the capacity decreasing by one + + expect(cache.info).toEqual({ + misses: 6, + hits: 12, + capacity: 1, + size: 1 + }) + }) + + it('Example 2 (Computing Fibonacci Series, size = 100)', () => { + const cache = new LRUCache(100) + + for (let i = 1; i <= 100; i++) { + fibonacciCache(i, cache) + } + + expect(cache.info).toEqual({ + misses: 103, + hits: 193, + capacity: 100, + size: 98 + }) + }) +}) diff --git a/Cache/test/Memoize.test.js b/Cache/test/Memoize.test.js new file mode 100644 index 0000000000..cc53e41615 --- /dev/null +++ b/Cache/test/Memoize.test.js @@ -0,0 +1,64 @@ +import { memoize } from '../Memoize' +import { union } from './cacheTest' +import { fibonacci } from '../../Dynamic-Programming/FibonacciNumber' +import { factorial } from '../../Recursive/Factorial' +import LFUCache from '../LFUCache' + +const multipleFactorials = (arr) => arr.map(factorial) + +describe('Testing Memoize', () => { + it('expects the fibonacci function to use the cache on the second call', () => { + const memoFibonacci = memoize(fibonacci) + + expect(memoFibonacci(5)).toEqual(fibonacci(5)) + expect(memoFibonacci(5)).toEqual(5) + expect(memoFibonacci(10)).toEqual(fibonacci(10)) + expect(memoFibonacci(10)).toEqual(55) + }) + + it('expects the factorial function to use the cache on the second call', () => { + const memoFactorial = memoize(factorial) + + expect(memoFactorial(5)).toEqual(factorial(5)) + expect(memoFactorial(5)).toEqual(120) + expect(memoFactorial(10)).toEqual(factorial(10)) + expect(memoFactorial(10)).toEqual(3628800) + }) + + it('expects the multipleFactorials function to use the cache on the second call', () => { + const memoMultipleFactorials = memoize(multipleFactorials) + const input = [2, 3, 4, 5] + + expect(memoMultipleFactorials(input)).toEqual([2, 6, 24, 120]) + expect(memoMultipleFactorials(input)).toEqual(multipleFactorials(input)) + }) + + it('expects the multipleFactorials function to use the cache on the second call', () => { + const memoMultipleFactorials = memoize(multipleFactorials) + const input = [2, 3, 4, 5] + + expect(memoMultipleFactorials(input)).toEqual([2, 6, 24, 120]) + expect(memoMultipleFactorials(input)).toEqual(multipleFactorials(input)) + }) + + it('expects the union function to use the cache on the second call', () => { + const memoUnion = memoize(union) + const inputs = [new Set([1, 2, 3]), new Set([4, 3, 2]), new Set([5, 3, 6])] + + expect(memoUnion(...inputs)).toEqual(new Set([1, 2, 3, 4, 5, 6])) + expect(memoUnion(...inputs)).toEqual(union(...inputs)) + }) + + it('Testing with explicit cache -> LFUCache', () => { + const LFU = new LFUCache(2) + + const memoizeFibonacci = memoize(fibonacci, LFU) // added LFU cache explicitly + const fibOfFiveHundred = memoizeFibonacci(500) + const fibOfOneHundred = memoizeFibonacci(100) + + expect(memoizeFibonacci(500)).toBe(fibOfFiveHundred) + expect(memoizeFibonacci(100)).toBe(fibOfOneHundred) + + expect(LFU.leastFrequency).toBe(2) + }) +}) diff --git a/Cache/test/cacheTest.js b/Cache/test/cacheTest.js new file mode 100644 index 0000000000..1aa7bc6d6e --- /dev/null +++ b/Cache/test/cacheTest.js @@ -0,0 +1,35 @@ +/** + * @function fibonacciCache + * @description - this is a cached variant of fib number + * @param {number} n - Real number (n > -1) + * @param {Object} cache + * @returns {number} + */ +export const fibonacciCache = (n, cache = null) => { + if (cache) { + const value = cache.get(n) + + if (value !== null) { + return value + } + } + + if (n === 1 || n === 2) { + return 1 + } + + const result = fibonacciCache(n - 1, cache) + fibonacciCache(n - 2, cache) + + cache && cache.set(n, result) + + return result +} + +/** + * @title implementation of union function + * @param {Set} sets + * @return {new Set} + */ +export const union = (...sets) => { + return new Set(sets.reduce((flatArray, set) => [...flatArray, ...set], [])) +} diff --git a/Cellular-Automata/ConwaysGameOfLife.js b/Cellular-Automata/ConwaysGameOfLife.js new file mode 100644 index 0000000000..3175216f02 --- /dev/null +++ b/Cellular-Automata/ConwaysGameOfLife.js @@ -0,0 +1,44 @@ +/* +Conway's Game of Life +The Game of Life is a cellular automaton devised by the British mathematician John Horton Conway in 1970. The universe of the Game of Life is an infinite, two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, live or dead, (or populated and unpopulated, respectively). Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur: + 1. Any live cell with two or three live neighbours survives. + 2. Any dead cell with three live neighbours becomes a live cell. + 3. All other live cells die in the next generation. Similarly, all other dead cells stay dead. +(description adapted from https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life ) +(example adapted from https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/conways_game_of_life.py ) +*/ + +/** + * Generates the next generation for a given state of Conway's Game of Life. + */ +export function newGeneration(cells) { + const nextGeneration = [] + for (let i = 0; i < cells.length; i++) { + const nextGenerationRow = [] + for (let j = 0; j < cells[i].length; j++) { + // Get the number of living neighbours + let neighbourCount = 0 + if (i > 0 && j > 0) neighbourCount += cells[i - 1][j - 1] + if (i > 0) neighbourCount += cells[i - 1][j] + if (i > 0 && j < cells[i].length - 1) + neighbourCount += cells[i - 1][j + 1] + if (j > 0) neighbourCount += cells[i][j - 1] + if (j < cells[i].length - 1) neighbourCount += cells[i][j + 1] + if (i < cells.length - 1 && j > 0) neighbourCount += cells[i + 1][j - 1] + if (i < cells.length - 1) neighbourCount += cells[i + 1][j] + if (i < cells.length - 1 && j < cells[i].length - 1) + neighbourCount += cells[i + 1][j + 1] + + // Decide whether the cell is alive or dead + const alive = cells[i][j] === 1 + + const cellIsAlive = + (alive && neighbourCount >= 2 && neighbourCount <= 3) || + (!alive && neighbourCount === 3) + + nextGenerationRow.push(cellIsAlive ? 1 : 0) + } + nextGeneration.push(nextGenerationRow) + } + return nextGeneration +} diff --git a/Cellular-Automata/Elementary.js b/Cellular-Automata/Elementary.js new file mode 100644 index 0000000000..7eb0b51090 --- /dev/null +++ b/Cellular-Automata/Elementary.js @@ -0,0 +1,111 @@ +/** + * Author: Jacoby Johnson (cobyj33) + * + * Generates generations of Elementary 1D cellular automata + * + * Wikipedia: https://en.wikipedia.org/wiki/Elementary_cellular_automaton + * See all 255 possible rules and find another explanation here: https://mathworld.wolfram.com/ElementaryCellularAutomaton.html + * + * My personal take on the explanation of Elementary Cellular Automata: + * + * Elementary 1D cellular automata defines the growth and decay of populations of "cells" according to a specific rule, where the population is a line (array) and each cell is in either an "alive" (1) or a "dead" (0) state. + * + * The next generation of for a cell in the simulation ONLY depends on the state of its neighborhood (the state of the cell itself as well as the states of the cells to the immediate right and the immediate left) + * Therefore, since each neighborhood consists of 3 cells each with 2 states there are 2^3 possibilities that determine a cell's next state, where each possible neighborhood could be represented in binary as + * + * 111 + * 110 + * 101 + * 100 + * 011 + * 010 + * 001 + * 000 + * + * Where "1" represents the cell being alive and "0" represents the cell being dead. The leftmost bit represents the left neighbor of the currently analyzed cell, the middle bit represents the currently analyzed cell, and the rightmost bit represents the right neighbor of the currently analyzed cell. + * + * Rules are represented between 0 and 255 (0 and 255 inclusive), or more conceptually is seen as an 8 bit binary number. Each bit represents whether a cell should survive according to one of the 8 states of a cell's neighborhood. In this way, the number can act like an array of data of length 8. + * The most significant bit (ex: ->10100100 ) represents the output for the "all on" state while the least significant bit (ex: 10100100<- ) represents the output for the "all off" state + * In other words, all of the 8 possible neighborhood configurations map toward the 8 bits in a rule's output values + * + * Therefore, to find whether the a cell is born, survives, or dies, one could convert the state of a cell and it's neighbors into a binary number, then use the numerical value of that binary number as an index to find the corresponding rule's output, which is what has been implemented below + * This analysis of a cell's neighborhood is performed on each cell in a generation until a new generation is created and returned. + * + * Rules are usually demonstrated visually by how a single cell grows independently according to that rule + * + * Example: First generations of Rule 94 First Generations of Rule 126 + * 000000000000000000000000001000000000000000000000000 000000000000000000000000001000000000000000000000000 + * 000000000000000000000000011100000000000000000000000 000000000000000000000000011100000000000000000000000 + * 000000000000000000000000110110000000000000000000000 000000000000000000000000110110000000000000000000000 + * 000000000000000000000001110111000000000000000000000 000000000000000000000001111111000000000000000000000 + * 000000000000000000000011010101100000000000000000000 000000000000000000000011000001100000000000000000000 + * 000000000000000000000111010101110000000000000000000 000000000000000000000111100011110000000000000000000 + * 000000000000000000001101010101011000000000000000000 000000000000000000001100110110011000000000000000000 + * 000000000000000000011101010101011100000000000000000 000000000000000000011111111111111100000000000000000 + * 000000000000000000110101010101010110000000000000000 000000000000000000110000000000000110000000000000000 + * 000000000000000001110101010101010111000000000000000 000000000000000001111000000000001111000000000000000 + * 000000000000000011010101010101010101100000000000000 000000000000000011001100000000011001100000000000000 + * 000000000000000111010101010101010101110000000000000 000000000000000111111110000000111111110000000000000 + * 000000000000001101010101010101010101011000000000000 000000000000001100000011000001100000011000000000000 + * 000000000000011101010101010101010101011100000000000 000000000000011110000111100011110000111100000000000 + * 000000000000110101010101010101010101010110000000000 000000000000110011001100110110011001100110000000000 + * 000000000001110101010101010101010101010111000000000 000000000001111111111111111111111111111111000000000 + * 000000000011010101010101010101010101010101100000000 000000000011000000000000000000000000000001100000000 + * 000000000111010101010101010101010101010101110000000 000000000111100000000000000000000000000011110000000 + * 000000001101010101010101010101010101010101011000000 000000001100110000000000000000000000000110011000000 + * 000000011101010101010101010101010101010101011100000 000000011111111000000000000000000000001111111100000 + * + * DEV NOTE: This implementation assumes that cells on the edge (who only have 1 neighbor) have 1 neighbor and a permanently "dead" neighbor, which is technically correct in a finite space. However, most diagrams of these elementary cellular automata rules assume a infinite line of cells. Therefore, the edges of the array may not evolve perfectly in line with pictured diagrams which assume infinite space. + */ + +/** + * Find the next Elementary Cell Automata Generation given the previous generation and the rule [0-255] to follow + * @param {(0 | 1)[]} generation The current generation of the Elementary Cellular Automata simulation + * @param {number} rule The current rule of the Elementary Cellular Automata simulation. Must be an integer between 0 and 255 inclusive + * @returns {(0 | 1)[]} The next generation according to the inputted rule + */ +export function getNextElementaryGeneration(generation, rule) { + const NUM_ELEMENTARY_NEIGHBORHOOD_STATES = 8 + const MIN_RULE = 0 + const MAX_RULE = 255 + + if (!Number.isInteger(rule)) { + throw new Error( + `Rule must be an integer between the values 0 and 255 (got ${rule})` + ) + } + if (rule < MIN_RULE || rule > MAX_RULE) { + throw new RangeError( + `Rule must be an integer between the values 0 and 255 (got ${rule})` + ) + } + + const binaryRule = rule + .toString(2) + .padStart(NUM_ELEMENTARY_NEIGHBORHOOD_STATES, '0') + const ruleData = binaryRule.split('').map((bit) => Number.parseInt(bit)) // note that ruleData[0] represents "all alive" while ruleData[7] represents "all dead" + const output = new Array(generation.length) + const LEFT_DEAD = 4 // 100 in binary + const MIDDLE_DEAD = 2 // 010 in binary + const RIGHT_DEAD = 1 // 001 in binary + + for (let i = 0; i < generation.length; i++) { + let neighborhoodValue = LEFT_DEAD | MIDDLE_DEAD | RIGHT_DEAD + + if (i - 1 > 0 && generation[i - 1] === 1) { + neighborhoodValue ^= LEFT_DEAD + } + + if (generation[i] === 1) { + neighborhoodValue ^= MIDDLE_DEAD + } + + if (i + 1 < generation.length && generation[i + 1] === 1) { + neighborhoodValue ^= RIGHT_DEAD + } + + output[i] = ruleData[neighborhoodValue] + } + + return output +} diff --git a/Cellular-Automata/test/ConwaysGameOfLife.test.js b/Cellular-Automata/test/ConwaysGameOfLife.test.js new file mode 100644 index 0000000000..2bf5ac3bbc --- /dev/null +++ b/Cellular-Automata/test/ConwaysGameOfLife.test.js @@ -0,0 +1,17 @@ +import { newGeneration } from '../ConwaysGameOfLife' + +describe('newGeneration', () => { + it('should produce the next generation according to the rules', () => { + expect( + newGeneration([ + [0, 1, 0], + [0, 1, 0], + [0, 1, 0] + ]) + ).toEqual([ + [0, 0, 0], + [1, 1, 1], + [0, 0, 0] + ]) + }) +}) diff --git a/Cellular-Automata/test/Elementary.test.js b/Cellular-Automata/test/Elementary.test.js new file mode 100644 index 0000000000..7c41163818 --- /dev/null +++ b/Cellular-Automata/test/Elementary.test.js @@ -0,0 +1,139 @@ +import { getNextElementaryGeneration } from '../Elementary' + +describe('Elementary Cellular Automata', () => { + describe('Rule Errors', () => { + it('Correct', () => { + expect(() => + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 128) + ).not.toThrow() + }) + + it('Less than 0', () => { + expect(() => + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], -1) + ).toThrow() + }) + + it('Greater than 255', () => { + expect(() => + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 256) + ).toThrow() + }) + + it('Decimal', () => { + expect(() => + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 100.4) + ).toThrow() + }) + }) + + describe('Rule 54 Iterations', () => { + it('Generation 1', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 54) + ).toEqual([0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0]) + }) + it('Generation 2', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0], 54) + ).toEqual([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]) + }) + it('Generation 3', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], 54) + ).toEqual([0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0]) + }) + it('Generation 4', () => { + expect( + getNextElementaryGeneration([0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0], 54) + ).toEqual([0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0]) + }) + }) + + describe('Rule 222 Iterations', () => { + it('Generation 1', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 222) + ).toEqual([0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0]) + }) + it('Generation 2', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0], 222) + ).toEqual([0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0]) + }) + it('Generation 3', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], 222) + ).toEqual([0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0]) + }) + it('Generation 4', () => { + expect( + getNextElementaryGeneration([0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], 222) + ).toEqual([0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]) + }) + }) + + describe('Rule 60 Iterations', () => { + it('Generation 1', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 60) + ).toEqual([0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0]) + }) + it('Generation 2', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0], 60) + ).toEqual([0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0]) + }) + it('Generation 3', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0], 60) + ).toEqual([0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0]) + }) + it('Generation 4', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0], 60) + ).toEqual([0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]) + }) + }) + + describe('Rule 90 Iterations', () => { + it('Generation 1', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 90) + ).toEqual([0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0]) + }) + it('Generation 2', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0], 90) + ).toEqual([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]) + }) + it('Generation 3', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], 90) + ).toEqual([0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0]) + }) + it('Generation 4', () => { + expect( + getNextElementaryGeneration([0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0], 90) + ).toEqual([0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0]) + }) + }) + + describe('Rule 30 Iterations', () => { + it('Generation 1', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], 30) + ).toEqual([0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0]) + }) + it('Generation 2', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0], 30) + ).toEqual([0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0]) + }) + it('Generation 3', () => { + expect( + getNextElementaryGeneration([0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0], 30) + ).toEqual([0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0]) + }) + }) +}) diff --git a/Ciphers/AffineCipher.js b/Ciphers/AffineCipher.js new file mode 100644 index 0000000000..c5097daf89 --- /dev/null +++ b/Ciphers/AffineCipher.js @@ -0,0 +1,105 @@ +/** + * @description - The affine cipher is a type of monoalphabetic substitution cipher, where each letter in an alphabet is mapped to its numeric equivalent, encrypted using a simple mathematical function, and converted back to a letter + * @see - [wiki](https://en.wikipedia.org/wiki/Affine_cipher) + */ + +import { CoPrimeCheck } from '../Maths/CoPrimeCheck' +// Default key for Affine Cipher +const key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + +/** + * Fix result for negative value in modulas equation + * @param {Number} n - Constant number + * @param {Number} m - Modulos value + * @return {Number} Return n mod m + */ +function mod(n, m) { + return ((n % m) + m) % m +} + +/** + * Modular multiplicative inverse + * @param {Number} a - A coefficient + * @param {Number} m - Modulos value + * @return {Number} Return modular multiplicative inverse of coefficient a and modulos m + */ +function inverseMod(a, m) { + for (let x = 1; x < m; x++) { + if (mod(a * x, m) === 1) return x + } +} + +/** + * Argument validation + * @param {String} str - String to be checked + * @param {Number} a - A coefficient to be checked + * @param {Number} b - B coefficient to be checked + * @return {Boolean} Result of the checking + */ +function isCorrectFormat(str, a, b) { + if (typeof a !== 'number' || typeof b !== 'number') { + throw new TypeError('Coefficient a, b should be number') + } + + if (typeof str !== 'string') { + throw new TypeError('Argument str should be String') + } + + if (!CoPrimeCheck(a, 26)) { + throw new Error(a + ' is not coprime of 26') + } + + return true +} + +/** + * Find character index based on ASCII order + * @param {String} char - Character index to be found + * @return {Boolean} Character index + */ +function findCharIndex(char) { + return char.toUpperCase().charCodeAt(0) - 'A'.charCodeAt(0) +} + +/** + * Encrypt a Affine Cipher + * @param {String} str - String to be encrypted + * @param {Number} a - A coefficient + * @param {Number} b - B coefficient + * @return {String} result - Encrypted string + */ + +function encrypt(str, a, b) { + let result = '' + if (isCorrectFormat(str, a, b)) { + for (let x = 0; x < str.length; x++) { + const charIndex = findCharIndex(str[x]) + if (charIndex < 0) result += '-1' + ' ' + else result += key.charAt(mod(a * charIndex + b, 26)) + ' ' + } + } + return result.trim() +} + +/** + * Decrypt a Affine Cipher + * @param {String} str - String to be decrypted + * @param {Number} a - A coefficient + * @param {Number} b - B coefficient + * @return {String} result - Decrypted string + */ +function decrypt(str, a, b) { + let result = '' + if (isCorrectFormat(str, a, b)) { + str = str.split(' ') + for (let x = 0; x < str.length; x++) { + if (str[x] === '-1') result += ' ' + else { + const charIndex = findCharIndex(str[x]) + result += key[mod(inverseMod(a, 26) * (charIndex - b), 26)] + } + } + return result + } +} +export { encrypt, decrypt } diff --git a/Ciphers/Atbash.js b/Ciphers/Atbash.js new file mode 100644 index 0000000000..fb0750503f --- /dev/null +++ b/Ciphers/Atbash.js @@ -0,0 +1,24 @@ +/** + * @function Atbash - Decrypt a Atbash cipher + * @description - The Atbash cipher is a particular type of monoalphabetic cipher formed by taking the alphabet and mapping it to its reverse, so that the first letter becomes the last letter, the second letter becomes the second to last letter, and so on. + * @param {string} str - string to be decrypted/encrypt + * @return {string} decrypted/encrypted string + * @see - [wiki](https://en.wikipedia.org/wiki/Atbash) + */ +const Atbash = (str) => { + if (typeof str !== 'string') { + throw new TypeError('Argument should be string') + } + + return str.replace(/[a-z]/gi, (char) => { + const charCode = char.charCodeAt() + + if (/[A-Z]/.test(char)) { + return String.fromCharCode(90 + 65 - charCode) + } + + return String.fromCharCode(122 + 97 - charCode) + }) +} + +export default Atbash diff --git a/Ciphers/CaesarCipher.js b/Ciphers/CaesarCipher.js new file mode 100644 index 0000000000..8a942564e9 --- /dev/null +++ b/Ciphers/CaesarCipher.js @@ -0,0 +1,32 @@ +/** + * @function caesarsCipher + * @description - In cryptography, a Caesar cipher, also known as Caesar's cipher, the shift cipher, Caesar's code or Caesar shift, is one of the simplest and most widely known encryption techniques. It is a type of substitution cipher in which each letter in the plaintext is replaced by a letter some fixed number of positions down the alphabet. For example, with a left shift of 3, D would be replaced by A, E would become B, and so on. The method is named after Julius Caesar, who used it in his private correspondence. + * @see - [wiki](https://en.wikipedia.org/wiki/Caesar_cipher) + * @param {string} str - string to be encrypted + * @param {number} rotation - the number of rotation, expect real number ( > 0) + * @return {string} - decrypted string + */ +const caesarCipher = (str, rotation) => { + if (typeof str !== 'string' || !Number.isInteger(rotation) || rotation < 0) { + throw new TypeError('Arguments are invalid') + } + + const alphabets = new Array(26) + .fill() + .map((_, index) => String.fromCharCode(97 + index)) // generate all lower alphabets array a-z + + const cipherMap = alphabets.reduce( + (map, char, index) => map.set(char, alphabets[(rotation + index) % 26]), + new Map() + ) + + return str.replace(/[a-z]/gi, (char) => { + if (/[A-Z]/.test(char)) { + return cipherMap.get(char.toLowerCase()).toUpperCase() + } + + return cipherMap.get(char) + }) +} + +export default caesarCipher diff --git a/Ciphers/CaesarsCipher.js b/Ciphers/CaesarsCipher.js deleted file mode 100644 index 00bf1f1c1b..0000000000 --- a/Ciphers/CaesarsCipher.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Caesar's Cipher - also known as the ROT13 Cipher is when - * a letter is replaced by the one that is 13 spaces away - * from it in the alphabet. If the letter is in the first half - * of the alphabet we add 13, if it's in the latter half we - * subtract 13 from the character code value. - */ - -/** - * Decrypt a ROT13 cipher - * @param {String} str - string to be decrypted - * @return {String} decrypted string - */ -function rot13 (str) { - const response = [] - const strLength = str.length - - for (let i = 0; i < strLength; i++) { - const char = str.charCodeAt(i) - - if (char < 65 || (char > 90 && char < 97) || char > 122) { - response.push(str.charAt(i)) - } else if ((char > 77 && char <= 90) || (char > 109 && char <= 122)) { - response.push(String.fromCharCode(str.charCodeAt(i) - 13)) - } else { - response.push(String.fromCharCode(str.charCodeAt(i) + 13)) - } - } - return response.join('') -} - -// Caesars Cipher Example -const encryptedString = 'Uryyb Jbeyq' -const decryptedString = rot13(encryptedString) - -console.log(decryptedString) // Hello World diff --git a/Ciphers/KeyFinder.js b/Ciphers/KeyFinder.js index 8a66facc65..64a3d1c9d4 100644 --- a/Ciphers/KeyFinder.js +++ b/Ciphers/KeyFinder.js @@ -1,8 +1,10 @@ -/****************************************************** - Find and retrieve the encryption key automatically - Note: This is a draft version, please help to modify, Thanks! - ******************************************************/ -function keyFinder (str) { // str is used to get the input of encrypted string +/** + * Find and retrieve the encryption key automatically. + * @param {string} str - The input encrypted string. + * @returns {number} - The encryption key found, or 0 if not found. + */ +function keyFinder(str) { + // str is used to get the input of encrypted string const wordBank = [ 'I ', 'You ', @@ -27,13 +29,13 @@ function keyFinder (str) { // str is used to get the input of encrypted string ' may ', 'May ', ' be ', - 'Be '] - // let wordbankelementCounter = 0; - // let key = 0; // return zero means the key can not be found + 'Be ' + ] const inStr = str.toString() // convert the input to String let outStr = '' // store the output value let outStrElement = '' // temporary store the word inside the outStr, it is used for comparison - for (let k = 0; k < 26; k++) { // try the number of key shifted, the sum of character from a-z or A-Z is 26 + for (let k = 0; k < 26; k++) { + // try the number of key shifted, the sum of character from a-z or A-Z is 26 outStr = caesarCipherEncodeAndDecodeEngine(inStr, k) // use the encryption engine to decrypt the input string // loop through the whole input string @@ -44,15 +46,11 @@ function keyFinder (str) { // str is used to get the input of encrypted string for (let w = 0; w < wordBank[i].length; w++) { outStrElement += outStr[s + w] } - - // console.log( k + outStrElement + wordBank[i] );//debug - // this part need to be optimize with the calculation of the number of occurrence of word's probabilities - // linked list will be used in the next stage of development to calculate the number of occurace of the key + // linked list will be used in the next stage of development to calculate the number of occurrence of the key if (wordBank[i] === outStrElement) { return k // return the key number if founded } - outStrElement = '' // reset the temp word } // end for ( let i=0; i < wordBank.length; i++) } @@ -60,89 +58,98 @@ function keyFinder (str) { // str is used to get the input of encrypted string return 0 // return 0 if found nothing } -/* this sub-function is used to assist the keyFinder to find the key */ -function caesarCipherEncodeAndDecodeEngine (inStr, numShifted) { +/** + * This sub-function is used to assist the keyFinder in finding the key. + * @param {string} inStr - The input string. + * @param {number} numShifted - The number of characters to shift in the Caesar cipher. + * @returns {string} - The decrypted string. + */ +function caesarCipherEncodeAndDecodeEngine(inStr, numShifted) { const shiftNum = numShifted let charCode = 0 - let outStr = '' let shiftedCharCode = 0 let result = 0 - for (let i = 0; i < inStr.length; i++) { - charCode = inStr[i].charCodeAt() - shiftedCharCode = charCode + shiftNum - result = charCode + return inStr + .split('') + .map((char) => { + charCode = char.charCodeAt() + shiftedCharCode = charCode + shiftNum + result = charCode - if ((charCode >= 48 && charCode <= 57)) { - if (shiftedCharCode < 48) { - let diff = Math.abs(48 - 1 - shiftedCharCode) % 10 + if (charCode >= 48 && charCode <= 57) { + if (shiftedCharCode < 48) { + let diff = Math.abs(48 - 1 - shiftedCharCode) % 10 - while (diff >= 10) { - diff = diff % 10 - } - document.getElementById('diffID').innerHTML = diff + while (diff >= 10) { + diff = diff % 10 + } + document.getElementById('diffID').innerHTML = diff - shiftedCharCode = 57 - diff + shiftedCharCode = 57 - diff - result = shiftedCharCode - } else if (shiftedCharCode >= 48 && shiftedCharCode <= 57) { - result = shiftedCharCode - } else if (shiftedCharCode > 57) { - let diff = Math.abs(57 + 1 - shiftedCharCode) % 10 + result = shiftedCharCode + } else if (shiftedCharCode >= 48 && shiftedCharCode <= 57) { + result = shiftedCharCode + } else if (shiftedCharCode > 57) { + let diff = Math.abs(57 + 1 - shiftedCharCode) % 10 - while (diff >= 10) { - diff = diff % 10 - } - document.getElementById('diffID').innerHTML = diff - - shiftedCharCode = 48 + diff + while (diff >= 10) { + diff = diff % 10 + } + document.getElementById('diffID').innerHTML = diff - result = shiftedCharCode - } - } else if ((charCode >= 65 && charCode <= 90)) { - if (shiftedCharCode <= 64) { - let diff = Math.abs(65 - 1 - shiftedCharCode) % 26 + shiftedCharCode = 48 + diff - while ((diff % 26) >= 26) { - diff = diff % 26 + result = shiftedCharCode } - shiftedCharCode = 90 - diff - result = shiftedCharCode - } else if (shiftedCharCode >= 65 && shiftedCharCode <= 90) { - result = shiftedCharCode - } else if (shiftedCharCode > 90) { - let diff = Math.abs(shiftedCharCode - 1 - 90) % 26 - - while ((diff % 26) >= 26) { - diff = diff % 26 + } else if (charCode >= 65 && charCode <= 90) { + if (shiftedCharCode <= 64) { + let diff = Math.abs(65 - 1 - shiftedCharCode) % 26 + + while (diff % 26 >= 26) { + diff = diff % 26 + } + shiftedCharCode = 90 - diff + result = shiftedCharCode + } else if (shiftedCharCode >= 65 && shiftedCharCode <= 90) { + result = shiftedCharCode + } else if (shiftedCharCode > 90) { + let diff = Math.abs(shiftedCharCode - 1 - 90) % 26 + + while (diff % 26 >= 26) { + diff = diff % 26 + } + shiftedCharCode = 65 + diff + result = shiftedCharCode } - shiftedCharCode = 65 + diff - result = shiftedCharCode - } - } else if ((charCode >= 97 && charCode <= 122)) { - if (shiftedCharCode <= 96) { - let diff = Math.abs(97 - 1 - shiftedCharCode) % 26 - - while ((diff % 26) >= 26) { - diff = diff % 26 - } - shiftedCharCode = 122 - diff - result = shiftedCharCode - } else if (shiftedCharCode >= 97 && shiftedCharCode <= 122) { - result = shiftedCharCode - } else if (shiftedCharCode > 122) { - let diff = Math.abs(shiftedCharCode - 1 - 122) % 26 - - while ((diff % 26) >= 26) { - diff = diff % 26 + } else if (charCode >= 97 && charCode <= 122) { + if (shiftedCharCode <= 96) { + let diff = Math.abs(97 - 1 - shiftedCharCode) % 26 + + while (diff % 26 >= 26) { + diff = diff % 26 + } + shiftedCharCode = 122 - diff + result = shiftedCharCode + } else if (shiftedCharCode >= 97 && shiftedCharCode <= 122) { + result = shiftedCharCode + } else if (shiftedCharCode > 122) { + let diff = Math.abs(shiftedCharCode - 1 - 122) % 26 + + while (diff % 26 >= 26) { + diff = diff % 26 + } + shiftedCharCode = 97 + diff + result = shiftedCharCode } - shiftedCharCode = 97 + diff - result = shiftedCharCode } - } - outStr = outStr + String.fromCharCode(parseInt(result)) - } - return outStr + return String.fromCharCode(parseInt(result)) + }) + .join('') } -console.log('Testing: ' + keyFinder('test')) // returns 0 +export { keyFinder } + +// > keyFinder('test') +// 0 diff --git a/Ciphers/KeywordShiftedAlphabet.js b/Ciphers/KeywordShiftedAlphabet.js new file mode 100644 index 0000000000..2c6f91fe48 --- /dev/null +++ b/Ciphers/KeywordShiftedAlphabet.js @@ -0,0 +1,112 @@ +/** + * Keyword shifted alphabet is a simple cipher using a translation table created with a help of a keyword. + * Keyword must be a word where each character can occur only once. + * To create the translation table, we write all the alphabet characters to the first. + * Second row start with the keyword, then we continue with the rest of the characters that are missing in alphabetical order. + * + * |A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z| + * |K|E|Y|W|O|R|D|A|B|C|F|G|H|I|J|L|M|N|P|Q|S|T|U|V|W|Z| + * + * Encryption is then just a matter of writing the matching (same index) letter from the second row instead of the first row: + * 'Hello world' -> 'Aoggj ujngw' + * + * Decryption is then just the reverse process of writing the matching (same index) letter from the first row instead of the second row + * 'Aogg ujngw' -> 'Hello world' + * + * Non alphabetical characters (space, exclamation mark, ...) are kept as they are + */ + +const alphabet = [ + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z' +] + +function checkKeywordValidity(keyword) { + keyword.split('').forEach((char, index) => { + const rest = keyword.slice(0, index) + keyword.slice(index + 1) + if (rest.indexOf(char) !== -1) { + return false + } + }) + return true +} + +function getEncryptedAlphabet(keyword) { + const encryptedAlphabet = keyword.split('') + alphabet.forEach((char) => { + if (encryptedAlphabet.indexOf(char) === -1) { + encryptedAlphabet.push(char) + } + }) + return encryptedAlphabet +} + +function translate(sourceAlphabet, targetAlphabet, message) { + return message.split('').reduce((encryptedMessage, char) => { + const isUpperCase = char === char.toUpperCase() + const encryptedCharIndex = sourceAlphabet.indexOf(char.toLowerCase()) + const encryptedChar = + encryptedCharIndex !== -1 ? targetAlphabet[encryptedCharIndex] : char + encryptedMessage += isUpperCase + ? encryptedChar.toUpperCase() + : encryptedChar + return encryptedMessage + }, '') +} + +function checkInputs(keyword, message) { + if (!keyword || !message) { + throw new Error('Both keyword and message must be specified') + } + + if (!checkKeywordValidity(keyword)) { + throw new Error('Invalid keyword!') + } +} + +function encrypt(keyword, message) { + checkInputs(keyword, message) + return translate( + alphabet, + getEncryptedAlphabet(keyword.toLowerCase()), + message + ) +} + +function decrypt(keyword, message) { + checkInputs(keyword, message) + return translate( + getEncryptedAlphabet(keyword.toLowerCase()), + alphabet, + message + ) +} + +export { encrypt, decrypt } + +// encrypt('keyword', 'Hello world!') // Prints 'Aoggj ujngw!' +// decrypt('keyword', 'Aoggj ujngw!') // Prints 'Hello world! diff --git a/Ciphers/MorseCode.js b/Ciphers/MorseCode.js new file mode 100644 index 0000000000..eeb580d520 --- /dev/null +++ b/Ciphers/MorseCode.js @@ -0,0 +1,90 @@ +/** + * @author mrmagic2020 + * @description Enciphers a combination of letters, numbers and symbols into morse code. + * @see https://en.wikipedia.org/wiki/Morse_code + * @param {string} msg The message to be enciphered. + * @param {string} dot Symbol representing the dots. + * @param {string} dash Symbol representing the dash. + * @returns {string} Enciphered morse code. + * @example morse('Hello World!') = '**** * *-** *-** --- *-- --- *-* *-** -** -*-*--' + */ +const morse = (msg, dot = '*', dash = '-') => { + const key = { + A: '*-', + B: '-***', + C: '-*-*', + D: '-**', + E: '*', + F: '**-*', + G: '--*', + H: '****', + I: '**', + J: '*---', + K: '-*-', + L: '*-**', + M: '--', + N: '-*', + O: '---', + P: '*--*', + Q: '--*-', + R: '*-*', + S: '***', + T: '-', + U: '**-', + V: '***-', + W: '*--', + X: '-**-', + Y: '-*--', + Z: '--**', + 1: '*----', + 2: '**---', + 3: '***--', + 4: '****-', + 5: '*****', + 6: '-****', + 7: '--***', + 8: '---**', + 9: '----*', + 0: '-----', + '.': '*-*-*-', + ',': '--**--', + '?': '**--**', + '!': '-*-*--', + "'": '*----*', + '"': '*-**-*', + '(': '-*--*', + ')': '-*--*-', + '&': '*-***', + ':': '---***', + ';': '-*-*-*', + '/': '-**-*', + _: '**--*-', + '=': '-***-', + '+': '*-*-*', + '-': '-****-', + $: '***-**-', + '@': '*--*-*' + } + + let newMsg = '' + + msg + .toString() + .split('') + .forEach((e) => { + if (/[a-zA-Z]/.test(e)) { + newMsg += key[e.toUpperCase()] + .replaceAll('*', dot) + .replaceAll('-', dash) + } else if (Object.keys(key).includes(e)) { + newMsg += key[e].replaceAll('*', dot).replaceAll('-', dash) + } else { + newMsg += e + } + newMsg += ' ' + }) + + return newMsg.trim() +} + +export { morse } diff --git a/Ciphers/ROT13.js b/Ciphers/ROT13.js new file mode 100644 index 0000000000..3bddb389af --- /dev/null +++ b/Ciphers/ROT13.js @@ -0,0 +1,24 @@ +/** + * @function ROT13 + * @description - ROT13 ("rotate by 13 places", sometimes hyphenated ROT-13) is a simple letter substitution cipher that replaces a letter with the 13th letter after it in the alphabet. ROT13 is a special case of the Caesar cipher which was developed in ancient Rome. Because there are 26 letters (2ร—13) in the basic Latin alphabet, ROT13 is its own inverse; that is, to undo ROT13, the same algorithm is applied, so the same action can be used for encoding and decoding. The algorithm provides virtually no cryptographic security, and is often cited as a canonical example of weak encryption. + * @see - [wiki](https://en.wikipedia.org/wiki/ROT13) + * @param {String} str - string to be decrypted + * @return {String} decrypted string + */ +function ROT13(str) { + if (typeof str !== 'string') { + throw new TypeError('Argument should be string') + } + + return str.replace(/[a-z]/gi, (char) => { + const charCode = char.charCodeAt() + + if (/[n-z]/i.test(char)) { + return String.fromCharCode(charCode - 13) + } + + return String.fromCharCode(charCode + 13) + }) +} + +export default ROT13 diff --git a/Ciphers/VigenereCipher.js b/Ciphers/VigenereCipher.js index 68ef3e85a0..9967b0a174 100644 --- a/Ciphers/VigenereCipher.js +++ b/Ciphers/VigenereCipher.js @@ -3,7 +3,7 @@ * @param {String} str - character to check * @return {object} An array with the character or null if isn't a letter */ -function isLetter (str) { +function isLetter(str) { return str.length === 1 && str.match(/[a-zA-Z]/i) } @@ -12,7 +12,7 @@ function isLetter (str) { * @param {String} character - character to check * @return {Boolean} result of the checking */ -function isUpperCase (character) { +function isUpperCase(character) { if (character === character.toUpperCase()) { return true } @@ -27,16 +27,22 @@ function isUpperCase (character) { * @param {String} key - key for encrypt * @return {String} result - encrypted string */ -function encrypt (message, key) { +function encrypt(message, key) { let result = '' for (let i = 0, j = 0; i < message.length; i++) { const c = message.charAt(i) if (isLetter(c)) { if (isUpperCase(c)) { - result += String.fromCharCode((c.charCodeAt(0) + key.toUpperCase().charCodeAt(j) - 2 * 65) % 26 + 65) // A: 65 + result += String.fromCharCode( + ((c.charCodeAt(0) + key.toUpperCase().charCodeAt(j) - 2 * 65) % 26) + + 65 + ) // A: 65 } else { - result += String.fromCharCode((c.charCodeAt(0) + key.toLowerCase().charCodeAt(j) - 2 * 97) % 26 + 97) // a: 97 + result += String.fromCharCode( + ((c.charCodeAt(0) + key.toLowerCase().charCodeAt(j) - 2 * 97) % 26) + + 97 + ) // a: 97 } } else { result += c @@ -52,16 +58,21 @@ function encrypt (message, key) { * @param {String} key - key for decrypt * @return {String} result - decrypted string */ -function decrypt (message, key) { +function decrypt(message, key) { let result = '' for (let i = 0, j = 0; i < message.length; i++) { const c = message.charAt(i) if (isLetter(c)) { if (isUpperCase(c)) { - result += String.fromCharCode(90 - (25 - (c.charCodeAt(0) - key.toUpperCase().charCodeAt(j))) % 26) + result += String.fromCharCode( + 90 - ((25 - (c.charCodeAt(0) - key.toUpperCase().charCodeAt(j))) % 26) + ) } else { - result += String.fromCharCode(122 - (25 - (c.charCodeAt(0) - key.toLowerCase().charCodeAt(j))) % 26) + result += String.fromCharCode( + 122 - + ((25 - (c.charCodeAt(0) - key.toLowerCase().charCodeAt(j))) % 26) + ) } } else { result += c @@ -71,8 +82,10 @@ function decrypt (message, key) { return result } -const messageEncrypt = encrypt('Hello World!', 'code') -console.log(messageEncrypt) // "Jhpnr Yrvng!" +export { encrypt, decrypt } -const messageDecrypt = decrypt('Jsopq Zstzg!', 'code') -console.log(messageDecrypt) // "Hello World!" +// > encrypt('Hello World!', 'code') +// 'Jsopq Zstzg!' + +// > decrypt('Jsopq Zstzg!', 'code') +// 'Hello World!' diff --git a/Ciphers/XORCipher.js b/Ciphers/XORCipher.js index 898b200dd6..91b7134c3f 100644 --- a/Ciphers/XORCipher.js +++ b/Ciphers/XORCipher.js @@ -1,26 +1,22 @@ /** + * @function XORCipher + * @description - Encrypt using an XOR cipher * The XOR cipher is a type of additive cipher. * Each character is bitwise XORed with the key. * We loop through the input string, XORing each * character with the key. + * @param {string} str - string to be encrypted + * @param {number} key - key for encryption + * @return {string} encrypted string */ - -/** - * Encrypt using an XOR cipher - * @param {String} str - String to be encrypted - * @param {Number} key - key for encryption - * @return {String} encrypted string - */ - -function XOR (str, key) { - let result = '' - for (const elem of str) { - result += String.fromCharCode(elem.charCodeAt(0) ^ key) +const XORCipher = (str, key) => { + if (typeof str !== 'string' || !Number.isInteger(key)) { + throw new TypeError('Arguments type are invalid') } - return result + + return str.replace(/./g, (char) => + String.fromCharCode(char.charCodeAt() ^ key) + ) } -const encryptedString = XOR('test string', 32) -console.log('Encrypted: ', encryptedString) -const decryptedString = XOR(encryptedString, 32) -console.log('Decrypted: ', decryptedString) +export default XORCipher diff --git a/Ciphers/test/AffineCipher.test.js b/Ciphers/test/AffineCipher.test.js new file mode 100644 index 0000000000..e044f1b418 --- /dev/null +++ b/Ciphers/test/AffineCipher.test.js @@ -0,0 +1,29 @@ +import { encrypt, decrypt } from '../AffineCipher' + +describe('Test Affine Cipher', () => { + it('Test - 1, Pass invalid input to encrypt function', () => { + expect(() => encrypt(null, null, null)).toThrow() + expect(() => encrypt('null', null, null)).toThrow() + expect(() => encrypt('null', 1, null)).toThrow() + expect(() => encrypt('null', null, 1)).toThrow() + expect(() => encrypt('null', 2, 1)).toThrow() + expect(() => encrypt('null', 4, 1)).toThrow() + }) + + it('Test - 2, Pass invalid input to decrypt function', () => { + expect(() => decrypt(null, null, null)).toThrow() + expect(() => decrypt('null', null, null)).toThrow() + expect(() => decrypt('null', 1, null)).toThrow() + expect(() => decrypt('null', null, 1)).toThrow() + expect(() => encrypt('null', 2, 1)).toThrow() + expect(() => encrypt('null', 4, 1)).toThrow() + }) + + it('Test - 3 Pass string value to encrypt and decrypt function', () => { + expect(decrypt(encrypt('HELLO WORLD', 5, 8), 5, 8)).toBe('HELLO WORLD') + expect(decrypt(encrypt('ABC DEF', 3, 5), 3, 5)).toBe('ABC DEF') + expect(decrypt(encrypt('Brown fox jump over the fence', 7, 3), 7, 3)).toBe( + 'BROWN FOX JUMP OVER THE FENCE' + ) + }) +}) diff --git a/Ciphers/test/Atbash.test.js b/Ciphers/test/Atbash.test.js new file mode 100644 index 0000000000..f4dd3594e8 --- /dev/null +++ b/Ciphers/test/Atbash.test.js @@ -0,0 +1,18 @@ +import Atbash from '../Atbash' + +describe('Testing Atbash function', () => { + it('Test - 1, passing a non-string as an argument', () => { + expect(() => Atbash(0x345)).toThrow() + expect(() => Atbash(123)).toThrow() + expect(() => Atbash(123n)).toThrow() + expect(() => Atbash(false)).toThrow() + expect(() => Atbash({})).toThrow() + expect(() => Atbash([])).toThrow() + }) + + it('Test - 2, passing a string as an argument', () => { + const clearText = 'The quick brown fox jumps over the lazy dog' + const cryptText = Atbash(clearText) + expect(Atbash(cryptText)).toBe(clearText) + }) +}) diff --git a/Ciphers/test/CaesarCipher.test.js b/Ciphers/test/CaesarCipher.test.js new file mode 100644 index 0000000000..c39c09d061 --- /dev/null +++ b/Ciphers/test/CaesarCipher.test.js @@ -0,0 +1,22 @@ +import caesarCipher from '../CaesarCipher' + +describe('Testing the caesarsCipher function', () => { + it('Test - 1, Testing for invalid types', () => { + expect(() => caesarCipher(false, 3)).toThrow() + expect(() => caesarCipher('false', -1)).toThrow() + expect(() => caesarCipher('true', null)).toThrow() + }) + + it('Test - 2, Testing for valid string and rotation', () => { + expect(caesarCipher('middle-Outz', 2)).toBe('okffng-Qwvb') + expect(caesarCipher('abcdefghijklmnopqrstuvwxyz', 3)).toBe( + 'defghijklmnopqrstuvwxyzabc' + ) + expect(caesarCipher('Always-Look-on-the-Bright-Side-of-Life', 5)).toBe( + 'Fqbfdx-Qttp-ts-ymj-Gwnlmy-Xnij-tk-Qnkj' + ) + expect( + caesarCipher('THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG', 23) + ).toBe('QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD') + }) +}) diff --git a/Ciphers/test/KeywordShiftedAlphabet.test.js b/Ciphers/test/KeywordShiftedAlphabet.test.js new file mode 100644 index 0000000000..38e0839488 --- /dev/null +++ b/Ciphers/test/KeywordShiftedAlphabet.test.js @@ -0,0 +1,13 @@ +import { encrypt, decrypt } from '../KeywordShiftedAlphabet' + +test('Hello world! === decrypt(encrypt(Hello world!))', () => { + const word = 'Hello world!' + const result = decrypt('keyword', encrypt('keyword', word)) + expect(result).toMatch(word) +}) + +test('The Algorithms === decrypt(encrypt(The Algorithms))', () => { + const word = 'The Algorithms' + const result = decrypt('keyword', encrypt('keyword', word)) + expect(result).toMatch(word) +}) diff --git a/Ciphers/test/MorseCode.test.js b/Ciphers/test/MorseCode.test.js new file mode 100644 index 0000000000..4785ad3aa4 --- /dev/null +++ b/Ciphers/test/MorseCode.test.js @@ -0,0 +1,20 @@ +import { morse } from '../MorseCode' + +describe('Testing morse function', () => { + it('should return an enciphered string with a given input string', () => { + expect(morse('Hello World!')).toBe( + '**** * *-** *-** --- *-- --- *-* *-** -** -*-*--' + ) + expect(morse('1+1=2')).toBe('*---- *-*-* *---- -***- **---') + }) + + it('should leave symbols that does not have its corresponding morse representation', () => { + expect(morse('ยฉ 2023 GitHub, Inc.')).toBe( + 'ยฉ **--- ----- **--- ***-- --* ** - **** **- -*** --**-- ** -* -*-* *-*-*-' + ) + }) + + it('should be able to accept custom morse code symbols', () => { + expect(morse('Nodejs', '.', '|')).toBe('|. ||| |.. . .||| ...') + }) +}) diff --git a/Ciphers/test/ROT13.test.js b/Ciphers/test/ROT13.test.js new file mode 100644 index 0000000000..d600cf64b4 --- /dev/null +++ b/Ciphers/test/ROT13.test.js @@ -0,0 +1,22 @@ +import ROT13 from '../ROT13' + +describe('Testing ROT13 function', () => { + it('Test - 1, passing a non-string as an argument', () => { + expect(() => ROT13(0x345)).toThrow() + expect(() => ROT13(123)).toThrow() + expect(() => ROT13(123n)).toThrow() + expect(() => ROT13(false)).toThrow() + expect(() => ROT13({})).toThrow() + expect(() => ROT13([])).toThrow() + }) + + it('Test - 2, passing a string as an argument', () => { + expect(ROT13('Uryyb Jbeyq')).toBe('Hello World') + expect(ROT13('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')).toBe( + 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm' + ) + expect(ROT13('The quick brown fox jumps over the lazy dog')).toBe( + 'Gur dhvpx oebja sbk whzcf bire gur ynml qbt' + ) + }) +}) diff --git a/Ciphers/test/VigenereCipher.test.js b/Ciphers/test/VigenereCipher.test.js new file mode 100644 index 0000000000..c7c154a958 --- /dev/null +++ b/Ciphers/test/VigenereCipher.test.js @@ -0,0 +1,13 @@ +import { encrypt, decrypt } from '../VigenereCipher' + +test('Hello world! === decrypt(encrypt(Hello world!))', () => { + const word = 'Hello world!' + const result = decrypt(encrypt(word, 'code'), 'code') + expect(result).toMatch(word) +}) + +test('The Algorithms === decrypt(encrypt(The Algorithms))', () => { + const word = 'The Algorithms' + const result = decrypt(encrypt(word, 'code'), 'code') + expect(result).toMatch(word) +}) diff --git a/Ciphers/test/XORCipher.test.js b/Ciphers/test/XORCipher.test.js new file mode 100644 index 0000000000..9697fc715c --- /dev/null +++ b/Ciphers/test/XORCipher.test.js @@ -0,0 +1,18 @@ +import XORCipher from '../XORCipher' + +describe('Testing XORCipher function', () => { + it('Test - 1, passing a non-string as an argument', () => { + expect(() => XORCipher(false, 0x345)).toThrow() + expect(() => XORCipher(true, 123)).toThrow() + expect(() => XORCipher(1n, 123n)).toThrow() + expect(() => XORCipher(false, 0.34)).toThrow() + expect(() => XORCipher({})).toThrow() + expect(() => XORCipher([])).toThrow() + }) + + it('Test - 2, passing a string & number as an argument', () => { + // NB: Node REPL might not output the null char '\x00' (charcode 0) + expect(XORCipher('test string', 32)).toBe('TEST\x00STRING') + expect(XORCipher('TEST\x00STRING', 32)).toBe('test string') + }) +}) diff --git a/Compression/RLE.js b/Compression/RLE.js new file mode 100644 index 0000000000..81a1538646 --- /dev/null +++ b/Compression/RLE.js @@ -0,0 +1,38 @@ +/* + * RLE (Run Length Encoding) is a simple form of data compression. + * The basic idea is to represent repeated successive characters as a single count and character. + * For example, the string "AAAABBBCCDAA" would be encoded as "4A3B2C1D2A". + * + * @author - [ddaniel27](https://github.com/ddaniel27) + */ + +function Compress(str) { + let compressed = '' + let count = 1 + + for (let i = 0; i < str.length; i++) { + if (str[i] !== str[i + 1]) { + compressed += count + str[i] + count = 1 + continue + } + + count++ + } + + return compressed +} + +function Decompress(str) { + let decompressed = '' + let match = [...str.matchAll(/(\d+)(\D)/g)] // match all groups of digits followed by a non-digit character + + match.forEach((item) => { + let [count, char] = [item[1], item[2]] + decompressed += char.repeat(count) + }) + + return decompressed +} + +export { Compress, Decompress } diff --git a/Compression/test/RLE.test.js b/Compression/test/RLE.test.js new file mode 100644 index 0000000000..0094b5b7e2 --- /dev/null +++ b/Compression/test/RLE.test.js @@ -0,0 +1,13 @@ +import { Compress, Decompress } from '../RLE' + +describe('Test RLE Compressor/Decompressor', () => { + it('Test - 1, Pass long repetitive strings', () => { + expect(Compress('AAAAAAAAAAAAAA')).toBe('14A') + expect(Compress('AAABBQQQQQFG')).toBe('3A2B5Q1F1G') + }) + + it('Test - 2, Pass compressed strings', () => { + expect(Decompress('14A')).toBe('AAAAAAAAAAAAAA') + expect(Decompress('3A2B5Q1F1G')).toBe('AAABBQQQQQFG') + }) +}) diff --git a/Conversions/ArbitraryBase.js b/Conversions/ArbitraryBase.js new file mode 100644 index 0000000000..d743c361c8 --- /dev/null +++ b/Conversions/ArbitraryBase.js @@ -0,0 +1,119 @@ +/** + * Divide two numbers and get the result of floor division and remainder + * @param {number} dividend + * @param {number} divisor + * @returns {[result: number, remainder: number]} + */ +const floorDiv = (dividend, divisor) => { + const remainder = dividend % divisor + const result = Math.floor(dividend / divisor) + + return [result, remainder] +} + +/** + * Converts a string from one base to other. Loses accuracy above the value of `Number.MAX_SAFE_INTEGER`. + * @param {string} stringInBaseOne String in input base + * @param {string} baseOneCharacters Character set for the input base + * @param {string} baseTwoCharacters Character set for the output base + * @returns {string} + */ +const convertArbitraryBase = ( + stringInBaseOne, + baseOneCharacterString, + baseTwoCharacterString +) => { + if ( + [stringInBaseOne, baseOneCharacterString, baseTwoCharacterString] + .map((arg) => typeof arg) + .some((type) => type !== 'string') + ) { + throw new TypeError('Only string arguments are allowed') + } + + const baseOneCharacters = [...baseOneCharacterString] + const baseTwoCharacters = [...baseTwoCharacterString] + + for (const charactersInBase of [baseOneCharacters, baseTwoCharacters]) { + if (charactersInBase.length !== new Set(charactersInBase).size) { + throw new TypeError( + 'Duplicate characters in character set are not allowed' + ) + } + } + const reversedStringOneChars = [...stringInBaseOne].reverse() + const stringOneBase = baseOneCharacters.length + let value = 0 + let placeValue = 1 + for (const digit of reversedStringOneChars) { + const digitNumber = baseOneCharacters.indexOf(digit) + if (digitNumber === -1) { + throw new TypeError(`Not a valid character: ${digit}`) + } + value += digitNumber * placeValue + placeValue *= stringOneBase + } + const outputChars = [] + const stringTwoBase = baseTwoCharacters.length + while (value > 0) { + const [divisionResult, remainder] = floorDiv(value, stringTwoBase) + outputChars.push(baseTwoCharacters[remainder]) + value = divisionResult + } + return outputChars.reverse().join('') || baseTwoCharacters[0] +} + +/** + * Converts a arbitrary-length string from one base to other. Doesn't lose accuracy. + * @param {string} stringInBaseOne String in input base + * @param {string} baseOneCharacters Character set for the input base + * @param {string} baseTwoCharacters Character set for the output base + * @returns {string} + */ +const convertArbitraryBaseBigIntVersion = ( + stringInBaseOne, + baseOneCharacterString, + baseTwoCharacterString +) => { + if ( + [stringInBaseOne, baseOneCharacterString, baseTwoCharacterString] + .map((arg) => typeof arg) + .some((type) => type !== 'string') + ) { + throw new TypeError('Only string arguments are allowed') + } + + const baseOneCharacters = [...baseOneCharacterString] + const baseTwoCharacters = [...baseTwoCharacterString] + + for (const charactersInBase of [baseOneCharacters, baseTwoCharacters]) { + if (charactersInBase.length !== new Set(charactersInBase).size) { + throw new TypeError( + 'Duplicate characters in character set are not allowed' + ) + } + } + const reversedStringOneChars = [...stringInBaseOne].reverse() + const stringOneBase = BigInt(baseOneCharacters.length) + let value = 0n + let placeValue = 1n + for (const digit of reversedStringOneChars) { + const digitNumber = BigInt(baseOneCharacters.indexOf(digit)) + if (digitNumber === -1n) { + throw new TypeError(`Not a valid character: ${digit}`) + } + value += digitNumber * placeValue + placeValue *= stringOneBase + } + const outputChars = [] + const stringTwoBase = BigInt(baseTwoCharacters.length) + while (value > 0n) { + const divisionResult = value / stringTwoBase + const remainder = value % stringTwoBase + outputChars.push(baseTwoCharacters[remainder]) + value = divisionResult + } + return outputChars.reverse().join('') || baseTwoCharacters[0] +} + +export { convertArbitraryBase, convertArbitraryBaseBigIntVersion } diff --git a/Conversions/ArrayBufferToBase64.js b/Conversions/ArrayBufferToBase64.js new file mode 100644 index 0000000000..1718501abc --- /dev/null +++ b/Conversions/ArrayBufferToBase64.js @@ -0,0 +1,43 @@ +// About base64: https://en.wikipedia.org/wiki/Base64 + +/** + * Converts an array of bytes to base64 encoding + * @param {ArrayBuffer} binaryData An ArrayBuffer which represents an array of bytes + * @returns {string} A string containing the base64 encoding of `binaryData` + */ +function bufferToBase64(binaryData) { + // The base64 encoding uses the following set of characters to encode any binary data as text + const base64Table = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + // Every 3 bytes translates to 4 base64 characters, if we have less than 3 bytes we must append '=' chars as padding + const padding = 3 - (binaryData.byteLength % 3) + // Create an instance of Uint8Array, to read from the binaryData array buffer + const byteView = new Uint8Array(binaryData) + let result = '' + + // Loop through all bytes in the buffer, in increments of 3 bytes + for (let i = 0; i < byteView.byteLength; i += 3) { + // Get the index for the next 4 base64 chars + const char1 = (byteView[i] & 252) >> 2 + const char2 = ((byteView[i] & 3) << 4) + ((byteView[i + 1] & 240) >> 4) + const char3 = ((byteView[i + 1] & 15) << 2) + ((byteView[i + 2] & 192) >> 6) + const char4 = byteView[i + 2] & 63 + + result += + base64Table[char1] + + base64Table[char2] + + base64Table[char3] + + base64Table[char4] + } + + // Add padding '=' chars if needed + if (padding !== 3) { + const paddedResult = + result.slice(0, result.length - padding) + '='.repeat(padding) + return paddedResult + } + + return result +} + +export { bufferToBase64 } diff --git a/Conversions/Base64ToArrayBuffer.js b/Conversions/Base64ToArrayBuffer.js new file mode 100644 index 0000000000..8b4f855121 --- /dev/null +++ b/Conversions/Base64ToArrayBuffer.js @@ -0,0 +1,48 @@ +// About base64: https://en.wikipedia.org/wiki/Base64 + +/** + * Converts a base64 string to an array of bytes + * @param {string} b64 A base64 string + * @returns {ArrayBuffer} An ArrayBuffer representing the bytes encoded by the base64 string + */ +function base64ToBuffer(b64) { + // The base64 encoding uses the following set of characters to encode any binary data as text + const base64Table = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + // Find the index of char '=' first occurrence + const paddingIdx = b64.indexOf('=') + // Remove padding chars from base64 string, if there are any + const b64NoPadding = paddingIdx !== -1 ? b64.slice(0, paddingIdx) : b64 + // Calculate the length of the result buffer + const bufferLength = Math.floor((b64NoPadding.length * 6) / 8) + // Create the result buffer + const result = new ArrayBuffer(bufferLength) + // Create an instance of Uint8Array, to write to the `result` buffer + const byteView = new Uint8Array(result) + + // Loop through all chars in the base64 string, in increments of 4 chars, and in increments of 3 bytes + for (let i = 0, j = 0; i < b64NoPadding.length; i += 4, j += 3) { + // Get the index of the next 4 base64 chars + const b64Char1 = base64Table.indexOf(b64NoPadding[i]) + const b64Char2 = base64Table.indexOf(b64NoPadding[i + 1]) + let b64Char3 = base64Table.indexOf(b64NoPadding[i + 2]) + let b64Char4 = base64Table.indexOf(b64NoPadding[i + 3]) + + // If base64 chars 3 and 4 don't exit, then set them to 0 + if (b64Char3 === -1) b64Char3 = 0 + if (b64Char4 === -1) b64Char4 = 0 + + // Calculate the next 3 bytes + const byte1 = (b64Char1 << 2) + ((b64Char2 & 48) >> 4) + const byte2 = ((b64Char2 & 15) << 4) + ((b64Char3 & 60) >> 2) + const byte3 = ((b64Char3 & 3) << 6) + b64Char4 + + byteView[j] = byte1 + byteView[j + 1] = byte2 + byteView[j + 2] = byte3 + } + + return result +} + +export { base64ToBuffer } diff --git a/Conversions/BinaryToDecimal.js b/Conversions/BinaryToDecimal.js index 9074684702..d149de0df2 100644 --- a/Conversions/BinaryToDecimal.js +++ b/Conversions/BinaryToDecimal.js @@ -1,11 +1,8 @@ -function binaryToDeicmal (binaryNumber) { +export default function binaryToDecimal(binaryString) { let decimalNumber = 0 - const binaryDigits = binaryNumber.split('').reverse() // Splits the binary number into reversed single digits + const binaryDigits = binaryString.split('').reverse() // Splits the binary number into reversed single digits binaryDigits.forEach((binaryDigit, index) => { - decimalNumber += binaryDigit * (Math.pow(2, index)) // Summation of all the decimal converted digits + decimalNumber += binaryDigit * Math.pow(2, index) // Summation of all the decimal converted digits }) - console.log(`Decimal of ${binaryNumber} is ${decimalNumber}`) + return decimalNumber } - -binaryToDeicmal('111001') -binaryToDeicmal('101') diff --git a/Conversions/BinaryToHex.js b/Conversions/BinaryToHex.js new file mode 100644 index 0000000000..d7f253b222 --- /dev/null +++ b/Conversions/BinaryToHex.js @@ -0,0 +1,75 @@ +const pad = (num, padlen) => { + const pad = new Array(1 + padlen).join(0) + return (pad + num).slice(-pad.length) +} + +const hexLookup = (bin) => { + let binary = bin + if (binary.length < 4) { + binary = pad(binary, 4) + } + switch (binary) { + case '0000': + return '0' + case '0001': + return '1' + case '0010': + return '2' + case '0011': + return '3' + case '0100': + return '4' + case '0101': + return '5' + case '0110': + return '6' + case '0111': + return '7' + case '1000': + return '8' + case '1001': + return '9' + case '1010': + return 'A' + case '1011': + return 'B' + case '1100': + return 'C' + case '1101': + return 'D' + case '1110': + return 'E' + case '1111': + return 'F' + } +} +const binaryToHex = (binaryString) => { + /* + Function for converting Binary to Hex + + 1. The conversion will start from Least Significant Digit (LSB) to the Most Significant Bit (MSB). + 2. We divide the bits into sections of 4-bits starting from LSB to MSB. + 3. If the MSB get less than 4 bits, then we pad 0s to the front of it. + + For Example: + Binary String = '1001101' + + 1. Divide it to 2 parts => ['100', '1101'] + 2. Pad 0s the MSB so it'll be => ['0100', '1101'] + 3. Use the lookup table and merge them, therefore the result is 4D. + + */ + + let result = '' + binaryString = binaryString.split('') + for (let i = binaryString.length - 1; i >= 0; i = i - 4) { + if (i >= 3) { + result += hexLookup(binaryString.slice(i - 3, i + 1).join('')) + } else { + result += hexLookup(binaryString.slice(0, i + 1).join('')) + } + } + return result.split('').reverse().join('') +} + +export default binaryToHex diff --git a/Conversions/DateDayDifference.js b/Conversions/DateDayDifference.js new file mode 100644 index 0000000000..726a634253 --- /dev/null +++ b/Conversions/DateDayDifference.js @@ -0,0 +1,36 @@ +/* + DateDayDifference Method + ------------------------ + DateDayDifference method calculates the number of days between two dates. + + Algorithm & Explanation : https://ncalculators.com/time-date/date-difference-calculator.htm +*/ + +import { isLeapYear } from '../Maths/LeapYear' +import { parseDate } from '../Timing-Functions/ParseDate' + +const DateToDay = (dd, mm, yyyy) => { + return ( + 365 * (yyyy - 1) + + Math.floor((yyyy - 1) / 4) - + Math.floor((yyyy - 1) / 100) + + Math.floor((yyyy - 1) / 400) + + dd + + Math.floor((367 * mm - 362) / 12) + + (mm <= 2 ? 0 : isLeapYear(yyyy) ? -1 : -2) + ) +} + +const DateDayDifference = (date1, date2) => { + const firstDate = parseDate(date1) + const secondDate = parseDate(date2) + + return Math.abs( + DateToDay(secondDate.day, secondDate.month, secondDate.year) - + DateToDay(firstDate.day, firstDate.month, firstDate.year) + ) +} + +// Example : DateDayDifference('17/08/2002', '10/10/2020') => 6630 + +export { DateDayDifference } diff --git a/Conversions/DateToDay.js b/Conversions/DateToDay.js new file mode 100644 index 0000000000..96ec01e82d --- /dev/null +++ b/Conversions/DateToDay.js @@ -0,0 +1,74 @@ +/* + DateToDay Method + ---------------- + The DateToDay method takes a date in string format and + returns the name of a day. The approach behind this method + is very simple, we first take a string date and check + whether their date is valid or not, if the date is valid + then we do this But apply the algorithm shown below. The + algorithm shown below gives us the number of the day and + finally converts it to the name of the day. + + Algorithm & Explanation : https://en.wikipedia.org/wiki/Zeller%27s_congruence +*/ + +import { parseDate } from '../Timing-Functions/ParseDate' + +// Array holding name of the day: Saturday - Sunday - Friday => 0 - 1 - 6 +const daysNameArr = [ + 'Saturday', + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday' +] + +const DateToDay = (date) => { + // firstly, check that input is a string or not. + const dateStruct = parseDate(date) + let year = dateStruct.year + let month = dateStruct.month + let day = dateStruct.day + + // In case of Jan and Feb: + // Year: we consider it as previous year + // e.g., 1/1/1987 here year is 1986 (-1) + // Month: we consider value as 13 & 14 respectively + if (month < 3) { + year-- + month += 12 + } + + // divide year into century and the last two digits of the century + const yearDigits = year % 100 + const century = Math.floor(year / 100) + + /* + In mathematics, remainders of divisions are usually defined to always be positive; + As an example, -2 mod 7 = 5. + Many programming languages including JavaScript implement the remainder of `n % m` as `sign(n) * (abs(n) % m)`. + This means the result has the same sign as the numerator. Here, `-2 % 7 = -1 * (2 % 7) = -2`. + + To ensure a positive numerator, the formula is adapted: `- 2 * century` is replaced with `+ 5 * century` + which does not alter the resulting numbers mod 7 since `7 - 2 = 5` + + The following example shows the issue with modulo division: + Without the adaption, the formula yields `weekDay = -6` for the date 2/3/2014; + With the adaption, it yields the positive result `weekDay = 7 - 6 = 1` (Sunday), which is what we need to index the array + */ + const weekDay = + (day + + Math.floor((month + 1) * 2.6) + + yearDigits + + Math.floor(yearDigits / 4) + + Math.floor(century / 4) + + 5 * century) % + 7 + return daysNameArr[weekDay] // name of the weekday +} + +// Example : DateToDay("18/12/2020") => Friday + +export { DateToDay } diff --git a/Conversions/DecimalToBinary.js b/Conversions/DecimalToBinary.js index a179dbaf2e..03b0df8bea 100644 --- a/Conversions/DecimalToBinary.js +++ b/Conversions/DecimalToBinary.js @@ -1,12 +1,19 @@ -function decimalToBinary (num) { - var bin = [] +function decimalToBinary(num) { + const bin = [] while (num > 0) { bin.unshift(num % 2) num >>= 1 // basically /= 2 without remainder if any } - console.log('The decimal in binary is ' + bin.join('')) + return bin.join('') } -decimalToBinary(2) -decimalToBinary(7) -decimalToBinary(35) +export { decimalToBinary } + +// > decimalToBinary(2) +// '10' + +// > decimalToBinary(7) +// '111' + +// > decimalToBinary(35) +// '100011' diff --git a/Conversions/DecimalToHex.js b/Conversions/DecimalToHex.js index 5b130de9a6..4382ce2c92 100644 --- a/Conversions/DecimalToHex.js +++ b/Conversions/DecimalToHex.js @@ -1,16 +1,22 @@ -function intToHex (num) { +function intToHex(num) { switch (num) { - case 10: return 'A' - case 11: return 'B' - case 12: return 'C' - case 13: return 'D' - case 14: return 'E' - case 15: return 'F' + case 10: + return 'A' + case 11: + return 'B' + case 12: + return 'C' + case 13: + return 'D' + case 14: + return 'E' + case 15: + return 'F' } return num } -function decimalToHex (num) { +function decimalToHex(num) { const hexOut = [] while (num > 15) { hexOut.unshift(intToHex(num % 16)) @@ -19,6 +25,4 @@ function decimalToHex (num) { return intToHex(num) + hexOut.join('') } -// test cases -console.log(decimalToHex(999098) === 'F3EBA') -console.log(decimalToHex(123) === '7B') +export { decimalToHex } diff --git a/Conversions/DecimalToOctal.js b/Conversions/DecimalToOctal.js index 6f9184b5ab..82b3cfe142 100644 --- a/Conversions/DecimalToOctal.js +++ b/Conversions/DecimalToOctal.js @@ -1,16 +1,27 @@ -function decimalToOctal (num) { +function decimalToOctal(num) { let oct = 0 let c = 0 while (num > 0) { const r = num % 8 - oct = oct + (r * Math.pow(10, c++)) + oct = oct + r * Math.pow(10, c++) num = Math.floor(num / 8) // basically /= 8 without remainder if any } - console.log('The decimal in octal is ' + oct) + return oct } -decimalToOctal(2) -decimalToOctal(8) -decimalToOctal(65) -decimalToOctal(216) -decimalToOctal(512) +export { decimalToOctal } + +// > decimalToOctal(2) +// 2 + +// > decimalToOctal(8) +// 10 + +// > decimalToOctal(65) +// 101 + +// > decimalToOctal(216) +// 330 + +// > decimalToOctal(512) +// 1000 diff --git a/Conversions/DecimalToRoman.js b/Conversions/DecimalToRoman.js new file mode 100644 index 0000000000..cea53fd1c6 --- /dev/null +++ b/Conversions/DecimalToRoman.js @@ -0,0 +1,52 @@ +/* + Decimal To Roman + + This algorithm take decimal number and convert to roman numeral according to standard form (https://en.wikipedia.org/wiki/Roman_numerals#Description) + + Algorithm & Explanation : https://www.rapidtables.com/convert/number/how-number-to-roman-numerals.html +*/ + +const values = { + M: 1000, + CM: 900, + D: 500, + CD: 400, + C: 100, + XC: 90, + L: 50, + XL: 40, + X: 10, + IX: 9, + V: 5, + IV: 4, + I: 1 +} + +const orders = [ + 'M', + 'CM', + 'D', + 'CD', + 'C', + 'XC', + 'L', + 'XL', + 'X', + 'IX', + 'V', + 'IV', + 'I' +] + +function decimalToRoman(num) { + let roman = '' + for (const symbol of orders) { + while (num >= values[symbol]) { + roman += symbol + num -= values[symbol] + } + } + return roman +} + +export { decimalToRoman } diff --git a/Conversions/HexToBinary.js b/Conversions/HexToBinary.js new file mode 100644 index 0000000000..913526fa84 --- /dev/null +++ b/Conversions/HexToBinary.js @@ -0,0 +1,39 @@ +const binLookup = (key) => + ({ + 0: '0000', + 1: '0001', + 2: '0010', + 3: '0011', + 4: '0100', + 5: '0101', + 6: '0110', + 7: '0111', + 8: '1000', + 9: '1001', + a: '1010', + b: '1011', + c: '1100', + d: '1101', + e: '1110', + f: '1111' + })[key.toLowerCase()] // select the binary number by valid hex key with the help javascript object + +const hexToBinary = (hexString) => { + if (typeof hexString !== 'string') { + throw new TypeError('Argument is not a string type') + } + + if (/[^\da-f]/gi.test(hexString)) { + throw new Error('Argument is not a valid HEX code!') + } + /* + Function for converting Hex to Binary + + 1. We convert every hexadecimal bit to 4 binary bits + 2. Conversion goes by searching in the lookup table + */ + + return hexString.replace(/[0-9a-f]/gi, (lexeme) => binLookup(lexeme)) +} + +export default hexToBinary diff --git a/Conversions/HexToDecimal.js b/Conversions/HexToDecimal.js new file mode 100644 index 0000000000..e402421399 --- /dev/null +++ b/Conversions/HexToDecimal.js @@ -0,0 +1,35 @@ +function hexToInt(hexNum) { + if (!/^[0-9A-F]+$/.test(hexNum)) { + throw new Error('Invalid hex string.') + } + const numArr = hexNum.split('') // converts number to array + return numArr.map((item, index) => { + switch (item) { + case 'A': + return 10 + case 'B': + return 11 + case 'C': + return 12 + case 'D': + return 13 + case 'E': + return 14 + case 'F': + return 15 + default: + return parseInt(item) + } + }) +} + +function hexToDecimal(hexNum) { + const intItemsArr = hexToInt(hexNum) + return intItemsArr.reduce((accumulator, current, index) => { + return ( + accumulator + current * Math.pow(16, intItemsArr.length - (1 + index)) + ) + }, 0) +} + +export { hexToDecimal } diff --git a/Conversions/HexToRGB.js b/Conversions/HexToRGB.js index f8f1662f5a..b0cfc4a362 100644 --- a/Conversions/HexToRGB.js +++ b/Conversions/HexToRGB.js @@ -1,14 +1,17 @@ -function hexStringToRGB (hexString) { - var r = (hexString.substring(1, 3)).toUpperCase() - var g = hexString.substring(3, 5).toUpperCase() - var b = hexString.substring(5, 7).toUpperCase() +function hexStringToRGB(hexString) { + let r = hexString.substring(0, 2) + let g = hexString.substring(2, 4) + let b = hexString.substring(4, 6) r = parseInt(r, 16) g = parseInt(g, 16) b = parseInt(b, 16) - var obj = { r, g, b } + const obj = { r, g, b } return obj } -console.log(hexStringToRGB('javascript rock !!')) +export { hexStringToRGB } + +// > hexStringToRGB('ffffff') +// { r: 255, g: 255, b: 255 } diff --git a/Conversions/LengthConversion.js b/Conversions/LengthConversion.js new file mode 100644 index 0000000000..783be3fb8d --- /dev/null +++ b/Conversions/LengthConversion.js @@ -0,0 +1,36 @@ +/** + * Converts a length from one unit to another. + * + * @param {number} length - The length to convert. + * @param {string} fromUnit - The unit to convert from (e.g., "km", "m", "cm"). + * @param {string} toUnit - The unit to convert to (e.g., "km", "m", "cm"). + * @returns {number} The converted length. + * @throws {Error} If the units are invalid or not found in the conversion dictionary. + */ + +const lengthConversion = (length, fromUnit, toUnit) => { + // Define a dictionary to map units to meters + const meters = { + mm: 0.001, + cm: 0.01, + m: 1, + km: 1000, + inch: 0.0254, + ft: 0.3048, + yd: 0.9144, + mi: 1609.34 + } + + // Check if the units are in the dictionary, otherwise, throw an error + if (!(fromUnit in meters) || !(toUnit in meters)) { + throw new Error('Invalid units') + } + + // Perform the conversion + const metersFrom = length * meters[fromUnit] + const convertedLength = metersFrom / meters[toUnit] + + return convertedLength +} + +export { lengthConversion } diff --git a/Conversions/LitersToImperialGallons.js b/Conversions/LitersToImperialGallons.js new file mode 100644 index 0000000000..5e8008ecbf --- /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 0000000000..62941a2c6d --- /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/LowerCaseConversion.js b/Conversions/LowerCaseConversion.js new file mode 100644 index 0000000000..bdac7d1b5d --- /dev/null +++ b/Conversions/LowerCaseConversion.js @@ -0,0 +1,35 @@ +/* + Explanation :- a user gives a String (it can be incomplete uppercase or + partial uppercase) and then the program would convert it into a + complete(all characters in lower case) lower case string. The + logic we have used in the following program is: All the upper case + characters (A-Z) has ASCII value ranging from 65 to 90 and their + corresponding lower case characters (a-z) have ASCII values 32 + greater than them. For example โ€˜Aโ€˜ has an ASCII value of 65 + and โ€˜aโ€˜ has an ASCII value of 97 (65+32). The same applies to other + characters. +*/ + +/** + * LowerCaseConversion takes any case-style string and converts it to the lower case-style string. + * @param {String} inputString any case style string + * @returns {String} lower case string + */ +const LowerCaseConversion = (inputString) => { + // Take a string and split it into characters. + const newString = inputString.split('').map((char) => { + // Get a character code by the use charCodeAt method. + const presentCharCode = char.charCodeAt() + // If the character code lies between 65 to 90 it means they are in the upper case so convert it. + if (presentCharCode >= 65 && presentCharCode <= 90) { + // Convert the case by use of the above explanation. + return String.fromCharCode(presentCharCode + 32) + } + // Else return the characters without any modification. + return char + }) + // After modification, with the help of the join method, join all the characters and return them. + return newString.join('') +} + +export { LowerCaseConversion } diff --git a/Conversions/MeterToFeetConversion.js b/Conversions/MeterToFeetConversion.js new file mode 100644 index 0000000000..c6094b06bd --- /dev/null +++ b/Conversions/MeterToFeetConversion.js @@ -0,0 +1,10 @@ +// Foot: https://en.wikipedia.org/wiki/Foot_(unit) +const feetToMeter = (feet) => { + return feet * 0.3048 +} + +const meterToFeet = (meter) => { + return meter / 0.3048 +} + +export { feetToMeter, meterToFeet } diff --git a/Conversions/OctToDecimal.js b/Conversions/OctToDecimal.js new file mode 100644 index 0000000000..6ae685aa88 --- /dev/null +++ b/Conversions/OctToDecimal.js @@ -0,0 +1,19 @@ +function octalToDecimal(num) { + let dec = 0 + let base = 1 + while (num > 0) { + const r = num % 10 + num = Math.floor(num / 10) + dec = dec + r * base + base = base * 8 + } + return dec +} + +export { octalToDecimal } + +// > octalToDecimal(56) +// 46 + +// > octalToDecimal(2365) +// 1269 diff --git a/Conversions/OuncesToKilograms.js b/Conversions/OuncesToKilograms.js new file mode 100644 index 0000000000..22c0f8aff3 --- /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/RGBToHex.js b/Conversions/RGBToHex.js new file mode 100644 index 0000000000..c44e9917aa --- /dev/null +++ b/Conversions/RGBToHex.js @@ -0,0 +1,17 @@ +function RGBToHex(r, g, b) { + if (typeof r !== 'number' || typeof g !== 'number' || typeof b !== 'number') { + throw new TypeError('argument is not a Number') + } + + const toHex = (n) => (n || '0').toString(16).padStart(2, '0') + + return `#${toHex(r)}${toHex(g)}${toHex(b)}` +} + +export { RGBToHex } + +// > RGBToHex(255, 255, 255) +// '#ffffff' + +// > RGBToHex(255, 99, 71) +// '#ff6347' diff --git a/Conversions/RailwayTimeConversion.js b/Conversions/RailwayTimeConversion.js new file mode 100644 index 0000000000..b499023223 --- /dev/null +++ b/Conversions/RailwayTimeConversion.js @@ -0,0 +1,46 @@ +/* + The time conversion of normalized time to the railway is a simple algorithm + because we know that if the time is in 'AM' value it means they only want + some changes on hours and minutes and if the time in 'PM' it means the only + want some changes in hour value. + + Input Format -> 07:05:45PM + Output Format -> 19:05:45 + + Problem & Explanation Source : https://www.mathsisfun.com/time.html +*/ + +/** + * RailwayTimeConversion method converts normalized time string to Railway time string. + * @param {String} timeString Normalized time string. + * @returns {String} Railway time string. + */ +const RailwayTimeConversion = (timeString) => { + // firstly, check that input is a string or not. + if (typeof timeString !== 'string') { + throw new TypeError('Argument is not a string.') + } + // split the string by ':' character. + const [hour, minute, secondWithShift] = timeString.split(':') + // split second and shift value. + const [second, shift] = [ + secondWithShift.substring(0, 2), + secondWithShift.substring(2) + ] + // convert shifted time to not-shift time(Railway time) by using the above explanation. + if (shift === 'PM') { + if (parseInt(hour) === 12) { + return `${hour}:${minute}:${second}` + } else { + return `${parseInt(hour) + 12}:${minute}:${second}` + } + } else { + if (parseInt(hour) === 12) { + return `00:${minute}:${second}` + } else { + return `${hour}:${minute}:${second}` + } + } +} + +export { RailwayTimeConversion } diff --git a/Conversions/RgbHslConversion.js b/Conversions/RgbHslConversion.js new file mode 100644 index 0000000000..7e014f1318 --- /dev/null +++ b/Conversions/RgbHslConversion.js @@ -0,0 +1,85 @@ +/** + * Given a color in RGB format, convert it to HSL format. + * + * For more info: https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/ + * + * @param {number[]} colorRgb - One dimensional array of integers (RGB color format). + * @returns {number[]} - One dimensional array of integers (HSL color format). + * + * @example + * const colorRgb = [24, 98, 118] + * + * const result = rgbToHsl(colorRgb) + * + * // The function returns the corresponding color in HSL format: + * // result = [193, 66, 28] + */ + +const checkRgbFormat = (colorRgb) => colorRgb.every((c) => c >= 0 && c <= 255) + +const rgbToHsl = (colorRgb) => { + if (!checkRgbFormat(colorRgb)) { + throw new Error('Input is not a valid RGB color.') + } + + let colorHsl = colorRgb + + let red = Math.round(colorRgb[0]) + let green = Math.round(colorRgb[1]) + let blue = Math.round(colorRgb[2]) + + const limit = 255 + + colorHsl[0] = red / limit + colorHsl[1] = green / limit + colorHsl[2] = blue / limit + + let minValue = Math.min(...colorHsl) + let maxValue = Math.max(...colorHsl) + + let channel = 0 + + if (maxValue === colorHsl[1]) { + channel = 1 + } else if (maxValue === colorHsl[2]) { + channel = 2 + } + + let luminance = (minValue + maxValue) / 2 + + let saturation = 0 + + if (minValue !== maxValue) { + if (luminance <= 0.5) { + saturation = (maxValue - minValue) / (maxValue + minValue) + } else { + saturation = (maxValue - minValue) / (2 - maxValue - minValue) + } + } + + let hue = 0 + + if (saturation !== 0) { + if (channel === 0) { + hue = (colorHsl[1] - colorHsl[2]) / (maxValue - minValue) + } else if (channel === 1) { + hue = 2 + (colorHsl[2] - colorHsl[0]) / (maxValue - minValue) + } else { + hue = 4 + (colorHsl[0] - colorHsl[1]) / (maxValue - minValue) + } + } + + hue *= 60 + + if (hue < 0) { + hue += 360 + } + + colorHsl[0] = Math.round(hue) + colorHsl[1] = Math.round(saturation * 100) + colorHsl[2] = Math.round(luminance * 100) + + return colorHsl +} + +export { rgbToHsl } diff --git a/Conversions/RgbHsvConversion.js b/Conversions/RgbHsvConversion.js new file mode 100644 index 0000000000..d6d1714188 --- /dev/null +++ b/Conversions/RgbHsvConversion.js @@ -0,0 +1,133 @@ +/* + * The RGB color model is an additive color model in which red, green, and blue light are added + * together in various ways to reproduce a broad array of colors. The name of the model comes from + * the initials of the three additive primary colors, red, green, and blue. Meanwhile, the HSV + * representation models how colors appear under light. In it, colors are represented using three + * components: hue, saturation and (brightness-)value. This file provides functions for converting + * colors from one representation to the other. (description adapted from + * https://en.wikipedia.org/wiki/RGB_color_model and https://en.wikipedia.org/wiki/HSL_and_HSV). + */ + +/** + * Conversion from the HSV-representation to the RGB-representation. + * + * @param hue Hue of the color. + * @param saturation Saturation of the color. + * @param value Brightness-value of the color. + * @return The tuple of RGB-components. + */ +export function hsvToRgb(hue, saturation, value) { + if (hue < 0 || hue > 360) { + throw new Error('hue should be between 0 and 360') + } + + if (saturation < 0 || saturation > 1) { + throw new Error('saturation should be between 0 and 1') + } + + if (value < 0 || value > 1) { + throw new Error('value should be between 0 and 1') + } + + const chroma = value * saturation + const hueSection = hue / 60 + const secondLargestComponent = chroma * (1 - Math.abs((hueSection % 2) - 1)) + const matchValue = value - chroma + + return getRgbBySection(hueSection, chroma, matchValue, secondLargestComponent) +} + +/** + * Conversion from the RGB-representation to the HSV-representation. + * + * @param red Red-component of the color. + * @param green Green-component of the color. + * @param blue Blue-component of the color. + * @return The tuple of HSV-components. + */ +export function rgbToHsv(red, green, blue) { + if (red < 0 || red > 255) { + throw new Error('red should be between 0 and 255') + } + + if (green < 0 || green > 255) { + throw new Error('green should be between 0 and 255') + } + + if (blue < 0 || blue > 255) { + throw new Error('blue should be between 0 and 255') + } + + const dRed = red / 255 + const dGreen = green / 255 + const dBlue = blue / 255 + const value = Math.max(Math.max(dRed, dGreen), dBlue) + const chroma = value - Math.min(Math.min(dRed, dGreen), dBlue) + const saturation = value === 0 ? 0 : chroma / value + let hue + + if (chroma === 0) { + hue = 0 + } else if (value === dRed) { + hue = 60 * ((dGreen - dBlue) / chroma) + } else if (value === dGreen) { + hue = 60 * (2 + (dBlue - dRed) / chroma) + } else { + hue = 60 * (4 + (dRed - dGreen) / chroma) + } + + hue = (hue + 360) % 360 + + return [hue, saturation, value] +} + +export function approximatelyEqualHsv(hsv1, hsv2) { + const bHue = Math.abs(hsv1[0] - hsv2[0]) < 0.2 + const bSaturation = Math.abs(hsv1[1] - hsv2[1]) < 0.002 + const bValue = Math.abs(hsv1[2] - hsv2[2]) < 0.002 + + return bHue && bSaturation && bValue +} + +function getRgbBySection( + hueSection, + chroma, + matchValue, + secondLargestComponent +) { + function convertToInt(input) { + return Math.round(255 * input) + } + + let red + let green + let blue + + if (hueSection >= 0 && hueSection <= 1) { + red = convertToInt(chroma + matchValue) + green = convertToInt(secondLargestComponent + matchValue) + blue = convertToInt(matchValue) + } else if (hueSection > 1 && hueSection <= 2) { + red = convertToInt(secondLargestComponent + matchValue) + green = convertToInt(chroma + matchValue) + blue = convertToInt(matchValue) + } else if (hueSection > 2 && hueSection <= 3) { + red = convertToInt(matchValue) + green = convertToInt(chroma + matchValue) + blue = convertToInt(secondLargestComponent + matchValue) + } else if (hueSection > 3 && hueSection <= 4) { + red = convertToInt(matchValue) + green = convertToInt(secondLargestComponent + matchValue) + blue = convertToInt(chroma + matchValue) + } else if (hueSection > 4 && hueSection <= 5) { + red = convertToInt(secondLargestComponent + matchValue) + green = convertToInt(matchValue) + blue = convertToInt(chroma + matchValue) + } else { + red = convertToInt(chroma + matchValue) + green = convertToInt(matchValue) + blue = convertToInt(secondLargestComponent + matchValue) + } + + return [red, green, blue] +} diff --git a/Conversions/RomanToDecimal.js b/Conversions/RomanToDecimal.js index 24f7b0fe61..0d62a3d468 100644 --- a/Conversions/RomanToDecimal.js +++ b/Conversions/RomanToDecimal.js @@ -1,4 +1,4 @@ -var values = { +const values = { I: 1, V: 5, X: 10, @@ -8,7 +8,7 @@ var values = { M: 1000 } -function romanToDecimal (romanNumber) { +export function romanToDecimal(romanNumber) { let prev = ' ' let sum = 0 @@ -32,7 +32,3 @@ function romanToDecimal (romanNumber) { } return sum } - -console.log(romanToDecimal('XXIIVV')) -console.log(romanToDecimal('MDCCCIV')) -console.log(romanToDecimal('XXIVI')) diff --git a/Conversions/TemperatureConversion.js b/Conversions/TemperatureConversion.js new file mode 100644 index 0000000000..059bb3e86b --- /dev/null +++ b/Conversions/TemperatureConversion.js @@ -0,0 +1,113 @@ +// This files has functions to convert different temperature units +// Functions take temperature value as a argument and returns corresponding converted value + +const celsiusToFahrenheit = (celsius) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + // Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + return Math.round((celsius * 9) / 5 + 32) +} + +const celsiusToKelvin = (celsius) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + // Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + return Math.round(celsius + 273.15) +} + +const celsiusToRankine = (celsius) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + // Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + return Math.round((celsius * 9) / 5 + 491.67) +} + +const fahrenheitToCelsius = (fahrenheit) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + // Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + return Math.round(((fahrenheit - 32) * 5) / 9) +} + +const fahrenheitToKelvin = (fahrenheit) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + // Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + return Math.round(((fahrenheit - 32) * 5) / 9 + 273.15) +} + +const fahrenheitToRankine = (fahrenheit) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + // Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + return Math.round(fahrenheit + 459.67) +} + +const kelvinToCelsius = (kelvin) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + // Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + return Math.round(kelvin - 273.15) +} + +const kelvinToFahrenheit = (kelvin) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + // Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + return Math.round(((kelvin - 273.15) * 9) / 5 + 32) +} + +const kelvinToRankine = (kelvin) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + // Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + return Math.round((kelvin * 9) / 5) +} + +const rankineToCelsius = (rankine) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + // Wikipedia reference: https://en.wikipedia.org/wiki/Celsius + return Math.round(((rankine - 491.67) * 5) / 9) +} + +const rankineToFahrenheit = (rankine) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + // Wikipedia reference: https://en.wikipedia.org/wiki/Fahrenheit + return Math.round(rankine - 459.67) +} + +const rankineToKelvin = (rankine) => { + // Wikipedia reference: https://en.wikipedia.org/wiki/Rankine_scale + // Wikipedia reference: https://en.wikipedia.org/wiki/Kelvin + return Math.round((rankine * 5) / 9) +} + +const reaumurToKelvin = (reaumur) => { + // Reference:- http://www.csgnetwork.com/temp2conv.html + return Math.round(reaumur * 1.25 + 273.15) +} + +const reaumurToFahrenheit = (reaumur) => { + // Reference:- http://www.csgnetwork.com/temp2conv.html + return Math.round(reaumur * 2.25 + 32) +} + +const reaumurToCelsius = (reaumur) => { + // Reference:- http://www.csgnetwork.com/temp2conv.html + return Math.round(reaumur * 1.25) +} + +const reaumurToRankine = (reaumur) => { + // Reference:- http://www.csgnetwork.com/temp2conv.html + return Math.round(reaumur * 2.25 + 32 + 459.67) +} + +export { + celsiusToFahrenheit, + celsiusToKelvin, + celsiusToRankine, + fahrenheitToCelsius, + fahrenheitToKelvin, + fahrenheitToRankine, + kelvinToCelsius, + kelvinToFahrenheit, + kelvinToRankine, + rankineToCelsius, + rankineToFahrenheit, + rankineToKelvin, + reaumurToCelsius, + reaumurToFahrenheit, + reaumurToKelvin, + reaumurToRankine +} diff --git a/Conversions/TitleCaseConversion.js b/Conversions/TitleCaseConversion.js new file mode 100644 index 0000000000..c057f7a332 --- /dev/null +++ b/Conversions/TitleCaseConversion.js @@ -0,0 +1,49 @@ +/* + Problem statement and Explanation : https://www.codeproject.com/Tips/162540/Letter-Case-Conversion-Algorithms-Title-Case-Toggl. + [Title case](https://en.wikipedia.org/wiki/Title_case) is a style where all words are capitalized. Officially, title case + does not capitalize some words, such as very short words like "a" or "is", but for the purposes of this function, a general approach + is taken where all words are capitalized regardless of length. +*/ + +/** + * The titleCaseConversion function converts a string into a title case string. + * @param {string} inputString The input string which can have any types of letter casing. + * @returns {string} A string that is in title case. + */ +const titleCaseConversion = (inputString) => { + if (inputString === '') return '' + // Extract all space separated string. + const stringCollections = inputString.split(' ').map((word) => { + let firstChar = '' + // Get the [ASCII](https://en.wikipedia.org/wiki/ASCII) character code by the use charCodeAt method. + const firstCharCode = word[0].charCodeAt() + // If the ASCII character code lies between 97 to 122 it means they are in the lowercase so convert it. + if (firstCharCode >= 97 && firstCharCode <= 122) { + // Convert the case by use of the above explanation. + firstChar += String.fromCharCode(firstCharCode - 32) + } else { + // Else store the characters without any modification. + firstChar += word[0] + } + const newWordChar = word + .slice(1) + .split('') + .map((char) => { + // Get the ASCII character code by the use charCodeAt method. + const presentCharCode = char.charCodeAt() + // If the ASCII character code lies between 65 to 90, it means they are in the uppercase so convert it. + if (presentCharCode >= 65 && presentCharCode <= 90) { + // Convert the case by use of the above explanation. + return String.fromCharCode(presentCharCode + 32) + } + // Else return the characters without any modification. + return char + }) + // Return the first converted character and remaining character string. + return firstChar + newWordChar.join('') + }) + // Convert all words in a string and return it. + return stringCollections.join(' ') +} + +export { titleCaseConversion } diff --git a/Conversions/UpperCaseConversion.js b/Conversions/UpperCaseConversion.js new file mode 100644 index 0000000000..8bfb7ec128 --- /dev/null +++ b/Conversions/UpperCaseConversion.js @@ -0,0 +1,35 @@ +/* + Explanation :- A user gives a string (it can be incomplete lowercase or + partially in lowercase) and then the program converts it into a + completely (all characters in uppercase) uppercase string. The + logic we have used in the following program is: All the lowercase + characters (a-z) has [ASCII](https://en.wikipedia.org/wiki/ASCII) value ranging from 97 to 122 and their + corresponding uppercase characters (A-Z) have ASCII values 32 + lesser than them. For example โ€˜aโ€˜ has an ASCII value of 97 + and โ€˜Aโ€˜ has an ASCII value of 65 (97 - 32). The same applies to other + characters. +*/ + +/** + * upperCaseConversion takes any case-style string and converts it to the uppercase-style string. + * @param {string} inputString Any case style string + * @returns {string} Uppercase string + */ +const upperCaseConversion = (inputString) => { + // Take a string and split it into characters. + const newString = inputString.split('').map((char) => { + // Get a character code by the use charCodeAt method. + const presentCharCode = char.charCodeAt() + // If the character code lies between 97 to 122, it means they are in the lowercase so convert it. + if (presentCharCode >= 97 && presentCharCode <= 122) { + // Convert the case by use of the above explanation. + return String.fromCharCode(presentCharCode - 32) + } + // Else return the characters without any modification. + return char + }) + // After modification, with the help of the join method, join all the characters and return them. + return newString.join('') +} + +export { upperCaseConversion } diff --git a/Conversions/test/ArbitraryBase.test.js b/Conversions/test/ArbitraryBase.test.js new file mode 100644 index 0000000000..3cfbd11081 --- /dev/null +++ b/Conversions/test/ArbitraryBase.test.js @@ -0,0 +1,59 @@ +import { + convertArbitraryBase, + convertArbitraryBaseBigIntVersion +} from '../ArbitraryBase' + +test('Check the answer of convertArbitraryBase(98, 0123456789, 01234567) is 142', () => { + const res = convertArbitraryBase('98', '0123456789', '01234567') + expect(res).toBe('142') +}) + +test('Check the answer of convertArbitraryBase(98, 0123456789, abcdefgh) is bec', () => { + const res = convertArbitraryBase('98', '0123456789', 'abcdefgh') + expect(res).toBe('bec') +}) + +test('Check the answer of convertArbitraryBase(98, 0123456789, 98765432) is 857', () => { + const res = convertArbitraryBase('98', '0123456789', '98765432') + expect(res).toBe('857') +}) + +test('Check the answer of convertArbitraryBase(129, 0123456789, 01234567) is 201', () => { + const res = convertArbitraryBase('129', '0123456789', '01234567') + expect(res).toBe('201') +}) + +test('Check the answer of convertArbitraryBase(112, 0123456789, 12345678) is 271', () => { + const res = convertArbitraryBase('112', '0123456789', '12345678') + expect(res).toBe('271') +}) + +test('Check the answer of convertArbitraryBase(112, 0123456789, 123456789) is 245', () => { + const res = convertArbitraryBase('112', '0123456789', '123456789') + expect(res).toBe('245') +}) + +test('Check the answer of convertArbitraryBase(111, 0123456789, abcdefgh) is bfh', () => { + const res = convertArbitraryBase('111', '0123456789', 'abcdefgh') + expect(res).toBe('bfh') +}) + +test('Unicode awareness', () => { + const res = convertArbitraryBase('98', '0123456789', '๐Ÿ’๐ŸŽธ๐Ÿฆ„') + expect(res).toBe('๐ŸŽธ๐Ÿ’๐ŸŽธ๐Ÿฆ„๐Ÿฆ„') +}) + +test('zero', () => { + const res = convertArbitraryBase('0', '0123456789', 'abc') + expect(res).toBe('a') +}) + +test('BigInt version with input string of arbitrary length', () => { + const resBigIntVersion = convertArbitraryBaseBigIntVersion( + String(10n ** 100n), + '0123456789', + '0123456789abcdefghijklmnopqrstuvwxyz' + ) + + expect(resBigIntVersion).toBe((10n ** 100n).toString(36)) +}) diff --git a/Conversions/test/ArrayBufferToBase64.test.js b/Conversions/test/ArrayBufferToBase64.test.js new file mode 100644 index 0000000000..3dd72f4fed --- /dev/null +++ b/Conversions/test/ArrayBufferToBase64.test.js @@ -0,0 +1,24 @@ +import { bufferToBase64 } from '../ArrayBufferToBase64' +import { TextEncoder } from 'util' + +describe('ArrayBufferToBase64', () => { + it('should encode "Hello, world!" as "SGVsbG8sIHdvcmxkIQ=="', () => { + const testString = 'Hello, world!' + const encoder = new TextEncoder() + const helloWorldBuffer = encoder.encode(testString) + const result = bufferToBase64(helloWorldBuffer) + expect(result).toBe('SGVsbG8sIHdvcmxkIQ==') + }) + + it('should encode binary buffer [55,23,177,234,68,26,90] as "Nxex6kQaWg=="', () => { + const testBuffer = new Uint8Array([55, 23, 177, 234, 68, 26, 90]) + const result = bufferToBase64(testBuffer) + expect(result).toBe('Nxex6kQaWg==') + }) + + it('should encode binary buffer [0,1,2,3,4,5,6,7,8,9] as "AAECAwQFBgcICQ=="', () => { + const testBuffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + const result = bufferToBase64(testBuffer) + expect(result).toBe('AAECAwQFBgcICQ==') + }) +}) diff --git a/Conversions/test/Base64ToArrayBuffer.test.js b/Conversions/test/Base64ToArrayBuffer.test.js new file mode 100644 index 0000000000..91071491d2 --- /dev/null +++ b/Conversions/test/Base64ToArrayBuffer.test.js @@ -0,0 +1,26 @@ +import { base64ToBuffer } from '../Base64ToArrayBuffer' +import { TextDecoder } from 'util' + +describe('Base64ToArrayBuffer', () => { + it('should decode "SGVsbG8sIHdvcmxkIQ==" as "Hello, world!"', () => { + const testBase64String = 'SGVsbG8sIHdvcmxkIQ==' + const buffer = base64ToBuffer(testBase64String) + const decoder = new TextDecoder() + const helloWorldString = decoder.decode(buffer) + expect(helloWorldString).toBe('Hello, world!') + }) + + it('should decode base64 "Nxex6kQaWg==" as binary buffer [55,23,177,234,68,26,90]', () => { + const testBase64String = 'Nxex6kQaWg==' + const buffer = base64ToBuffer(testBase64String) + const array = [...new Uint8Array(buffer)] + expect(array).toEqual([55, 23, 177, 234, 68, 26, 90]) + }) + + it('should decode base64 "AAECAwQFBgcICQ==" as binary buffer [0,1,2,3,4,5,6,7,8,9]', () => { + const testBase64String = 'AAECAwQFBgcICQ==' + const buffer = base64ToBuffer(testBase64String) + const array = [...new Uint8Array(buffer)] + expect(array).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + }) +}) diff --git a/Conversions/test/BinaryToDecimal.test.js b/Conversions/test/BinaryToDecimal.test.js new file mode 100644 index 0000000000..97f23aeb49 --- /dev/null +++ b/Conversions/test/BinaryToDecimal.test.js @@ -0,0 +1,15 @@ +import binaryToDecimal from '../BinaryToDecimal' + +describe('BinaryToDecimal', () => { + it('expects to return correct decimal value', () => { + expect(binaryToDecimal('1000')).toBe(8) + }) + + it('expects to return correct hexadecimal value for more than one hex digit', () => { + expect(binaryToDecimal('01101000')).toBe(104) + }) + + it('expects to return correct hexadecimal value for padding-required binary', () => { + expect(binaryToDecimal('1000101')).toBe(69) + }) +}) diff --git a/Conversions/test/BinaryToHex.test.js b/Conversions/test/BinaryToHex.test.js new file mode 100644 index 0000000000..0444e08e67 --- /dev/null +++ b/Conversions/test/BinaryToHex.test.js @@ -0,0 +1,21 @@ +import binaryToHex from '../BinaryToHex' + +describe('BinaryToHex', () => { + it('expects to return correct hexadecimal value', () => { + expect(binaryToHex('1000')).toBe('8') + }) + + it('expects to return correct hexadecimal value for more than one hex digit', () => { + expect(binaryToHex('11101010')).toBe('EA') + }) + + it('expects to return correct hexadecimal value for padding-required binary', () => { + expect(binaryToHex('1001101')).toBe('4D') + }) + + it('expects to return correct hexadecimal value, matching (num).toString(16)', () => { + expect(binaryToHex('1111')).toBe( + parseInt('1111', 2).toString(16).toUpperCase() + ) + }) +}) diff --git a/Conversions/test/DateDayDiffernce.test.js b/Conversions/test/DateDayDiffernce.test.js new file mode 100644 index 0000000000..a0f8834593 --- /dev/null +++ b/Conversions/test/DateDayDiffernce.test.js @@ -0,0 +1,36 @@ +import { DateDayDifference } from '../DateDayDifference' + +describe('DateDayDifference', () => { + it.each([ + ['17/08/2002', '10/10/2020', 6629], + ['18/02/2001', '16/03/2022', 7696], + ['11/11/2011', '12/12/2012', 397], + ['01/01/2001', '16/03/2011', 3726], + ['04/03/2024', '04/03/2024', 0], + ['03/03/2024', '04/03/2024', 1], + ['02/03/2024', '04/03/2024', 2], + ['01/03/2024', '04/03/2024', 3], + ['29/02/2024', '04/03/2024', 4], + ['04/03/2024', '04/03/2025', 365], + ['04/03/2023', '04/03/2024', 366] + ])( + 'The difference between %s and %s is %i', + (firstDate, secondDate, expected) => { + expect(DateDayDifference(firstDate, secondDate)).toBe(expected) + expect(DateDayDifference(secondDate, firstDate)).toBe(expected) + } + ) + + it('should throw when any input is not a string', () => { + expect(() => DateDayDifference(10102024, '11/10/2024')).toThrowError() + expect(() => DateDayDifference('11/10/2024', 10102024)).toThrowError() + }) + + it.each(['32/01/2000', '00/01/2000', '15/00/2000', '15/13/2000'])( + 'should throw when input is not a correct date %s', + (wrongDate) => { + expect(() => DateDayDifference(wrongDate, '04/03/2024')).toThrowError() + expect(() => DateDayDifference('04/03/2024', wrongDate)).toThrowError() + } + ) +}) diff --git a/Conversions/test/DateToDay.test.js b/Conversions/test/DateToDay.test.js new file mode 100644 index 0000000000..38b63d33da --- /dev/null +++ b/Conversions/test/DateToDay.test.js @@ -0,0 +1,28 @@ +import { DateToDay } from '../DateToDay' + +describe('DateToDay', () => { + it.each([ + ['18/02/2001', 'Sunday'], + ['18/12/2020', 'Friday'], + ['12/12/2012', 'Wednesday'], + ['01/01/2001', 'Monday'], + ['1/1/2020', 'Wednesday'], + ['2/3/2014', 'Sunday'], + ['28/2/2017', 'Tuesday'], + ['02/03/2024', 'Saturday'], + ['29/02/2024', 'Thursday'] + ])('%s is %s', (date, day) => { + expect(DateToDay(date)).toBe(day) + }) + + it('should throw when input is not a string', () => { + expect(() => DateToDay(100)).toThrowError() + }) + + it.each(['32/01/2000', '00/01/2000', '15/00/2000', '15/13/2000'])( + 'should throw when input is not a correct date %s', + (wrongDate) => { + expect(() => DateToDay(wrongDate)).toThrowError() + } + ) +}) diff --git a/Conversions/test/DecimalToBinary.test.js b/Conversions/test/DecimalToBinary.test.js new file mode 100644 index 0000000000..b3a26e1b76 --- /dev/null +++ b/Conversions/test/DecimalToBinary.test.js @@ -0,0 +1,26 @@ +import { decimalToBinary } from '../DecimalToBinary' + +test('The Binary representation of 35 is 100011', () => { + const res = decimalToBinary(35) + expect(res).toBe('100011') +}) + +test('The Binary representation of 1 is 1', () => { + const res = decimalToBinary(1) + expect(res).toBe('1') +}) + +test('The Binary representation of 1000 is 1111101000', () => { + const res = decimalToBinary(1000) + expect(res).toBe('1111101000') +}) + +test('The Binary representation of 2 is 10', () => { + const res = decimalToBinary(2) + expect(res).toBe('10') +}) + +test('The Binary representation of 17 is 10001', () => { + const res = decimalToBinary(17) + expect(res).toBe('10001') +}) diff --git a/Conversions/test/DecimalToHex.test.js b/Conversions/test/DecimalToHex.test.js new file mode 100644 index 0000000000..53cd884738 --- /dev/null +++ b/Conversions/test/DecimalToHex.test.js @@ -0,0 +1,15 @@ +import { decimalToHex } from '../DecimalToHex' + +describe('DecimalToHex', () => { + it('expects to return correct hexadecimal value', () => { + expect(decimalToHex(255)).toBe('FF') + }) + + it('expects to return correct hexadecimal value, matching (num).toString(16)', () => { + expect(decimalToHex(32768)).toBe((32768).toString(16).toUpperCase()) + }) + + it('expects to not handle negative numbers', () => { + expect(decimalToHex(-32768)).not.toBe((-32768).toString(16).toUpperCase()) + }) +}) diff --git a/Conversions/test/DecimalToOctal.test.js b/Conversions/test/DecimalToOctal.test.js new file mode 100644 index 0000000000..0e4214b0c6 --- /dev/null +++ b/Conversions/test/DecimalToOctal.test.js @@ -0,0 +1,26 @@ +import { decimalToOctal } from '../DecimalToOctal' + +test('The Octal representation of 8 is 10', () => { + const res = decimalToOctal(8) + expect(res).toBe(10) +}) + +test('The Octal representation of 1 is 1', () => { + const res = decimalToOctal(1) + expect(res).toBe(1) +}) + +test('The Octal representation of 0 is 0', () => { + const res = decimalToOctal(0) + expect(res).toBe(0) +}) + +test('The Octal representation of 100 is 144', () => { + const res = decimalToOctal(100) + expect(res).toBe(144) +}) + +test('The Octal representation of 111 is 157', () => { + const res = decimalToOctal(111) + expect(res).toBe(157) +}) diff --git a/Conversions/test/DecimalToRoman.test.js b/Conversions/test/DecimalToRoman.test.js new file mode 100644 index 0000000000..59d25a1af2 --- /dev/null +++ b/Conversions/test/DecimalToRoman.test.js @@ -0,0 +1,13 @@ +import { decimalToRoman } from '../DecimalToRoman' + +describe('decimalToRoman', () => { + it('expects to return correct roman numeral of given number', () => { + expect(decimalToRoman(34)).toBe('XXXIV') + }) + it('expects to return correct roman numeral of given number', () => { + expect(decimalToRoman(28)).toBe('XXVIII') + }) + it('expects to return correct roman numeral of given number', () => { + expect(decimalToRoman(2021)).toBe('MMXXI') + }) +}) diff --git a/Conversions/test/HexToBinary.test.js b/Conversions/test/HexToBinary.test.js new file mode 100644 index 0000000000..6a7ec265d9 --- /dev/null +++ b/Conversions/test/HexToBinary.test.js @@ -0,0 +1,31 @@ +import hexToBinary from '../HexToBinary' + +describe('Testing hexToBinary', () => { + it('expects throw error in invalid types', () => { + expect(() => hexToBinary(false)).toThrowError() + expect(() => hexToBinary(null)).toThrowError() + expect(() => hexToBinary(23464)).toThrowError() + }) + + it('expects throw error in invalid hex', () => { + expect(() => hexToBinary('Hello i am not a valid Hex')).toThrowError() + expect(() => hexToBinary('Gf46f')).toThrowError() + expect(() => hexToBinary('M')).toThrowError() + }) + + it('expects to return correct hexadecimal value', () => { + expect(hexToBinary('8')).toBe('1000') + }) + + it('expects to return correct binary value for more than one hex digit', () => { + expect(hexToBinary('EA')).toBe('11101010') + }) + + it('expects to test its robustness as it should be case-insensitive', () => { + expect(hexToBinary('4d')).toBe('01001101') + }) + + it('expects to return correct hexadecimal value, matching (num).toString(2)', () => { + expect(hexToBinary('F')).toBe(parseInt('F', 16).toString(2)) + }) +}) diff --git a/Conversions/test/HexToDecimal.test.js b/Conversions/test/HexToDecimal.test.js new file mode 100644 index 0000000000..816c70511d --- /dev/null +++ b/Conversions/test/HexToDecimal.test.js @@ -0,0 +1,24 @@ +import { hexToDecimal } from '../HexToDecimal' + +describe('Testing HexToDecimal', () => { + it.each([ + ['0', 0], + ['1', 1], + ['A', 10], + ['B', 11], + ['C', 12], + ['D', 13], + ['E', 14], + ['F', 15], + ['10', 16], + ['859', 2137], + ['4D2', 1234], + ['81323ABD92', 554893491602] + ])('check with %s', (hexStr, expected) => { + expect(hexToDecimal(hexStr)).toBe(expected) + }) + + it.each(['a', '-1', 'G', ''])('throws for %s', (hexStr) => { + expect(() => hexToDecimal(hexStr)).toThrowError() + }) +}) diff --git a/Conversions/test/HexToRGB.test.js b/Conversions/test/HexToRGB.test.js new file mode 100644 index 0000000000..0a16045f39 --- /dev/null +++ b/Conversions/test/HexToRGB.test.js @@ -0,0 +1,16 @@ +import { hexStringToRGB } from '../HexToRGB' + +test('The RGB form of Hex String E1E1E1 is {r: 225, g: 225, b: 225}', () => { + const res = hexStringToRGB('E1E1E1') + expect(res).toEqual({ r: 225, g: 225, b: 225 }) +}) + +test('The RGB form of Hex String 000000 is {r: 0, g: 0, b: 0}', () => { + const res = hexStringToRGB('000000') + expect(res).toEqual({ r: 0, g: 0, b: 0 }) +}) + +test('The RGB form of Hex String 6CE1CD is {r: 108, g: 225, b: 205}', () => { + const res = hexStringToRGB('6CE1CD') + expect(res).toEqual({ r: 108, g: 225, b: 205 }) +}) diff --git a/Conversions/test/LengthConversion.test.js b/Conversions/test/LengthConversion.test.js new file mode 100644 index 0000000000..8adae08b51 --- /dev/null +++ b/Conversions/test/LengthConversion.test.js @@ -0,0 +1,53 @@ +import { lengthConversion } from '../LengthConversion.js' + +describe('LengthConversion', () => { + it.each` + length | fromUnit | toUnit | expected + ${10} | ${'km'} | ${'m'} | ${10000} + ${100} | ${'m'} | ${'km'} | ${0.1} + ${5} | ${'cm'} | ${'mm'} | ${50} + ${12} | ${'ft'} | ${'inch'} | ${144.00000000000003} + `( + 'converts $length $fromUnit to $toUnit', + ({ length, fromUnit, toUnit, expected }) => { + try { + const result = lengthConversion(length, fromUnit, toUnit) + expect(result).toBe(expected) + } catch (error) { + expect(error).toBeUndefined() + } + } + ) + + it.each` + length | fromUnit | toUnit | expected + ${10} | ${'m'} | ${'km'} | ${0.01} + ${1000} | ${'mm'} | ${'cm'} | ${100} + ${1} | ${'inch'} | ${'ft'} | ${0.08333333333} + `( + 'converts $length $fromUnit to $toUnit (vice versa)', + ({ length, fromUnit, toUnit, expected }) => { + try { + const result = lengthConversion(length, fromUnit, toUnit) + expect(result).toBeCloseTo(expected, 10) // Close comparison due to floating-point precision + } catch (error) { + expect(error).toBeUndefined() + } + } + ) + + it.each` + length | fromUnit | toUnit | expectedError + ${10} | ${'km'} | ${'invalid'} | ${'Invalid units'} + ${5} | ${'invalid'} | ${'m'} | ${'Invalid units'} + `( + 'returns error message for invalid units: $fromUnit to $toUnit', + ({ length, fromUnit, toUnit, expectedError }) => { + try { + lengthConversion(length, fromUnit, toUnit) + } catch (error) { + expect(error.message).toBe(expectedError) + } + } + ) +}) diff --git a/Conversions/test/LitersToImperialGallons.test.js b/Conversions/test/LitersToImperialGallons.test.js new file mode 100644 index 0000000000..3a019887d8 --- /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 0000000000..a1ad26b754 --- /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/LowerCaseConversion.test.js b/Conversions/test/LowerCaseConversion.test.js new file mode 100644 index 0000000000..1b84f2c0d0 --- /dev/null +++ b/Conversions/test/LowerCaseConversion.test.js @@ -0,0 +1,26 @@ +import { LowerCaseConversion } from '../LowerCaseConversion' + +test('The LowerCaseConversion of ApoLO is apolo', () => { + const res = LowerCaseConversion('ApoLO') + expect(res).toBe('apolo') +}) + +test('The LowerCaseConversion of WEB is web', () => { + const res = LowerCaseConversion('WEB') + expect(res).toBe('web') +}) + +test('The LowerCaseConversion of EaRTh is earth', () => { + const res = LowerCaseConversion('EaRTh') + expect(res).toBe('earth') +}) + +test('The LowerCaseConversion of TiGER is tiger', () => { + const res = LowerCaseConversion('TiGER') + expect(res).toBe('tiger') +}) + +test('The LowerCaseConversion of Cricket is cricket', () => { + const res = LowerCaseConversion('Cricket') + expect(res).toBe('cricket') +}) diff --git a/Conversions/test/MeterToFeetConversion.test.js b/Conversions/test/MeterToFeetConversion.test.js new file mode 100644 index 0000000000..2258f97415 --- /dev/null +++ b/Conversions/test/MeterToFeetConversion.test.js @@ -0,0 +1,13 @@ +import { meterToFeet, feetToMeter } from '../MeterToFeetConversion' + +describe('Testing conversion of Meter to Feet', () => { + it('with feet value', () => { + expect(meterToFeet(30.48)).toBe(100) + }) +}) + +describe('Testing conversion of Feet to Meter', () => { + it('with feet value', () => { + expect(feetToMeter(10)).toBe(3.048) + }) +}) diff --git a/Conversions/test/OctToDecimal.test.js b/Conversions/test/OctToDecimal.test.js new file mode 100644 index 0000000000..d36327508d --- /dev/null +++ b/Conversions/test/OctToDecimal.test.js @@ -0,0 +1,26 @@ +import { octalToDecimal } from '../OctToDecimal' + +test('The Decimal representation of Octal number 56 is 46', () => { + const res = octalToDecimal(56) + expect(res).toBe(46) +}) + +test('The Decimal representation of Octal number 99 is 81', () => { + const res = octalToDecimal(99) + expect(res).toBe(81) +}) + +test('The Decimal representation of Octal number 17 is 15', () => { + const res = octalToDecimal(17) + expect(res).toBe(15) +}) + +test('The Decimal representation of Octal number 100 is 64', () => { + const res = octalToDecimal(100) + expect(res).toBe(64) +}) + +test('The Decimal representation of Octal number 0 is 0', () => { + const res = octalToDecimal(0) + expect(res).toBe(0) +}) diff --git a/Conversions/test/OuncesToKilogram.test.js b/Conversions/test/OuncesToKilogram.test.js new file mode 100644 index 0000000000..e72e06958a --- /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/Conversions/test/RGBToHex.test.js b/Conversions/test/RGBToHex.test.js new file mode 100644 index 0000000000..0438639988 --- /dev/null +++ b/Conversions/test/RGBToHex.test.js @@ -0,0 +1,21 @@ +import { RGBToHex } from '../RGBToHex' + +test('The Hex format of RGB (225, 225, 225) is #ffffff', () => { + const res = RGBToHex(255, 255, 255) + expect(res).toBe('#ffffff') +}) + +test('The Hex format of RGB (190, 108, 217) is #be6cd9', () => { + const res = RGBToHex(190, 108, 217) + expect(res).toBe('#be6cd9') +}) + +test('The Hex format of RGB (255, 99, 71) is #ff6347', () => { + const res = RGBToHex(255, 99, 71) + expect(res).toBe('#ff6347') +}) + +test('The Hex format of RGB (100, 108, 217) is #646cd9', () => { + const res = RGBToHex(100, 108, 217) + expect(res).toBe('#646cd9') +}) diff --git a/Conversions/test/RailwayTimeConversion.test.js b/Conversions/test/RailwayTimeConversion.test.js new file mode 100644 index 0000000000..6579420049 --- /dev/null +++ b/Conversions/test/RailwayTimeConversion.test.js @@ -0,0 +1,25 @@ +import { RailwayTimeConversion } from '../RailwayTimeConversion' + +test('The RailwayTimeConversion of 07:05:45AM is 07:05:45', () => { + const res = RailwayTimeConversion('07:05:45AM') + expect(res).toEqual('07:05:45') +}) + +test('The RailwayTimeConversion of 07:05:45PM is 19:05:45', () => { + const res = RailwayTimeConversion('07:05:45PM') + expect(res).toEqual('19:05:45') +}) + +test('The RailwayTimeConversion of 10:20:00AM is 10:20:00', () => { + const res = RailwayTimeConversion('10:20:00AM') + expect(res).toEqual('10:20:00') +}) + +test('The RailwayTimeConversion of 11:20:00PM is 23:20:00', () => { + const res = RailwayTimeConversion('11:20:00PM') + expect(res).toEqual('23:20:00') +}) + +test('The RailwayTimeConversion throws when input is not a string', () => { + expect(() => RailwayTimeConversion(1120)).toThrowError() +}) diff --git a/Conversions/test/RgbHslConversion.test.js b/Conversions/test/RgbHslConversion.test.js new file mode 100644 index 0000000000..5dec4835cd --- /dev/null +++ b/Conversions/test/RgbHslConversion.test.js @@ -0,0 +1,43 @@ +import { rgbToHsl } from '../RgbHslConversion' +describe('RgbHslConversion', () => { + test.each([ + [ + [215, 19, 180], + [311, 84, 46] + ], + [ + [21, 190, 18], + [119, 83, 41] + ], + [ + [80, 100, 160], + [225, 33, 47] + ], + [ + [80, 1, 16], + [349, 98, 16] + ], + [ + [8, 20, 0], + [96, 100, 4] + ], + [ + [0, 0, 0], + [0, 0, 0] + ], + [ + [255, 255, 255], + [0, 0, 100] + ] + ])('Should return the color in HSL format.', (colorRgb, expected) => { + expect(rgbToHsl(colorRgb)).toEqual(expected) + }) + + test.each([ + [[256, 180, 9], 'Input is not a valid RGB color.'], + [[-90, 46, 8], 'Input is not a valid RGB color.'], + [[1, 39, 900], 'Input is not a valid RGB color.'] + ])('Should return the error message.', (colorRgb, expected) => { + expect(() => rgbToHsl(colorRgb)).toThrowError(expected) + }) +}) diff --git a/Conversions/test/RgbHsvConversion.test.js b/Conversions/test/RgbHsvConversion.test.js new file mode 100644 index 0000000000..02c16f438f --- /dev/null +++ b/Conversions/test/RgbHsvConversion.test.js @@ -0,0 +1,49 @@ +import { approximatelyEqualHsv, hsvToRgb, rgbToHsv } from '../RgbHsvConversion' + +describe('hsvToRgb', () => { + // Expected RGB-values taken from https://www.rapidtables.com/convert/color/hsv-to-rgb.html + it('should calculate the correct RGB values', () => { + expect(hsvToRgb(0, 0, 0)).toEqual([0, 0, 0]) + expect(hsvToRgb(0, 0, 1)).toEqual([255, 255, 255]) + expect(hsvToRgb(0, 1, 1)).toEqual([255, 0, 0]) + expect(hsvToRgb(60, 1, 1)).toEqual([255, 255, 0]) + expect(hsvToRgb(120, 1, 1)).toEqual([0, 255, 0]) + expect(hsvToRgb(240, 1, 1)).toEqual([0, 0, 255]) + expect(hsvToRgb(300, 1, 1)).toEqual([255, 0, 255]) + expect(hsvToRgb(180, 0.5, 0.5)).toEqual([64, 128, 128]) + expect(hsvToRgb(234, 0.14, 0.88)).toEqual([193, 196, 224]) + expect(hsvToRgb(330, 0.75, 0.5)).toEqual([128, 32, 80]) + }) +}) + +describe('rgbToHsv', () => { + // "approximatelyEqualHsv" needed because of small deviations due to rounding for the RGB-values + it('should calculate the correct HSV values', () => { + expect(approximatelyEqualHsv(rgbToHsv(0, 0, 0), [0, 0, 0])).toEqual(true) + expect(approximatelyEqualHsv(rgbToHsv(255, 255, 255), [0, 0, 1])).toEqual( + true + ) + expect(approximatelyEqualHsv(rgbToHsv(255, 0, 0), [0, 1, 1])).toEqual(true) + expect(approximatelyEqualHsv(rgbToHsv(255, 255, 0), [60, 1, 1])).toEqual( + true + ) + expect(approximatelyEqualHsv(rgbToHsv(0, 255, 0), [120, 1, 1])).toEqual( + true + ) + expect(approximatelyEqualHsv(rgbToHsv(0, 0, 255), [240, 1, 1])).toEqual( + true + ) + expect(approximatelyEqualHsv(rgbToHsv(255, 0, 255), [300, 1, 1])).toEqual( + true + ) + expect( + approximatelyEqualHsv(rgbToHsv(64, 128, 128), [180, 0.5, 0.5]) + ).toEqual(true) + expect( + approximatelyEqualHsv(rgbToHsv(193, 196, 224), [234, 0.14, 0.88]) + ).toEqual(true) + expect( + approximatelyEqualHsv(rgbToHsv(128, 32, 80), [330, 0.75, 0.5]) + ).toEqual(true) + }) +}) diff --git a/Conversions/test/RomanToDecimal.test.js b/Conversions/test/RomanToDecimal.test.js new file mode 100644 index 0000000000..763de32b92 --- /dev/null +++ b/Conversions/test/RomanToDecimal.test.js @@ -0,0 +1,15 @@ +import { romanToDecimal } from '../RomanToDecimal' + +describe('romanToDecimal', () => { + it('XXIIVV', () => { + expect(romanToDecimal('XXIIVV')).toBe(28) + }) + + it('MDCCCIV', () => { + expect(romanToDecimal('MDCCCIV')).toBe(1804) + }) + + it('XXIVI', () => { + expect(romanToDecimal('XXIVI')).toBe(25) + }) +}) diff --git a/Conversions/test/TemperatureConversion.test.js b/Conversions/test/TemperatureConversion.test.js new file mode 100644 index 0000000000..862eb07747 --- /dev/null +++ b/Conversions/test/TemperatureConversion.test.js @@ -0,0 +1,106 @@ +import * as tc from '../TemperatureConversion.js' + +describe('Testing Conversion of Celsius to fahrenheit', () => { + it('with celsius value', () => { + const test1 = tc.celsiusToFahrenheit(10) + expect(test1).toBe(50) + }) +}) + +describe('Testing Conversion of Celsius to kelvin', () => { + it('with celsius value', () => { + const test1 = tc.celsiusToKelvin(15) + expect(test1).toBe(288) + }) +}) + +describe('Testing Conversion of Celsius to Rankine', () => { + it('with celsius value', () => { + const test1 = tc.celsiusToRankine(28) + expect(test1).toBe(542) + }) +}) + +describe('Testing Conversion of Fahrenheit to Celsius', () => { + it('with Fahrenheit value', () => { + const test1 = tc.fahrenheitToCelsius(134) + expect(test1).toBe(57) + }) +}) + +describe('Testing Conversion of Fahrenheit to Kelvin', () => { + it('with Fahrenheit value', () => { + const test1 = tc.fahrenheitToKelvin(125) + expect(test1).toBe(325) + }) +}) + +describe('Testing Conversion of Fahrenheit to Rankine', () => { + it('with Fahrenheit value', () => { + const test1 = tc.fahrenheitToRankine(10) + expect(test1).toBe(470) + }) +}) + +describe('Testing Conversion of Kelvin to Celsius', () => { + it('with Kelvin value', () => { + const test1 = tc.kelvinToCelsius(100) + expect(test1).toBe(-173) + }) +}) + +describe('Testing Conversion of Kelvin to Fahrenheit', () => { + it('with Kelvin value', () => { + const test1 = tc.kelvinToFahrenheit(20) + expect(test1).toBe(-424) + }) +}) + +describe('Testing Conversion of Kelvin to Rankine', () => { + it('with kelvin value', () => { + const test1 = tc.kelvinToRankine(69) + expect(test1).toBe(124) + }) +}) +describe('Testing Conversion of Rankine to Celsius', () => { + it('with Rankine value', () => { + const test1 = tc.rankineToCelsius(234) + expect(test1).toBe(-143) + }) +}) +describe('Testing Conversion of Rankine to Fahrenheit', () => { + it('with Rankine value', () => { + const test1 = tc.rankineToFahrenheit(98) + expect(test1).toBe(-362) + }) +}) +describe('Testing Conversion of Rankine to Kelvin', () => { + it('with Rankine value', () => { + const test1 = tc.rankineToKelvin(10) + expect(test1).toBe(6) + }) +}) +describe('Testing Conversion of Reaumur to Celsius', () => { + it('with Reaumur value', () => { + const test1 = tc.reaumurToCelsius(100) + expect(test1).toBe(125) + }) +}) +describe('Testing Conversion of Reaumur to Fahrenheit', () => { + it('with Reaumur value', () => { + const test1 = tc.reaumurToFahrenheit(100) + expect(test1).toBe(257) + }) +}) +describe('Testing Conversion of Reaumur to Kelvin', () => { + it('with Reamur value', () => { + const test1 = tc.reaumurToKelvin(100) + expect(test1).toBe(398) + }) +}) +describe('Testing Conversion of Reamur to Rankine', () => { + it('with Reamur value', () => { + const test1 = tc.reaumurToRankine(100) + expect(test1).toBe(717) + }) +}) diff --git a/Conversions/test/TitleCaseConversion.test.js b/Conversions/test/TitleCaseConversion.test.js new file mode 100644 index 0000000000..16741662a0 --- /dev/null +++ b/Conversions/test/TitleCaseConversion.test.js @@ -0,0 +1,57 @@ +import { titleCaseConversion } from '../TitleCaseConversion' + +describe('Tests for the titleCaseConversion function', () => { + it('should return an empty string when the input is an empty string', () => { + expect(titleCaseConversion('')).toEqual('') + }) + + it('should return the input string when the input string is a title case string', () => { + expect(titleCaseConversion('A Proper Title Case String')).toEqual( + 'A Proper Title Case String' + ) + }) + + it('should return a title case string when input is an all-uppercase string', () => { + expect(titleCaseConversion('ALL UPPER CASE')).toEqual('All Upper Case') + }) + + it('should return a title case string when input is a title case string of with spaces', () => { + expect(titleCaseConversion('ALL UPPERCASE')).toEqual('All Uppercase') + }) + + it('should return a title case string when input is a title case string of with no spaces', () => { + expect(titleCaseConversion('ALLUPPERCASE')).toEqual('Alluppercase') + }) + + it('should return a title case string when input is a title case string with punctuation', () => { + expect(titleCaseConversion('All Title Case!')).toEqual('All Title Case!') + }) + + it('should return a title case string when input is an all-lowercase string with no spaces', () => { + expect(titleCaseConversion('lowercaseinput')).toEqual('Lowercaseinput') + }) + + it('should return a title case string when input is an all-lowercase string with spaces', () => { + expect(titleCaseConversion('lowercase input')).toEqual('Lowercase Input') + }) + + it('should return a title case string when input is an all-lowercase string with punctuation', () => { + expect(titleCaseConversion('lower, case, input.')).toEqual( + 'Lower, Case, Input.' + ) + }) + + it('should return a title case string when input is an mixed-case string', () => { + expect(titleCaseConversion('mixeD CaSe INPuT')).toEqual('Mixed Case Input') + }) + + it('should return a title case string when input is an mixed-case string with no spaces', () => { + expect(titleCaseConversion('mixeDCaSeINPuT')).toEqual('Mixedcaseinput') + }) + + it('should return a title case string when input is an mixed-case string with punctuation', () => { + expect(titleCaseConversion('mixeD, CaSe, INPuT!')).toEqual( + 'Mixed, Case, Input!' + ) + }) +}) diff --git a/Conversions/test/UpperCaseConverstion.test.js b/Conversions/test/UpperCaseConverstion.test.js new file mode 100644 index 0000000000..d9b4f82fd3 --- /dev/null +++ b/Conversions/test/UpperCaseConverstion.test.js @@ -0,0 +1,47 @@ +import { upperCaseConversion } from '../UpperCaseConversion' + +describe('Test the upperCaseConversion function', () => { + it('should return an empty string when the input is an empty string', () => { + expect(upperCaseConversion('')).toEqual('') + }) + + it('should return an all-uppercase string when input is an all-uppercase string', () => { + expect(upperCaseConversion('ALLUPPERCASE')).toEqual('ALLUPPERCASE') + }) + + it('should return an all-uppercase string when input is an all-uppercase string with spaces', () => { + expect(upperCaseConversion('ALL UPPERCASE')).toEqual('ALL UPPERCASE') + }) + + it('should return an all-uppercase string when input is an all-uppercase string with punctuation', () => { + expect(upperCaseConversion('ALL UPPER-CASE!')).toEqual('ALL UPPER-CASE!') + }) + + it('should return an all-uppercase string when input is an all-lowercase string', () => { + expect(upperCaseConversion('lowercaseinput')).toEqual('LOWERCASEINPUT') + }) + + it('should return an all-uppercase string when input is an all-lowercase string with spaces', () => { + expect(upperCaseConversion('lowercase input')).toEqual('LOWERCASE INPUT') + }) + + it('should return an all-uppercase string when input is an all-lowercase string with punctuation', () => { + expect(upperCaseConversion('lower-case, input.')).toEqual( + 'LOWER-CASE, INPUT.' + ) + }) + + it('should return an all-uppercase string when input is an mixed-case string', () => { + expect(upperCaseConversion('mixeDCaSeINPuT')).toEqual('MIXEDCASEINPUT') + }) + + it('should return an all-uppercase string when input is an mixed-case string with spaces', () => { + expect(upperCaseConversion('mixeD CaSe INPuT')).toEqual('MIXED CASE INPUT') + }) + + it('should return an all-uppercase string when input is an mixed-case string with punctuation', () => { + expect(upperCaseConversion('mixeD-CaSe INPuT!')).toEqual( + 'MIXED-CASE INPUT!' + ) + }) +}) diff --git a/DIRECTORY.md b/DIRECTORY.md index cd8e304ee9..5e8e1f401a 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,133 +1,406 @@ - -## back-tracking - * [KnightTour](https://github.com/TheAlgorithms/Javascript/blob/master/back-tracking/KnightTour.js) - * [NQueen](https://github.com/TheAlgorithms/Javascript/blob/master/back-tracking/NQueen.js) - * [Sudoku](https://github.com/TheAlgorithms/Javascript/blob/master/back-tracking/Sudoku.js) - -## Cache - * [LFUCache](https://github.com/TheAlgorithms/Javascript/blob/master/Cache/LFUCache.js) - * [LRUCache](https://github.com/TheAlgorithms/Javascript/blob/master/Cache/LRUCache.js) - -## Ciphers - * [CaesarsCipher](https://github.com/TheAlgorithms/Javascript/blob/master/Ciphers/CaesarsCipher.js) - * [KeyFinder](https://github.com/TheAlgorithms/Javascript/blob/master/Ciphers/KeyFinder.js) - * [VigenereCipher](https://github.com/TheAlgorithms/Javascript/blob/master/Ciphers/VigenereCipher.js) - * [XORCipher](https://github.com/TheAlgorithms/Javascript/blob/master/Ciphers/XORCipher.js) - -## Conversions - * [BinaryToDecimal](https://github.com/TheAlgorithms/Javascript/blob/master/Conversions/BinaryToDecimal.js) - * [DecimalToBinary](https://github.com/TheAlgorithms/Javascript/blob/master/Conversions/DecimalToBinary.js) - * [DecimalToHex](https://github.com/TheAlgorithms/Javascript/blob/master/Conversions/DecimalToHex.js) - * [DecimalToOctal](https://github.com/TheAlgorithms/Javascript/blob/master/Conversions/DecimalToOctal.js) - * [HexToRGB](https://github.com/TheAlgorithms/Javascript/blob/master/Conversions/HexToRGB.js) - * [RomanToDecimal](https://github.com/TheAlgorithms/Javascript/blob/master/Conversions/RomanToDecimal.js) - -## Data-Structures - * Array - * [QuickSelect](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Array/QuickSelect.js) - * Graph - * [Graph](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Graph/Graph.js) - * [Graph2](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Graph/Graph2.js) - * Heap - * [MaxHeap](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Heap/MaxHeap.js) - * [MinPriorityQueue](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Heap/MinPriorityQueue.js) - * Linked-List - * [DoublyLinkedList](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Linked-List/DoublyLinkedList.js) - * [SinglyLinkList](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Linked-List/SinglyLinkList.js) - * Queue - * [Queue](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Queue/Queue.js) - * [QueueUsing2Stacks](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Queue/QueueUsing2Stacks.js) - * Stack - * [Stack](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Stack/Stack.js) - * [StackES6](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Stack/StackES6.js) - * Tree - * [BinarySearchTree](https://github.com/TheAlgorithms/Javascript/blob/master/Data-Structures/Tree/BinarySearchTree.js) - -## Dynamic-Programming - * [CoinChange](https://github.com/TheAlgorithms/Javascript/blob/master/Dynamic-Programming/CoinChange.js) - * [KadaneAlgo](https://github.com/TheAlgorithms/Javascript/blob/master/Dynamic-Programming/KadaneAlgo.js) - * [LevenshteinDistance](https://github.com/TheAlgorithms/Javascript/blob/master/Dynamic-Programming/LevenshteinDistance.js) - * [MaxNonAdjacentSum](https://github.com/TheAlgorithms/Javascript/blob/master/Dynamic-Programming/MaxNonAdjacentSum.js) - * [NumberOfSubsetEqualToGivenSum](https://github.com/TheAlgorithms/Javascript/blob/master/Dynamic-Programming/NumberOfSubsetEqualToGivenSum.js) - * [SieveOfEratosthenes](https://github.com/TheAlgorithms/Javascript/blob/master/Dynamic-Programming/SieveOfEratosthenes.js) - -## Graphs - * [ConnectedComponents](https://github.com/TheAlgorithms/Javascript/blob/master/Graphs/ConnectedComponents.js) - * [DepthFirstSearchIterative](https://github.com/TheAlgorithms/Javascript/blob/master/Graphs/DepthFirstSearchIterative.js) - * [DepthFirstSearchRecursive](https://github.com/TheAlgorithms/Javascript/blob/master/Graphs/DepthFirstSearchRecursive.js) - * [Dijkstra](https://github.com/TheAlgorithms/Javascript/blob/master/Graphs/Dijkstra.js) - * [DijkstraSmallestPath](https://github.com/TheAlgorithms/Javascript/blob/master/Graphs/DijkstraSmallestPath.js) - * [KruskalMST](https://github.com/TheAlgorithms/Javascript/blob/master/Graphs/KruskalMST.js) - * [PrimMST](https://github.com/TheAlgorithms/Javascript/blob/master/Graphs/PrimMST.js) - -## Hashes - * [SHA1](https://github.com/TheAlgorithms/Javascript/blob/master/Hashes/SHA1.js) - * [SHA256](https://github.com/TheAlgorithms/Javascript/blob/master/Hashes/SHA256.js) - -## Linear-Algebra - * src - * [la_lib](https://github.com/TheAlgorithms/Javascript/blob/master/Linear-Algebra/src/la_lib.js) - * test - * [test](https://github.com/TheAlgorithms/Javascript/blob/master/Linear-Algebra/test/test.js) - -## Maths - * [Abs](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/Abs.js) - * [AverageMean](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/AverageMean.js) - * [Factorial](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/Factorial.js) - * [Fibonacci](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/Fibonacci.js) - * [FindHcf](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/FindHcf.js) - * [FindLcm](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/FindLcm.js) - * [GridGet](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/GridGet.js) - * [Palindrome](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/Palindrome.js) - * [PascalTriangle](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/PascalTriangle.js) - * [PiApproximationMonteCarlo](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/PiApproximationMonteCarlo.js) - * [SieveOfEratosthenes](https://github.com/TheAlgorithms/Javascript/blob/master/Maths/SieveOfEratosthenes.js) - -## Recursive - * [EucledianGCD](https://github.com/TheAlgorithms/Javascript/blob/master/Recursive/EucledianGCD.js) - -## Search - * [BinarySearch](https://github.com/TheAlgorithms/Javascript/blob/master/Search/BinarySearch.js) - * [ExponentialSearch](https://github.com/TheAlgorithms/Javascript/blob/master/Search/ExponentialSearch.js) - * [InterpolationSearch](https://github.com/TheAlgorithms/Javascript/blob/master/Search/InterpolationSearch.js) - * [JumpSearch](https://github.com/TheAlgorithms/Javascript/blob/master/Search/JumpSearch.js) - * [LinearSearch](https://github.com/TheAlgorithms/Javascript/blob/master/Search/LinearSearch.js) - -## Sorts - * [BogoSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/BogoSort.js) - * [BubbleSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/BubbleSort.js) - * [BucketSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/BucketSort.js) - * [CocktailShakerSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/CocktailShakerSort.js) - * [CombSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/CombSort.js) - * [CountingSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/CountingSort.js) - * [CycleSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/CycleSort.js) - * [FlashSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/FlashSort.js) - * [GnomeSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/GnomeSort.js) - * [HeapSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/HeapSort.js) - * [HeapSortV2](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/HeapSortV2.js) - * [InsertionSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/InsertionSort.js) - * [IntroSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/IntroSort.js) - * [MergeSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/MergeSort.js) - * [QuickSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/QuickSort.js) - * [RadixSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/RadixSort.js) - * [SelectionSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/SelectionSort.js) - * [ShellSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/ShellSort.js) - * [TopologicalSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/TopologicalSort.js) - * [WiggleSort](https://github.com/TheAlgorithms/Javascript/blob/master/Sorts/WiggleSort.js) - -## String - * [CheckAnagram](https://github.com/TheAlgorithms/Javascript/blob/master/String/CheckAnagram.js) - * [CheckPalindrome](https://github.com/TheAlgorithms/Javascript/blob/master/String/CheckPalindrome.js) - * [PatternMatching](https://github.com/TheAlgorithms/Javascript/blob/master/String/PatternMatching.js) - * [ReverseString](https://github.com/TheAlgorithms/Javascript/blob/master/String/ReverseString.js) - * [ReverseWords](https://github.com/TheAlgorithms/Javascript/blob/master/String/ReverseWords.js) - -## TimingFunctions - * [IntervalTimer](https://github.com/TheAlgorithms/Javascript/blob/master/TimingFunctions/IntervalTimer.js) - -## Trees - * [DepthFirstSearch](https://github.com/TheAlgorithms/Javascript/blob/master/Trees/DepthFirstSearch.js) - -## Web-Programming - * [OpenWeatherMaps](https://github.com/TheAlgorithms/Javascript/blob/master/Web-Programming/OpenWeatherMaps.js) - * [StockPrice](https://github.com/TheAlgorithms/Javascript/blob/master/Web-Programming/StockPrice.js) +* **Backtracking** + * [AllCombinationsOfSizeK](Backtracking/AllCombinationsOfSizeK.js) + * [generateParentheses](Backtracking/generateParentheses.js) + * [GeneratePermutations](Backtracking/GeneratePermutations.js) + * [KnightTour](Backtracking/KnightTour.js) + * [MColoringProblem](Backtracking/MColoringProblem.js) + * [NQueens](Backtracking/NQueens.js) + * [RatInAMaze](Backtracking/RatInAMaze.js) + * [Sudoku](Backtracking/Sudoku.js) + * [SumOfSubset](Backtracking/SumOfSubset.js) +* **Bit-Manipulation** + * [BinaryCountSetBits](Bit-Manipulation/BinaryCountSetBits.js) + * [GenerateSubSets](Bit-Manipulation/GenerateSubSets.js) + * [GrayCodes](Bit-Manipulation/GrayCodes.js) + * [IsPowerofFour](Bit-Manipulation/IsPowerofFour.js) + * [IsPowerOfTwo](Bit-Manipulation/IsPowerOfTwo.js) + * [LogTwo](Bit-Manipulation/LogTwo.js) + * [NextPowerOfTwo](Bit-Manipulation/NextPowerOfTwo.js) + * [SetBit](Bit-Manipulation/SetBit.js) + * [UniqueElementInAnArray](Bit-Manipulation/UniqueElementInAnArray.js) +* **Cache** + * [LFUCache](Cache/LFUCache.js) + * [LRUCache](Cache/LRUCache.js) + * [Memoize](Cache/Memoize.js) +* **Cellular-Automata** + * [ConwaysGameOfLife](Cellular-Automata/ConwaysGameOfLife.js) + * [Elementary](Cellular-Automata/Elementary.js) +* **Ciphers** + * [AffineCipher](Ciphers/AffineCipher.js) + * [Atbash](Ciphers/Atbash.js) + * [CaesarCipher](Ciphers/CaesarCipher.js) + * [KeyFinder](Ciphers/KeyFinder.js) + * [KeywordShiftedAlphabet](Ciphers/KeywordShiftedAlphabet.js) + * [MorseCode](Ciphers/MorseCode.js) + * [ROT13](Ciphers/ROT13.js) + * [VigenereCipher](Ciphers/VigenereCipher.js) + * [XORCipher](Ciphers/XORCipher.js) +* **Compression** + * [RLE](Compression/RLE.js) +* **Conversions** + * [ArbitraryBase](Conversions/ArbitraryBase.js) + * [ArrayBufferToBase64](Conversions/ArrayBufferToBase64.js) + * [Base64ToArrayBuffer](Conversions/Base64ToArrayBuffer.js) + * [BinaryToDecimal](Conversions/BinaryToDecimal.js) + * [BinaryToHex](Conversions/BinaryToHex.js) + * [DateDayDifference](Conversions/DateDayDifference.js) + * [DateToDay](Conversions/DateToDay.js) + * [DecimalToBinary](Conversions/DecimalToBinary.js) + * [DecimalToHex](Conversions/DecimalToHex.js) + * [DecimalToOctal](Conversions/DecimalToOctal.js) + * [DecimalToRoman](Conversions/DecimalToRoman.js) + * [HexToBinary](Conversions/HexToBinary.js) + * [HexToDecimal](Conversions/HexToDecimal.js) + * [HexToRGB](Conversions/HexToRGB.js) + * [LengthConversion](Conversions/LengthConversion.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) + * [RgbHslConversion](Conversions/RgbHslConversion.js) + * [RgbHsvConversion](Conversions/RgbHsvConversion.js) + * [RGBToHex](Conversions/RGBToHex.js) + * [RomanToDecimal](Conversions/RomanToDecimal.js) + * [TemperatureConversion](Conversions/TemperatureConversion.js) + * [TitleCaseConversion](Conversions/TitleCaseConversion.js) + * [UpperCaseConversion](Conversions/UpperCaseConversion.js) +* **Data-Structures** + * **Array** + * [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) + * [Graph3](Data-Structures/Graph/Graph3.js) + * **Heap** + * [BinaryHeap](Data-Structures/Heap/BinaryHeap.js) + * [KeyPriorityQueue](Data-Structures/Heap/KeyPriorityQueue.js) + * [MinPriorityQueue](Data-Structures/Heap/MinPriorityQueue.js) + * **Linked-List** + * [AddTwoNumbers](Data-Structures/Linked-List/AddTwoNumbers.js) + * [CycleDetection](Data-Structures/Linked-List/CycleDetection.js) + * [CycleDetectionII](Data-Structures/Linked-List/CycleDetectionII.js) + * [DoublyLinkedList](Data-Structures/Linked-List/DoublyLinkedList.js) + * [MergeTwoSortedLinkedLists](Data-Structures/Linked-List/MergeTwoSortedLinkedLists.js) + * [ReverseSinglyLinkedList](Data-Structures/Linked-List/ReverseSinglyLinkedList.js) + * [SinglyCircularLinkedList](Data-Structures/Linked-List/SinglyCircularLinkedList.js) + * [SinglyLinkedList](Data-Structures/Linked-List/SinglyLinkedList.js) + * **Queue** + * [CircularQueue](Data-Structures/Queue/CircularQueue.js) + * [Queue](Data-Structures/Queue/Queue.js) + * [QueueUsing2Stacks](Data-Structures/Queue/QueueUsing2Stacks.js) + * **Stack** + * [EvaluateExpression](Data-Structures/Stack/EvaluateExpression.js) + * [Stack](Data-Structures/Stack/Stack.js) + * [StackES6](Data-Structures/Stack/StackES6.js) + * **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) +* **Dynamic-Programming** + * [Abbreviation](Dynamic-Programming/Abbreviation.js) + * [CatalanNumbers](Dynamic-Programming/CatalanNumbers.js) + * [ClimbingStairs](Dynamic-Programming/ClimbingStairs.js) + * [CoinChange](Dynamic-Programming/CoinChange.js) + * [EditDistance](Dynamic-Programming/EditDistance.js) + * [FastFibonacciNumber](Dynamic-Programming/FastFibonacciNumber.js) + * [FibonacciNumber](Dynamic-Programming/FibonacciNumber.js) + * [FindMonthCalendar](Dynamic-Programming/FindMonthCalendar.js) + * [KadaneAlgo](Dynamic-Programming/KadaneAlgo.js) + * [LevenshteinDistance](Dynamic-Programming/LevenshteinDistance.js) + * [LongestCommonSubsequence](Dynamic-Programming/LongestCommonSubsequence.js) + * [LongestIncreasingSubsequence](Dynamic-Programming/LongestIncreasingSubsequence.js) + * [LongestPalindromicSubsequence](Dynamic-Programming/LongestPalindromicSubsequence.js) + * [LongestValidParentheses](Dynamic-Programming/LongestValidParentheses.js) + * [MaxNonAdjacentSum](Dynamic-Programming/MaxNonAdjacentSum.js) + * [MaxProductOfThree](Dynamic-Programming/MaxProductOfThree.js) + * [MinimumCostPath](Dynamic-Programming/MinimumCostPath.js) + * [NumberOfSubsetEqualToGivenSum](Dynamic-Programming/NumberOfSubsetEqualToGivenSum.js) + * [RodCutting](Dynamic-Programming/RodCutting.js) + * [Shuf](Dynamic-Programming/Shuf.js) + * [SieveOfEratosthenes](Dynamic-Programming/SieveOfEratosthenes.js) + * **Sliding-Window** + * [HouseRobber](Dynamic-Programming/Sliding-Window/HouseRobber.js) + * [LongestSubstringWithoutRepeatingCharacters](Dynamic-Programming/Sliding-Window/LongestSubstringWithoutRepeatingCharacters.js) + * [MaxConsecutiveOnes](Dynamic-Programming/Sliding-Window/MaxConsecutiveOnes.js) + * [MaxConsecutiveOnesIII](Dynamic-Programming/Sliding-Window/MaxConsecutiveOnesIII.js) + * [PermutationinString](Dynamic-Programming/Sliding-Window/PermutationinString.js) + * [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) + * [Density](Graphs/Density.js) + * [DepthFirstSearchIterative](Graphs/DepthFirstSearchIterative.js) + * [DepthFirstSearchRecursive](Graphs/DepthFirstSearchRecursive.js) + * [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) +* **Hashes** + * [MD5](Hashes/MD5.js) + * [SHA1](Hashes/SHA1.js) + * [SHA256](Hashes/SHA256.js) +* **Maths** + * [Abs](Maths/Abs.js) + * [AliquotSum](Maths/AliquotSum.js) + * [Area](Maths/Area.js) + * [ArithmeticGeometricMean](Maths/ArithmeticGeometricMean.js) + * [ArmstrongNumber](Maths/ArmstrongNumber.js) + * [AutomorphicNumber](Maths/AutomorphicNumber.js) + * [AverageMean](Maths/AverageMean.js) + * [AverageMedian](Maths/AverageMedian.js) + * [BinaryConvert](Maths/BinaryConvert.js) + * [BinaryExponentiationIterative](Maths/BinaryExponentiationIterative.js) + * [BinaryExponentiationRecursive](Maths/BinaryExponentiationRecursive.js) + * [BinomialCoefficient](Maths/BinomialCoefficient.js) + * [BisectionMethod](Maths/BisectionMethod.js) + * [CheckKishnamurthyNumber](Maths/CheckKishnamurthyNumber.js) + * [CircularArc](Maths/CircularArc.js) + * [CollatzSequence](Maths/CollatzSequence.js) + * [Coordinate](Maths/Coordinate.js) + * [CoPrimeCheck](Maths/CoPrimeCheck.js) + * [CountNumbersDivisible](Maths/CountNumbersDivisible.js) + * [DecimalExpansion](Maths/DecimalExpansion.js) + * [DecimalIsolate](Maths/DecimalIsolate.js) + * [DegreeToRadian](Maths/DegreeToRadian.js) + * [Determinant](Maths/Determinant.js) + * [EuclideanDistance](Maths/EuclideanDistance.js) + * [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) + * [FareyApproximation](Maths/FareyApproximation.js) + * [FermatPrimalityTest](Maths/FermatPrimalityTest.js) + * [Fibonacci](Maths/Fibonacci.js) + * [FigurateNumber](Maths/FigurateNumber.js) + * [FindHcf](Maths/FindHcf.js) + * [FindLcm](Maths/FindLcm.js) + * [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) + * [isPalindromeIntegerNumber](Maths/isPalindromeIntegerNumber.js) + * [IsPronic](Maths/IsPronic.js) + * [IsSquareFree](Maths/IsSquareFree.js) + * [JugglerSequence](Maths/JugglerSequence.js) + * [LeapYear](Maths/LeapYear.js) + * [LinearSieve](Maths/LinearSieve.js) + * [LiouvilleFunction](Maths/LiouvilleFunction.js) + * [LucasSeries](Maths/LucasSeries.js) + * [Mandelbrot](Maths/Mandelbrot.js) + * [MatrixExponentiationRecursive](Maths/MatrixExponentiationRecursive.js) + * [MatrixMultiplication](Maths/MatrixMultiplication.js) + * [MeanAbsoluteDeviation](Maths/MeanAbsoluteDeviation.js) + * [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) + * [ParityOutlier](Maths/ParityOutlier.js) + * [PascalTriangle](Maths/PascalTriangle.js) + * [PerfectCube](Maths/PerfectCube.js) + * [PerfectNumber](Maths/PerfectNumber.js) + * [PerfectSquare](Maths/PerfectSquare.js) + * [PermutationAndCombination](Maths/PermutationAndCombination.js) + * [PiApproximationMonteCarlo](Maths/PiApproximationMonteCarlo.js) + * [Polynomial](Maths/Polynomial.js) + * [Pow](Maths/Pow.js) + * [PowLogarithmic](Maths/PowLogarithmic.js) + * [PrimeCheck](Maths/PrimeCheck.js) + * [PrimeFactors](Maths/PrimeFactors.js) + * [QuadraticRoots](Maths/QuadraticRoots.js) + * [RadianToDegree](Maths/RadianToDegree.js) + * [ReverseNumber](Maths/ReverseNumber.js) + * [ReversePolishNotation](Maths/ReversePolishNotation.js) + * [RowEchelon](Maths/RowEchelon.js) + * [ShorsAlgorithm](Maths/ShorsAlgorithm.js) + * [SieveOfEratosthenes](Maths/SieveOfEratosthenes.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) + * [TwoSum](Maths/TwoSum.js) + * [Volume](Maths/Volume.js) + * [WhileLoopFactorial](Maths/WhileLoopFactorial.js) + * [ZellersCongruenceAlgorithm](Maths/ZellersCongruenceAlgorithm.js) +* **Navigation** + * [Haversine](Navigation/Haversine.js) +* **Project-Euler** + * [Problem001](Project-Euler/Problem001.js) + * [Problem002](Project-Euler/Problem002.js) + * [Problem003](Project-Euler/Problem003.js) + * [Problem004](Project-Euler/Problem004.js) + * [Problem005](Project-Euler/Problem005.js) + * [Problem006](Project-Euler/Problem006.js) + * [Problem007](Project-Euler/Problem007.js) + * [Problem008](Project-Euler/Problem008.js) + * [Problem009](Project-Euler/Problem009.js) + * [Problem010](Project-Euler/Problem010.js) + * [Problem011](Project-Euler/Problem011.js) + * [Problem012](Project-Euler/Problem012.js) + * [Problem013](Project-Euler/Problem013.js) + * [Problem014](Project-Euler/Problem014.js) + * [Problem015](Project-Euler/Problem015.js) + * [Problem016](Project-Euler/Problem016.js) + * [Problem017](Project-Euler/Problem017.js) + * [Problem018](Project-Euler/Problem018.js) + * [Problem019](Project-Euler/Problem019.js) + * [Problem020](Project-Euler/Problem020.js) + * [Problem021](Project-Euler/Problem021.js) + * [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) + * [BinarySearch](Recursive/BinarySearch.js) + * [Factorial](Recursive/Factorial.js) + * [FibonacciNumberRecursive](Recursive/FibonacciNumberRecursive.js) + * [FloodFill](Recursive/FloodFill.js) + * [KochSnowflake](Recursive/KochSnowflake.js) + * [LetterCombination](Recursive/LetterCombination.js) + * [Palindrome](Recursive/Palindrome.js) + * [PalindromePartitioning](Recursive/PalindromePartitioning.js) + * [Partition](Recursive/Partition.js) + * [SubsequenceRecursive](Recursive/SubsequenceRecursive.js) + * [TowerOfHanoi](Recursive/TowerOfHanoi.js) +* **Search** + * [BinarySearch](Search/BinarySearch.js) + * [ExponentialSearch](Search/ExponentialSearch.js) + * [FibonacciSearch](Search/FibonacciSearch.js) + * [InterpolationSearch](Search/InterpolationSearch.js) + * [JumpSearch](Search/JumpSearch.js) + * [LinearSearch](Search/LinearSearch.js) + * [Minesweeper](Search/Minesweeper.js) + * [QuickSelectSearch](Search/QuickSelectSearch.js) + * [RabinKarp](Search/RabinKarp.js) + * [SlidingWindow](Search/SlidingWindow.js) + * [StringSearch](Search/StringSearch.js) + * [TernarySearch](Search/TernarySearch.js) + * [UnionFind](Search/UnionFind.js) +* **Sorts** + * [AlphaNumericalSort](Sorts/AlphaNumericalSort.js) + * [BeadSort](Sorts/BeadSort.js) + * [BinaryInsertionSort](Sorts/BinaryInsertionSort.js) + * [BogoSort](Sorts/BogoSort.js) + * [BubbleSort](Sorts/BubbleSort.js) + * [BucketSort](Sorts/BucketSort.js) + * [CocktailShakerSort](Sorts/CocktailShakerSort.js) + * [CombSort](Sorts/CombSort.js) + * [CountingSort](Sorts/CountingSort.js) + * [CycleSort](Sorts/CycleSort.js) + * [DutchNationalFlagSort](Sorts/DutchNationalFlagSort.js) + * [FindSecondLargestElement](Sorts/FindSecondLargestElement.js) + * [FisherYatesShuffle](Sorts/FisherYatesShuffle.js) + * [FlashSort](Sorts/FlashSort.js) + * [GnomeSort](Sorts/GnomeSort.js) + * [HeapSort](Sorts/HeapSort.js) + * [HeapSortV2](Sorts/HeapSortV2.js) + * [InsertionSort](Sorts/InsertionSort.js) + * [IntroSort](Sorts/IntroSort.js) + * [MergeSort](Sorts/MergeSort.js) + * [OddEvenSort](Sorts/OddEvenSort.js) + * [PancakeSort](Sorts/PancakeSort.js) + * [PigeonHoleSort](Sorts/PigeonHoleSort.js) + * [QuickSort](Sorts/QuickSort.js) + * [QuickSortRecursive](Sorts/QuickSortRecursive.js) + * [RadixSort](Sorts/RadixSort.js) + * [SelectionSort](Sorts/SelectionSort.js) + * [ShellSort](Sorts/ShellSort.js) + * [SimplifiedWiggleSort](Sorts/SimplifiedWiggleSort.js) + * [StoogeSort](Sorts/StoogeSort.js) + * [SwapSort](Sorts/SwapSort.js) + * [TimSort](Sorts/TimSort.js) + * [TopologicalSort](Sorts/TopologicalSort.js) +* **String** + * [AlphaNumericPalindrome](String/AlphaNumericPalindrome.js) + * [AlternativeStringArrange](String/AlternativeStringArrange.js) + * [BoyerMoore](String/BoyerMoore.js) + * [CheckAnagram](String/CheckAnagram.js) + * [CheckCamelCase](String/CheckCamelCase.js) + * [CheckExceeding](String/CheckExceeding.js) + * [CheckFlatCase](String/CheckFlatCase.js) + * [CheckKebabCase](String/CheckKebabCase.js) + * [CheckPalindrome](String/CheckPalindrome.js) + * [CheckPangram](String/CheckPangram.js) + * [CheckPascalCase](String/CheckPascalCase.js) + * [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) + * [DiceCoefficient](String/DiceCoefficient.js) + * [FirstUniqueCharacter](String/FirstUniqueCharacter.js) + * [FormatPhoneNumber](String/FormatPhoneNumber.js) + * [GenerateGUID](String/GenerateGUID.js) + * [HammingDistance](String/HammingDistance.js) + * [IsPalindrome](String/IsPalindrome.js) + * [KMPPatternSearching](String/KMPPatternSearching.js) + * [LengthofLongestSubstringWithoutRepetition](String/LengthofLongestSubstringWithoutRepetition.js) + * [LevenshteinDistance](String/LevenshteinDistance.js) + * [Lower](String/Lower.js) + * [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) + * [ScrambleStrings](String/ScrambleStrings.js) + * [Upper](String/Upper.js) + * [ValidateCreditCard](String/ValidateCreditCard.js) + * [ValidateEmail](String/ValidateEmail.js) + * [ZFunction](String/ZFunction.js) +* **Timing-Functions** + * [GetMonthDays](Timing-Functions/GetMonthDays.js) + * [IntervalTimer](Timing-Functions/IntervalTimer.js) + * [ParseDate](Timing-Functions/ParseDate.js) +* **Trees** + * [BreadthFirstTreeTraversal](Trees/BreadthFirstTreeTraversal.js) + * [DepthFirstSearch](Trees/DepthFirstSearch.js) + * [FenwickTree](Trees/FenwickTree.js) diff --git a/Data-Structures/Array/LocalMaximomPoint.js b/Data-Structures/Array/LocalMaximomPoint.js new file mode 100644 index 0000000000..f971c62814 --- /dev/null +++ b/Data-Structures/Array/LocalMaximomPoint.js @@ -0,0 +1,49 @@ +/** + * [LocalMaxima](https://www.geeksforgeeks.org/find-indices-of-all-local-maxima-and-local-minima-in-an-array/) is an algorithm to find relative bigger numbers compared to their neighbors + * + * Notes: + * - works by using divide and conquer + * - the function gets the array A with n Real numbers and returns the index of local max point (if more than one exists return the first one) + * + * @complexity: O(log(n)) (on average ) + * @complexity: O(log(n)) (worst case) + * @flow + */ +const findMaxPointIndex = ( + array, + rangeStartIndex, + rangeEndIndex, + originalLength +) => { + // find index range middle point + const middleIndex = + rangeStartIndex + parseInt((rangeEndIndex - rangeStartIndex) / 2) + + // handle array bounds + if ( + (middleIndex === 0 || array[middleIndex - 1] <= array[middleIndex]) && + (middleIndex === originalLength - 1 || + array[middleIndex + 1] <= array[middleIndex]) + ) { + return middleIndex + } else if (middleIndex > 0 && array[middleIndex - 1] > array[middleIndex]) { + return findMaxPointIndex( + array, + rangeStartIndex, + middleIndex - 1, + originalLength + ) + } else { + // regular local max + return findMaxPointIndex( + array, + middleIndex + 1, + rangeEndIndex, + originalLength + ) + } +} + +const LocalMaximomPoint = (A) => findMaxPointIndex(A, 0, A.length - 1, A.length) + +export { LocalMaximomPoint } diff --git a/Data-Structures/Array/NumberOfLocalMaximumPoints.js b/Data-Structures/Array/NumberOfLocalMaximumPoints.js new file mode 100644 index 0000000000..0df6a1d0ce --- /dev/null +++ b/Data-Structures/Array/NumberOfLocalMaximumPoints.js @@ -0,0 +1,45 @@ +/** + * [NumberOfLocalMaximumPoints](https://www.geeksforgeeks.org/find-indices-of-all-local-maxima-and-local-minima-in-an-array/) is an algorithm to find relative bigger numbers compared to their neighbors + * + * Notes: + * - like the other similar local maxima search function find relative maxima points in array but doesn't stop at one but returns total point count + * - runs on array A of size n and returns the local maxima count using divide and conquer methodology + * + * @complexity: O(n) (on average ) + * @complexity: O(n) (worst case) + * @flow + */ + +// check if returned index is a local maxima +const IsMaximumPoint = (array, index) => { + // handle array bounds + // array start + if (index === 0) { + return array[index] > array[index + 1] + // array end + } else if (index === array.length - 1) { + return array[index] > array[index - 1] + // handle index inside array bounds + } else { + return array[index] > array[index + 1] && array[index] > array[index - 1] + } +} + +const CountLocalMaximumPoints = (array, startIndex, endIndex) => { + // stop check in divide and conquer recursion + if (startIndex === endIndex) { + return IsMaximumPoint(array, startIndex) ? 1 : 0 + } + + // handle the two halves + const middleIndex = parseInt((startIndex + endIndex) / 2) + return ( + CountLocalMaximumPoints(array, startIndex, middleIndex) + + CountLocalMaximumPoints(array, middleIndex + 1, endIndex) + ) +} + +const NumberOfLocalMaximumPoints = (A) => + CountLocalMaximumPoints(A, 0, A.length - 1) + +export { NumberOfLocalMaximumPoints } diff --git a/Data-Structures/Array/QuickSelect.js b/Data-Structures/Array/QuickSelect.js index 7a90d2fcf4..53c8c8e855 100644 --- a/Data-Structures/Array/QuickSelect.js +++ b/Data-Structures/Array/QuickSelect.js @@ -1,9 +1,9 @@ /** - * QuickSelect is an algorithm to find the kth smallest number + * [QuickSelect](https://www.geeksforgeeks.org/quickselect-algorithm/) is an algorithm to find the kth smallest number * * Notes: * -QuickSelect is related to QuickSort, thus has optimal best and average - * case (O(n)) but unlikely poor worst case (O(n^2)) + * -case (O(n)) but unlikely poor worst case (O(n^2)) * -This implementation uses randomly selected pivots for better performance * * @complexity: O(n) (on average ) @@ -11,16 +11,15 @@ * @flow */ -function QuickSelect (items, kth) { +function QuickSelect(items, kth) { + if (kth < 1 || kth > items.length) { + throw new RangeError('Index Out of Bound') + } + return RandomizedSelect(items, 0, items.length - 1, kth) } -function RandomizedSelect ( - items, - left, - right, - i -) { +function RandomizedSelect(items, left, right, i) { if (left === right) return items[left] const pivotIndex = RandomizedPartition(items, left, right) @@ -32,13 +31,13 @@ function RandomizedSelect ( return RandomizedSelect(items, pivotIndex + 1, right, i - k) } -function RandomizedPartition (items, left, right) { +function RandomizedPartition(items, left, right) { const rand = getRandomInt(left, right) Swap(items, rand, right) return Partition(items, left, right) } -function Partition (items, left, right) { +function Partition(items, left, right) { const x = items[right] let pivotIndex = left - 1 @@ -54,13 +53,12 @@ function Partition (items, left, right) { return pivotIndex + 1 } -function getRandomInt (min, max) { +function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min } -function Swap (arr, x, y) { - [arr[x], arr[y]] = [arr[y], arr[x]] +function Swap(arr, x, y) { + ;[arr[x], arr[y]] = [arr[y], arr[x]] } -// testing -console.log(QuickSelect([1, 4, 2, -2, 4, 5])) +export { QuickSelect } diff --git a/Data-Structures/Array/Reverse.js b/Data-Structures/Array/Reverse.js new file mode 100644 index 0000000000..ca08f35f8a --- /dev/null +++ b/Data-Structures/Array/Reverse.js @@ -0,0 +1,15 @@ +/** 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--) + [arr[i], arr[j]] = [arr[j], arr[i]] + + return arr +} +export { Reverse } diff --git a/Data-Structures/Array/test/LocalMaximomPoint.test.js b/Data-Structures/Array/test/LocalMaximomPoint.test.js new file mode 100644 index 0000000000..db7d911949 --- /dev/null +++ b/Data-Structures/Array/test/LocalMaximomPoint.test.js @@ -0,0 +1,29 @@ +import { LocalMaximomPoint } from '../LocalMaximomPoint' + +describe('LocalMaximumPoint tests', () => { + it('test boundary maximum points - last element', () => { + const Array = [1, 2, 3, 4, 5, 6, 12] + expect(LocalMaximomPoint(Array)).toEqual(6) + }) + + it('test boundary maximum points - first element', () => { + const Array2 = [13, 6, 5, 4, 3, 2, 1] + expect(LocalMaximomPoint(Array2)).toEqual(0) + }) + + it('test boundary maximum points - should find first maximom point from the top', () => { + // Test a mix of number types (i.e., positive/negative, numbers with decimals, fractions) + const Array = [13, 2, 3, 4, 5, 6, 12] + expect(LocalMaximomPoint(Array)).toEqual(6) + }) + + it('test inner points - second element', () => { + const Array2 = [13, 16, 5, 4, 3, 2, 1] + expect(LocalMaximomPoint(Array2)).toEqual(1) + }) + + it('test inner points - element some where in the middle', () => { + const Array2 = [13, 16, 5, 41, 3, 2, 1] + expect(LocalMaximomPoint(Array2)).toEqual(3) + }) +}) diff --git a/Data-Structures/Array/test/NumberOfLocalMaximumPoints.test.js b/Data-Structures/Array/test/NumberOfLocalMaximumPoints.test.js new file mode 100644 index 0000000000..41e7b04b52 --- /dev/null +++ b/Data-Structures/Array/test/NumberOfLocalMaximumPoints.test.js @@ -0,0 +1,43 @@ +import { NumberOfLocalMaximumPoints } from '../NumberOfLocalMaximumPoints' + +describe('LocalMaximomPoint tests', () => { + it('test boundary maximum points - last element', () => { + const Array = [1, 2, 3, 4, 5, 6, 12] + expect(NumberOfLocalMaximumPoints(Array)).toEqual(1) + }) + + it('test boundary maximum points - first element', () => { + const Array = [13, 6, 5, 4, 3, 2, 1] + expect(NumberOfLocalMaximumPoints(Array)).toEqual(1) + }) + + it('test boundary maximum points - both boundaries have maximum points', () => { + // Test a mix of number types (i.e., positive/negative, numbers with decimals, fractions) + const Array = [13, 2, 3, 4, 5, 6, 12] + expect(NumberOfLocalMaximumPoints(Array)).toEqual(2) + }) + + it('multiple maximum points in the middle', () => { + // Test a mix of number types (i.e., positive/negative, numbers with decimals, fractions) + const Array = [1, 3, 2, 5, 6, 9, 2, 7, 12, 1, 0] + expect(NumberOfLocalMaximumPoints(Array)).toEqual(3) + }) + + it('multiple maximum points in the middle with one at end', () => { + // Test a mix of number types (i.e., positive/negative, numbers with decimals, fractions) + const Array = [1, 3, 2, 5, 6, 9, 2, 7, 12, 1, 10] + expect(NumberOfLocalMaximumPoints(Array)).toEqual(4) + }) + + it('multiple maximum points in the middle with one at start', () => { + // Test a mix of number types (i.e., positive/negative, numbers with decimals, fractions) + const Array = [10, 3, 2, 5, 6, 9, 2, 7, 12, 1, 0] + expect(NumberOfLocalMaximumPoints(Array)).toEqual(3) + }) + + it('multiple maximum points in the middle with two more at both ends', () => { + // Test a mix of number types (i.e., positive/negative, numbers with decimals, fractions) + const Array = [10, 3, 11, 5, 6, 9, 2, 7, 12, 1, 10] + expect(NumberOfLocalMaximumPoints(Array)).toEqual(5) + }) +}) diff --git a/Data-Structures/Array/test/QuickSelect.test.js b/Data-Structures/Array/test/QuickSelect.test.js new file mode 100644 index 0000000000..1af373e7ce --- /dev/null +++ b/Data-Structures/Array/test/QuickSelect.test.js @@ -0,0 +1,49 @@ +import { QuickSelect } from '../QuickSelect' + +describe('QuickSelect tests', () => { + it('should return the only element of a list of length 1', () => { + // Test a mix of number types (i.e., positive/negative, numbers with decimals, fractions) + expect(QuickSelect([100], 1)).toEqual(100) + expect(QuickSelect([-23], 1)).toEqual(-23) + expect(QuickSelect([2007.102], 1)).toEqual(2007.102) + expect(QuickSelect([0.9], 1)).toEqual(0.9) + expect(QuickSelect([-0.075], 1)).toEqual(-0.075) + expect(QuickSelect([0], 1)).toEqual(0) + expect(QuickSelect([1], 1)).toEqual(1) + }) + + it('should throw an Error when k is greater than the length of the list', () => { + expect(() => QuickSelect([100, 2], 5)).toThrow('Index Out of Bound') + }) + + it('should throw an Error when k is less than 1', () => { + expect(() => QuickSelect([100, 2], 0)).toThrow('Index Out of Bound') + expect(() => QuickSelect([100, 2], -1)).toThrow('Index Out of Bound') + }) + + describe('varieties of list composition', () => { + it('should return the kth smallest element of a list that is in increasing order', () => { + expect(QuickSelect([10, 22, 33, 44, 55], 1)).toEqual(10) + expect(QuickSelect([10, 22, 33, 44, 55], 2)).toEqual(22) + expect(QuickSelect([10, 22, 33, 44, 55], 3)).toEqual(33) + expect(QuickSelect([10, 22, 33, 44, 55], 4)).toEqual(44) + expect(QuickSelect([10, 22, 33, 44, 55], 5)).toEqual(55) + }) + + it('should return the kth smallest element of an input list that is in decreasing order', () => { + expect(QuickSelect([82, 33.12, 4.0, 1], 1)).toEqual(1) + expect(QuickSelect([82, 33.12, 4.0, 1], 2)).toEqual(4.0) + expect(QuickSelect([82, 33.12, 4.0, 1], 2)).toEqual(4) + expect(QuickSelect([82, 33.12, 4.0, 1], 3)).toEqual(33.12) + expect(QuickSelect([82, 33.12, 4.0, 1], 4)).toEqual(82) + }) + + it('should return the kth smallest element of an input list that is no particular order', () => { + expect(QuickSelect([123, 14231, -10, 0, 15], 3)).toEqual(15) + expect(QuickSelect([0, 15, 123, 14231, -10], 3)).toEqual(15) + expect(QuickSelect([-10, 15, 123, 14231, 0], 3)).toEqual(15) + expect(QuickSelect([14231, 0, 15, 123, -10], 3)).toEqual(15) + expect(QuickSelect([14231, 0, 15, -10, 123], 3)).toEqual(15) + }) + }) +}) diff --git a/Data-Structures/Array/test/Reverse.test.js b/Data-Structures/Array/test/Reverse.test.js new file mode 100644 index 0000000000..7b3344a096 --- /dev/null +++ b/Data-Structures/Array/test/Reverse.test.js @@ -0,0 +1,14 @@ +import { Reverse } from '../Reverse.js' + +describe('reverse elements in an array', () => { + it.each([ + [[], []], + [[1], [1]], + [ + [1, 2, 3, 4], + [4, 3, 2, 1] + ] + ])('returns %j when given %j', (array, expected) => { + expect(Reverse(array)).toEqual(expected) + }) +}) diff --git a/Data-Structures/Graph/Graph.js b/Data-Structures/Graph/Graph.js index 503d9b4981..d5764e4f9f 100644 --- a/Data-Structures/Graph/Graph.js +++ b/Data-Structures/Graph/Graph.js @@ -1,60 +1,74 @@ class Graph { - constructor () { + constructor() { this.adjacencyMap = {} } - addVertex (v) { - this.adjacencyMap[v] = [] + addVertex(vertex) { + this.adjacencyMap[vertex] = [] } - containsVertex (vertex) { - return typeof (this.adjacencyMap[vertex]) !== 'undefined' + containsVertex(vertex) { + return typeof this.adjacencyMap[vertex] !== 'undefined' } - addEdge (v, w) { - let result = false - if (this.containsVertex(v) && this.containsVertex(w)) { - this.adjacencyMap[v].push(w) - this.adjacencyMap[w].push(v) - result = true + addEdge(vertex1, vertex2) { + if (this.containsVertex(vertex1) && this.containsVertex(vertex2)) { + this.adjacencyMap[vertex1].push(vertex2) + this.adjacencyMap[vertex2].push(vertex1) } - return result } - printGraph () { + printGraph(output = (value) => console.log(value)) { const keys = Object.keys(this.adjacencyMap) for (const i of keys) { const values = this.adjacencyMap[i] let vertex = '' - for (const j of values) { vertex += j + ' ' } - console.log(i + ' -> ' + vertex) + for (const j of values) { + vertex += j + ' ' + } + output(i + ' -> ' + vertex) } } /** * Prints the Breadth first traversal of the graph from source. - * * @param {number} source The source vertex to start BFS. */ - bfs (source) { - const queue = [] + bfs(source, output = (value) => console.log(value)) { + const queue = [[source, 0]] // level of source is 0 const visited = new Set() - queue.unshift([source, 0]) // level of source is 0 - visited.add(source) + while (queue.length) { - const front = queue[0] - const node = front[0] - const level = front[1] - queue.shift() // remove the front of the queue - console.log(`Visited node ${node} at level ${level}.`) + const [node, level] = queue.shift() // remove the front of the queue + if (visited.has(node)) { + // visited + continue + } + + visited.add(node) + output(`Visited node ${node} at level ${level}.`) for (const next of this.adjacencyMap[node]) { - if (!visited.has(next)) { // not visited - queue.unshift([next, level + 1]) // level 1 more than current - visited.add(next) - } + queue.push([next, level + 1]) // level 1 more than current } } } + + /** + * Prints the Depth first traversal of the graph from source. + * @param {number} source The source vertex to start DFS. + */ + dfs(source, visited = new Set(), output = (value) => console.log(value)) { + if (visited.has(source)) { + // visited + return + } + + output(`Visited node ${source}`) + visited.add(source) + for (const neighbour of this.adjacencyMap[source]) { + this.dfs(neighbour, visited, output) + } + } } const example = () => { @@ -68,11 +82,22 @@ const example = () => { g.addEdge(1, 3) g.addEdge(2, 4) g.addEdge(2, 5) - console.log('Printing the adjacency list:\n') - g.printGraph() - // perform a breadth first search - console.log('\nBreadth first search at node 1:\n') + // Graph + // 1 -> 2 3 + // 2 -> 1 4 5 + // 3 -> 1 + // 4 -> 2 + // 5 -> 2 + + // Printing the adjacency list + // g.printGraph() + + // Breadth first search at node 1 g.bfs(1) + + // Depth first search at node 1 + g.dfs(1) } -example() + +export { Graph, example } diff --git a/Data-Structures/Graph/Graph2.js b/Data-Structures/Graph/Graph2.js index 3d98737591..645bf5294b 100644 --- a/Data-Structures/Graph/Graph2.js +++ b/Data-Structures/Graph/Graph2.js @@ -2,7 +2,7 @@ class Graph { // defining vertex array and // adjacent list - constructor (noOfVertices) { + constructor(noOfVertices) { this.noOfVertices = noOfVertices this.AdjList = new Map() } @@ -17,7 +17,7 @@ class Graph { // dfs(v) // add vertex to the graph - addVertex (v) { + addVertex(v) { // initialize the adjacent list with a // null array @@ -25,7 +25,7 @@ class Graph { } // add edge to the graph - addEdge (v, w) { + addEdge(v, w) { // get the list for vertex v and put the // vertex w denoting edge between v and w this.AdjList.get(v).push(w) @@ -36,13 +36,13 @@ class Graph { } // Prints the vertex and adjacency list - printGraph () { + printGraph(output = (value) => console.log(value)) { // get all the vertices const getKeys = this.AdjList.keys() // iterate over the vertices for (const i of getKeys) { - // great the corresponding adjacency list + // get the corresponding adjacency list // for the vertex const getValues = this.AdjList.get(i) let conc = '' @@ -54,35 +54,9 @@ class Graph { } // print the vertex and its adjacency list - console.log(i + ' -> ' + conc) + output(i + ' -> ' + conc) } } } -// Example -const graph = new Graph(6) -const vertices = ['A', 'B', 'C', 'D', 'E', 'F'] -// adding vertices -for (let i = 0; i < vertices.length; i++) { - graph.addVertex(vertices[i]) -} - -// adding edges -graph.addEdge('A', 'B') -graph.addEdge('A', 'D') -graph.addEdge('A', 'E') -graph.addEdge('B', 'C') -graph.addEdge('D', 'E') -graph.addEdge('E', 'F') -graph.addEdge('E', 'C') -graph.addEdge('C', 'F') - -// prints all vertex and -// its adjacency list -// A -> B D E -// B -> A C -// C -> B E F -// D -> A E -// E -> A D F C -// F -> E C -graph.printGraph() +export { Graph } diff --git a/Data-Structures/Graph/Graph3.js b/Data-Structures/Graph/Graph3.js new file mode 100644 index 0000000000..894a892bdd --- /dev/null +++ b/Data-Structures/Graph/Graph3.js @@ -0,0 +1,108 @@ +class Graph { + constructor() { + this.adjacencyObject = {} + } + + addVertex(vertex) { + if (!this.adjacencyObject[vertex]) this.adjacencyObject[vertex] = [] + } + + addEdge(vertex1, vertex2) { + this.adjacencyObject[vertex1].push(vertex2) + this.adjacencyObject[vertex2].push(vertex1) + } + + removeEdge(vertex1, vertex2) { + this.adjacencyObject[vertex1] = this.adjacencyObject[vertex1].filter( + (v) => v !== vertex2 + ) + this.adjacencyObject[vertex2] = this.adjacencyObject[vertex2].filter( + (v) => v !== vertex1 + ) + } + + removeVertex(vertex) { + while (this.adjacencyObject[vertex].length) { + const adjacentVertex = this.adjacencyObject[vertex].pop() + this.removeEdge(vertex, adjacentVertex) + } + } + + /** + * Return DFS (Depth First Search) List Using Recursive Method + */ + DFS(start) { + if (!start) return null + + const result = [] + const visited = {} + const adjacencyObject = this.adjacencyObject + + function dfs(vertex) { + if (!vertex) return null + visited[vertex] = true + result.push(vertex) + adjacencyObject[vertex].forEach((neighbor) => { + if (!visited[neighbor]) { + dfs(neighbor) + } + }) + } + + dfs(start) + return result + } + + /** + * Return DFS(Depth First Search) List Using Iteration + */ + DFSIterative(start) { + if (!start) return null + + const stack = [start] + const visited = {} + visited[start] = true + + const result = [] + let currentVertex + + while (stack.length) { + currentVertex = stack.pop() + result.push(currentVertex) + + this.adjacencyObject[currentVertex].forEach((neighbor) => { + if (!visited[neighbor]) { + visited[neighbor] = true + stack.push(neighbor) + } + }) + } + return result + } + + BFS(start) { + if (!start) return null + + const queue = [start] + const visited = {} + visited[start] = true + + let currentVertex + const result = [] + + while (queue.length) { + currentVertex = queue.shift() + result.push(currentVertex) + + this.adjacencyObject[currentVertex].forEach((neighbor) => { + if (!visited[neighbor]) { + visited[neighbor] = true + queue.push(neighbor) + } + }) + } + return result + } +} + +export { Graph } diff --git a/Data-Structures/Graph/test/Graph2.test.js b/Data-Structures/Graph/test/Graph2.test.js new file mode 100644 index 0000000000..0bbd3caddb --- /dev/null +++ b/Data-Structures/Graph/test/Graph2.test.js @@ -0,0 +1,39 @@ +import { Graph } from '../Graph2' + +describe('Test Graph2', () => { + const vertices = ['A', 'B', 'C', 'D', 'E', 'F'] + const graph = new Graph(vertices.length) + + // adding vertices + vertices.forEach((vertice) => graph.addVertex(vertice)) + + // adding edges + graph.addEdge('A', 'B') + graph.addEdge('A', 'D') + graph.addEdge('A', 'E') + graph.addEdge('B', 'C') + graph.addEdge('D', 'E') + graph.addEdge('E', 'F') + graph.addEdge('E', 'C') + graph.addEdge('C', 'F') + + it('Check adjacency lists', () => { + const mockFn = vi.fn() + graph.printGraph(mockFn) + + // Expect one call per vertex + expect(mockFn.mock.calls.length).toBe(vertices.length) + + // Collect adjacency lists from output (call args) + const adjListArr = mockFn.mock.calls.map((v) => v[0]) + + expect(adjListArr).toEqual([ + 'A -> B D E ', + 'B -> A C ', + 'C -> B E F ', + 'D -> A E ', + 'E -> A D F C ', + 'F -> E C ' + ]) + }) +}) diff --git a/Data-Structures/Graph/test/Graph3.test.js b/Data-Structures/Graph/test/Graph3.test.js new file mode 100644 index 0000000000..d10cc8da2b --- /dev/null +++ b/Data-Structures/Graph/test/Graph3.test.js @@ -0,0 +1,75 @@ +import { Graph } from '../Graph3' + +describe('Test Graph3', () => { + const g = new Graph() + + // Add Vertices + g.addVertex('A') + g.addVertex('B') + g.addVertex('C') + g.addVertex('D') + g.addVertex('E') + g.addVertex('F') + + // Add Edges + g.addEdge('A', 'B') + g.addEdge('A', 'C') + g.addEdge('B', 'D') + g.addEdge('C', 'E') + g.addEdge('D', 'E') + g.addEdge('D', 'F') + g.addEdge('E', 'F') + + /** + * A - B - D + * | / \ + * C - - E - F + * + * DFS(Iterative): A-C-E-F-D-B + * DFS(Recursive): A-B-D-E-C-F + * BFS: A-B-C-D-E-F + */ + it('Check iterative DFS List', () => { + const iterativeDFSList = g.DFSIterative('A') + expect(iterativeDFSList).toEqual(['A', 'C', 'E', 'F', 'D', 'B']) + }) + + it('Check recursive DFS List', () => { + const recursiveDFSList = g.DFS('A') + expect(recursiveDFSList).toEqual(['A', 'B', 'D', 'E', 'C', 'F']) + }) + + it('Check BFS List', () => { + const BFSList = g.BFS('A') + expect(BFSList).toEqual(['A', 'B', 'C', 'D', 'E', 'F']) + }) + + /** + * Test After Remove 'B' Vertex + * A D + * | / \ + * C - - E - F + * + * DFS(Iterative): A-C-E-F-D + * DFS(Recursive): A-C-E-D-F + * BFS: A-C-E-D-F + */ + + it('Check iterative DFS List After Removing Vertex B', () => { + g.removeVertex('B') + const iterativeDFSList = g.DFSIterative('A') + expect(iterativeDFSList).toEqual(['A', 'C', 'E', 'F', 'D']) + }) + + it('Check recursive DFS List After Removing Vertex B', () => { + g.removeVertex('B') + const recursiveDFSList = g.DFS('A') + expect(recursiveDFSList).toEqual(['A', 'C', 'E', 'D', 'F']) + }) + + it('Check BFS List After Removing Vertex B', () => { + g.removeVertex('B') + const BFSList = g.BFS('A') + expect(BFSList).toEqual(['A', 'C', 'E', 'D', 'F']) + }) +}) diff --git a/Data-Structures/Heap/BinaryHeap.js b/Data-Structures/Heap/BinaryHeap.js new file mode 100644 index 0000000000..af4fe3ba75 --- /dev/null +++ b/Data-Structures/Heap/BinaryHeap.js @@ -0,0 +1,151 @@ +/** + * BinaryHeap class represents a binary heap data structure that can be configured as a Min Heap or Max Heap. + * + * Binary heaps are binary trees that are filled level by level and from left to right inside each level. + * They have the property that any parent node has a smaller (for Min Heap) or greater (for Max Heap) priority + * than its children, ensuring that the root of the tree always holds the extremal value. + */ +class BinaryHeap { + /** + * Creates a new BinaryHeap instance. + * @constructor + * @param {Function} comparatorFunction - The comparator function used to determine the order of elements (e.g., minHeapComparator or maxHeapComparator). + */ + constructor(comparatorFunction) { + /** + * The heap array that stores elements. + * @member {Array} + */ + this.heap = [] + + /** + * The comparator function used for ordering elements in the heap. + * @member {Function} + */ + this.comparator = comparatorFunction + } + + /** + * Inserts a new value into the heap. + * @param {*} value - The value to be inserted into the heap. + */ + insert(value) { + this.heap.push(value) + this.#bubbleUp(this.heap.length - 1) + } + + /** + * Returns the number of elements in the heap. + * @returns {number} - The number of elements in the heap. + */ + size() { + return this.heap.length + } + + /** + * Checks if the heap is empty. + * @returns {boolean} - True if the heap is empty, false otherwise. + */ + empty() { + return this.size() === 0 + } + + /** + * Bubbles up a value from the specified index to maintain the heap property. + * @param {number} currIdx - The index of the value to be bubbled up. + * @private + */ + #bubbleUp(currIdx) { + let parentIdx = Math.floor((currIdx - 1) / 2) + + while ( + currIdx > 0 && + this.comparator(this.heap[currIdx], this.heap[parentIdx]) + ) { + this.#swap(currIdx, parentIdx) + currIdx = parentIdx + parentIdx = Math.floor((currIdx - 1) / 2) + } + } + + /** + * Sinks down a value from the specified index to maintain the heap property. + * @param {number} currIdx - The index of the value to be sunk down. + * @private + */ + #sinkDown(currIdx) { + let childOneIdx = currIdx * 2 + 1 + + while (childOneIdx < this.size()) { + const childTwoIdx = childOneIdx + 1 < this.size() ? childOneIdx + 1 : -1 + const swapIdx = + childTwoIdx !== -1 && + this.comparator(this.heap[childTwoIdx], this.heap[childOneIdx]) + ? childTwoIdx + : childOneIdx + + if (this.comparator(this.heap[swapIdx], this.heap[currIdx])) { + this.#swap(currIdx, swapIdx) + currIdx = swapIdx + childOneIdx = currIdx * 2 + 1 + } else { + return + } + } + } + + /** + * Retrieves the top element of the heap without removing it. + * @returns {*} - The top element of the heap. + */ + peek() { + return this.heap[0] + } + + /** + * Removes and returns the top element of the heap. + * @returns {*} - The top element of the heap. + */ + extractTop() { + const top = this.peek() + const last = this.heap.pop() + + if (!this.empty()) { + this.heap[0] = last + this.#sinkDown(0) + } + + return top + } + + /** + * Swaps elements at two specified indices in the heap. + * @param {number} index1 - The index of the first element to be swapped. + * @param {number} index2 - The index of the second element to be swapped. + * @private + */ + #swap(index1, index2) { + ;[this.heap[index1], this.heap[index2]] = [ + this.heap[index2], + this.heap[index1] + ] + } +} + +/** + * Comparator function for creating a Min Heap. + * @param {*} a - The first element to compare. + * @param {*} b - The second element to compare. + * @returns {boolean} - True if 'a' should have higher priority than 'b' in the Min Heap, false otherwise. + */ +const minHeapComparator = (a, b) => a < b + +/** + * Comparator function for creating a Max Heap. + * @param {*} a - The first element to compare. + * @param {*} b - The second element to compare. + * @returns {boolean} - True if 'a' should have higher priority than 'b' in the Max Heap, false otherwise. + */ +const maxHeapComparator = (a, b) => a > b + +export { BinaryHeap, minHeapComparator, maxHeapComparator } diff --git a/Data-Structures/Heap/KeyPriorityQueue.js b/Data-Structures/Heap/KeyPriorityQueue.js new file mode 100644 index 0000000000..420933396e --- /dev/null +++ b/Data-Structures/Heap/KeyPriorityQueue.js @@ -0,0 +1,157 @@ +/** + * KeyPriorityQueue is a priority queue based on a Minimum Binary Heap. + * + * Minimum Binary Heaps are binary trees which are filled level by level + * and then from left to right inside a depth level. + * Their main property is that any parent node has a smaller or equal priority to all of its children, + * hence the root of the tree always has the smallest priority of all nodes. + * + * This implementation of the Minimum Binary Heap allows for nodes to be associated to both a key, + * which can be any datatype, and a priority. + * + * The heap is represented by an array with nodes ordered + * from root-to-leaf, left-to-right. + * Therefore, the parent-child node relationship is such that + * * the children nodes positions relative to their parent are: (parentPos * 2 + 1) and (parentPos * 2 + 2) + * * the parent node position relative to either of its children is: Math.floor((childPos - 1) / 2) + * + * More information and visuals on Binary Heaps can be found here: https://www.geeksforgeeks.org/binary-heap/ + */ + +// Priority Queue Helper functions +const getParentPosition = (position) => Math.floor((position - 1) / 2) +const getChildrenPositions = (position) => [2 * position + 1, 2 * position + 2] + +class KeyPriorityQueue { + // Priority Queue class using Minimum Binary Heap + constructor() { + this._heap = [] + this.priorities = new Map() + } + + /** + * Checks if the heap is empty + * @returns boolean + */ + isEmpty() { + return this._heap.length === 0 + } + + /** + * Adds an element to the queue + * @param {*} key + * @param {number} priority + */ + push(key, priority) { + this._heap.push(key) + this.priorities.set(key, priority) + this._shiftUp(this._heap.length - 1) + } + + /** + * Removes the element with least priority + * @returns the key of the element with least priority + */ + pop() { + this._swap(0, this._heap.length - 1) + const key = this._heap.pop() + this.priorities.delete(key) + this._shiftDown(0) + return key + } + + /** + * Checks whether a given key is present in the queue + * @param {*} key + * @returns boolean + */ + contains(key) { + return this.priorities.has(key) + } + + /** + * Updates the priority of the given element. + * Adds the element if it is not in the queue. + * @param {*} key the element to change + * @param {number} priority new priority of the element + */ + update(key, priority) { + const currPos = this._heap.indexOf(key) + // if the key does not exist yet, add it + if (currPos === -1) return this.push(key, priority) + // else update priority + this.priorities.set(key, priority) + const parentPos = getParentPosition(currPos) + const currPriority = this._getPriorityOrInfinite(currPos) + const parentPriority = this._getPriorityOrInfinite(parentPos) + const [child1Pos, child2Pos] = getChildrenPositions(currPos) + const child1Priority = this._getPriorityOrInfinite(child1Pos) + const child2Priority = this._getPriorityOrInfinite(child2Pos) + + if (parentPos >= 0 && parentPriority > currPriority) { + this._shiftUp(currPos) + } else if (child1Priority < currPriority || child2Priority < currPriority) { + this._shiftDown(currPos) + } + } + + _getPriorityOrInfinite(position) { + // Helper function, returns priority of the node, or Infinite if no node corresponds to this position + if (position >= 0 && position < this._heap.length) + return this.priorities.get(this._heap[position]) + else return Infinity + } + + _shiftUp(position) { + // Helper function to shift up a node to proper position (equivalent to bubbleUp) + let currPos = position + let parentPos = getParentPosition(currPos) + let currPriority = this._getPriorityOrInfinite(currPos) + let parentPriority = this._getPriorityOrInfinite(parentPos) + + while (parentPos >= 0 && parentPriority > currPriority) { + this._swap(currPos, parentPos) + currPos = parentPos + parentPos = getParentPosition(currPos) + currPriority = this._getPriorityOrInfinite(currPos) + parentPriority = this._getPriorityOrInfinite(parentPos) + } + } + + _shiftDown(position) { + // Helper function to shift down a node to proper position (equivalent to bubbleDown) + let currPos = position + let [child1Pos, child2Pos] = getChildrenPositions(currPos) + let child1Priority = this._getPriorityOrInfinite(child1Pos) + let child2Priority = this._getPriorityOrInfinite(child2Pos) + let currPriority = this._getPriorityOrInfinite(currPos) + + if (currPriority === Infinity) { + return + } + + while (child1Priority < currPriority || child2Priority < currPriority) { + if (child1Priority < currPriority && child1Priority < child2Priority) { + this._swap(child1Pos, currPos) + currPos = child1Pos + } else { + this._swap(child2Pos, currPos) + currPos = child2Pos + } + ;[child1Pos, child2Pos] = getChildrenPositions(currPos) + child1Priority = this._getPriorityOrInfinite(child1Pos) + child2Priority = this._getPriorityOrInfinite(child2Pos) + currPriority = this._getPriorityOrInfinite(currPos) + } + } + + _swap(position1, position2) { + // Helper function to swap 2 nodes + ;[this._heap[position1], this._heap[position2]] = [ + this._heap[position2], + this._heap[position1] + ] + } +} + +export { KeyPriorityQueue } diff --git a/Data-Structures/Heap/MaxHeap.js b/Data-Structures/Heap/MaxHeap.js deleted file mode 100644 index d6d849cbde..0000000000 --- a/Data-Structures/Heap/MaxHeap.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Author: Samarth Jain - * Max Heap implementation in Javascript - */ - -class BinaryHeap { - constructor () { - this.heap = [] - } - - insert (value) { - this.heap.push(value) - this.heapify() - } - - size () { - return this.heap.length - } - - empty () { - return this.size() === 0 - } - - // using iterative approach to reorder the heap after insertion - heapify () { - let index = this.size() - 1 - - while (index > 0) { - const element = this.heap[index] - const parentIndex = Math.floor((index - 1) / 2) - const parent = this.heap[parentIndex] - - if (parent[0] >= element[0]) break - this.heap[index] = parent - this.heap[parentIndex] = element - index = parentIndex - } - } - - // Extracting the maximum element from the Heap - extractMax () { - const max = this.heap[0] - const tmp = this.heap.pop() - if (!this.empty()) { - this.heap[0] = tmp - this.sinkDown(0) - } - return max - } - - // To restore the balance of the heap after extraction. - sinkDown (index) { - const left = 2 * index + 1 - const right = 2 * index + 2 - let largest = index - const length = this.size() - - if (left < length && this.heap[left][0] > this.heap[largest][0]) { - largest = left - } - if (right < length && this.heap[right][0] > this.heap[largest][0]) { - largest = right - } - // swap - if (largest !== index) { - const tmp = this.heap[largest] - this.heap[largest] = this.heap[index] - this.heap[index] = tmp - this.sinkDown(largest) - } - } -} - -const maxHeap = new BinaryHeap() -maxHeap.insert([4]) -maxHeap.insert([3]) -maxHeap.insert([6]) -maxHeap.insert([1]) -maxHeap.insert([8]) -maxHeap.insert([2]) - -while (!maxHeap.empty()) { - const mx = maxHeap.extractMax() - console.log(mx) -} diff --git a/Data-Structures/Heap/MinPriorityQueue.js b/Data-Structures/Heap/MinPriorityQueue.js index 0b52ca65dc..9d76c7aefd 100644 --- a/Data-Structures/Heap/MinPriorityQueue.js +++ b/Data-Structures/Heap/MinPriorityQueue.js @@ -1,19 +1,18 @@ - /* Minimum Priority Queue -* It is a part of heap data structure -* A heap is a specific tree based data structure -* in which all the nodes of tree are in a specific order. -* that is the children are arranged in some -* respect of their parents, can either be greater -* or less than the parent. This makes it a min priority queue -* or max priority queue. -*/ + * It is a part of heap data structure + * A heap is a specific tree based data structure + * in which all the nodes of tree are in a specific order. + * that is the children are arranged in some + * respect of their parents, can either be greater + * or less than the parent. This makes it a min priority queue + * or max priority queue. + */ // Functions: insert, delete, peek, isEmpty, print, heapSort, sink class MinPriorityQueue { // calls the constructor and initializes the capacity - constructor (c) { + constructor(c) { this.heap = [] this.capacity = c this.size = 0 @@ -21,7 +20,7 @@ class MinPriorityQueue { // inserts the key at the end and rearranges it // so that the binary heap is in appropriate order - insert (key) { + insert(key) { if (this.isFull()) return this.heap[this.size + 1] = key let k = this.size + 1 @@ -37,37 +36,48 @@ class MinPriorityQueue { } // returns the highest priority value - peek () { + peek() { return this.heap[1] } // returns boolean value whether the heap is empty or not - isEmpty () { + isEmpty() { return this.size === 0 } // returns boolean value whether the heap is full or not - isFull () { - if (this.size === this.capacity) return true - return false + isFull() { + return this.size === this.capacity } // prints the heap - print () { - console.log(this.heap.slice(1)) + print(output = (value) => console.log(value)) { + output(this.heap.slice(1)) } - // heap sorting can be done by performing - // delete function to the number of times of the size of the heap - // it returns reverse sort because it is a min priority queue - heapSort () { - for (let i = 1; i < this.capacity; i++) { - this.delete() + // heap reverse can be done by performing swapping the first + // element with the last, removing the last element to + // new array and calling sink function. + heapReverse() { + const heapSort = [] + while (this.size > 0) { + // swap first element with last element + ;[this.heap[1], this.heap[this.size]] = [ + this.heap[this.size], + this.heap[1] + ] + heapSort.push(this.heap.pop()) + this.size-- + this.sink() } + // first value from heap it's empty to respect + // structure with 1 as index of the first element + this.heap = [undefined, ...heapSort.reverse()] + this.size = heapSort.length } // this function reorders the heap after every delete function - sink () { + sink() { let k = 1 while (2 * k <= this.size || 2 * k + 1 <= this.size) { let minIndex @@ -85,8 +95,7 @@ class MinPriorityQueue { this.heap[k] > this.heap[2 * k] || this.heap[k] > this.heap[2 * k + 1] ) { - minIndex = - this.heap[2 * k] < this.heap[2 * k + 1] ? 2 * k : 2 * k + 1 + minIndex = this.heap[2 * k] < this.heap[2 * k + 1] ? 2 * k : 2 * k + 1 } else { minIndex = k } @@ -98,28 +107,21 @@ class MinPriorityQueue { } } - // deletes the highest priority value from the heap - delete () { + // deletes the highest priority value from the heap. The last + // element goes to ahead to first position and reorder heap + delete() { + // checks empty and one element array conditions + if (this.isEmpty()) return + if (this.size === 1) { + this.size-- + return this.heap.pop() + } const min = this.heap[1] - this.heap[1] = this.heap[this.size] - this.heap[this.size] = min + this.heap[1] = this.heap.pop() this.size-- this.sink() return min } } -// testing -const q = new MinPriorityQueue(8) - -q.insert(5) -q.insert(2) -q.insert(4) -q.insert(1) -q.insert(7) -q.insert(6) -q.insert(3) -q.insert(8) -q.print() // [ 1, 2, 3, 5, 7, 6, 4, 8 ] -q.heapSort() -q.print() // [ 8, 7, 6, 5, 4, 3, 2, 1 ] +export { MinPriorityQueue } diff --git a/Data-Structures/Heap/test/BinaryHeap.test.js b/Data-Structures/Heap/test/BinaryHeap.test.js new file mode 100644 index 0000000000..ee9d5a0570 --- /dev/null +++ b/Data-Structures/Heap/test/BinaryHeap.test.js @@ -0,0 +1,71 @@ +import { BinaryHeap, minHeapComparator } from '../BinaryHeap' + +describe('BinaryHeap', () => { + describe('MinHeap', () => { + let minHeap + + beforeEach(() => { + // Initialize a MinHeap + minHeap = new BinaryHeap(minHeapComparator) + minHeap.insert(4) + minHeap.insert(3) + minHeap.insert(6) + minHeap.insert(1) + minHeap.insert(8) + minHeap.insert(2) + }) + + it('should initialize a heap from an input array', () => { + // Check if the heap is initialized correctly + expect(minHeap.heap).toEqual([1, 3, 2, 4, 8, 6]) + }) + + it('should show the top value in the heap', () => { + // Check if the top value is as expected + const minValue = minHeap.peek() + expect(minValue).toEqual(1) + }) + + it('should remove and return the top value in the heap', () => { + // Check if the top value is removed correctly + const minValue = minHeap.extractTop() + expect(minValue).toEqual(1) + expect(minHeap.heap).toEqual([2, 3, 6, 4, 8]) + }) + + it('should handle insertion of duplicate values', () => { + // Check if the heap handles duplicate values correctly + minHeap.insert(2) + expect(minHeap.heap).toEqual([1, 3, 2, 4, 8, 6, 2]) + }) + + it('should handle an empty heap', () => { + // Check if an empty heap behaves as expected + const emptyHeap = new BinaryHeap(minHeapComparator) + expect(emptyHeap.peek()).toBeUndefined() + expect(emptyHeap.extractTop()).toBeUndefined() + }) + + it('should handle extracting all elements from the heap', () => { + // Check if all elements can be extracted in the correct order + const extractedValues = [] + while (!minHeap.empty()) { + extractedValues.push(minHeap.extractTop()) + } + expect(extractedValues).toEqual([1, 2, 3, 4, 6, 8]) + }) + + it('should insert elements in ascending order', () => { + // Check if elements are inserted in ascending order + const ascendingHeap = new BinaryHeap(minHeapComparator) + ascendingHeap.insert(4) + ascendingHeap.insert(3) + ascendingHeap.insert(2) + ascendingHeap.insert(1) + expect(ascendingHeap.extractTop()).toEqual(1) + expect(ascendingHeap.extractTop()).toEqual(2) + expect(ascendingHeap.extractTop()).toEqual(3) + expect(ascendingHeap.extractTop()).toEqual(4) + }) + }) +}) diff --git a/Data-Structures/Heap/test/KeyPriorityQueue.test.js b/Data-Structures/Heap/test/KeyPriorityQueue.test.js new file mode 100644 index 0000000000..b9e9ea6202 --- /dev/null +++ b/Data-Structures/Heap/test/KeyPriorityQueue.test.js @@ -0,0 +1,126 @@ +import { KeyPriorityQueue } from '../KeyPriorityQueue.js' + +describe('Key Priority Queue', () => { + describe('Method isEmpty', () => { + test('Check heap is empty', () => { + const queue = new KeyPriorityQueue() + const res = queue.isEmpty() + expect(res).toEqual(true) + }) + + test('Check heap is not empty', () => { + const queue = new KeyPriorityQueue() + queue.push(0, 2) + const res = queue.isEmpty() + expect(res).toEqual(false) + }) + }) + + describe('Methods push and pop', () => { + test('Test Case 1', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 7) + queue.push(2, 9) + queue.push(3, 13) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(1, 7) + expectedQueue.push(3, 13) + expectedQueue.push(2, 9) + // result from popping element from the queue + queue.pop() + expect(queue).toEqual(expectedQueue) + }) + + test('Test Case 2', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 9) + queue.push(2, 7) + queue.push(3, 13) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(2, 7) + expectedQueue.push(1, 9) + expectedQueue.push(3, 13) + // result from popping element from the queue + queue.pop() + expect(queue).toEqual(expectedQueue) + }) + + test('Test Case 3', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 7) + queue.push(2, 9) + queue.push(3, 12) + queue.push(4, 13) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(1, 7) + expectedQueue.push(3, 12) + expectedQueue.push(2, 9) + expectedQueue.push(4, 13) + // result from popping element from the queue + queue.pop() + expect(queue).toEqual(expectedQueue) + }) + }) + + describe('Method contains', () => { + test('Check heap does not contain element', () => { + const queue = new KeyPriorityQueue() + const res = queue.contains(0) + expect(res).toEqual(false) + }) + + test('Check heap contains element', () => { + const queue = new KeyPriorityQueue() + queue.push(0, 2) + const res = queue.contains(0) + expect(res).toEqual(true) + }) + }) + + describe('Method update', () => { + test('Update without change in position', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 5) + queue.push(2, 7) + queue.push(3, 11) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(0, 2) + expectedQueue.push(1, 5) + expectedQueue.push(2, 7) + expectedQueue.push(3, 11) + // result from updating to similar priority + queue.update(0, 2) + expect(queue).toEqual(expectedQueue) + }) + + test('Update with change in position', () => { + // create queue + const queue = new KeyPriorityQueue() + queue.push(0, 3) + queue.push(1, 5) + queue.push(2, 7) + queue.push(3, 11) + // create expected queue + const expectedQueue = new KeyPriorityQueue() + expectedQueue.push(1, 5) + expectedQueue.push(3, 11) + expectedQueue.push(2, 7) + expectedQueue.push(0, 9) + // result from updating to similar priority + queue.update(0, 9) + expect(queue).toEqual(expectedQueue) + }) + }) +}) diff --git a/Data-Structures/Heap/test/MinPriorityQueue.test.js b/Data-Structures/Heap/test/MinPriorityQueue.test.js new file mode 100644 index 0000000000..31549a1626 --- /dev/null +++ b/Data-Structures/Heap/test/MinPriorityQueue.test.js @@ -0,0 +1,55 @@ +import { MinPriorityQueue } from '../MinPriorityQueue' + +describe('MinPriorityQueue', () => { + const values = [5, 2, 4, 1, 7, 6, 3, 8] + const capacity = values.length + let queue + + beforeEach(() => { + queue = new MinPriorityQueue(capacity) + values.forEach((v) => queue.insert(v)) + }) + + it('Check heap ordering', () => { + const mockFn = vi.fn() + queue.print(mockFn) + + expect(mockFn.mock.calls.length).toBe(1) // Expect one call + expect(mockFn.mock.calls[0].length).toBe(1) // Expect one argument + + const heap = mockFn.mock.calls[0][0] + expect(heap.length).toBe(capacity) + expect(heap).toStrictEqual([1, 2, 3, 5, 7, 6, 4, 8]) + }) + + it('heapSort() expected to reverse the heap ordering', () => { + queue.heapReverse() + const mockFn = vi.fn() + queue.print(mockFn) + + expect(mockFn.mock.calls.length).toBe(1) + expect(mockFn.mock.calls[0].length).toBe(1) + + const heap = mockFn.mock.calls[0][0] + expect(heap.length).toBe(capacity) + expect(heap).toStrictEqual([8, 7, 6, 5, 4, 3, 2, 1]) + }) + + describe('delete() function work properly', () => { + it('return undefined if heap is empty', () => { + const minqueue = new MinPriorityQueue(capacity) + const min = minqueue.delete() + expect(min).toBe(undefined) + }) + it('return min value and remove it', () => { + const sortedValues = values.sort() + let initialSize = queue.size + sortedValues.forEach((minValue, index) => { + const min = queue.delete() + expect(min).toBe(minValue) + expect(queue.size).toBe(--initialSize) + }) + expect(queue.size).toBe(0) + }) + }) +}) diff --git a/Data-Structures/Linked-List/AddTwoNumbers.js b/Data-Structures/Linked-List/AddTwoNumbers.js new file mode 100644 index 0000000000..b17c2e3fc4 --- /dev/null +++ b/Data-Structures/Linked-List/AddTwoNumbers.js @@ -0,0 +1,59 @@ +/** + * A LinkedList based solution for Add Two Numbers + * + */ +import { Node } from './SinglyLinkedList.js' + +/* +Problem Statement: +Given two non-empty linked lists representing two non-negative integers. +The digits are stored in reverse order and each of their nodes contain a single digit. +Add the two numbers and return it as a linked list. + +Link for the Problem: https://leetcode.com/problems/add-two-numbers/ +*/ + +class AddTwoNumbers { + constructor() { + this.dummyNode = new Node(0) + } + + solution(firstList, secondList) { + let firstRunner = firstList + let secondRunner = secondList + let tail = this.dummyNode + let carry = 0 + while (firstRunner != null || secondRunner != null) { + const firstNumber = firstRunner ? firstRunner.data : 0 + const secondNumber = secondRunner ? secondRunner.data : 0 + const sum = carry + firstNumber + secondNumber + carry = parseInt(sum / 10) + tail.next = new Node(sum % 10) + tail = tail.next + if (firstRunner) { + firstRunner = firstRunner.next + } + if (secondRunner) { + secondRunner = secondRunner.next + } + } + if (carry > 0) { + tail.next = new Node(carry % 10) + } + + return this.dummyNode.next + } + + solutionToArray() { + const list = [] + let currentNode = this.dummyNode.next + while (currentNode) { + list.push(currentNode.data) + currentNode = currentNode.next + } + + return list + } +} + +export { AddTwoNumbers } diff --git a/Data-Structures/Linked-List/CycleDetection.js b/Data-Structures/Linked-List/CycleDetection.js new file mode 100644 index 0000000000..007ace6560 --- /dev/null +++ b/Data-Structures/Linked-List/CycleDetection.js @@ -0,0 +1,28 @@ +/** + * A LinkedList based solution for Detecting a Cycle in a list. + * https://en.wikipedia.org/wiki/Cycle_detection + */ + +function detectCycle(head) { + /* + Problem Statement: + Given head, the head of a linked list, determine if the linked list has a cycle in it. + Link for the Problem: https://leetcode.com/problems/linked-list-cycle/ + */ + if (!head) { + return false + } + + let slow = head + let fast = head.next + while (fast && fast.next) { + if (fast === slow) { + return true + } + fast = fast.next.next + slow = slow.next + } + return false +} + +export { detectCycle } diff --git a/Data-Structures/Linked-List/CycleDetectionII.js b/Data-Structures/Linked-List/CycleDetectionII.js new file mode 100644 index 0000000000..478586e6eb --- /dev/null +++ b/Data-Structures/Linked-List/CycleDetectionII.js @@ -0,0 +1,57 @@ +/** + * A LinkedList based solution for finding the starting node of the cycle in a list. + * @returns the node where cycle begins in the linked list. If there is no cycle present, returns null. + * @see https://en.wikipedia.org/wiki/Cycle_detection + * @see https://leetcode.com/problems/linked-list-cycle-ii/ + */ + +function findCycleStart(head) { + let length = 0 + let fast = head + let slow = head + + while (fast !== null && fast.next !== null) { + fast = fast.next.next + slow = slow.next + if (fast === slow) { + length = cycleLength(slow) + break + } + } + + if (length === 0) { + // If there is no cycle, return null. + return null + } + + let ahead = head + let behind = head + // Move slow pointer ahead 'length' of cycle times + while (length > 0) { + ahead = ahead.next + length-- + } + + // Now move both pointers until they meet - this will be the start of cycle + while (ahead !== behind) { + ahead = ahead.next + behind = behind.next + } + + // return the meeting node + return ahead +} + +// head is a node on a cycle +function cycleLength(head) { + // How long until we visit head again? + let cur = head + let len = 0 + do { + cur = cur.next + len++ + } while (cur != head) + return len +} + +export { findCycleStart } diff --git a/Data-Structures/Linked-List/DoublyLinkedList.js b/Data-Structures/Linked-List/DoublyLinkedList.js index fdd8ea7418..0d391181ac 100644 --- a/Data-Structures/Linked-List/DoublyLinkedList.js +++ b/Data-Structures/Linked-List/DoublyLinkedList.js @@ -1,54 +1,57 @@ -// Hamza chabchoub contribution for a university project -function DoubleLinkedList () { - const Node = function (element) { +class Node { + constructor(element) { this.element = element this.next = null this.prev = null } +} - let length = 0 - let head = null - let tail = null +class DoubleLinkedList { + constructor() { + this.length = 0 + this.head = null + this.tail = null + } // Add new element - this.append = function (element) { + append(element) { const node = new Node(element) - if (!head) { - head = node - tail = node + if (!this.head) { + this.head = node + this.tail = node } else { - node.prev = tail - tail.next = node - tail = node + node.prev = this.tail + this.tail.next = node + this.tail = node } - length++ + this.length++ } // Add element - this.insert = function (position, element) { + insert(position, element) { // Check of out-of-bound values - if (position >= 0 && position <= length) { + if (position >= 0 && position <= this.length) { const node = new Node(element) - let current = head + let current = this.head let previous = 0 let index = 0 if (position === 0) { - if (!head) { - head = node - tail = node + if (!this.head) { + this.head = node + this.tail = node } else { node.next = current current.prev = node - head = node + this.head = node } - } else if (position === length) { - current = tail + } else if (position === this.length) { + current = this.tail current.next = node node.prev = current - tail = node + this.tail = node } else { while (index++ < position) { previous = current @@ -63,7 +66,7 @@ function DoubleLinkedList () { node.prev = previous } - length++ + this.length++ return true } else { return false @@ -71,27 +74,27 @@ function DoubleLinkedList () { } // Remove element at any position - this.removeAt = function (position) { + removeAt(position) { // look for out-of-bounds value - if (position > -1 && position < length) { - let current = head + if (position > -1 && position < this.length) { + let current = this.head let previous = 0 let index = 0 // Removing first item if (position === 0) { - head = current.next + this.head = current.next - // if there is only one item, update tail //NEW - if (length === 1) { - tail = null + // if there is only one item, update this.tail //NEW + if (this.length === 1) { + this.tail = null } else { - head.prev = null + this.head.prev = null } - } else if (position === length - 1) { - current = tail - tail = current.prev - tail.next = null + } else if (position === this.length - 1) { + current = this.tail + this.tail = current.prev + this.tail.next = null } else { while (index++ < position) { previous = current @@ -103,7 +106,7 @@ function DoubleLinkedList () { current.next.prev = previous } - length-- + this.length-- return current.element } else { return null @@ -111,8 +114,8 @@ function DoubleLinkedList () { } // Get the indexOf item - this.indexOf = function (elm) { - let current = head + indexOf(elm) { + let current = this.head let index = -1 // If element found then return its position @@ -130,28 +133,28 @@ function DoubleLinkedList () { } // Find the item in the list - this.isPresent = (elm) => { + isPresent(elm) { return this.indexOf(elm) !== -1 } // Delete an item from the list - this.delete = (elm) => { + delete(elm) { return this.removeAt(this.indexOf(elm)) } // Delete first item from the list - this.deleteHead = function () { + deleteHead() { this.removeAt(0) } // Delete last item from the list - this.deleteTail = function () { - this.removeAt(length - 1) + deleteTail() { + this.removeAt(this.length - 1) } // Print item of the string - this.toString = function () { - let current = head + toString() { + let current = this.head let string = '' while (current) { @@ -163,9 +166,9 @@ function DoubleLinkedList () { } // Convert list to array - this.toArray = function () { + toArray() { const arr = [] - let current = head + let current = this.head while (current) { arr.push(current.element) @@ -176,27 +179,59 @@ function DoubleLinkedList () { } // Check if list is empty - this.isEmpty = function () { - return length === 0 + isEmpty() { + return this.length === 0 } // Get the size of the list - this.size = function () { - return length + size() { + return this.length + } + + // Get the this.head + getHead() { + return this.head + } + + // Get the this.tail + getTail() { + return this.tail } - // Get the head - this.getHead = function () { - return head + // Method to iterate over the LinkedList + iterator() { + let currentNode = this.getHead() + if (currentNode === null) return -1 + + const iterate = function* () { + while (currentNode) { + yield currentNode.element + currentNode = currentNode.next + } + } + return iterate() } - // Get the tail - this.getTail = function () { - return tail + // Method to log the LinkedList, for debugging + // it' a circular structure, so can't use stringify to debug the whole structure + log() { + let currentNode = this.getHead() + while (currentNode) { + console.log(currentNode.element) + currentNode = currentNode.next + } } } -const newDoubleLinkedList = new DoubleLinkedList() -newDoubleLinkedList.append(1) -newDoubleLinkedList.append(2) -console.log('Testing: ' + newDoubleLinkedList.size()) // returns 2 +// Example + +// const newDoubleLinkedList = new DoubleLinkedList() +// newDoubleLinkedList.append(1) +// newDoubleLinkedList.append(2) +// newDoubleLinkedList.size() // returns 2 +// const iterate = newDoubleLinkedList.iterator() +// console.log(iterate.next().value) // 1 +// console.log(iterate.next().value) // 2 +// console.log(newDoubleLinkedList.log()) + +export { DoubleLinkedList } diff --git a/Data-Structures/Linked-List/MergeTwoSortedLinkedLists.js b/Data-Structures/Linked-List/MergeTwoSortedLinkedLists.js new file mode 100644 index 0000000000..df11c0ac2c --- /dev/null +++ b/Data-Structures/Linked-List/MergeTwoSortedLinkedLists.js @@ -0,0 +1,45 @@ +import { LinkedList } from './SinglyLinkedList.js' +/** + * A LinkedList-based solution for merging two sorted linked lists into one sorted list. + * + * @param {LinkedList} list1 - The the first sorted linked list. + * @param {LinkedList} list2 - The second sorted linked list. + * @returns {LinkedList} - The merged sorted linked list. + * + * @example + * const list1 = new LinkedList([1,2,4]); + * + * const list2 = new LinkedList([1,3,4]); + * + * const result = mergeLinkedLists(list1, list2); + * // Returns the merged linked list representing 1 -> 1 -> 2 -> 3 -> 4 -> 4 + */ + +function mergeLinkedLists(list1, list2) { + const mergedList = new LinkedList() + + let current1 = list1.headNode + let current2 = list2.headNode + + while (current1 || current2) { + if (!current1) { + mergedList.addLast(current2.data) + current2 = current2.next + } else if (!current2) { + mergedList.addLast(current1.data) + current1 = current1.next + } else { + if (current1.data < current2.data) { + mergedList.addLast(current1.data) + current1 = current1.next + } else { + mergedList.addLast(current2.data) + current2 = current2.next + } + } + } + + return mergedList +} + +export { mergeLinkedLists } diff --git a/Data-Structures/Linked-List/ReverseSinglyLinkedList.js b/Data-Structures/Linked-List/ReverseSinglyLinkedList.js new file mode 100644 index 0000000000..6e20956fda --- /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/SinglyCircularLinkedList.js b/Data-Structures/Linked-List/SinglyCircularLinkedList.js new file mode 100644 index 0000000000..f79766a19b --- /dev/null +++ b/Data-Structures/Linked-List/SinglyCircularLinkedList.js @@ -0,0 +1,160 @@ +// Methods - size, head, isEmpty, getElementAt, addAtFirst, add, clean, insertAt, remove, removeData, printData, get, clear +import { Node } from './SinglyLinkedList.js' + +class SinglyCircularLinkedList { + constructor() { + this.headNode = null + this.length = 0 + } + + // Get size of the linkedList + size = () => this.length + // Get the headNode data + head = () => this.headNode?.data || null + // Check if the linkedList is empty + isEmpty = () => this.length === 0 + + // initiate the node and index + initiateNodeAndIndex() { + return { currentNode: this.headNode, currentIndex: 0 } + } + + // get the data specific to an index + getElementAt(index) { + if (this.length !== 0 && index >= 0 && index <= this.length) { + let { currentNode } = this.initiateNodeAndIndex() + for (let i = 0; i < index && currentNode !== null; i++) { + currentNode = currentNode.next + } + return currentNode + } + return undefined + } + + // Add the element in the first position + addAtFirst(data) { + const node = new Node(data) + node.next = this.headNode + this.headNode = node + this.length++ + return this.length + } + + // Add any data to the end of the linkedList + add(data) { + if (!this.headNode) { + return this.addAtFirst(data) + } + const node = new Node(data) + // Getting the last node + const currentNode = this.getElementAt(this.length - 1) + currentNode.next = node + node.next = this.headNode + this.length++ + return this.length + } + + // insert data at a specific position + insertAt(index, data) { + if (index === 0) return this.addAtFirst(data) + if (index === this.length) return this.add(data) + if (index < 0 || index > this.length) + throw new RangeError(`Index is out of range max ${this.length}`) + const node = new Node(data) + const previousNode = this.getElementAt(index - 1) + node.next = previousNode.next + previousNode.next = node + this.length++ + return this.length + } + + // find the first index of the data + indexOf(data) { + let { currentNode } = this.initiateNodeAndIndex() + // initializing currentIndex as -1 + let currentIndex = -1 + while (currentNode) { + if (currentNode.data === data) { + return currentIndex + 1 + } + currentIndex++ + currentNode = currentNode.next + } + return -1 + } + + // remove the data from the end of the list + remove() { + if (this.isEmpty()) return null + const secondLastNode = this.getElementAt(this.length - 2) + const removedNode = secondLastNode.next + secondLastNode.next = this.headNode + this.length-- + return removedNode.data || null + } + + // remove the data from the first of the list + removeFirst() { + if (this.isEmpty()) return null + const removedNode = this.headNode + if (this.length === 1) { + this.clear() + return removedNode.data + } + const lastNode = this.getElementAt(this.length - 1) + this.headNode = this.headNode.next + lastNode.next = this.headNode + this.length-- + return removedNode.data || null + } + + // remove the data from the index + removeAt(index) { + if (this.isEmpty()) return null + if (index === 0) return this.removeFirst() + if (index === this.length) return this.remove() + if (index < 0 && index > this.length) return null + const previousNode = this.getElementAt(index - 1) + const currentNode = previousNode.next + previousNode.next = currentNode.next + this.length-- + return currentNode.data || null + } + + // remove if the data is present + removeData(data) { + if (this.isEmpty()) return null + const index = this.indexOf(data) + return this.removeAt(index) + } + + // logs the data + printData(output = (value) => console.log(value)) { + let { currentIndex, currentNode } = this.initiateNodeAndIndex() + + while (currentNode !== null && currentIndex < this.length) { + output(currentNode.data) + currentNode = currentNode.next + currentIndex++ + } + } + + // get the data from the linkedList + get() { + let { currentIndex, currentNode } = this.initiateNodeAndIndex() + const list = [] + while (currentNode !== null && currentIndex < this.length) { + list.push(currentNode.data) + currentNode = currentNode.next + currentIndex++ + } + return list + } + + clear() { + this.headNode = null + this.length = 0 + } +} + +export { SinglyCircularLinkedList } diff --git a/Data-Structures/Linked-List/SinglyLinkList.js b/Data-Structures/Linked-List/SinglyLinkList.js deleted file mode 100644 index fbfdacf22c..0000000000 --- a/Data-Structures/Linked-List/SinglyLinkList.js +++ /dev/null @@ -1,209 +0,0 @@ -/* SinglyLinkedList!! -* A linked list is implar to an array, it hold values. -* However, links in a linked list do not have indexes. With -* a linked list you do not need to predetermine it's size as -* it grows and shrinks as it is edited. This is an example of -* a singly linked list. -*/ - -// Functions - add, remove, indexOf, elementAt, addAt, removeAt, view - -// class LinkedList and constructor -// Creates a LinkedList -var LinkedList = (function () { - function LinkedList () { - // Length of linklist and head is null at start - this.length = 0 - this.head = null - } - - // class node (constructor) - // Creating Node with element's value - var Node = (function () { - function Node (element) { - this.element = element - this.next = null - } - return Node - }()) - - // Returns length - LinkedList.prototype.size = function () { - return this.length - } - - // Returns the head - LinkedList.prototype.head = function () { - return this.head - } - - // Creates a node and adds it to linklist - LinkedList.prototype.add = function (element) { - var node = new Node(element) - // Check if its the first element - if (this.head === null) { - this.head = node - } else { - var currentNode = this.head - - // Loop till there is node present in the list - while (currentNode.next) { - currentNode = currentNode.next - } - - // Adding node to the end of the list - currentNode.next = node - } - // Increment the length - this.length++ - } - - // Removes the node with the value as param - LinkedList.prototype.remove = function (element) { - var currentNode = this.head - var previousNode - - // Check if the head node is the element to remove - if (currentNode.element === element) { - this.head = currentNode.next - } else { - // Check which node is the node to remove - while (currentNode.element !== element) { - previousNode = currentNode - currentNode = currentNode.next - } - - // Removing the currentNode - previousNode.next = currentNode.next - } - - // Decrementing the length - this.length-- - } - - // Return if the list is empty - LinkedList.prototype.isEmpty = function () { - return this.length === 0 - } - - // Returns the index of the element passed as param otherwise -1 - LinkedList.prototype.indexOf = function (element) { - var currentNode = this.head - var index = -1 - - while (currentNode) { - index++ - - // Checking if the node is the element we are searching for - if (currentNode.element === element) { - return index + 1 - } - currentNode = currentNode.next - } - - return -1 - } - - // Returns the element at an index - LinkedList.prototype.elementAt = function (index) { - var currentNode = this.head - var count = 0 - while (count < index) { - count++ - currentNode = currentNode.next - } - return currentNode.element - } - - // Adds the element at specified index - LinkedList.prototype.addAt = function (index, element) { - index-- - var node = new Node(element) - - var currentNode = this.head - var previousNode - var currentIndex = 0 - - // Check if index is out of bounds of list - if (index > this.length) { - return false - } - - // Check if index is the start of list - if (index === 0) { - node.next = currentNode - this.head = node - } else { - while (currentIndex < index) { - currentIndex++ - previousNode = currentNode - currentNode = currentNode.next - } - - // Adding the node at specified index - node.next = currentNode - previousNode.next = node - } - - // Incrementing the length - this.length++ - return true - } - - // Removes the node at specified index - LinkedList.prototype.removeAt = function (index) { - index-- - var currentNode = this.head - var previousNode - var currentIndex = 0 - - // Check if index is present in list - if (index < 0 || index >= this.length) { - return null - } - - // Check if element is the first element - if (index === 0) { - this.head = currentNode.next - } else { - while (currentIndex < index) { - currentIndex++ - previousNode = currentNode - currentNode = currentNode.next - } - previousNode.next = currentNode.next - } - - // Decrementing the length - this.length-- - return currentNode.element - } - - // Function to view the LinkedList - LinkedList.prototype.view = function () { - var currentNode = this.head - var count = 0 - while (count < this.length) { - count++ - console.log(currentNode.element) - currentNode = currentNode.next - } - } - - // returns the constructor - return LinkedList -}()) - -// Implementation of LinkedList -var linklist = new LinkedList() -linklist.add(2) -linklist.add(5) -linklist.add(8) -linklist.add(12) -linklist.add(17) -console.log(linklist.size()) -console.log(linklist.removeAt(4)) -linklist.addAt(4, 15) -console.log(linklist.indexOf(8)) -console.log(linklist.size()) -linklist.view() diff --git a/Data-Structures/Linked-List/SinglyLinkedList.js b/Data-Structures/Linked-List/SinglyLinkedList.js new file mode 100644 index 0000000000..a5f875d1b3 --- /dev/null +++ b/Data-Structures/Linked-List/SinglyLinkedList.js @@ -0,0 +1,305 @@ +/* SinglyLinkedList!! + * A linked list is similar to an array, it holds a list of values. + * However, links in a linked list do not have indexes. With + * a linked list you do not need to predetermine its size as + * it grows and shrinks as it is edited. This is an example of + * a singly linked list. + */ + +// Methods - size, head, addLast, addFirst, addAt, removeFirst, removeLast, remove, removeAt, indexOf, isEmpty, elementAt, findMiddle, get, clean, rotateListRight + +class Node { + constructor(data) { + this.data = data + this.next = null + } +} + +class LinkedList { + constructor(listOfValues) { + this.headNode = null + this.tailNode = null + this.length = 0 + + if (listOfValues instanceof Array) { + for (const value of listOfValues) { + this.addLast(value) + } + } + } + + // initiates the currentNode and currentIndex and return as an object + initiateNodeAndIndex() { + return { currentNode: this.headNode, currentIndex: 0 } + } + + // Returns length + size() { + return this.length + } + + // Returns the head + head() { + return this.headNode?.data ?? null + } + + // Returns the tail + tail() { + return this.tailNode?.data ?? null + } + + // Return if the list is empty + isEmpty() { + return this.length === 0 + } + + // add a node at last it to linklist + addLast(element) { + // Check if its the first element + if (this.headNode === null) { + return this.addFirst(element) + } + const node = new Node(element) + // Adding node at the end of the list and increase the length + this.tailNode.next = node + this.tailNode = node + this.length++ + return this.size() + } + + // add a node at first it to linklist + addFirst(element) { + const node = new Node(element) + // Check if its the first element + if (this.headNode === null) { + this.tailNode = node + } + // Adding node at the start of the list and increase the length + node.next = this.headNode + this.headNode = node + this.length++ + return this.size() + } + + // remove the first from the linklist + removeFirst() { + // Check if head is null + if (this.headNode === null) { + return null + } + // Removing node at the start of the list and decrease the length + const removedNode = this.headNode + this.headNode = this.headNode.next + this.length-- + // Check if the list is empty + if (this.isEmpty()) { + this.tailNode = null + } + return removedNode?.data + } + + // remove the last from the linklist + removeLast() { + if (this.isEmpty()) return null + // Check if there is only one element + if (this.length === 1) { + return this.removeFirst() + } + // Removing node at the end of the list and decrease the length + const removedNode = this.tailNode + let { currentNode } = this.initiateNodeAndIndex() + while (currentNode.next.next) { + currentNode = currentNode.next + } + currentNode.next = null + this.tailNode = currentNode + this.length-- + return removedNode.data + } + + // Removes the node with the value as param + remove(element) { + if (this.isEmpty()) return null + let { currentNode } = this.initiateNodeAndIndex() + let removedNode = null + // Check if the head node is the element to remove + if (currentNode.data === element) { + return this.removeFirst() + } + // Check if the tail node is the element to remove + if (this.tailNode.data === element) { + return this.removeLast() + } + // Check which node is the node to remove + while (currentNode.next) { + if (currentNode.next.data === element) { + removedNode = currentNode.next + currentNode.next = currentNode.next.next + this.length-- + return removedNode.data + } + currentNode = currentNode.next + } + return removedNode?.data || null + } + + // Returns the index of the element passed as param otherwise -1 + indexOf(element) { + if (this.isEmpty()) return -1 + let { currentNode, currentIndex } = this.initiateNodeAndIndex() + while (currentNode) { + if (currentNode.data === element) { + return currentIndex + } + currentNode = currentNode.next + currentIndex++ + } + return -1 + } + + // Returns the element at an index + elementAt(index) { + if (index >= this.length || index < 0) { + throw new RangeError('Out of Range index') + } + let { currentIndex, currentNode } = this.initiateNodeAndIndex() + while (currentIndex < index) { + currentIndex++ + currentNode = currentNode.next + } + return currentNode.data + } + + // Adds the element at specified index + addAt(index, element) { + // Check if index is out of bounds of list + if (index > this.length || index < 0) { + throw new RangeError('Out of Range index') + } + if (index === 0) return this.addFirst(element) + if (index === this.length) return this.addLast(element) + let { currentIndex, currentNode } = this.initiateNodeAndIndex() + const node = new Node(element) + + while (currentIndex !== index - 1) { + currentIndex++ + currentNode = currentNode.next + } + + // Adding the node at specified index + const tempNode = currentNode.next + currentNode.next = node + node.next = tempNode + // Incrementing the length + this.length++ + return this.size() + } + + // Removes the node at specified index + removeAt(index) { + // Check if index is present in list + if (index < 0 || index >= this.length) { + throw new RangeError('Out of Range index') + } + if (index === 0) return this.removeFirst() + if (index === this.length - 1) return this.removeLast() + + let { currentIndex, currentNode } = this.initiateNodeAndIndex() + while (currentIndex !== index - 1) { + currentIndex++ + currentNode = currentNode.next + } + const removedNode = currentNode.next + currentNode.next = currentNode.next.next + this.length-- + return removedNode.data + } + + // Returns a reference to middle node of linked list + findMiddle() { + // If there are two middle nodes, return the second middle node. + let fast = this.headNode + let slow = this.headNode + + while (fast !== null && fast.next !== null) { + fast = fast.next.next + slow = slow.next + } + return slow + } + + // make the linkedList Empty + clean() { + this.headNode = null + this.tailNode = null + this.length = 0 + } + + // Method to get the LinkedList + get() { + const list = [] + let { currentNode } = this.initiateNodeAndIndex() + while (currentNode) { + list.push(currentNode.data) + currentNode = currentNode.next + } + return list + } + + // Method for Rotating a List to the right by k places + rotateListRight(k) { + if (!this.headNode) return + let current = this.headNode + let tail = this.tailNode + let count = 1 + while (current.next) { + count++ + current = current.next + } + current.next = this.headNode + tail = current + k %= count + while (count - k > 0) { + tail = tail.next + count-- + } + this.headNode = tail.next + tail.next = null + } + + // Method to iterate over the LinkedList + iterator() { + let { currentNode } = this.initiateNodeAndIndex() + if (currentNode === null) return -1 + + const iterate = function* () { + while (currentNode) { + yield currentNode.data + currentNode = currentNode.next + } + } + return iterate() + } + + // Method to log the 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.tailNode = this.headNode + this.headNode = prev + } +} + +export { Node, LinkedList } diff --git a/Data-Structures/Linked-List/test/AddTwoNumbers.test.js b/Data-Structures/Linked-List/test/AddTwoNumbers.test.js new file mode 100644 index 0000000000..caeb77f7fc --- /dev/null +++ b/Data-Structures/Linked-List/test/AddTwoNumbers.test.js @@ -0,0 +1,26 @@ +import { AddTwoNumbers } from '../AddTwoNumbers.js' +import { LinkedList } from '../SinglyLinkedList' + +describe('AddTwoNumbers', () => { + it('Check Sum Of Two Linked List', () => { + const list1 = new LinkedList() + list1.addFirst(2) + list1.addLast(4) + list1.addLast(3) + + const list2 = new LinkedList() + list2.addFirst(5) + list2.addLast(6) + list2.addLast(4) + + const expected = new LinkedList() + expected.addFirst(7) + expected.addLast(0) + expected.addLast(8) + + const addTwoLinkedList = new AddTwoNumbers() + addTwoLinkedList.solution(list1.headNode, list2.headNode) + + expect(addTwoLinkedList.solutionToArray()).toEqual(expected.get()) + }) +}) diff --git a/Data-Structures/Linked-List/test/CycleDetection.test.js b/Data-Structures/Linked-List/test/CycleDetection.test.js new file mode 100644 index 0000000000..0041dece59 --- /dev/null +++ b/Data-Structures/Linked-List/test/CycleDetection.test.js @@ -0,0 +1,31 @@ +import { detectCycle } from '../CycleDetection' +import { Node } from '../SinglyLinkedList' + +describe('Detect Cycle', () => { + it('should detect loop and return true', () => { + // Creating list and making a loop + const headNode = new Node(10) + headNode.next = new Node(20) + headNode.next.next = new Node(30) + headNode.next.next.next = new Node(40) + headNode.next.next.next.next = headNode + expect(detectCycle(headNode)).toEqual(true) + }) + + it('should not detect a loop and return false', () => { + // Case 0: When head is null, there is no loop. + expect(detectCycle(null)).toEqual(false) + const headNode = new Node(10) + + // Case 1: List with single node doesn't have any loop + expect(detectCycle(headNode)).toEqual(false) + + headNode.next = new Node(20) + headNode.next.next = new Node(30) + headNode.next.next.next = new Node(40) + headNode.next.next.next.next = new Node(50) + + // Case 2: List not having any loops + expect(detectCycle(headNode)).toEqual(false) + }) +}) diff --git a/Data-Structures/Linked-List/test/CycleDetectionII.test.js b/Data-Structures/Linked-List/test/CycleDetectionII.test.js new file mode 100644 index 0000000000..f741c53622 --- /dev/null +++ b/Data-Structures/Linked-List/test/CycleDetectionII.test.js @@ -0,0 +1,39 @@ +import { findCycleStart } from '../CycleDetectionII' +import { Node } from '../SinglyLinkedList' + +describe('Detect Cycle', () => { + it('no cycle', () => { + const head = new Node(1) + head.next = new Node(2) + + expect(findCycleStart(head)).toBeNull() + }) + + it('simple cycle', () => { + const head = new Node(1) + head.next = new Node(2) + head.next.next = new Node(3) + head.next.next.next = head.next // Creates a cycle + + expect(findCycleStart(head)).toBe(head.next) + }) + + it('long list with cycle', () => { + const head = new Node(1) + head.next = new Node(2) + head.next.next = new Node(3) + head.next.next.next = new Node(4) + head.next.next.next.next = new Node(5) + head.next.next.next.next.next = head.next.next // Cycle + + expect(findCycleStart(head)).toBe(head.next.next) + }) + + it('cycle on last node', () => { + const head = new Node(1) + head.next = new Node(2) + head.next.next = head + + expect(findCycleStart(head)).toBe(head) + }) +}) diff --git a/Data-Structures/Linked-List/test/DoublyLinkedList.test.js b/Data-Structures/Linked-List/test/DoublyLinkedList.test.js new file mode 100644 index 0000000000..ce87864774 --- /dev/null +++ b/Data-Structures/Linked-List/test/DoublyLinkedList.test.js @@ -0,0 +1,127 @@ +import { DoubleLinkedList } from '../DoublyLinkedList' + +describe('DoubleLinkedList', () => { + it('Check append', () => { + const list = new DoubleLinkedList() + + list.append(1) + expect(list.getHead().element).toEqual(1) + + list.append(2) + expect(list.getTail().element).toEqual(2) + }) + + it('Check insert', () => { + const list = new DoubleLinkedList() + + list.insert(0, 1) + expect(list.getHead().element).toEqual(1) + + list.insert(1, 20) + expect(list.getTail().element).toEqual(20) + }) + + it('Check removeAt', () => { + const list = new DoubleLinkedList() + + list.insert(0, 10) + list.insert(1, 40) + list.insert(2, 30) + + list.removeAt(0) + expect(list.getHead().element).toEqual(40) + + list.removeAt(1) + expect(list.getTail().element).toEqual(40) + }) + + it('Check delete', () => { + const list = new DoubleLinkedList() + + list.insert(0, 10) + list.insert(1, 40) + + list.delete(10) + expect(list.getHead().element).toEqual(40) + }) + + it('Check deleteTail', () => { + const list = new DoubleLinkedList() + + list.insert(0, 10) + list.insert(1, 40) + + list.deleteTail() + expect(list.getTail().element).toEqual(10) + }) + + it('Check toString', () => { + const list = new DoubleLinkedList() + + list.insert(0, 20) + expect(list.toString()).toEqual('20') + }) + + it('Check isEmpty', () => { + const list = new DoubleLinkedList() + + expect(list.isEmpty()).toEqual(true) + + list.insert(0, 'Hello') + expect(list.isEmpty()).toEqual(false) + }) + + it('Check size', () => { + const list = new DoubleLinkedList() + expect(list.size()).toBe(0) + + list.append(10) + expect(list.size()).toBe(1) + + list.removeAt(1) + expect(list.size()).toBe(1) + }) + + it('Check toArray', () => { + const list = new DoubleLinkedList() + list.append(1) + list.append(2) + + const listArray = list.toArray() + expect(listArray).toEqual([1, 2]) + }) + + it('Check getHead', () => { + const list = new DoubleLinkedList() + expect(list.getHead()).toEqual(null) + + list.append(1) + list.append(2) + expect(list.getHead()).toBeInstanceOf(Object) + }) + + it('Check Iterator', () => { + const list = new DoubleLinkedList() + + let iterate = list.iterator() + expect(iterate).toBe(-1) + + const arr = [10, 20, 5] + list.append(arr[0]) + list.append(arr[1]) + list.append(arr[2]) + iterate = list.iterator() + + for (let i = 0; i < arr.length; i++) { + expect(iterate.next().value).toBe(arr[i]) + } + expect(iterate.next().value).toBe(undefined) + + iterate = list.iterator() + let count = 0 + for (const item of iterate) { + expect(item).toBe(arr[count]) + count++ + } + }) +}) diff --git a/Data-Structures/Linked-List/test/MergeTwoSortedLinkedLists.test.js b/Data-Structures/Linked-List/test/MergeTwoSortedLinkedLists.test.js new file mode 100644 index 0000000000..bc5bea953e --- /dev/null +++ b/Data-Structures/Linked-List/test/MergeTwoSortedLinkedLists.test.js @@ -0,0 +1,39 @@ +import { expect } from 'vitest' +import { mergeLinkedLists } from '../MergeTwoSortedLinkedLists.js' +import { LinkedList } from '../SinglyLinkedList.js' + +describe('MergeTwoSortedLinkedLists', () => { + it('Merges two sorted linked lists', () => { + const list1 = new LinkedList([1, 2, 4]) + + const list2 = new LinkedList([1, 3, 4]) + + const expectedResult = new LinkedList([1, 1, 2, 3, 4, 4]) + + const result = mergeLinkedLists(list1, list2) + + expect(result).toEqual(expectedResult) + }) + + it('Merges two empty linked lists', () => { + const list1 = new LinkedList() + const list2 = new LinkedList() + + const expectedResult = new LinkedList() + + const result = mergeLinkedLists(list1, list2) + + expect(result).toEqual(expectedResult) + }) + + it('Merges one empty linked list with a non-empty one', () => { + const list1 = new LinkedList() + const list2 = new LinkedList([1]) + + const expectedResult = new LinkedList([1]) + + const result = mergeLinkedLists(list1, list2) + + expect(result).toEqual(expectedResult) + }) +}) 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 0000000000..847026db89 --- /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/SinglyCircularLinkedList.test.js b/Data-Structures/Linked-List/test/SinglyCircularLinkedList.test.js new file mode 100644 index 0000000000..00628a6e5b --- /dev/null +++ b/Data-Structures/Linked-List/test/SinglyCircularLinkedList.test.js @@ -0,0 +1,147 @@ +import { SinglyCircularLinkedList } from '../SinglyCircularLinkedList' + +describe('SinglyCircularLinkedList', () => { + let list + beforeEach(() => { + list = new SinglyCircularLinkedList() + }) + it('Check get', () => { + expect(list.get()).toEqual([]) + expect(list.add(1)).toEqual(1) + expect(list.get()).toEqual([1]) + expect(list.add(5)).toEqual(2) + expect(list.get()).toEqual([1, 5]) + }) + + it('Check size', () => { + expect(list.size()).toEqual(0) + expect(list.add(1)).toEqual(1) + expect(list.add(1)).toEqual(2) + expect(list.size()).toEqual(2) + }) + + it('Check head', () => { + expect(list.head()).toEqual(null) + expect(list.add(1)).toEqual(1) + expect(list.head()).toEqual(1) + expect(list.add(1)).toEqual(2) + expect(list.head()).toEqual(1) + expect(list.addAtFirst(100)).toEqual(3) + expect(list.head()).toEqual(100) + expect(list.insertAt(0, 500)).toEqual(4) + expect(list.head()).toEqual(500) + list.clear() + expect(list.head()).toEqual(null) + }) + + it('Check isEmpty', () => { + expect(list.isEmpty()).toEqual(true) + expect(list.add(1)).toEqual(1) + expect(list.add(1)).toEqual(2) + expect(list.isEmpty()).toEqual(false) + }) + + it('Check getElementAt', () => { + list.add(100) + list.add(200) + list.add(300) + list.add(500) + list.add(900) + + expect(list.getElementAt(1).data).toEqual(200) + expect(list.getElementAt(3).data).toEqual(500) + }) + + it('Check addAtFirst', () => { + list.add(1) + list.add(5) + list.add(7) + list.add(9) + list.add(0) + expect(list.get()).toEqual([1, 5, 7, 9, 0]) + list.addAtFirst(100) + expect(list.get()).toEqual([100, 1, 5, 7, 9, 0]) + }) + + it('Check add', () => { + list.add(1) + list.add(5) + list.add(7) + list.add(9) + list.add(0) + expect(list.get()).toEqual([1, 5, 7, 9, 0]) + list.add(100) + expect(list.get()).toEqual([1, 5, 7, 9, 0, 100]) + }) + + it('Check insertAt', () => { + expect(list.insertAt(0, 100)).toEqual(1) + expect(list.get()).toEqual([100]) + expect(list.insertAt(0, 200)).toEqual(2) + expect(list.get()).toEqual([200, 100]) + expect(list.insertAt(2, 300)).toEqual(3) + expect(list.get()).toEqual([200, 100, 300]) + }) + + it('Checks indexOf', () => { + expect(list.indexOf(200)).toEqual(-1) + list.add(100) + list.add(200) + list.add(300) + list.add(500) + list.add(900) + expect(list.indexOf(200)).toEqual(1) + }) + + it('Check remove', () => { + expect(list.remove()).toEqual(null) + list.add(100) + list.add(200) + list.add(300) + list.add(500) + list.add(900) + expect(list.get()).toEqual([100, 200, 300, 500, 900]) + const removedData = list.remove() + expect(removedData).toEqual(900) + expect(list.get()).toEqual([100, 200, 300, 500]) + }) + + it('Check removeFirst', () => { + expect(list.removeFirst()).toEqual(null) + list.add(100) + list.add(200) + list.add(300) + list.add(500) + list.add(900) + expect(list.get()).toEqual([100, 200, 300, 500, 900]) + const removedData = list.removeFirst() + expect(removedData).toEqual(100) + expect(list.get()).toEqual([200, 300, 500, 900]) + }) + + it('Check removeAt', () => { + expect(list.removeAt(1)).toEqual(null) + list.add(100) + list.add(200) + list.add(300) + list.add(500) + list.add(900) + expect(list.get()).toEqual([100, 200, 300, 500, 900]) + const removedData = list.removeAt(2) + expect(removedData).toEqual(300) + expect(list.get()).toEqual([100, 200, 500, 900]) + }) + + it('Check removeData', () => { + expect(list.removeData(100)).toEqual(null) + list.add(100) + list.add(200) + list.add(300) + list.add(500) + list.add(900) + expect(list.get()).toEqual([100, 200, 300, 500, 900]) + const removedData = list.removeData(200) + expect(removedData).toEqual(200) + expect(list.get()).toEqual([100, 300, 500, 900]) + }) +}) diff --git a/Data-Structures/Linked-List/test/SinglyLinkedList.test.js b/Data-Structures/Linked-List/test/SinglyLinkedList.test.js new file mode 100644 index 0000000000..53be70884d --- /dev/null +++ b/Data-Structures/Linked-List/test/SinglyLinkedList.test.js @@ -0,0 +1,278 @@ +import { LinkedList } from '../SinglyLinkedList' + +describe('SinglyLinkedList', () => { + it('Check addLast', () => { + const list = new LinkedList() + expect(list.get()).toEqual([]) + + expect(list.addLast(1)).toEqual(1) + expect(list.get()).toEqual([1]) + + expect(list.addLast(5)).toEqual(2) + expect(list.get()).toEqual([1, 5]) + }) + + it('Check addFirst', () => { + const list = new LinkedList() + expect(list.get()).toEqual([]) + + expect(list.addFirst(1)).toEqual(1) + expect(list.get()).toEqual([1]) + + expect(list.addFirst(5)).toEqual(2) + expect(list.get()).toEqual([5, 1]) + }) + + it('Check addAt', () => { + const list = new LinkedList() + expect(list.get()).toEqual([]) + + expect(list.addAt(0, 10)).toEqual(1) + expect(list.get()).toEqual([10]) + + expect(list.addAt(1, 20)).toEqual(2) + expect(list.get()).toEqual([10, 20]) + + expect(list.addAt(1, 30)).toEqual(3) + expect(list.get()).toEqual([10, 30, 20]) + + expect(list.addAt(3, 40)).toEqual(4) + expect(list.get()).toEqual([10, 30, 20, 40]) + }) + + it('Check removeLast', () => { + const list = new LinkedList() + list.addLast(1) + list.addLast(2) + expect(list.get()).toEqual([1, 2]) + + expect(list.removeLast()).toEqual(2) + expect(list.get()).toEqual([1]) + + expect(list.removeLast()).toEqual(1) + expect(list.get()).toEqual([]) + }) + + it('Check removeFirst', () => { + const list = new LinkedList() + list.addLast(1) + list.addLast(2) + expect(list.get()).toEqual([1, 2]) + + expect(list.removeFirst()).toEqual(1) + expect(list.get()).toEqual([2]) + + expect(list.removeFirst()).toEqual(2) + expect(list.get()).toEqual([]) + }) + + it('Check removeAt', () => { + const list = new LinkedList() + list.addLast(10) + list.addLast(20) + list.addLast(30) + list.addLast(40) + list.addLast(50) + expect(list.get()).toEqual([10, 20, 30, 40, 50]) + + expect(list.removeAt(0)).toEqual(10) + expect(list.get()).toEqual([20, 30, 40, 50]) + + expect(list.removeAt(3)).toEqual(50) + expect(list.get()).toEqual([20, 30, 40]) + + expect(list.removeAt(1)).toEqual(30) + expect(list.get()).toEqual([20, 40]) + }) + + it('Check remove', () => { + const list = new LinkedList() + list.addLast(10) + list.addLast(20) + list.addLast(30) + expect(list.get()).toEqual([10, 20, 30]) + + expect(list.remove(10)).toEqual(10) + expect(list.get()).toEqual([20, 30]) + + expect(list.remove(100)).toEqual(null) + expect(list.get()).toEqual([20, 30]) + }) + + it('Check indexOf', () => { + const list = new LinkedList() + list.addLast(10) + list.addLast(20) + list.addLast(30) + list.addLast(40) + list.addLast(50) + expect(list.indexOf(10)).toBe(0) + expect(list.indexOf(30)).toBe(2) + expect(list.indexOf(50)).toBe(4) + expect(list.indexOf(70)).toBe(-1) + }) + + it('Check elementAt', () => { + const list = new LinkedList() + list.addLast(10) + list.addLast(20) + list.addLast(30) + list.addLast(40) + list.addLast(50) + expect(list.elementAt(0)).toBe(10) + expect(list.elementAt(1)).toBe(20) + expect(list.elementAt(3)).toBe(40) + expect(list.elementAt(4)).toBe(50) + }) + + it('Check isEmpty', () => { + const list = new LinkedList() + expect(list.isEmpty()).toBe(true) + list.addLast(10) + list.addLast(20) + list.addLast(30) + list.addLast(40) + list.addLast(50) + expect(list.isEmpty()).toBe(false) + }) + + it('Check head', () => { + const list = new LinkedList() + expect(list.head()).toBe(null) + + list.addLast(10) + expect(list.head()).toBe(10) + + list.addLast(20) + expect(list.head()).toBe(10) + + list.addFirst(30) + expect(list.head()).toBe(30) + + // check for a falsy head data + list.addFirst(false) + expect(list.head()).toBe(false) + }) + + it('Check tail', () => { + const list = new LinkedList() + expect(list.tail()).toBe(null) + + list.addLast(10) + expect(list.tail()).toBe(10) + + list.addLast(20) + expect(list.tail()).toBe(20) + + list.addFirst(30) + expect(list.tail()).toBe(20) + + // check for a falsy tail data + list.addLast(false) + expect(list.tail()).toBe(false) + }) + + it('Check size', () => { + const list = new LinkedList() + expect(list.size()).toBe(0) + + list.addLast(10) + expect(list.size()).toBe(1) + + list.addLast(20) + expect(list.size()).toBe(2) + + list.removeFirst() + expect(list.size()).toBe(1) + }) + + it('Middle node of linked list', () => { + const list = new LinkedList() + list.addFirst(1) + + // Middle node for list having single node + expect(list.findMiddle().data).toEqual(1) + + list.addLast(2) + list.addLast(3) + list.addLast(4) + list.addLast(5) + list.addLast(6) + list.addLast(7) + + // Middle node for list having odd number of nodes + expect(list.findMiddle().data).toEqual(4) + + list.addLast(10) + + // Middle node for list having even number of nodes + expect(list.findMiddle().data).toEqual(5) + }) + + it('Check Iterator', () => { + const list = new LinkedList() + + let iterate = list.iterator() + expect(iterate).toBe(-1) + + const arr = [10, 20, 5] + list.addLast(arr[0]) + list.addLast(arr[1]) + list.addLast(arr[2]) + iterate = list.iterator() + + for (let i = 0; i < arr.length; i++) { + expect(iterate.next().value).toBe(arr[i]) + } + expect(iterate.next().value).toBe(undefined) + + iterate = list.iterator() + let count = 0 + for (const item of iterate) { + expect(item).toBe(arr[count]) + count++ + } + }) + it('Cleans the linkedList', () => { + const list = new LinkedList() + list.addLast(10) + list.addLast(20) + list.addLast(30) + list.addLast(40) + list.addLast(50) + expect(list.size()).toEqual(5) + list.clean() + expect(list.isEmpty()).toBe(true) + }) + + it('should shift every node by k steps towards right, shifts tail nodes towards the start and change head of the list', () => { + // Case 0: When head of list is null + const tempNode = new LinkedList() + expect(tempNode.get()).toEqual([]) + + // Creating list + const headNode = new LinkedList([10, 20, 30, 40, 50]) + + // Case 1: when k = 0 => List should be unaffected + headNode.rotateListRight(0) + expect(headNode.get()).toEqual([10, 20, 30, 40, 50]) + + // Case 2: Rotate right by 2 steps + headNode.rotateListRight(2) + expect(headNode.get()).toEqual([40, 50, 10, 20, 30]) + + // Case 3: Rotate right by 12 steps + headNode.rotateListRight(12) + expect(headNode.get()).toEqual([20, 30, 40, 50, 10]) + + // Case 4: when k = length of the list = 5 => List should be unaffected + 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/Queue/CircularQueue.js b/Data-Structures/Queue/CircularQueue.js new file mode 100644 index 0000000000..c716251668 --- /dev/null +++ b/Data-Structures/Queue/CircularQueue.js @@ -0,0 +1,89 @@ +// Circular Queues offer a quick to store FIFO data with a maximum size. +// Conserves memory as we only store up to our capacity +// It is opposed to a queue which could continue to grow if input outpaces output +// Doesnโ€™t use dynamic memory so No memory leaks + +class CircularQueue { + constructor(maxLength) { + this.queue = [] + this.front = 0 + this.rear = 0 + this.maxLength = maxLength + } + + // ADD ELEMENTS TO QUEUE + enqueue(value) { + if (this.checkOverflow()) return + if (this.checkEmpty()) { + this.front += 1 + this.rear += 1 + } else { + if (this.rear === this.maxLength) { + this.rear = 1 + } else this.rear += 1 + } + this.queue[this.rear] = value + } + + // REMOVES ELEMENTS + dequeue() { + if (this.checkEmpty()) { + // UNDERFLOW + return + } + const y = this.queue[this.front] + this.queue[this.front] = '*' + if (!this.checkSingleelement()) { + if (this.front === this.maxLength) this.front = 1 + else { + this.front += 1 + } + } + + return y // Returns the removed element and replaces it with a star + } + + // checks if the queue is empty or not + checkEmpty() { + if (this.front === 0 && this.rear === 0) { + return true + } + } + + checkSingleelement() { + if (this.front === this.rear && this.rear !== 0) { + this.front = this.rear = 0 + return true + } + } + + // Checks if max capacity of queue has been reached or not + checkOverflow() { + if ( + (this.front === 1 && this.rear === this.maxLength) || + this.front === this.rear + 1 + ) { + // CIRCULAR QUEUE OVERFLOW + return true + } + } + + // Prints the entire array ('*' represents blank space) + display(output = (value) => console.log(value)) { + for (let index = 1; index < this.queue.length; index++) { + output(this.queue[index]) + } + } + + // Displays the length of queue + length() { + return this.checkEmpty() ? 0 : this.queue.length - 1 + } + + // Display the top most value of queue + peek() { + return this.queue[this.front] + } +} + +export { CircularQueue } diff --git a/Data-Structures/Queue/Queue.js b/Data-Structures/Queue/Queue.js index aaab824cd6..20af54a200 100644 --- a/Data-Structures/Queue/Queue.js +++ b/Data-Structures/Queue/Queue.js @@ -1,80 +1,114 @@ /* Queue -* A Queue is a data structure that allows you to add an element to the end of -* a list and remove the item at the front. A queue follows a "First In First Out" -* system, where the first item to enter the queue is the first to be removed. This -* implementation uses an array to store the queue. -*/ - -// Functions: enqueue, dequeue, peek, view, length - -var Queue = (function () { - // constructor - function Queue () { - // This is the array representation of the queue - this.queue = [] + * A Queue is a data structure that allows you to add an element to the end of + * a list and remove the item at the front. A queue follows a FIFO (First In First Out) + * system, where the first item to enter the queue is the first to be removed, + * All these operation complexities are O(1). + * This implementation following the linked list structure. + */ + +class Queue { + #size + + constructor() { + this.head = null + this.tail = null + this.#size = 0 + + return Object.seal(this) } - // methods - // Add a value to the end of the queue - Queue.prototype.enqueue = function (item) { - this.queue[this.queue.length] = item + get length() { + return this.#size } - // Removes the value at the front of the queue - Queue.prototype.dequeue = function () { - if (this.queue.length === 0) { + /** + * @description - Add a value to the end of the queue + * @param {*} data + * @returns {number} - The current size of queue + */ + enqueue(data) { + const node = { data, next: null } + + if (!this.head && !this.tail) { + this.head = node + this.tail = node + } else { + this.tail.next = node + this.tail = node + } + + return ++this.#size + } + + /** + * @description - Removes the value at the front of the queue + * @returns {*} - The first data of the queue + */ + dequeue() { + if (this.isEmpty()) { throw new Error('Queue is Empty') } - var result = this.queue[0] - this.queue.splice(0, 1) // remove the item at position 0 from the array + const firstData = this.peekFirst() + + this.head = this.head.next - return result + if (!this.head) { + this.tail = null + } + + this.#size-- + + return firstData } - // Return the length of the queue - Queue.prototype.length = function () { - return this.queue.length + /** + * @description - Return the item at the front of the queue + * @returns {*} + */ + peekFirst() { + if (this.isEmpty()) { + throw new Error('Queue is Empty') + } + + return this.head.data } - // Return the item at the front of the queue - Queue.prototype.peek = function () { - return this.queue[0] + /** + * @description - Return the item at the tail of the queue + * @returns {*} + */ + peekLast() { + if (this.isEmpty()) { + throw new Error('Queue is Empty') + } + + return this.tail.data } - // List all the items in the queue - Queue.prototype.view = function () { - console.log(this.queue) + /** + * @description - Return the array of Queue + * @returns {Array<*>} + */ + toArray() { + const array = [] + let node = this.head + + while (node) { + array.push(node.data) + node = node.next + } + + return array } - return Queue -}()) - -// Implementation -var myQueue = new Queue() - -myQueue.enqueue(1) -myQueue.enqueue(5) -myQueue.enqueue(76) -myQueue.enqueue(69) -myQueue.enqueue(32) -myQueue.enqueue(54) - -myQueue.view() - -console.log('Length: ' + myQueue.length()) -console.log('Front item: ' + myQueue.peek()) -console.log('Removed ' + myQueue.dequeue() + ' from front.') -console.log('New front item: ' + myQueue.peek()) -console.log('Removed ' + myQueue.dequeue() + ' from front.') -console.log('New front item: ' + myQueue.peek()) -myQueue.enqueue(55) -console.log('Inserted 55') -console.log('New front item: ' + myQueue.peek()) - -for (var i = 0; i < 5; i++) { - myQueue.dequeue() - myQueue.view() + /** + * @description - Return is queue empty or not + * @returns {boolean} + */ + isEmpty() { + return this.length === 0 + } } -// console.log(myQueue.dequeue()); // throws exception! +export default Queue diff --git a/Data-Structures/Queue/QueueUsing2Stacks.js b/Data-Structures/Queue/QueueUsing2Stacks.js index 6218688ef0..9a51ee3a72 100644 --- a/Data-Structures/Queue/QueueUsing2Stacks.js +++ b/Data-Structures/Queue/QueueUsing2Stacks.js @@ -2,63 +2,33 @@ // contribution made by hamza chabchoub for a university project class Queue { - constructor () { + constructor() { this.inputStack = [] this.outputStack = [] } // Push item into the inputstack - enqueue (item) { + enqueue(item) { this.inputStack.push(item) } - dequeue (item) { + dequeue() { // push all items to outputstack this.outputStack = [] - if (this.inputStack.length > 0) { - while (this.inputStack.length > 0) { - this.outputStack.push(this.inputStack.pop()) - } + while (this.inputStack.length > 0) { + this.outputStack.push(this.inputStack.pop()) } - // display the top element of the outputstack + // return the top element of the outputstack if any if (this.outputStack.length > 0) { - console.log(this.outputStack.pop()) + const top = this.outputStack.pop() // repush all the items to the inputstack this.inputStack = [] while (this.outputStack.length > 0) { this.inputStack.push(this.outputStack.pop()) } - } - } - - // display elements of the inputstack - listIn () { - let i = 0 - while (i < this.inputStack.length) { - console.log(this.inputStack[i]) - i++ - } - } - - // display element of the outputstack - listOut () { - let i = 0 - while (i < this.outputStack.length) { - console.log(this.outputStack[i]) - i++ + return top } } } -// testing - -const queue = new Queue() -queue.enqueue(1) -queue.enqueue(2) -queue.enqueue(8) -queue.enqueue(9) - -console.log(queue.dequeue()) -// ans = 1 -console.log(queue.dequeue()) -// ans = 2 +export { Queue } diff --git a/Data-Structures/Queue/test/Queue.test.js b/Data-Structures/Queue/test/Queue.test.js new file mode 100644 index 0000000000..2a8b44ee7f --- /dev/null +++ b/Data-Structures/Queue/test/Queue.test.js @@ -0,0 +1,46 @@ +import Queue from '../Queue' + +describe('Testing the Queue DS', () => { + const queue = new Queue() + + it('Testing enqueue method', () => { + expect(queue.enqueue(1)).toBe(1) + expect(queue.enqueue(2)).toBe(2) + expect(queue.enqueue(8)).toBe(3) + expect(queue.enqueue(9)).toBe(4) + }) + + it('Testing length after enqueue', () => { + expect(queue.length).toBe(4) + }) + + it('Testing peekFirst & peekLast methods', () => { + expect(queue.peekFirst()).toBe(1) + expect(queue.peekLast()).toBe(9) + }) + + it('Testing toArray method', () => { + expect(queue.toArray()).toEqual([1, 2, 8, 9]) + }) + + it('Testing dequeue method', () => { + expect(queue.dequeue()).toBe(1) + expect(queue.dequeue()).toBe(2) + }) + + it('Testing length after dequeue', () => { + expect(queue.length).toBe(2) + }) + + it('Testing isEmpty method', () => { + const queue = new Queue() + expect(queue.isEmpty()).toBeTruthy() + + queue.enqueue(1) + queue.enqueue(2) + queue.enqueue(8) + queue.enqueue(9) + + expect(queue.isEmpty()).toBeFalsy() + }) +}) diff --git a/Data-Structures/Queue/test/QueueUsing2Stacks.test.js b/Data-Structures/Queue/test/QueueUsing2Stacks.test.js new file mode 100644 index 0000000000..06f26a6d8c --- /dev/null +++ b/Data-Structures/Queue/test/QueueUsing2Stacks.test.js @@ -0,0 +1,15 @@ +import { Queue } from '../QueueUsing2Stacks' + +describe('QueueUsing2Stacks', () => { + const queue = new Queue() + + it('Check enqueue/dequeue', () => { + queue.enqueue(1) + queue.enqueue(2) + queue.enqueue(8) + queue.enqueue(9) + + expect(queue.dequeue()).toBe(1) + expect(queue.dequeue()).toBe(2) + }) +}) diff --git a/Data-Structures/Stack/EvaluateExpression.js b/Data-Structures/Stack/EvaluateExpression.js new file mode 100644 index 0000000000..f8e976e16e --- /dev/null +++ b/Data-Structures/Stack/EvaluateExpression.js @@ -0,0 +1,58 @@ +/** + * Evaluate a numeric operations string in postfix notation using a stack. + * Supports basic arithmetic operations: +, -, *, / + * @see https://www.geeksforgeeks.org/evaluation-of-postfix-expression/ + * @param {string} expression - Numeric operations expression to evaluate. Must be a valid postfix expression. + * @returns {number|null} - Result of the expression evaluation, or null if the expression is invalid. + */ +function evaluatePostfixExpression(expression) { + const stack = [] + + // Helper function to perform an operation and push the result to the stack. Returns success. + function performOperation(operator) { + const rightOp = stack.pop() // Right operand is the top of the stack + const leftOp = stack.pop() // Left operand is the next item on the stack + + if (leftOp === undefined || rightOp === undefined) { + return false // Invalid expression + } + switch (operator) { + case '+': + stack.push(leftOp + rightOp) + break + case '-': + stack.push(leftOp - rightOp) + break + case '*': + stack.push(leftOp * rightOp) + break + case '/': + if (rightOp === 0) { + return false + } + stack.push(leftOp / rightOp) + break + default: + return false // Unknown operator + } + return true + } + + const tokens = expression.split(/\s+/) + + for (const token of tokens) { + if (!isNaN(parseFloat(token))) { + // If the token is a number, push it to the stack + stack.push(parseFloat(token)) + } else { + // If the token is an operator, perform the operation + if (!performOperation(token)) { + return null // Invalid expression + } + } + } + + return stack.length === 1 ? stack[0] : null +} + +export { evaluatePostfixExpression } diff --git a/Data-Structures/Stack/Stack.js b/Data-Structures/Stack/Stack.js index f95b940599..d8531fdebf 100644 --- a/Data-Structures/Stack/Stack.js +++ b/Data-Structures/Stack/Stack.js @@ -1,15 +1,15 @@ /* Stack!! -* A stack is exactly what it sounds like. An element gets added to the top of -* the stack and only the element on the top may be removed. This is an example -* of an array implementation of a Stack. So an element can only be added/removed -* from the end of the array. -*/ + * A stack is exactly what it sounds like. An element gets added to the top of + * the stack and only the element on the top may be removed. This is an example + * of an array implementation of a Stack. So an element can only be added/removed + * from the end of the array. + */ // Functions: push, pop, peek, view, length // Creates a stack constructor -var Stack = (function () { - function Stack () { +class Stack { + constructor() { // The top of the Stack this.top = 0 // The array representation of the stack @@ -17,56 +17,39 @@ var Stack = (function () { } // Adds a value onto the end of the stack - Stack.prototype.push = function (value) { + push(value) { this.stack[this.top] = value this.top++ } // Removes and returns the value at the end of the stack - Stack.prototype.pop = function () { + pop() { if (this.top === 0) { return 'Stack is Empty' } this.top-- - var result = this.stack[this.top] + const result = this.stack[this.top] this.stack = this.stack.splice(0, this.top) return result } // Returns the size of the stack - Stack.prototype.size = function () { + size() { return this.top } // Returns the value at the end of the stack - Stack.prototype.peek = function () { + peek() { return this.stack[this.top - 1] } // To see all the elements in the stack - Stack.prototype.view = function () { - for (var i = 0; i < this.top; i++) { console.log(this.stack[i]) } + view(output = (value) => console.log(value)) { + for (let i = 0; i < this.top; i++) { + output(this.stack[i]) + } } +} - return Stack -}()) - -// Implementation -var myStack = new Stack() - -myStack.push(1) -myStack.push(5) -myStack.push(76) -myStack.push(69) -myStack.push(32) -myStack.push(54) -console.log(myStack.size()) -console.log(myStack.peek()) -console.log(myStack.pop()) -console.log(myStack.peek()) -console.log(myStack.pop()) -console.log(myStack.peek()) -myStack.push(55) -console.log(myStack.peek()) -myStack.view() +export { Stack } diff --git a/Data-Structures/Stack/StackES6.js b/Data-Structures/Stack/StackES6.js index 6e54bab842..b70e14512a 100644 --- a/Data-Structures/Stack/StackES6.js +++ b/Data-Structures/Stack/StackES6.js @@ -10,19 +10,19 @@ // Class declaration class Stack { - constructor () { + constructor() { this.stack = [] this.top = 0 } // Adds a value to the end of the Stack - push (newValue) { + push(newValue) { this.stack.push(newValue) this.top += 1 } // Returns and removes the last element of the Stack - pop () { + pop() { if (this.top !== 0) { this.top -= 1 return this.stack.pop() @@ -31,17 +31,17 @@ class Stack { } // Returns the number of elements in the Stack - get length () { + get length() { return this.top } // Returns true if stack is empty, false otherwise - get isEmpty () { + get isEmpty() { return this.top === 0 } // Returns the last element without removing it - get last () { + get last() { if (this.top !== 0) { return this.stack[this.stack.length - 1] } @@ -49,20 +49,9 @@ class Stack { } // Checks if an object is the instance os the Stack class - static isStack (el) { + static isStack(el) { return el instanceof Stack } } -const newStack = new Stack() -console.log('Is it a Stack?,', Stack.isStack(newStack)) -console.log('Is stack empty? ', newStack.isEmpty) -newStack.push('Hello world') -newStack.push(42) -newStack.push({ a: 6, b: 7 }) -console.log('The length of stack is ', newStack.length) -console.log('Is stack empty? ', newStack.isEmpty) -console.log('Give me the last one ', newStack.last) -console.log('Pop the latest ', newStack.pop()) -console.log('Pop the latest ', newStack.pop()) -console.log('Pop the latest ', newStack.pop()) -console.log('Is stack empty? ', newStack.isEmpty) + +export { Stack } diff --git a/Data-Structures/Stack/test/EvaluateExpression.test.js b/Data-Structures/Stack/test/EvaluateExpression.test.js new file mode 100644 index 0000000000..69a2e16365 --- /dev/null +++ b/Data-Structures/Stack/test/EvaluateExpression.test.js @@ -0,0 +1,21 @@ +import { evaluatePostfixExpression } from '../EvaluateExpression.js' + +describe('evaluatePostfixExpression', () => { + it('should evaluate a valid expression', () => { + const expression = '3 4 * 2 / 5 +' // (3 * 4) / 2 + 5 = 11 + const result = evaluatePostfixExpression(expression) + expect(result).toBe(11) + }) + + it('should handle division by zero', () => { + const expression = '3 0 /' // Division by zero + const result = evaluatePostfixExpression(expression) + expect(result).toBe(null) + }) + + it('should handle an invalid expression', () => { + const expression = '3 * 4 2 / +' // Invalid expression + const result = evaluatePostfixExpression(expression) + expect(result).toBe(null) + }) +}) diff --git a/Data-Structures/Tree/AVLTree.js b/Data-Structures/Tree/AVLTree.js new file mode 100644 index 0000000000..dd673a9330 --- /dev/null +++ b/Data-Structures/Tree/AVLTree.js @@ -0,0 +1,280 @@ +/** + * Adelson-Velsky and Landis Tree + * [Wikipedia](https://en.wikipedia.org/wiki/AVL_tree) + * [A video lecture](http://www.youtube.com/watch?v=TbvhGcf6UJU) + */ +'use strict' + +/** + * A utility class for comparator + * A comparator is expected to have following structure + * + * comp(a, b) RETURN < 0 if a < b + * RETURN > 0 if a > b + * MUST RETURN 0 if a == b + */ +let utils +;(function (_utils) { + function comparator() { + return function (v1, v2) { + if (v1 < v2) return -1 + if (v2 < v1) return 1 + return 0 + } + } + _utils.comparator = comparator +})(utils || (utils = {})) + +/** + * @constructor + * A class for AVL Tree + * @argument comp - A function used by AVL Tree For Comparison + * If no argument is sent it uses utils.comparator + */ +class AVLTree { + constructor(comp) { + /** @public comparator function */ + this._comp = undefined + this._comp = comp !== undefined ? comp : utils.comparator() + + /** @public root of the AVL Tree */ + this.root = null + /** @public number of elements in AVL Tree */ + this.size = 0 + } + + /* Public Functions */ + /** + * For Adding Elements to AVL Tree + * @param {any} _val + * Since in AVL Tree an element can only occur once so + * if a element exists it return false + * @returns {Boolean} element added or not + */ + add(_val) { + const prevSize = this.size + this.root = insert(this.root, _val, this) + return this.size !== prevSize + } + + /** + * TO check is a particular element exists or not + * @param {any} _val + * @returns {Boolean} exists or not + */ + find(_val) { + const temp = searchAVLTree(this.root, _val, this) + return temp != null + } + + /** + * + * @param {any} _val + * It is possible that element doesn't exists in tree + * in that case it return false + * @returns {Boolean} if element was found and deleted + */ + remove(_val) { + const prevSize = this.size + this.root = deleteElement(this.root, _val, this) + return prevSize !== this.size + } +} + +// creates new Node Object +class Node { + constructor(val) { + this._val = val + this._left = null + this._right = null + this._height = 1 + } +} + +// get height of a node +const getHeight = function (node) { + if (node == null) { + return 0 + } + return node._height +} + +// height difference or balance factor of a node +const getHeightDifference = function (node) { + return node == null ? 0 : getHeight(node._left) - getHeight(node._right) +} + +// update height of a node based on children's heights +const updateHeight = function (node) { + if (node == null) { + return + } + node._height = Math.max(getHeight(node._left), getHeight(node._right)) + 1 +} + +// Helper: To check if the balanceFactor is valid +const isValidBalanceFactor = (balanceFactor) => + [0, 1, -1].includes(balanceFactor) + +// rotations of AVL Tree +const leftRotate = function (node) { + const temp = node._right + node._right = temp._left + temp._left = node + updateHeight(node) + updateHeight(temp) + return temp +} +const rightRotate = function (node) { + const temp = node._left + node._left = temp._right + temp._right = node + updateHeight(node) + updateHeight(temp) + return temp +} + +// check if tree is balanced else balance it for insertion +const insertBalance = function (node, _val, balanceFactor, tree) { + if (balanceFactor > 1 && tree._comp(_val, node._left._val) < 0) { + return rightRotate(node) // Left Left Case + } + if (balanceFactor < 1 && tree._comp(_val, node._right._val) > 0) { + return leftRotate(node) // Right Right Case + } + if (balanceFactor > 1 && tree._comp(_val, node._left._val) > 0) { + node._left = leftRotate(node._left) // Left Right Case + return rightRotate(node) + } + node._right = rightRotate(node._right) + return leftRotate(node) +} + +// check if tree is balanced after deletion +const delBalance = function (node) { + const balanceFactor1 = getHeightDifference(node) + if (isValidBalanceFactor(balanceFactor1)) { + return node + } + if (balanceFactor1 > 1) { + if (getHeightDifference(node._left) >= 0) { + return rightRotate(node) // Left Left + } + node._left = leftRotate(node._left) + return rightRotate(node) // Left Right + } + if (getHeightDifference(node._right) > 0) { + node._right = rightRotate(node._right) + return leftRotate(node) // Right Left + } + return leftRotate(node) // Right Right +} + +// implement avl tree insertion +const insert = function (root, val, tree) { + if (root == null) { + tree.size++ + return new Node(val) + } + if (tree._comp(root._val, val) < 0) { + root._right = insert(root._right, val, tree) + } else if (tree._comp(root._val, val) > 0) { + root._left = insert(root._left, val, tree) + } else { + return root + } + updateHeight(root) + const balanceFactor = getHeightDifference(root) + return isValidBalanceFactor(balanceFactor) + ? root + : insertBalance(root, val, balanceFactor, tree) +} + +// delete am element +const deleteElement = function (root, _val, tree) { + if (root == null) { + return root + } + if (tree._comp(root._val, _val) === 0) { + // key found case + if (root._left === null && root._right === null) { + root = null + tree.size-- + } else if (root._left === null) { + root = root._right + tree.size-- + } else if (root._right === null) { + root = root._left + tree.size-- + } else { + let temp = root._right + while (temp._left != null) { + temp = temp._left + } + root._val = temp._val + root._right = deleteElement(root._right, temp._val, tree) + } + } else { + if (tree._comp(root._val, _val) < 0) { + root._right = deleteElement(root._right, _val, tree) + } else { + root._left = deleteElement(root._left, _val, tree) + } + } + updateHeight(root) + root = delBalance(root) + return root +} +// search tree for a element +const searchAVLTree = function (root, val, tree) { + if (root == null) { + return null + } + if (tree._comp(root._val, val) === 0) { + return root + } + if (tree._comp(root._val, val) < 0) { + return searchAVLTree(root._right, val, tree) + } + return searchAVLTree(root._left, val, tree) +} + +/** + * A Code for Testing the AVLTree + */ +// (function test () { +// const newAVL = new AVLTree() +// const size = Math.floor(Math.random() * 1000000) +// let uniques = 0 +// let i, temp, j +// const array = [] +// for (i = 0; i < size; i++) { +// temp = Math.floor(Math.random() * Number.MAX_VALUE) +// if (newAVL.add(temp)) { +// uniques++ +// array.push(temp) +// } +// } +// if (newAVL.size !== uniques) { +// throw new Error('elements not inserted properly') +// } +// const findTestSize = Math.floor(Math.random() * uniques) +// for (i = 0; i < findTestSize; i++) { +// j = Math.floor(Math.random() * uniques) +// if (!newAVL.find(array[j])) { +// throw new Error('inserted elements not found') +// } +// } +// const deleteTestSize = Math.floor(uniques * Math.random()) +// for (i = 0; i < deleteTestSize; i++) { +// j = Math.floor(Math.random() * uniques) +// temp = array[j] +// if (newAVL.find(temp)) { +// if (!newAVL.remove(temp)) { +// throw new Error('delete not working properly') +// } +// } +// } +// })() + +export { AVLTree } diff --git a/Data-Structures/Tree/BinarySearchTree.js b/Data-Structures/Tree/BinarySearchTree.js index 943332d54c..8a65c8e650 100644 --- a/Data-Structures/Tree/BinarySearchTree.js +++ b/Data-Structures/Tree/BinarySearchTree.js @@ -1,114 +1,161 @@ /* Binary Search Tree!! -* -* Nodes that will go on the Binary Tree. -* They consist of the data in them, the node to the left, the node -* to the right, and the parent from which they came from. -* -* A binary tree is a data structure in which an element -* has two successors(children). The left child is usually -* smaller than the parent, and the right child is usually -* bigger. -*/ + * + * Nodes that will go on the Binary Tree. + * They consist of the data in them, the node to the left, the node + * to the right, and the parent from which they came from. + * + * A binary tree is a data structure in which an element + * has two successors(children). The left child is usually + * smaller than the parent, and the right child is usually + * bigger. + */ // class Node -var Node = (function () { +const Node = (function Node() { // Node in the tree - function Node (val) { - this.value = val - this.left = null - this.right = null - } - - // Search the tree for a value - Node.prototype.search = function (val) { - if (this.value === val) { - return this - } else if (val < this.value && this.left != null) { - return this.left.search(val) - } else if (val > this.value && this.right != null) { - return this.right.search(val) + class Node { + constructor(val) { + this.value = val + this.left = null + this.right = null } - return null - } - // Visit a node - Node.prototype.visit = function () { - // Recursively go left - if (this.left != null) { - this.left.visit() + // Search the tree for a value + search(val) { + if (this.value === val) { + return this + } else if (val < this.value && this.left !== null) { + return this.left.search(val) + } else if (val > this.value && this.right !== null) { + return this.right.search(val) + } + return null } - // Print out value - console.log(this.value) - // Recursively go right - if (this.right != null) { - this.right.visit() + + // Visit a node + visit(output = (value) => console.log(value)) { + // Recursively go left + if (this.left !== null) { + this.left.visit(output) + } + // Print out value + output(this.value) + // Recursively go right + if (this.right !== null) { + this.right.visit(output) + } } - } - // Add a node - Node.prototype.addNode = function (n) { - if (n.value < this.value) { - if (this.left == null) { - this.left = n - } else { - this.left.addNode(n) + // Add a node + addNode(n) { + if (n.value < this.value) { + if (this.left === null) { + this.left = n + } else { + this.left.addNode(n) + } + } else if (n.value > this.value) { + if (this.right === null) { + this.right = n + } else { + this.right.addNode(n) + } } - } else if (n.value > this.value) { - if (this.right == null) { - this.right = n - } else { - this.right.addNode(n) + } + + // remove a node + removeNode(val) { + if (val === this.value) { + if (!this.left && !this.right) { + return null + } else { + if (this.left) { + const leftMax = maxVal(this.left) + this.value = leftMax + this.left = this.left.removeNode(leftMax) + } else { + const rightMin = minVal(this.right) + this.value = rightMin + this.right = this.right.removeNode(rightMin) + } + } + } else if (val < this.value) { + this.left = this.left && this.left.removeNode(val) + } else if (val > this.value) { + this.right = this.right && this.right.removeNode(val) } + return this + } + } + + // find maximum value in the tree + const maxVal = function (node) { + if (!node.right) { + return node.value } + return maxVal(node.right) } + // find minimum value in the tree + const minVal = function (node) { + if (!node.left) { + return node.value + } + return minVal(node.left) + } // returns the constructor return Node -}()) +})() // class Tree -var Tree = (function () { - function Tree () { - // Just store the root - this.root = null - }; +const Tree = (function () { + class Tree { + constructor() { + // Just store the root + this.root = null + } - // Inorder traversal - Tree.prototype.traverse = function () { - this.root.visit() - } + // Inorder traversal + traverse(output = (value) => console.log(value)) { + if (!this.root) { + // No nodes are there in the tree till now + return + } + this.root.visit(output) + } - // Start by searching the root - Tree.prototype.search = function (val) { - const found = this.root.search(val) - if (found === null) { - console.log(val + ' not found') - } else { - console.log('Found:' + found.value) + // Start by searching the root + search(val) { + if (this.root) { + const found = this.root.search(val) + if (found !== null) { + return found.value + } + } + + // not found + return null + } + + // Add a new value to the tree + addValue(val) { + const n = new Node(val) + if (this.root === null) { + this.root = n + } else { + this.root.addNode(n) + } } - } - // Add a new value to the tree - Tree.prototype.addValue = function (val) { - const n = new Node(val) - if (this.root == null) { - this.root = n - } else { - this.root.addNode(n) + // remove a value from the tree + removeValue(val) { + // remove something if root exists + this.root = this.root && this.root.removeNode(val) } } // returns the constructor return Tree -}()) +})() -// Implementation of BST -var bst = new Tree() -bst.addValue(6) -bst.addValue(3) -bst.addValue(9) -bst.addValue(2) -bst.addValue(8) -bst.addValue(4) -bst.traverse() -bst.search(8) +export { Tree } diff --git a/Data-Structures/Tree/SegmentTree.js b/Data-Structures/Tree/SegmentTree.js new file mode 100644 index 0000000000..3d27981a07 --- /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/Trie.js b/Data-Structures/Tree/Trie.js new file mode 100644 index 0000000000..0d4018f74f --- /dev/null +++ b/Data-Structures/Tree/Trie.js @@ -0,0 +1,132 @@ +class TrieNode { + constructor(key, parent) { + this.key = key + this.count = 0 + this.children = Object.create(null) + if (parent === undefined) { + this.parent = null + } else { + this.parent = parent + } + } +} + +class Trie { + constructor() { + // create only root with null key and parent + this.root = new TrieNode(null, null) + } + + // Recursively finds the occurrence of all words in a given node + static findAllWords(root, word, output) { + if (root === null) return + if (root.count > 0) { + if (typeof output === 'object') { + output.push({ word, count: root.count }) + } + } + let key + for (key in root.children) { + word += key + this.findAllWords(root.children[key], word, output) + word = word.slice(0, -1) + } + } + + insert(word) { + if (typeof word !== 'string') return + if (word === '') { + this.root.count += 1 + return + } + let node = this.root + const len = word.length + let i + for (i = 0; i < len; i++) { + if (node.children[word.charAt(i)] === undefined) { + node.children[word.charAt(i)] = new TrieNode(word.charAt(i), node) + } + node = node.children[word.charAt(i)] + } + node.count += 1 + } + + findPrefix(word) { + if (typeof word !== 'string') return null + let node = this.root + const len = word.length + let i + // After end of this loop node will be at desired prefix + for (i = 0; i < len; i++) { + if (node.children[word.charAt(i)] === undefined) return null // No such prefix exists + node = node.children[word.charAt(i)] + } + return node + } + + remove(word, count) { + if (typeof word !== 'string') return + if (typeof count !== 'number') count = 1 + else if (count <= 0) return + + // for empty string just delete count of root + if (word === '') { + if (this.root.count >= count) this.root.count -= count + else this.root.count = 0 + return + } + + let child = this.root + const len = word.length + let i, key + // child: node which is to be deleted + for (i = 0; i < len; i++) { + key = word.charAt(i) + if (child.children[key] === undefined) return + child = child.children[key] + } + + // Delete no of occurrences specified + if (child.count >= count) child.count -= count + else child.count = 0 + + // If some occurrences are left we don't delete it or else + // if the object forms some other objects prefix we don't delete it + // For checking an empty object + // https://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object + if ( + child.count <= 0 && + Object.keys(child.children).length && + child.children.constructor === Object + ) { + child.parent.children[child.key] = undefined + } + } + + findAllWords(prefix) { + const output = [] + // find the node with provided prefix + const node = this.findPrefix(prefix) + // No such prefix exists + if (node === null) return output + Trie.findAllWords(node, prefix, output) + return output + } + + contains(word) { + // find the node with given prefix + const node = this.findPrefix(word) + // No such word exists + return node !== null && node.count !== 0 + } + + findOccurrences(word) { + // find the node with given prefix + const node = this.findPrefix(word) + // No such word exists + if (node === null) return 0 + return node.count + } +} + +export { Trie } diff --git a/Data-Structures/Tree/test/AVLTree.test.js b/Data-Structures/Tree/test/AVLTree.test.js new file mode 100644 index 0000000000..862af38756 --- /dev/null +++ b/Data-Structures/Tree/test/AVLTree.test.js @@ -0,0 +1,71 @@ +import { AVLTree } from '../AVLTree' + +describe('AVLTree Implementation: ', () => { + const avlTree = new AVLTree() + const dataList = [] + const demoData = [1, 4, 6, 22, 7, 99, 4, 66, 77, 98, 87, 54, 32, 15] + + const avlStringTree = new AVLTree() + const collator = new Intl.Collator() + const stringData = ['S', 'W', 'z', 'B', 'a'] + + const emptyTree = new AVLTree(collator.compare) + + beforeAll(() => { + demoData.forEach((item) => { + if (avlTree.add(item)) { + dataList.push(item) + } + }) + + avlStringTree._comp = collator.compare + stringData.forEach((item) => avlStringTree.add(item)) + }) + + it('delete and search from empty tree', () => { + expect(emptyTree.remove(0)).toBeFalsy() + expect(emptyTree.find(0)).toBeFalsy() + }) + + it('checks if element is inserted properly', () => { + expect(dataList.length).toEqual(avlTree.size) + expect(stringData.length).toEqual(avlStringTree.size) + }) + + it('search if inserted element is present', () => { + demoData.forEach((data) => { + expect(avlTree.find(data)).toBeTruthy() + }) + stringData.forEach((data) => { + expect(avlStringTree.find(data)).toBeTruthy() + }) + }) + + it('delete element with two valid children', () => { + expect(avlTree.remove(77)).toBeTruthy() + }) + + it('delete element missing L-child', () => { + expect(avlTree.remove(98)).toBeTruthy() + }) + + it('delete elements forcing single R-rotation', () => { + expect(avlTree.remove(99)).toBeTruthy() + expect(avlTree.remove(87)).toBeTruthy() + }) + + it('delete elements forcing R-rotation and L-rotation', () => { + expect(avlTree.remove(1)).toBeTruthy() + expect(avlTree.remove(4)).toBeTruthy() + }) + + it('delete elements forcing single L-rotation', () => { + expect(avlTree.remove(7)).toBeTruthy() + expect(avlTree.remove(15)).toBeTruthy() + expect(avlTree.remove(6)).toBeTruthy() + }) + + it('delete element forcing single L-rotation and R-rotation', () => { + expect(avlTree.remove(66)).toBeTruthy() + }) +}) diff --git a/Data-Structures/Tree/test/BinarySearchTree.test.js b/Data-Structures/Tree/test/BinarySearchTree.test.js new file mode 100644 index 0000000000..4c6c353592 --- /dev/null +++ b/Data-Structures/Tree/test/BinarySearchTree.test.js @@ -0,0 +1,66 @@ +import { Tree } from '../BinarySearchTree.js' + +describe('Binary Search Tree', () => { + let tree + + beforeEach(() => { + tree = new Tree() + tree.addValue(10) + tree.addValue(5) + tree.addValue(15) + tree.addValue(3) + tree.addValue(8) + }) + + test('should add values to the tree', () => { + tree.addValue(12) + + expect(tree.search(12)).toBe(12) + expect(tree.search(5)).toBe(5) + expect(tree.search(15)).toBe(15) + }) + + test('should perform in-order traversal', () => { + const values = [] + const output = (val) => values.push(val) + tree.traverse(output) + expect(values).toEqual([3, 5, 8, 10, 15]) + }) + + test('should remove leaf nodes correctly', () => { + tree.removeValue(5) + expect(tree.search(5)).toBeNull() + }) + + test('should remove nodes with one child correctly', () => { + tree.addValue(12) + tree.removeValue(15) + + expect(tree.search(15)).toBeNull() + expect(tree.search(12)).toBe(12) + }) + + test('should remove nodes with two children correctly', () => { + tree.addValue(18) + tree.removeValue(15) + + expect(tree.search(15)).toBeNull() + expect(tree.search(18)).toBe(18) + }) + + test('should return null for non-existent values', () => { + expect(tree.search(20)).toBeNull() + expect(tree.search(0)).toBeNull() + }) + + test('should handle removal of root node correctly', () => { + tree.removeValue(10) + expect(tree.search(10)).toBeNull() + }) + + test('should handle empty tree gracefully', () => { + const newTree = new Tree() + newTree.removeValue(22) // Should not throw + expect(newTree.search(22)).toBeNull() + }) +}) diff --git a/Data-Structures/Tree/test/SegmentTree.test.js b/Data-Structures/Tree/test/SegmentTree.test.js new file mode 100644 index 0000000000..440fbd8283 --- /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/Data-Structures/Vectors/Vector2.js b/Data-Structures/Vectors/Vector2.js new file mode 100644 index 0000000000..f736b3e057 --- /dev/null +++ b/Data-Structures/Vectors/Vector2.js @@ -0,0 +1,142 @@ +/** + * In mathematics and physics, a vector is an element of a vector space. + * + * The Vector2-class implements 2-dimensional vectors together with various vector-operations. + * @see https://en.wikipedia.org/wiki/Vector_(mathematics_and_physics). + */ + +class Vector2 { + constructor(x, y) { + this.x = x + this.y = y + } + + /** + * Check for exact vector equality. + * + * @param vector The vector to compare to. + * @returns Whether they are exactly equal or not. + */ + equalsExactly(vector) { + return this.x === vector.x && this.y === vector.y + } + + /** + * Check for approximate vector equality. + * + * @param vector The vector to compare to. + * @param epsilon The allowed discrepancy for the x-values and the y-values. + * @returns Whether they are approximately equal or not. + */ + equalsApproximately(vector, epsilon) { + return ( + Math.abs(this.x - vector.x) < epsilon && + Math.abs(this.y - vector.y) < epsilon + ) + } + + /** + * Vector length. + * + * @returns The length of the vector. + */ + length() { + return Math.sqrt(this.x * this.x + this.y * this.y) + } + + /** + * Normalization sets the vector to length 1 while maintaining its direction. + * + * @returns The normalized vector. + */ + normalize() { + const length = this.length() + if (length === 0) { + throw new Error('Cannot normalize vectors of length 0') + } + return new Vector2(this.x / length, this.y / length) + } + + /** + * Vector addition + * + * @param vector The vector to be added. + * @returns The sum-vector. + */ + add(vector) { + const x = this.x + vector.x + const y = this.y + vector.y + return new Vector2(x, y) + } + + /** + * Vector subtraction + * + * @param vector The vector to be subtracted. + * @returns The difference-vector. + */ + subtract(vector) { + const x = this.x - vector.x + const y = this.y - vector.y + return new Vector2(x, y) + } + + /** + * Vector scalar multiplication + * + * @param scalar The factor by which to multiply the vector. + * @returns The scaled vector. + */ + multiply(scalar) { + const x = this.x * scalar + const y = this.y * scalar + return new Vector2(x, y) + } + + /** + * Distance between this vector and another vector. + * + * @param vector The vector to which to calculate the distance. + * @returns The distance. + */ + distance(vector) { + const difference = vector.subtract(this) + return difference.length() + } + + /** + * Vector dot product + * + * @param vector The vector used for the multiplication. + * @returns The resulting dot product. + */ + dotProduct(vector) { + return this.x * vector.x + this.y * vector.y + } + + /** + * Vector rotation (see https://en.wikipedia.org/wiki/Rotation_matrix) + * + * @param angleInRadians The angle in radians by which to rotate the vector. + * @returns The rotated vector. + */ + rotate(angleInRadians) { + const ca = Math.cos(angleInRadians) + const sa = Math.sin(angleInRadians) + const x = ca * this.x - sa * this.y + const y = sa * this.x + ca * this.y + return new Vector2(x, y) + } + + /** + * Measure angle between two vectors + * + * @param vector The 2nd vector for the measurement. + * @returns The angle in radians. + */ + angleBetween(vector) { + return Math.atan2(vector.y, vector.x) - Math.atan2(this.y, this.x) + } +} + +export { Vector2 } diff --git a/Data-Structures/Vectors/test/Vector2.test.js b/Data-Structures/Vectors/test/Vector2.test.js new file mode 100644 index 0000000000..f2fa548fb4 --- /dev/null +++ b/Data-Structures/Vectors/test/Vector2.test.js @@ -0,0 +1,150 @@ +import { Vector2 } from '../Vector2.js' + +describe('Vector2', () => { + describe('#equalsExactly', () => { + it('should compare equality correctly', () => { + expect(new Vector2(1, 0).equalsExactly(new Vector2(1, 0))).toBe(true) + + expect(new Vector2(1.23, 4.56).equalsExactly(new Vector2(0, 0))).toBe( + false + ) + }) + }) + + describe('#equalsApproximately', () => { + it('should compare equality (approximately) correctly', () => { + expect( + new Vector2(1, 0).equalsApproximately( + new Vector2(1, 0.0000001), + 0.000001 + ) + ).toBe(true) + + expect( + new Vector2(1.23, 4.56).equalsApproximately( + new Vector2(1.24, 4.56), + 0.000001 + ) + ).toBe(false) + }) + }) + + describe('#add', () => { + it('should add two vectors correctly', () => { + expect( + new Vector2(1, 0) + .add(new Vector2(0, 1)) + .equalsApproximately(new Vector2(1, 1), 0.000001) + ).toBe(true) + + expect( + new Vector2(-3.3, -9) + .add(new Vector2(-2.2, 3)) + .equalsApproximately(new Vector2(-5.5, -6), 0.000001) + ).toBe(true) + }) + }) + + describe('#subtract', () => { + it('should subtract two vectors correctly', () => { + expect( + new Vector2(1, 0) + .subtract(new Vector2(0, 1)) + .equalsApproximately(new Vector2(1, -1), 0.000001) + ).toBe(true) + + expect( + new Vector2(234.5, 1.7) + .subtract(new Vector2(3.3, 2.7)) + .equalsApproximately(new Vector2(231.2, -1), 0.000001) + ).toBe(true) + }) + }) + + describe('#multiply', () => { + it('should multiply two vectors correctly', () => { + expect( + new Vector2(1, 0) + .multiply(5) + .equalsApproximately(new Vector2(5, 0), 0.000001) + ).toBe(true) + + expect( + new Vector2(3.41, -7.12) + .multiply(-3.1) + .equalsApproximately(new Vector2(-10.571, 22.072), 0.000001) + ).toBe(true) + }) + }) + + describe('#length', () => { + it("should calculate it's length correctly", () => { + expect(new Vector2(1, 0).length()).toBe(1) + + expect(new Vector2(-1, 1).length()).toBe(Math.sqrt(2)) + }) + }) + + describe('#normalize', () => { + it('should normalize vectors correctly', () => { + expect( + new Vector2(1, 0) + .normalize() + .equalsApproximately(new Vector2(1, 0), 0.000001) + ).toBe(true) + + expect( + new Vector2(1, -1) + .normalize() + .equalsApproximately( + new Vector2(Math.sqrt(2) / 2, -Math.sqrt(2) / 2), + 0.000001 + ) + ).toBe(true) + }) + }) + + describe('#distance', () => { + it('should calculate the distance between two vectors correctly', () => { + expect(new Vector2(0, 0).distance(new Vector2(0, -1))).toBe(1) + + expect(new Vector2(1, 0).distance(new Vector2(0, 1))).toBe(Math.sqrt(2)) + }) + }) + + describe('#dotProduct', () => { + it('should calculate the dot product correctly', () => { + expect(new Vector2(1, 0).dotProduct(new Vector2(0, 1))).toBe(0) + + expect(new Vector2(1, 2).dotProduct(new Vector2(3, 4))).toBe(11) // 1 * 3 + 2 * 4 + }) + }) + + describe('#rotate', () => { + it('should rotate a vector correctly', () => { + expect( + new Vector2(0, -1) + .rotate(Math.PI / 2) + .equalsApproximately(new Vector2(1, 0), 0.000001) + ).toBe(true) + + expect( + new Vector2(1.23, -4.56) + .rotate(Math.PI) + .equalsApproximately(new Vector2(-1.23, 4.56), 0.000001) + ).toBe(true) + }) + }) + + describe('#angleBetween', () => { + it('should calculate the angle between two vectors correctly', () => { + expect(new Vector2(1, 0).angleBetween(new Vector2(0, 1))).toBe( + Math.PI / 2 + ) + + expect(new Vector2(1, 0).angleBetween(new Vector2(1, -1))).toBe( + -Math.PI / 4 + ) + }) + }) +}) diff --git a/Dynamic-Programming/Abbreviation.js b/Dynamic-Programming/Abbreviation.js new file mode 100644 index 0000000000..06092216b8 --- /dev/null +++ b/Dynamic-Programming/Abbreviation.js @@ -0,0 +1,47 @@ +/** + * @description + * Given two strings, `source` and `target`, determine if it's possible to make `source` equal + * to `target` You can perform the following operations on the string `source`: + * 1. Capitalize zero or more of `source`'s lowercase letters. + * 2. Delete all the remaining lowercase letters in `source`. + * + * Time Complexity: (O(|source|*|target|)) where `|source|` => length of string `source` + * + * @param {String} source - The string to be transformed. + * @param {String} target - The string we want to transform `source` into. + * @returns {Boolean} - Whether the transformation is possible. + * @see https://www.hackerrank.com/challenges/abbr/problem - Related problem on HackerRank. + */ +export const isAbbreviation = (source, target) => { + const sourceLength = source.length + const targetLength = target.length + + // Initialize a table to keep track of possible abbreviations + let canAbbreviate = Array.from({ length: sourceLength + 1 }, () => + Array(targetLength + 1).fill(false) + ) + // Empty strings are trivially abbreviatable + canAbbreviate[0][0] = true + + for (let sourceIndex = 0; sourceIndex < sourceLength; sourceIndex++) { + for (let targetIndex = 0; targetIndex <= targetLength; targetIndex++) { + if (canAbbreviate[sourceIndex][targetIndex]) { + // If characters at the current position are equal, move to the next position in both strings. + if ( + targetIndex < targetLength && + source[sourceIndex].toUpperCase() === target[targetIndex] + ) { + canAbbreviate[sourceIndex + 1][targetIndex + 1] = true + } + // If the current character in `source` is lowercase, explore two possibilities: + // a) Capitalize it (which is akin to "using" it in `source` to match `target`), or + // b) Skip it (effectively deleting it from `source`). + if (source[sourceIndex] === source[sourceIndex].toLowerCase()) { + canAbbreviate[sourceIndex + 1][targetIndex] = true + } + } + } + } + + return canAbbreviate[sourceLength][targetLength] +} diff --git a/Dynamic-Programming/CatalanNumbers.js b/Dynamic-Programming/CatalanNumbers.js new file mode 100644 index 0000000000..a21a0c965a --- /dev/null +++ b/Dynamic-Programming/CatalanNumbers.js @@ -0,0 +1,30 @@ +/* + * Author: IcarusTheFly (https://github.com/IcarusTheFly) + * Catalan Numbers explanation can be found in the following links: + * Wikipedia: https://en.wikipedia.org/wiki/Catalan_number + * Brilliant: https://brilliant.org/wiki/catalan-numbers + */ + +/** + * @function catalanNumbers + * @description Returns all catalan numbers from index 0 to n + * @param {number} n + * @returns {number[]} Array with the catalan numbers from 0 to n + */ + +export const catalanNumbers = (n) => { + if (n === 0) { + return [1] + } + const catList = [1, 1] + + for (let i = 2; i <= n; i++) { + let newNumber = 0 + for (let j = 0; j < i; j++) { + newNumber += catList[j] * catList[i - j - 1] + } + catList.push(newNumber) + } + + return catList +} diff --git a/Dynamic-Programming/ClimbingStairs.js b/Dynamic-Programming/ClimbingStairs.js new file mode 100644 index 0000000000..dccc0c4dbf --- /dev/null +++ b/Dynamic-Programming/ClimbingStairs.js @@ -0,0 +1,22 @@ +/** + * @function ClimbStairs + * @description You are climbing a stair case. It takes n steps to reach to the top.Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? + * @param {Integer} n - The input integer + * @return {Integer} distinct ways can you climb to the top. + * @see [Climb_Stairs](https://www.geeksforgeeks.org/count-ways-reach-nth-stair/) + */ + +const climbStairs = (n) => { + let prev = 0 + let cur = 1 + let temp + + for (let i = 0; i < n; i++) { + temp = prev + prev = cur + cur += temp + } + return cur +} + +export { climbStairs } diff --git a/Dynamic-Programming/CoinChange.js b/Dynamic-Programming/CoinChange.js index c7050177b5..35825d7fb6 100644 --- a/Dynamic-Programming/CoinChange.js +++ b/Dynamic-Programming/CoinChange.js @@ -1,45 +1,33 @@ -function change (coins, amount) { +/** + * @params {Array} coins + * @params {Number} amount + */ +export const change = (coins, amount) => { + // Create and initialize the storage const combinations = new Array(amount + 1).fill(0) combinations[0] = 1 - + // Determine the direction of smallest sub-problem for (let i = 0; i < coins.length; i++) { - const coin = coins[i] - - for (let j = coin; j < amount + 1; j++) { - combinations[j] += combinations[j - coin] + // Travel and fill the combinations array + for (let j = coins[i]; j < combinations.length; j++) { + combinations[j] += combinations[j - coins[i]] } } return combinations[amount] } - -function minimumCoins (coins, amount) { - // minimumCoins[i] will store the minimum coins needed for amount i - const minimumCoins = new Array(amount + 1).fill(0) - - minimumCoins[0] = 0 - - for (let i = 1; i < amount + 1; i++) { - minimumCoins[i] = Number.MAX_SAFE_INTEGER - } - for (let i = 1; i < amount + 1; i++) { - for (let j = 0; j < coins.length; j++) { - const coin = coins[j] - if (coin <= i) { - const subRes = minimumCoins[i - coin] - if (subRes !== Number.MAX_SAFE_INTEGER && subRes + 1 < minimumCoins[i]) { - minimumCoins[i] = subRes + 1 - } - } +/** + * @params {Array} coins + * @params {Number} amount + */ +export const coinChangeMin = (coins, amount) => { + const map = { 0: 1 } + for (let i = 1; i <= amount; i++) { + let min = Infinity + for (const coin of coins) { + if (i < coin) continue + min = Math.min(min, 1 + map[i - coin]) } + map[i] = min } - return minimumCoins[amount] -} - -function main () { - const amount = 12 - const coins = [2, 4, 5] - console.log('Number of combinations of getting change for ' + amount + ' is: ' + change(coins, amount)) - console.log('Minimum number of coins required for amount :' + amount + ' is: ' + minimumCoins(coins, amount)) + return map[amount] === Infinity ? -1 : map[amount] - 1 } - -main() diff --git a/Dynamic-Programming/EditDistance.js b/Dynamic-Programming/EditDistance.js new file mode 100644 index 0000000000..64b0d5a7fe --- /dev/null +++ b/Dynamic-Programming/EditDistance.js @@ -0,0 +1,54 @@ +/* +Wikipedia -> https://en.wikipedia.org/wiki/Edit_distance + +Q. -> Given two strings `word1` and `word2`. You can perform these operations on any of the string to make both strings similar. + - Insert + - Remove + - Replace +Find the minimum operation cost required to make both same. Each operation cost is 1. + +Algorithm details -> +time complexity - O(n*m) +space complexity - O(n*m) +*/ + +const minimumEditDistance = (word1, word2) => { + const n = word1.length + const m = word2.length + const dp = new Array(m + 1).fill(0).map((item) => []) + + /* + fill dp matrix with default values - + - first row is filled considering no elements in word2. + - first column filled considering no elements in word1. + */ + + for (let i = 0; i < n + 1; i++) { + dp[0][i] = i + } + + for (let i = 0; i < m + 1; i++) { + dp[i][0] = i + } + + /* + indexing is 1 based for dp matrix as we defined some known values at first row and first column/ + */ + + for (let i = 1; i < m + 1; i++) { + for (let j = 1; j < n + 1; j++) { + const letter1 = word1[j - 1] + const letter2 = word2[i - 1] + + if (letter1 === letter2) { + dp[i][j] = dp[i - 1][j - 1] + } else { + dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1], dp[i][j - 1]) + 1 + } + } + } + + return dp[m][n] +} + +export { minimumEditDistance } diff --git a/Dynamic-Programming/FastFibonacciNumber.js b/Dynamic-Programming/FastFibonacciNumber.js new file mode 100644 index 0000000000..b70116782d --- /dev/null +++ b/Dynamic-Programming/FastFibonacciNumber.js @@ -0,0 +1,25 @@ +/** + * @function fastFibonacci + * @description fastFibonacci is same as fibonacci algorithm by calculating the sum of previous two fibonacci numbers but in O(log(n)). + * @param {Integer} N - The input integer + * @return {Integer} fibonacci of N. + * @see [Fast_Fibonacci_Numbers](https://www.geeksforgeeks.org/fast-doubling-method-to-find-the-nth-fibonacci-number/) + */ + +// recursive function that returns (F(n), F(n-1)) +const fib = (N) => { + if (N === 0) return [0, 1] + const [a, b] = fib(Math.trunc(N / 2)) + const c = a * (b * 2 - a) + const d = a * a + b * b + return N % 2 ? [d, c + d] : [c, d] +} + +const fastFibonacci = (N) => { + if (!Number.isInteger(N)) { + throw new TypeError('Input should be integer') + } + return fib(N)[0] +} + +export { fastFibonacci } diff --git a/Dynamic-Programming/FibonacciNumber.js b/Dynamic-Programming/FibonacciNumber.js new file mode 100644 index 0000000000..6c389ca121 --- /dev/null +++ b/Dynamic-Programming/FibonacciNumber.js @@ -0,0 +1,27 @@ +/** + * @function fibonacci + * @description Fibonacci is the sum of previous two fibonacci numbers. + * @param {Integer} N - The input integer + * @return {Integer} fibonacci of N. + * @see [Fibonacci_Numbers](https://en.wikipedia.org/wiki/Fibonacci_number) + */ +const fibonacci = (N) => { + if (!Number.isInteger(N)) { + throw new TypeError('Input should be integer') + } + + // memoize the last two numbers + let firstNumber = 0 + let secondNumber = 1 + + for (let i = 1; i < N; i++) { + const sumOfNumbers = firstNumber + secondNumber + // update last two numbers + firstNumber = secondNumber + secondNumber = sumOfNumbers + } + + return N ? secondNumber : firstNumber +} + +export { fibonacci } diff --git a/Dynamic-Programming/FindMonthCalendar.js b/Dynamic-Programming/FindMonthCalendar.js new file mode 100644 index 0000000000..20c2fb4129 --- /dev/null +++ b/Dynamic-Programming/FindMonthCalendar.js @@ -0,0 +1,119 @@ +/* + * This algorithm accepts a month in the format mm/yyyy. + * And prints out the month's calendar. + * It uses an epoch of 1/1/1900, Monday. + */ +import { isLeapYear } from '../Maths/LeapYear' + +class Month { + constructor() { + this.Days = ['M', 'T', 'W', 'Th', 'F', 'S', 'Su'] + this.BDays = ['M', 'Su', 'S', 'F', 'Th', 'W', 'T'] + this.epoch = { month: 1, year: 1900 } + this.monthDays = [31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + this.monthDaysLeap = [31, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + } + + printCal(days, startDay, output = (value) => console.log(value)) { + output('M T W Th F S Su') + const dates = [] + let i + for (i = 1; i <= days; i++) { + dates.push(i) + } + for (i = 0; i < this.Days.indexOf(startDay); i++) { + dates.unshift(' ') + } + while (true) { + let row = '' + for (i = 0; i < 7 && dates.length !== 0; i++) { + row += dates.shift() + while (row.length % 4 !== 0) { + row += ' ' + } + } + output(row) + if (dates.length === 0) break + } + } + + parseDate(date) { + const dateAr = [] + let block = '' + let i + for (i = 0; i < date.length; i++) { + if (date[i] === '/') { + dateAr.push(parseInt(block)) + block = '' + continue + } + block += date[i] + } + dateAr.push(parseInt(block)) + if (dateAr.length !== 2) throw new Error('Improper string encoding') + const dateOb = { month: dateAr[0], year: dateAr[1] } + return dateOb + } + + isGreater(startDate, endDate) { + if (startDate.year > endDate.year) { + return true + } else if (startDate.year < endDate.year) { + return false + } else if (startDate.month > endDate.month) { + return true + } else if (startDate.month < endDate.month) { + return false + } + return true + } + + getDayDiff(startDate, endDate) { + if (this.isGreater(startDate, endDate) === null) { + return 0 + } else if (this.isGreater(startDate, endDate) === true) { + const midDate = startDate + startDate = endDate + endDate = midDate + } + let diff = 0 + while (startDate.year !== endDate.year) { + diff += isLeapYear(startDate.year) ? 366 : 365 + startDate.year = startDate.year + 1 + } + while (startDate.month !== endDate.month) { + if (startDate.month < endDate.month) { + if (isLeapYear(startDate.year)) + diff += this.monthDaysLeap[startDate.month] + else diff += this.monthDays[startDate.month] + startDate.month = startDate.month + 1 + } else { + if (isLeapYear(startDate.year)) + diff -= this.monthDaysLeap[startDate.month - 1] + else diff -= this.monthDays[startDate.month - 1] + startDate.month = startDate.month - 1 + } + } + return diff + } + + generateMonthCal(date) { + const Month = this.parseDate(date) + let day = '' + let difference = this.getDayDiff(this.epoch, Month) + difference = difference % 7 + let Month2 = this.parseDate(date) + day = this.isGreater(Month2, this.epoch) + ? this.Days[difference] + : this.BDays[difference] + Month2 = this.parseDate(date) + if (isLeapYear(Month2.year)) + this.printCal(this.monthDaysLeap[Month2.month], day) + else this.printCal(this.monthDays[Month2.month], day) + } +} + +export { Month } + +// const x = new Month() +// x.generateMonthCal('1/2021') diff --git a/Dynamic-Programming/KadaneAlgo.js b/Dynamic-Programming/KadaneAlgo.js index 75a86b34fb..b3c7462cb6 100644 --- a/Dynamic-Programming/KadaneAlgo.js +++ b/Dynamic-Programming/KadaneAlgo.js @@ -1,20 +1,25 @@ -function KadaneAlgo (array) { - let cummulativeSum = 0 - let maxSum = 0 - for (var i = 0; i < array.length; i++) { - cummulativeSum = cummulativeSum + array[i] - if (cummulativeSum < 0) { - cummulativeSum = 0 - } else if (maxSum < cummulativeSum) { - maxSum = cummulativeSum +/* Kadane's algorithm is one of the most efficient ways to + * calculate the maximum contiguous subarray sum for a given array. + * Below is the implementation of Kadane's algorithm along with + * some sample test cases. + * There might be a special case in this problem if al the elements + * of the given array are negative. In such a case, the maximum negative + * value present in the array is the answer. + * + * Reference article :- https://www.geeksforgeeks.org/largest-sum-contiguous-subarray/ + */ + +export function kadaneAlgo(array) { + let cumulativeSum = 0 + let maxSum = Number.NEGATIVE_INFINITY // maxSum has the least possible value + for (let i = 0; i < array.length; i++) { + cumulativeSum = cumulativeSum + array[i] + if (maxSum < cumulativeSum) { + maxSum = cumulativeSum + } else if (cumulativeSum < 0) { + cumulativeSum = 0 } } return maxSum - // This function returns largest sum contigous sum in a array + // This function returns largest sum contiguous sum in a array } -function main () { - var myArray = [1, 2, 3, 4, -6] - var result = KadaneAlgo(myArray) - console.log(result) -} -main() diff --git a/Dynamic-Programming/LevenshteinDistance.js b/Dynamic-Programming/LevenshteinDistance.js index 1341056d2d..fb8b75bfa4 100644 --- a/Dynamic-Programming/LevenshteinDistance.js +++ b/Dynamic-Programming/LevenshteinDistance.js @@ -1,9 +1,13 @@ /** - * A Dynamic Programming based solution for calculation of the Levenshtein Distance - * https://en.wikipedia.org/wiki/Levenshtein_distance + * @function calculateLevenshteinDp + * @description A Dynamic Programming based solution for calculation of the Levenshtein Distance. + * @param {String} x - Word to be converted. + * @param {String} y - Desired result after operations. + * @return {Integer} The Levenshtein distance between x and y. + * @see [Levenshtein_distance](https://en.wikipedia.org/wiki/Levenshtein_distance) */ -function minimum (a, b, c) { +function minimum(a, b, c) { if (a < b && a < c) { return a } else if (b < a && b < c) { @@ -13,11 +17,12 @@ function minimum (a, b, c) { } } -function costOfSubstitution (x, y) { +function costOfSubstitution(x, y) { return x === y ? 0 : 1 } -function calculate (x, y) { +// Levenshtein distance between x and y +function calculateLevenshteinDp(x, y) { const dp = new Array(x.length + 1) for (let i = 0; i < x.length + 1; i++) { dp[i] = new Array(y.length + 1) @@ -30,7 +35,12 @@ function calculate (x, y) { } else if (j === 0) { dp[i][j] = i } else { - dp[i][j] = minimum(dp[i - 1][j - 1] + costOfSubstitution(x.charAt(i - 1), y.charAt(j - 1)), dp[i - 1][j] + 1, dp[i][j - 1] + 1) + dp[i][j] = minimum( + dp[i - 1][j - 1] + + costOfSubstitution(x.charAt(i - 1), y.charAt(j - 1)), + dp[i - 1][j] + 1, + dp[i][j - 1] + 1 + ) } } } @@ -38,12 +48,4 @@ function calculate (x, y) { return dp[x.length][y.length] } -function main () { - const x = '' // enter your string here - const y = '' // enter your string here - - console.log('Levenshtein distance between ' + x + ' and ' + y + ' is: ') - console.log(calculate(x, y)) -} - -main() +export { calculateLevenshteinDp } diff --git a/Dynamic-Programming/LongestCommonSubsequence.js b/Dynamic-Programming/LongestCommonSubsequence.js new file mode 100644 index 0000000000..9c2a35fc6f --- /dev/null +++ b/Dynamic-Programming/LongestCommonSubsequence.js @@ -0,0 +1,59 @@ +/* +Problem: +Given two sequences, find the length of longest subsequence present in both of them. +A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous. +For example, โ€œabcโ€, โ€œabgโ€, โ€œbdfโ€, โ€œaegโ€, โ€˜โ€acefgโ€, .. etc are subsequences of โ€œabcdefgโ€ + +Our Solution: +We use recursion with tabular memoization. +Time complexity: O(M x N) +Solving each subproblem has a cost of O(1). Again, there are MxN subproblems, +and so we get a total time complexity of O(MxN). +Space complexity: O(M x N) +We need to store the answer for each of the MxN subproblems. + +Improvement: +It's possible to optimize space complexity to O(min(M, N)) or time to O((N + r)log(N)) +where r is the number of matches between the two sequences. Try to figure out how. + +References: +[wikipedia](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem) +[leetcode](https://leetcode.com/problems/longest-common-subsequence/) +*/ + +/** + * Finds length of the longest common subsequence among the two input string + * @param {string} str1 Input string #1 + * @param {string} str2 Input string #2 + * @returns {number} Length of the longest common subsequence + */ +function longestCommonSubsequence(str1, str2) { + const memo = new Array(str1.length + 1) + .fill(null) + .map(() => new Array(str2.length + 1).fill(null)) + + function recursive(end1, end2) { + if (end1 === -1 || end2 === -1) { + return 0 + } + + if (memo[end1][end2] !== null) { + return memo[end1][end2] + } + + if (str1[end1] === str2[end2]) { + memo[end1][end2] = 1 + recursive(end1 - 1, end2 - 1) + return memo[end1][end2] + } else { + memo[end1][end2] = Math.max( + recursive(end1 - 1, end2), + recursive(end1, end2 - 1) + ) + return memo[end1][end2] + } + } + + return recursive(str1.length - 1, str2.length - 1) +} + +export { longestCommonSubsequence } diff --git a/Dynamic-Programming/LongestIncreasingSubsequence.js b/Dynamic-Programming/LongestIncreasingSubsequence.js new file mode 100644 index 0000000000..b8e1847cd8 --- /dev/null +++ b/Dynamic-Programming/LongestIncreasingSubsequence.js @@ -0,0 +1,30 @@ +/** + * A Dynamic Programming based solution for calculating Longest Increasing Subsequence + * https://en.wikipedia.org/wiki/Longest_increasing_subsequence + */ + +// Return the length of the Longest Increasing Subsequence, given array x +function longestIncreasingSubsequence(x) { + const length = x.length + if (length == 0) { + return 0 + } + const dp = Array(length).fill(1) + + let res = 1 + + for (let i = 0; i < length; i++) { + for (let j = 0; j < i; j++) { + if (x[i] > x[j]) { + dp[i] = Math.max(dp[i], 1 + dp[j]) + if (dp[i] > res) { + res = dp[i] + } + } + } + } + + return res +} + +export { longestIncreasingSubsequence } diff --git a/Dynamic-Programming/LongestPalindromicSubsequence.js b/Dynamic-Programming/LongestPalindromicSubsequence.js new file mode 100644 index 0000000000..69a779c402 --- /dev/null +++ b/Dynamic-Programming/LongestPalindromicSubsequence.js @@ -0,0 +1,33 @@ +/* + LeetCode -> https://leetcode.com/problems/longest-palindromic-subsequence/ + + Given a string s, find the longest palindromic subsequence's length in s. + You may assume that the maximum length of s is 1000. + +*/ + +export const longestPalindromeSubsequence = function (s) { + const n = s.length + + const dp = new Array(n) + .fill(0) + .map((item) => new Array(n).fill(0).map((item) => 0)) + + // fill predefined for single character + for (let i = 0; i < n; i++) { + dp[i][i] = 1 + } + + for (let i = 1; i < n; i++) { + for (let j = 0; j < n - i; j++) { + const col = j + i + if (s[j] === s[col]) { + dp[j][col] = 2 + dp[j + 1][col - 1] + } else { + dp[j][col] = Math.max(dp[j][col - 1], dp[j + 1][col]) + } + } + } + + return dp[0][n - 1] +} diff --git a/Dynamic-Programming/LongestValidParentheses.js b/Dynamic-Programming/LongestValidParentheses.js new file mode 100644 index 0000000000..d891082472 --- /dev/null +++ b/Dynamic-Programming/LongestValidParentheses.js @@ -0,0 +1,35 @@ +/* + LeetCode -> https://leetcode.com/problems/longest-valid-parentheses/ + + Given a string containing just the characters '(' and ')', + find the length of the longest valid (well-formed) parentheses substring. +*/ + +export const longestValidParentheses = (s) => { + const n = s.length + const stack = [] + + // storing results + const res = new Array(n).fill(-Infinity) + + for (let i = 0; i < n; i++) { + const bracket = s[i] + + if (bracket === ')' && s[stack[stack.length - 1]] === '(') { + res[i] = 1 + res[stack[stack.length - 1]] = 1 + stack.pop() + } else { + stack.push(i) + } + } + + // summing all adjacent valid + for (let i = 1; i < n; i++) { + res[i] = Math.max(res[i], res[i] + res[i - 1]) + } + + // adding 0 if there are none so it will return 0 instead of -Infinity + res.push(0) + return Math.max(...res) +} diff --git a/Dynamic-Programming/MaxNonAdjacentSum.js b/Dynamic-Programming/MaxNonAdjacentSum.js index 5c946e434e..7c04f5f351 100644 --- a/Dynamic-Programming/MaxNonAdjacentSum.js +++ b/Dynamic-Programming/MaxNonAdjacentSum.js @@ -1,9 +1,9 @@ -function maximumNonAdjacentSum (nums) { +function maximumNonAdjacentSum(nums) { /* - * Find the maximum non-adjacent sum of the integers in the nums input list - * :param nums: Array of Numbers - * :return: The maximum non-adjacent sum - */ + * Find the maximum non-adjacent sum of the integers in the nums input list + * :param nums: Array of Numbers + * :return: The maximum non-adjacent sum + */ if (nums.length < 0) return 0 @@ -19,11 +19,11 @@ function maximumNonAdjacentSum (nums) { return Math.max(maxExcluding, maxIncluding) } -function main () { - console.log(maximumNonAdjacentSum([1, 2, 3])) - console.log(maximumNonAdjacentSum([1, 5, 3, 7, 2, 2, 6])) - console.log(maximumNonAdjacentSum([-1, -5, -3, -7, -2, -2, -6])) - console.log(maximumNonAdjacentSum([499, 500, -3, -7, -2, -2, -6])) -} +// Example + +// maximumNonAdjacentSum([1, 2, 3])) +// maximumNonAdjacentSum([1, 5, 3, 7, 2, 2, 6])) +// maximumNonAdjacentSum([-1, -5, -3, -7, -2, -2, -6])) +// maximumNonAdjacentSum([499, 500, -3, -7, -2, -2, -6])) -main() +export { maximumNonAdjacentSum } diff --git a/Dynamic-Programming/MaxProductOfThree.js b/Dynamic-Programming/MaxProductOfThree.js new file mode 100644 index 0000000000..5df275df56 --- /dev/null +++ b/Dynamic-Programming/MaxProductOfThree.js @@ -0,0 +1,38 @@ +/** + * Given an array of numbers, return the maximum product + * of 3 numbers from the array + * https://wsvincent.com/javascript-three-sum-highest-product-of-three-numbers/ + * @param {number[]} arrayItems + * @returns number + */ +export function maxProductOfThree(arrayItems) { + // if size is less than 3, no triplet exists + const n = arrayItems.length + if (n < 3) throw new Error('Triplet cannot exist with the given array') + let max1 = arrayItems[0] + let max2 = null + let max3 = null + let min1 = arrayItems[0] + let min2 = null + for (let i = 1; i < n; i++) { + if (arrayItems[i] > max1) { + max3 = max2 + max2 = max1 + max1 = arrayItems[i] + } else if (max2 === null || arrayItems[i] > max2) { + max3 = max2 + max2 = arrayItems[i] + } else if (max3 === null || arrayItems[i] > max3) { + max3 = arrayItems[i] + } + if (arrayItems[i] < min1) { + min2 = min1 + min1 = arrayItems[i] + } else if (min2 === null || arrayItems[i] < min2) { + min2 = arrayItems[i] + } + } + const prod1 = max1 * max2 * max3 + const prod2 = max1 * min1 * min2 + return Math.max(prod1, prod2) +} diff --git a/Dynamic-Programming/MinimumCostPath.js b/Dynamic-Programming/MinimumCostPath.js new file mode 100644 index 0000000000..3fd8181751 --- /dev/null +++ b/Dynamic-Programming/MinimumCostPath.js @@ -0,0 +1,45 @@ +// Problem Statement => https://www.youtube.com/watch?v=lBRtnuxg-gU + +const minCostPath = (matrix) => { + /* + Find the min cost path from top-left to bottom-right in matrix + >>> minCostPath([[2, 1], [3, 1], [4, 2]]) + >>> 6 + */ + + const n = matrix.length + const m = matrix[0].length + + // moves[i][j] => minimum number of moves to reach cell i, j + const moves = new Array(n) + for (let i = 0; i < moves.length; i++) moves[i] = new Array(m) + + // base conditions + moves[0][0] = matrix[0][0] // to reach cell (0, 0) from (0, 0) is of no moves + for (let i = 1; i < m; i++) moves[0][i] = moves[0][i - 1] + matrix[0][i] + for (let i = 1; i < n; i++) moves[i][0] = moves[i - 1][0] + matrix[i][0] + + for (let i = 1; i < n; i++) { + for (let j = 1; j < m; j++) { + moves[i][j] = Math.min(moves[i - 1][j], moves[i][j - 1]) + matrix[i][j] + } + } + + return moves[n - 1][m - 1] +} + +export { minCostPath } + +// Example + +// minCostPath([ +// [2, 1], +// [3, 1], +// [4, 2] +// ]) + +// minCostPath([ +// [2, 1, 4], +// [2, 1, 3], +// [3, 2, 1] +// ]) diff --git a/Dynamic-Programming/NumberOfSubsetEqualToGivenSum.js b/Dynamic-Programming/NumberOfSubsetEqualToGivenSum.js index f45d069858..fd8e70c4d6 100644 --- a/Dynamic-Programming/NumberOfSubsetEqualToGivenSum.js +++ b/Dynamic-Programming/NumberOfSubsetEqualToGivenSum.js @@ -1,12 +1,19 @@ /* -Given an array of non-negative integers and a value sum, +Given an array of positive integers and a value sum, determine the total number of the subset with sum equal to the given sum. */ /* - Given solution is O(n*sum) Time complexity and O(sum) Space complexity + Given solution is O(n*sum) Time complexity and O(sum) Space complexity */ -function NumberOfSubsetSum (array, sum) { +function NumberOfSubsetSum(array, sum) { + if (sum < 0) { + throw new Error('The sum must be non-negative.') + } + + if (!array.every((num) => num > 0)) { + throw new Error('All of the inputs of the array must be positive.') + } const dp = [] // create an dp array where dp[i] denote number of subset with sum equal to i for (let i = 1; i <= sum; i++) { dp[i] = 0 @@ -23,10 +30,4 @@ function NumberOfSubsetSum (array, sum) { return dp[sum] } -function main () { - const array = [1, 1, 2, 2, 3, 1, 1] - const sum = 4 - const result = NumberOfSubsetSum(array, sum) - console.log(result) -} -main() +export { NumberOfSubsetSum } diff --git a/Dynamic-Programming/RodCutting.js b/Dynamic-Programming/RodCutting.js new file mode 100644 index 0000000000..d9d5397d5c --- /dev/null +++ b/Dynamic-Programming/RodCutting.js @@ -0,0 +1,19 @@ +/* + * You are given a rod of 'n' length and an array of prices associated with all the lengths less than 'n'. + * Find the maximum profit possible by cutting the rod and selling the pieces. + */ + +export function rodCut(prices, n) { + const memo = new Array(n + 1) + memo[0] = 0 + + for (let i = 1; i <= n; i++) { + let maxVal = Number.MIN_VALUE + for (let j = 0; j < i; j++) { + maxVal = Math.max(maxVal, prices[j] + memo[i - j - 1]) + } + memo[i] = maxVal + } + + return memo[n] +} diff --git a/Dynamic-Programming/Shuf.js b/Dynamic-Programming/Shuf.js new file mode 100644 index 0000000000..58d5a8a568 --- /dev/null +++ b/Dynamic-Programming/Shuf.js @@ -0,0 +1,96 @@ +/* +Given a data set of an unknown size, +Get a random sample in a random order +It's used in data analytics, often as a way to get a small random sample from a data lake or warehouse, or from a large CSV file +*/ +function shuf(datasetSource, sampleSize) { + const output = fillBaseSample(datasetSource, sampleSize) + + return randomizeOutputFromDataset(datasetSource, output) +} + +/** + * Fills the output if possible, with the minimum number of values + * @param {Iterable.} datasetSource The iterable source of data + * @param {number} sampleSize The size of the sample to extract from the dataset + * @returns {Array.} The random sample, as an array + * @template T + */ +function fillBaseSample(datasetSource, sampleSize) { + let filledIndexes = [] + let output = new Array(sampleSize) + + // Spread data out filling the array + while (true) { + const iterator = datasetSource.next() + if (iterator.done) break + + let insertTo = Math.floor(Math.random() * output.length) + while (filledIndexes.includes(insertTo)) { + insertTo++ + if (insertTo === output.length) { + insertTo = 0 + } + } + output[insertTo] = { + value: iterator.value + } + + filledIndexes = [...filledIndexes, insertTo] + + if (filledIndexes.length === sampleSize) { + break + } + } + + if (filledIndexes.length < output.length) { + // Not a large enough dataset to fill the sample - trim empty values + output = output.filter((_, i) => filledIndexes.includes(i)) + } + + return output.map((o) => o.value) +} + +/** + * Replaces values in the output randomly with new ones from the dataset + * @param {Iterable.} datasetSource The iterable source of data + * @param {Array.} output The output so far, filled with data + * @returns {Array.} The random sample, as an array + * @template T + */ +function randomizeOutputFromDataset(datasetSource, output) { + const newOutput = [...output] + let readSoFar = output.length + + while (true) { + const iterator = datasetSource.next() + if (iterator.done) break + readSoFar++ + + const insertTo = Math.floor(Math.random() * readSoFar) + if (insertTo < newOutput.length) { + newOutput[insertTo] = iterator.value + } + } + + return newOutput +} + +// Example + +/** + * Generates a random range of data, with values between 0 and 2^31 - 1 + * @param {number} length The number of data items to generate + * @returns {Iterable} Random iterable data + */ +function* generateRandomData(length) { + const maxValue = Math.pow(2, 31) - 1 + for (let i = 0; i < length; i++) { + yield Math.floor(Math.random() * maxValue) + } +} + +// const source = generateRandomData(1000) +// const result = shuf(source, 10) + +export { shuf, generateRandomData } diff --git a/Dynamic-Programming/SieveOfEratosthenes.js b/Dynamic-Programming/SieveOfEratosthenes.js index 1e0a2e2b21..9860273d78 100644 --- a/Dynamic-Programming/SieveOfEratosthenes.js +++ b/Dynamic-Programming/SieveOfEratosthenes.js @@ -1,31 +1,33 @@ -function sieveOfEratosthenes (n) { - /* - * Calculates prime numbers till a number n - * :param n: Number upto which to calculate primes - * :return: A boolean list contaning only primes - */ - const primes = new Array(n + 1) - primes.fill(true) // set all as true initially +/** + * @function SieveOfEratosthenes + * @description Calculates prime numbers till input number n + * @param {Number} n - The input integer + * @return {Number[]} List of Primes till n. + * @see [Sieve_of_Eratosthenes](https://www.geeksforgeeks.org/sieve-of-eratosthenes/) + */ +function sieveOfEratosthenes(n) { + if (n <= 1) return [] + const primes = new Array(n + 1).fill(true) // set all as true initially primes[0] = primes[1] = false // Handling case for 0 and 1 - const sqrtn = Math.ceil(Math.sqrt(n)) - for (let i = 2; i <= sqrtn; i++) { + for (let i = 2; i * i <= n; i++) { if (primes[i]) { - for (let j = 2 * i; j <= n; j += i) { + for (let j = i * i; j <= n; j += i) { primes[j] = false } } } - return primes -} -function main () { - const n = 69 // number till where we wish to find primes - const primes = sieveOfEratosthenes(n) - for (let i = 2; i <= n; i++) { - if (primes[i]) { - console.log(i) + return primes.reduce((result, isPrime, index) => { + if (isPrime) { + result.push(index) } - } + return result + }, []) } -main() +// Example + +// const n = 69 // number till where we wish to find primes +// const primes = sieveOfEratosthenes(n) + +export { sieveOfEratosthenes } diff --git a/Dynamic-Programming/Sliding-Window/HouseRobber.js b/Dynamic-Programming/Sliding-Window/HouseRobber.js new file mode 100644 index 0000000000..8d8dd5b43e --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/HouseRobber.js @@ -0,0 +1,23 @@ +/** + * @function houseRobber + * @description Given an integer array nums representing the amount of money of each house, return the maximum amount of money you can rob tonight without alerting the police. + * @param {number[]} nums + * @return {number} + * @see [Leetcode link](https://leetcode.com/problems/house-robber/) + */ +export const houseRobber = (nums) => { + const length = nums.length + + if (length === 0) return 0 + if (length === 1) return nums[0] + if (length === 2) return Math.max(nums[0], nums[1]) + + const dp = Array(length) // last element of this array always contains biggest loot possible + dp[0] = nums[0] + dp[1] = Math.max(nums[0], nums[1]) + + for (let i = 2; i < length; i++) { + dp[i] = Math.max(nums[i] + dp[i - 2], dp[i - 1]) + } + return dp[length - 1] +} diff --git a/Dynamic-Programming/Sliding-Window/LongestSubstringWithoutRepeatingCharacters.js b/Dynamic-Programming/Sliding-Window/LongestSubstringWithoutRepeatingCharacters.js new file mode 100644 index 0000000000..e9a60b05d9 --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/LongestSubstringWithoutRepeatingCharacters.js @@ -0,0 +1,49 @@ +/** + * @name The-Sliding-Window Algorithm is primarily used for the problems dealing with linear data structures like Arrays, Lists, Strings etc. + * These problems can easily be solved using Brute Force techniques which result in quadratic or exponential time complexity. + * Sliding window technique reduces the required time to linear O(n). + * @see [The-Sliding-Window](https://www.geeksforgeeks.org/window-sliding-technique/) + */ + +/** + * @function LongestSubstringWithoutRepeatingCharacters + * @description Get the length of the longest substring without repeating characters + * @param {String} s - The input string + */ +export function LongestSubstringWithoutRepeatingCharacters(s) { + let maxLength = 0 + let start = 0 + let end = 0 + const map = {} + while (end < s.length) { + if (map[s[end]] === undefined) { + map[s[end]] = 1 + maxLength = Math.max(maxLength, end - start + 1) + end++ + } else { + while (s[start] !== s[end]) { + delete map[s[start]] + start++ + } + delete map[s[start]] + start++ + } + } + return maxLength +} + +// Example 1: +// Input: s = "abcabcbb" +// Output: 3 +// Explanation: The answer is "abc", with the length of 3. + +// Example 2: +// Input: s = "bbbbb" +// Output: 1 +// Explanation: The answer is "b", with the length of 1. + +// Example 3: +// Input: s = "pwwkew" +// Output: 3 +// Explanation: The answer is "wke", with the length of 3. +// Notice that the answer must be a substring, "pwke" is a subsequence and not a substring. diff --git a/Dynamic-Programming/Sliding-Window/MaxConsecutiveOnes.js b/Dynamic-Programming/Sliding-Window/MaxConsecutiveOnes.js new file mode 100644 index 0000000000..9e248b338c --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/MaxConsecutiveOnes.js @@ -0,0 +1,30 @@ +/** + * @function maxConsecutiveOnes + * @description Given a binary array nums, return the maximum number of consecutive 1's in the array. + * @param {number[]} nums + * @return {number} + * @see [Leetcode link](https://leetcode.com/problems/max-consecutive-ones/) + */ +export const maxConsecutiveOnes = (nums) => { + if (!nums.length) return 0 + + let result = 0 + let k = 0 + + for ( + let slowPointer = 0, fastPointer = 0; + fastPointer < nums.length; + fastPointer++ + ) { + if (nums[fastPointer] === 0) k-- + + while (k < 0) { + if (nums[slowPointer] === 0) { + k++ + } + slowPointer++ + } + result = Math.max(result, fastPointer - slowPointer + 1) + } + return result +} diff --git a/Dynamic-Programming/Sliding-Window/MaxConsecutiveOnesIII.js b/Dynamic-Programming/Sliding-Window/MaxConsecutiveOnesIII.js new file mode 100644 index 0000000000..f4366453ec --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/MaxConsecutiveOnesIII.js @@ -0,0 +1,32 @@ +/** + * @function maxConsecutiveOnesIII + * @description Given a binary array nums and an integer k, return the maximum number of consecutive 1's in the array if you can flip at most k 0's. + * @param {number[]} nums + * @param {number} k + * @return {number} + * @see [Leetcode link](https://leetcode.com/problems/max-consecutive-ones-iii/) + */ +export const maxConsecutiveOnesIII = (nums, k) => { + if (!nums.length) return 0 + + let result = 0 + + for ( + let slowPointer = 0, fastPointer = 0; + fastPointer < nums.length; + fastPointer++ + ) { + if (nums[fastPointer] === 0) { + k-- + } + + while (k < 0) { + if (slowPointer === 0) { + k++ + } + slowPointer++ + } + result = Math.max(result, fastPointer - slowPointer + 1) + } + return result +} diff --git a/Dynamic-Programming/Sliding-Window/PermutationinString.js b/Dynamic-Programming/Sliding-Window/PermutationinString.js new file mode 100644 index 0000000000..28111fe8cf --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/PermutationinString.js @@ -0,0 +1,58 @@ +/** + * @name The-Sliding-Window Algorithm is primarily used for the problems dealing with linear data structures like Arrays, Lists, Strings etc. + * These problems can easily be solved using Brute Force techniques which result in quadratic or exponential time complexity. + * Sliding window technique reduces the required time to linear O(n). + * @see [The-Sliding-Window](https://www.geeksforgeeks.org/window-sliding-technique/) + */ +/** + * @function PermutationinString + * @description Given two strings s1 and s2, return true if s2 contains a permutation of s1, or false otherwise. + * @param {String} s1 - The input string + * @param {String} s2 - The input string + * @return {boolean} - Returns true if s2 contains a permutation of s1, or false otherwise. + */ + +export function PermutationinString(s1, s2) { + if (s1.length > s2.length) return false + let start = 0 + let end = s1.length - 1 + const s1Set = SetHash() + const s2Set = SetHash() + for (let i = 0; i < s1.length; i++) { + s1Set[s1[i]]++ + s2Set[s2[i]]++ + } + if (equals(s1Set, s2Set)) return true + while (end < s2.length - 1) { + if (equals(s1Set, s2Set)) return true + end++ + const c1 = s2[start] + const c2 = s2[end] + if (s2Set[c1] > 0) s2Set[c1]-- + s2Set[c2]++ + start++ + if (equals(s1Set, s2Set)) return true + } + return false +} +function equals(a, b) { + return JSON.stringify(a) === JSON.stringify(b) +} + +function SetHash() { + const set = new Set() + const alphabets = 'abcdefghijklmnopqrstuvwxyz' + for (let i = 0; i < alphabets.length; i++) { + set[alphabets[i]] = 0 + } + return set +} + +// Example 1: +// Input: s1 = "ab", s2 = "eidbaooo" +// Output: true +// Explanation: s2 contains one permutation of s1 ("ba"). + +// Example 2: +// Input: s1 = "ab", s2 = "eidboaoo" +// Output: false diff --git a/Dynamic-Programming/Sliding-Window/test/HouseRobber.test.js b/Dynamic-Programming/Sliding-Window/test/HouseRobber.test.js new file mode 100644 index 0000000000..cb32498825 --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/test/HouseRobber.test.js @@ -0,0 +1,20 @@ +import { houseRobber } from '../HouseRobber' + +describe('houseRobber', () => { + it('expects to return 0 when argument is empty array', () => { + expect(houseRobber([])).toBe(0) + }) + + it('expects to return element at index 0 when argument is array of length one', () => { + expect(houseRobber([9])).toBe(9) + }) + + it('expects to return greater number when argument is an array of length two', () => { + expect(houseRobber([3, 6])).toBe(6) + }) + + it('expects to return the maximum loot possible', () => { + expect(houseRobber([1, 2, 3, 1])).toBe(4) + expect(houseRobber([2, 7, 9, 3, 1])).toBe(12) + }) +}) diff --git a/Dynamic-Programming/Sliding-Window/test/LongestSubstringWithoutRepeatingCharacters.test.js b/Dynamic-Programming/Sliding-Window/test/LongestSubstringWithoutRepeatingCharacters.test.js new file mode 100644 index 0000000000..3a77a92082 --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/test/LongestSubstringWithoutRepeatingCharacters.test.js @@ -0,0 +1,11 @@ +import { LongestSubstringWithoutRepeatingCharacters } from '../LongestSubstringWithoutRepeatingCharacters.js' + +describe('LongestSubstringWithoutRepeatingCharacters', () => { + it('should return longest substring without repeating characters', () => { + expect(LongestSubstringWithoutRepeatingCharacters('abcabcbb')).toEqual(3) + expect(LongestSubstringWithoutRepeatingCharacters('bbbbb')).toEqual(1) + expect(LongestSubstringWithoutRepeatingCharacters('pwwkew')).toEqual(3) + expect(LongestSubstringWithoutRepeatingCharacters('a')).toEqual(1) + expect(LongestSubstringWithoutRepeatingCharacters('')).toEqual(0) + }) +}) diff --git a/Dynamic-Programming/Sliding-Window/test/MaxConsecutiveOnes.test.js b/Dynamic-Programming/Sliding-Window/test/MaxConsecutiveOnes.test.js new file mode 100644 index 0000000000..38e4917251 --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/test/MaxConsecutiveOnes.test.js @@ -0,0 +1,19 @@ +import { maxConsecutiveOnes } from '../MaxConsecutiveOnes.js' + +describe('maxConsecutiveOnes', () => { + it('expects to return 0 when argument is empty array', () => { + expect(maxConsecutiveOnes([])).toBe(0) + }) + + it('expects to return 3', () => { + expect(maxConsecutiveOnes([1, 1, 0, 1, 1, 1])).toBe(3) + }) + + it('expects to return 2', () => { + expect(maxConsecutiveOnes([1, 0, 1, 1, 0, 1])).toBe(2) + }) + + it('expects to return 5', () => { + expect(maxConsecutiveOnes([0, 1, 1, 1, 1, 1, 0, 0, 1, 0])).toBe(5) + }) +}) diff --git a/Dynamic-Programming/Sliding-Window/test/MaxConsecutiveOnesIII.test.js b/Dynamic-Programming/Sliding-Window/test/MaxConsecutiveOnesIII.test.js new file mode 100644 index 0000000000..5ea7df0ba3 --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/test/MaxConsecutiveOnesIII.test.js @@ -0,0 +1,15 @@ +import { maxConsecutiveOnesIII } from '../MaxConsecutiveOnesIII' + +describe('maxConsecutiveOnesIIIIII', () => { + it('expects to return 0 when argument is empty array', () => { + expect(maxConsecutiveOnesIII([], 3)).toBe(0) + }) + + it('expects to return 6', () => { + expect(maxConsecutiveOnesIII([1, 1, 0, 1, 1, 1], 1)).toBe(6) + }) + + it('expects to return 8', () => { + expect(maxConsecutiveOnesIII([1, 0, 1, 1, 1, 1, 0, 1], 2)).toBe(8) + }) +}) diff --git a/Dynamic-Programming/Sliding-Window/test/PermutationinString.test.js b/Dynamic-Programming/Sliding-Window/test/PermutationinString.test.js new file mode 100644 index 0000000000..8ffe9accc9 --- /dev/null +++ b/Dynamic-Programming/Sliding-Window/test/PermutationinString.test.js @@ -0,0 +1,10 @@ +import { PermutationinString } from '../PermutationinString.js' + +describe('PermutationinString', () => { + it("should return true if one of s1's permutations is the substring of s2", () => { + expect(PermutationinString('ab', 'eidbaooo')).toEqual(true) + expect(PermutationinString('abc', 'bcab')).toEqual(true) + expect(PermutationinString('ab', 'eidboaoo')).toEqual(false) + expect(PermutationinString('abc', '')).toEqual(false) + }) +}) diff --git a/Dynamic-Programming/SudokuSolver.js b/Dynamic-Programming/SudokuSolver.js new file mode 100644 index 0000000000..6c40474670 --- /dev/null +++ b/Dynamic-Programming/SudokuSolver.js @@ -0,0 +1,48 @@ +const isValid = (board, row, col, k) => { + for (let i = 0; i < 9; i++) { + const m = 3 * Math.floor(row / 3) + Math.floor(i / 3) + const n = 3 * Math.floor(col / 3) + (i % 3) + if (board[row][i] === k || board[i][col] === k || board[m][n] === k) { + return false + } + } + return true +} + +const sudokuSolver = (data) => { + for (let i = 0; i < 9; i++) { + for (let j = 0; j < 9; j++) { + if (data[i][j] === '.') { + for (let k = 1; k <= 9; k++) { + if (isValid(data, i, j, `${k}`)) { + data[i][j] = `${k}` + if (sudokuSolver(data)) { + return true + } else { + data[i][j] = '.' + } + } + } + return false + } + } + } + return true +} + +// testing + +// const board = [ +// ['.', '9', '.', '.', '4', '2', '1', '3', '6'], +// ['.', '.', '.', '9', '6', '.', '4', '8', '5'], +// ['.', '.', '.', '5', '8', '1', '.', '.', '.'], +// ['.', '.', '4', '.', '.', '.', '.', '.', '.'], +// ['5', '1', '7', '2', '.', '.', '9', '.', '.'], +// ['6', '.', '2', '.', '.', '.', '3', '7', '.'], +// ['1', '.', '.', '8', '.', '4', '.', '2', '.'], +// ['7', '.', '6', '.', '.', '.', '8', '1', '.'], +// ['3', '.', '.', '.', '9', '.', '.', '.', '.'] +// ] +// sudokuSolver(board) // -> board updated by reference + +export { sudokuSolver } diff --git a/Dynamic-Programming/TrappingRainWater.js b/Dynamic-Programming/TrappingRainWater.js new file mode 100644 index 0000000000..bea568cbcd --- /dev/null +++ b/Dynamic-Programming/TrappingRainWater.js @@ -0,0 +1,61 @@ +/** + * @param {number[]} height + * @return {number} + */ + +/* 42. Trapping Rain Water +https://leetcode.com/problems/trapping-rain-water/ + +Helpful animation of this prompt: https://youtu.be/HmBbcDiJapY?t=51 + +Given n non-negative integers representing an elevation map where +the width of each bar is 1, compute how much water it is able to trap +after raining. + +VIEW ELEVATION MAP ON LEETCODE + +Example: + +Input: [0,1,0,2,1,0,1,3,2,1,2,1] +Output: 6 + +Plan: +iterate through and find left maxes +iterate through and find right maxes +create minheight and assign it to the min(leftmax, rightmax) +if current height(element) < minheight + push minheight - height into water array +else + push 0 onto water array + +sum up water array and return + +left maxes = [0,0,1,1,2,2,2,2,3,3,3,3] +right maxes = [3,3,3,3,3,3,3,2,2,2,1,0] +water contained = [0,0,1,0,1,2,1,0,0,1,0,0] -> sum = 6 +*/ + +export const trap = (heights) => { + const maxes = new Array(heights.length).fill(0) + + let leftMax = 0 + for (let i = 0; i < heights.length; i++) { + const height = heights[i] + maxes[i] = leftMax + leftMax = Math.max(leftMax, height) + } + + let rightMax = 0 + for (let i = heights.length - 1; i >= 0; i -= 1) { + const height = heights[i] + const minHeight = Math.min(rightMax, maxes[i]) + + if (height < minHeight) { + maxes[i] = minHeight - height + } else { + maxes[i] = 0 + } + rightMax = Math.max(rightMax, height) + } + return maxes.reduce((a, b) => a + b, 0) +} diff --git a/Dynamic-Programming/TribonacciNumber.js b/Dynamic-Programming/TribonacciNumber.js new file mode 100644 index 0000000000..67a95a0e46 --- /dev/null +++ b/Dynamic-Programming/TribonacciNumber.js @@ -0,0 +1,20 @@ +/** + * @function Tribonacci + * @description Tribonacci is the sum of previous three tribonacci numbers. + * @param {Integer} n - The input integer + * @return {Integer} tribonacci of n. + * @see [Tribonacci_Numbers](https://www.geeksforgeeks.org/tribonacci-numbers/) + */ +const tribonacci = (n) => { + // creating array to store previous tribonacci numbers + const dp = new Array(n + 1) + dp[0] = 0 + dp[1] = 1 + dp[2] = 1 + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3] + } + return dp[n] +} + +export { tribonacci } diff --git a/Dynamic-Programming/UniquePaths.js b/Dynamic-Programming/UniquePaths.js new file mode 100644 index 0000000000..a37049b2f4 --- /dev/null +++ b/Dynamic-Programming/UniquePaths.js @@ -0,0 +1,40 @@ +/* + * + * 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 0000000000..14bc1e6d7b --- /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 recurrent 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/ZeroOneKnapsack.js b/Dynamic-Programming/ZeroOneKnapsack.js new file mode 100644 index 0000000000..52a06aa130 --- /dev/null +++ b/Dynamic-Programming/ZeroOneKnapsack.js @@ -0,0 +1,83 @@ +/** + * A Dynamic Programming based solution for calculating Zero One Knapsack + * https://en.wikipedia.org/wiki/Knapsack_problem + * + * Time and Space Complexity: O(n*cap) + */ +const zeroOneKnapsack = (arr, n, cap, cache) => { + // Base Case: No capacity or no items + if (cap === 0 || n === 0) { + cache[n][cap] = 0 + return cache[n][cap] + } + + // Lookup (value already calculated) + if (cache[n][cap] !== -1) { + return cache[n][cap] + } + + // Profit when excluding the nth item + let notPick = zeroOneKnapsack(arr, n - 1, cap, cache) + + // Profit when including the nth item + let pick = 0 + if (arr[n - 1][0] <= cap) { + // If weight of the nth item is within the capacity + pick = + arr[n - 1][1] + zeroOneKnapsack(arr, n - 1, cap - arr[n - 1][0], cache) + } + + cache[n][cap] = Math.max(pick, notPick) // maximize profit + return cache[n][cap] +} + +const example = () => { + /* + Problem Statement: + You are a thief carrying a single bag with limited capacity S. The museum you stole had N artifact that you could steal. Unfortunately you might not be able to steal all the artifact because of your limited bag capacity. + You have to cherry pick the artifact in order to maximize the total value of the artifacts you stole. + + Link for the Problem: https://www.hackerrank.com/contests/srin-aadc03/challenges/classic-01-knapsack + */ + let input = `1 + 4 5 + 1 8 + 2 4 + 3 0 + 2 5 + 2 3` + + input = input.trim().split('\n') + input.shift() + const length = input.length + + const output = [] + + let i = 0 + while (i < length) { + const cap = Number(input[i].trim().split(' ')[0]) + const currlen = Number(input[i].trim().split(' ')[1]) + let j = i + 1 + const arr = [] + while (j <= i + currlen) { + arr.push(input[j]) + j++ + } + const newArr = arr.map((e) => e.trim().split(' ').map(Number)) + const cache = [] + for (let i = 0; i <= currlen; i++) { + const temp = [] + for (let j = 0; j <= cap; j++) { + temp.push(-1) + } + cache.push(temp) + } + const result = zeroOneKnapsack(newArr, currlen, cap, cache) + output.push(result) + i += currlen + 1 + } + + return output +} + +export { zeroOneKnapsack, example } diff --git a/Dynamic-Programming/tests/Abbreviation.test.js b/Dynamic-Programming/tests/Abbreviation.test.js new file mode 100644 index 0000000000..86e0f97336 --- /dev/null +++ b/Dynamic-Programming/tests/Abbreviation.test.js @@ -0,0 +1,30 @@ +import { isAbbreviation } from '../Abbreviation.js' + +const expectPositive = (word, abbr) => + expect(isAbbreviation(word, abbr)).toBe(true) +const expectNegative = (word, abbr) => + expect(isAbbreviation(word, abbr)).toBe(false) + +describe('Abbreviation - Positive Tests', () => { + test('it should correctly abbreviate or transform the source string to match the target string', () => { + expectPositive('', '') + expectPositive('a', '') + expectPositive('a', 'A') + expectPositive('abcDE', 'ABCDE') + expectPositive('ABcDE', 'ABCDE') + expectPositive('abcde', 'ABCDE') + expectPositive('abcde', 'ABC') + expectPositive('abcXYdefghijKLmnopqrs', 'XYKL') + expectPositive('abc123', 'ABC') + expectPositive('abc123', 'ABC123') + expectPositive('abc!@#def', 'ABC') + }) +}) + +describe('Abbreviation - Negative Tests', () => { + test('it should fail to abbreviate or transform the source string when it is not possible to match the target string', () => { + expectNegative('', 'A') + expectNegative('a', 'ABC') + expectNegative('aBcXYdefghijKLmnOpqrs', 'XYKLOP') + }) +}) diff --git a/Dynamic-Programming/tests/CatalanNumbers.test.js b/Dynamic-Programming/tests/CatalanNumbers.test.js new file mode 100644 index 0000000000..be5db5af76 --- /dev/null +++ b/Dynamic-Programming/tests/CatalanNumbers.test.js @@ -0,0 +1,14 @@ +import { catalanNumbers } from '../CatalanNumbers' + +describe('Testing catalanNumbers function', () => { + test('should return the expected array for inputs from 0 to 20', () => { + const expectedOutput = [ + 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, + 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420 + ] + + for (let i = 0; i <= 20; i++) { + expect(catalanNumbers(i)).toStrictEqual(expectedOutput.slice(0, i + 1)) + } + }) +}) diff --git a/Dynamic-Programming/tests/ClimbingStairs.test.js b/Dynamic-Programming/tests/ClimbingStairs.test.js new file mode 100644 index 0000000000..382c32b540 --- /dev/null +++ b/Dynamic-Programming/tests/ClimbingStairs.test.js @@ -0,0 +1,19 @@ +import { climbStairs } from '../ClimbingStairs' + +describe('ClimbingStairs', () => { + it('climbStairs of 0', () => { + expect(climbStairs(0)).toBe(1) + }) + + it('climbStairs of 1', () => { + expect(climbStairs(1)).toBe(1) + }) + + it('climbStairs of 10', () => { + expect(climbStairs(10)).toBe(89) + }) + + it('climbStairs of 15', () => { + expect(climbStairs(15)).toBe(987) + }) +}) diff --git a/Dynamic-Programming/tests/CoinChange.test.js b/Dynamic-Programming/tests/CoinChange.test.js new file mode 100644 index 0000000000..6df3c66a3b --- /dev/null +++ b/Dynamic-Programming/tests/CoinChange.test.js @@ -0,0 +1,44 @@ +import { change, coinChangeMin } from '../CoinChange' + +test('Base Case 1', () => { + const coins = [2, 3, 5] + const amount = 0 + expect(change(coins, amount)).toBe(1) + expect(coinChangeMin(coins, amount)).toBe(0) +}) +test('Base Case 2', () => { + const coins = [] + const amount = 100 + expect(change(coins, amount)).toBe(0) + expect(coinChangeMin(coins, amount)).toBe(-1) +}) +test('Test Case 1', () => { + const coins = [2, 4, 5] + const amount = 12 + expect(change(coins, amount)).toBe(5) + expect(coinChangeMin(coins, amount)).toBe(3) +}) +test('Test Case 2', () => { + const coins = [5, 2, 3, 7, 6, 1, 12, 11, 9, 15] + const amount = 45 + expect(change(coins, amount)).toBe(12372) + expect(coinChangeMin(coins, amount)).toBe(3) +}) +test('Test Case 3', () => { + const coins = [2] + const amount = 3 + expect(change(coins, amount)).toBe(0) + expect(coinChangeMin(coins, amount)).toBe(-1) +}) +test('Test Case 4', () => { + const coins = [3, 5, 7, 8, 9, 10, 11] + const amount = 500 + expect(change(coins, amount)).toBe(35502874) + expect(coinChangeMin(coins, amount)).toBe(46) +}) +test('Test Case 5', () => { + const coins = [10] + const amount = 10 + expect(change(coins, amount)).toBe(1) + expect(coinChangeMin(coins, amount)).toBe(1) +}) diff --git a/Dynamic-Programming/tests/EditDistance.test.js b/Dynamic-Programming/tests/EditDistance.test.js new file mode 100644 index 0000000000..b7ea2eaa22 --- /dev/null +++ b/Dynamic-Programming/tests/EditDistance.test.js @@ -0,0 +1,22 @@ +import { minimumEditDistance } from '../EditDistance' + +test('minimumEditDistance(kitten, sitten) => 1', () => { + const str1 = 'kitten' + const str2 = 'sitten' + const res = minimumEditDistance(str1, str2) + expect(res).toEqual(1) +}) + +test('minimumEditDistance(school, skull) => 4', () => { + const str1 = 'school' + const str2 = 'skull' + const res = minimumEditDistance(str1, str2) + expect(res).toEqual(4) +}) + +test('minimumEditDistance(Algorithm, Algorithm) => 0', () => { + const str1 = 'Algorithm' + const str2 = 'Algorithm' + const res = minimumEditDistance(str1, str2) + expect(res).toEqual(0) +}) diff --git a/Dynamic-Programming/tests/FastFibonacciNumber.test.js b/Dynamic-Programming/tests/FastFibonacciNumber.test.js new file mode 100644 index 0000000000..af45a3c92b --- /dev/null +++ b/Dynamic-Programming/tests/FastFibonacciNumber.test.js @@ -0,0 +1,23 @@ +import { fastFibonacci } from '../FastFibonacciNumber' + +describe('Testing FibonacciNumber', () => { + const errorCases = ['0', '12', true] + + test.each(errorCases)('throws an error if %p is invalid', (input) => { + expect(() => { + fastFibonacci(input) + }).toThrow() + }) + + const testCases = [ + [0, 0], + [1, 1], + [10, 55], + [25, 75025], + [40, 102334155] + ] + + test.each(testCases)('if input is %i it returns %i', (input, expected) => { + expect(fastFibonacci(input)).toBe(expected) + }) +}) diff --git a/Dynamic-Programming/tests/FibonacciNumber.test.js b/Dynamic-Programming/tests/FibonacciNumber.test.js new file mode 100644 index 0000000000..0b2da2dfe3 --- /dev/null +++ b/Dynamic-Programming/tests/FibonacciNumber.test.js @@ -0,0 +1,25 @@ +import { fibonacci } from '../FibonacciNumber' + +describe('Testing FibonacciNumber', () => { + it('Testing for invalid type', () => { + expect(() => fibonacci('0')).toThrowError() + expect(() => fibonacci('12')).toThrowError() + expect(() => fibonacci(true)).toThrowError() + }) + + it('fibonacci of 0', () => { + expect(fibonacci(0)).toBe(0) + }) + + it('fibonacci of 1', () => { + expect(fibonacci(1)).toBe(1) + }) + + it('fibonacci of 10', () => { + expect(fibonacci(10)).toBe(55) + }) + + it('fibonacci of 25', () => { + expect(fibonacci(25)).toBe(75025) + }) +}) diff --git a/Dynamic-Programming/tests/KadaneAlgo.test.js b/Dynamic-Programming/tests/KadaneAlgo.test.js new file mode 100644 index 0000000000..c1f8831130 --- /dev/null +++ b/Dynamic-Programming/tests/KadaneAlgo.test.js @@ -0,0 +1,8 @@ +import { kadaneAlgo } from '../KadaneAlgo' +test('it is being checked that 15 is the answer to the corresponding array input', () => { + expect(kadaneAlgo([1, 2, 3, 4, 5])).toBe(15) +}) + +test('it is being checked that 5 is the answer to the corresponding array input', () => { + expect(kadaneAlgo([-1, -2, -3, -4, 5])).toBe(5) +}) diff --git a/Dynamic-Programming/tests/LevenshteinDistance.test.js b/Dynamic-Programming/tests/LevenshteinDistance.test.js new file mode 100644 index 0000000000..6d13e31c20 --- /dev/null +++ b/Dynamic-Programming/tests/LevenshteinDistance.test.js @@ -0,0 +1,19 @@ +import { calculateLevenshteinDp } from '../LevenshteinDistance' + +test('Should return the distance counting additions and removals', () => { + const from = 'kitten' + const to = 'sitting' + expect(calculateLevenshteinDp(from, to)).toBe(3) +}) + +test('Should return the distance based on replacements in the middle of the strings', () => { + const from = 'book' + const to = 'back' + expect(calculateLevenshteinDp(from, to)).toBe(2) +}) + +test('Should return the distance for strings with different length', () => { + const from = 'sunday' + const to = 'saturday' + expect(calculateLevenshteinDp(from, to)).toBe(3) +}) diff --git a/Dynamic-Programming/tests/LongestCommonSubsequence.test.js b/Dynamic-Programming/tests/LongestCommonSubsequence.test.js new file mode 100644 index 0000000000..f797b0ecd8 --- /dev/null +++ b/Dynamic-Programming/tests/LongestCommonSubsequence.test.js @@ -0,0 +1,36 @@ +import { longestCommonSubsequence } from '../LongestCommonSubsequence' + +describe('LongestCommonSubsequence', () => { + it('expects to return an empty string for empty inputs', () => { + expect(longestCommonSubsequence('', '')).toEqual(''.length) + expect(longestCommonSubsequence('aaa', '')).toEqual(''.length) + expect(longestCommonSubsequence('', 'bbb')).toEqual(''.length) + }) + + it('expects to return an empty string for inputs without a common subsequence', () => { + expect(longestCommonSubsequence('abc', 'deffgf')).toEqual(''.length) + expect(longestCommonSubsequence('de', 'ghm')).toEqual(''.length) + expect(longestCommonSubsequence('aupj', 'xyz')).toEqual(''.length) + }) + + it('expects to return the longest common subsequence, short inputs', () => { + expect(longestCommonSubsequence('abc', 'abc')).toEqual('abc'.length) + expect(longestCommonSubsequence('abc', 'abcd')).toEqual('abc'.length) + expect(longestCommonSubsequence('abc', 'ab')).toEqual('ab'.length) + expect(longestCommonSubsequence('abc', 'a')).toEqual('a'.length) + expect(longestCommonSubsequence('abc', 'b')).toEqual('b'.length) + expect(longestCommonSubsequence('abc', 'c')).toEqual('c'.length) + expect(longestCommonSubsequence('abd', 'abcd')).toEqual('abd'.length) + expect(longestCommonSubsequence('abd', 'ab')).toEqual('ab'.length) + expect(longestCommonSubsequence('abc', 'abd')).toEqual('ab'.length) + }) + + it('expects to return the longest common subsequence, medium-length inputs', () => { + expect(longestCommonSubsequence('bsbininm', 'jmjkbkjkv')).toEqual( + 'b'.length + ) + expect(longestCommonSubsequence('oxcpqrsvwf', 'shmtulqrypy')).toEqual( + 'qr'.length + ) + }) +}) diff --git a/Dynamic-Programming/tests/LongestIncreasingSubsequence.test.js b/Dynamic-Programming/tests/LongestIncreasingSubsequence.test.js new file mode 100644 index 0000000000..9a8024aa95 --- /dev/null +++ b/Dynamic-Programming/tests/LongestIncreasingSubsequence.test.js @@ -0,0 +1,24 @@ +import { longestIncreasingSubsequence } from '../LongestIncreasingSubsequence' + +describe('Testing longestIncreasingSubsequence', () => { + it.each([ + [[], 0], + [[1], 1], + [[2, 2], 1], + [[3, 3, 3], 1], + [[4, 4, 4, 4], 1], + [[1, 2], 2], + [[1, 2, 2, 2, 2], 2], + [[1, 0, 2], 2], + [[1, 10, 2, 30], 3], + [[5, 8, 3, 7, 9, 1], 3], + [[10, 9, 2, 5, 3, 7, 101, 18], 4], + [[10, 10, 9, 9, 2, 2, 5, 5, 3, 3, 7, 7, 101, 101, 18, 18], 4], + [[0, 1, 0, 3, 2, 3], 4], + [[1, 1, 2, 2, 2], 2], + [[1, 1, 2, 2, 2, 3, 3, 3, 3], 3], + [[0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15], 6] + ])('check with %j', (input, expected) => { + expect(longestIncreasingSubsequence(input)).toBe(expected) + }) +}) diff --git a/Dynamic-Programming/tests/LongestPalindromicSubsequence.test.js b/Dynamic-Programming/tests/LongestPalindromicSubsequence.test.js new file mode 100644 index 0000000000..bf5267acb9 --- /dev/null +++ b/Dynamic-Programming/tests/LongestPalindromicSubsequence.test.js @@ -0,0 +1,19 @@ +import { longestPalindromeSubsequence } from '../LongestPalindromicSubsequence' + +describe('LongestPalindromicSubsequence', () => { + it('expects to return 1 as longest palindromic subsequence', () => { + expect(longestPalindromeSubsequence('abcdefgh')).toBe(1) + }) + + it('expects to return 4 as longest palindromic subsequence', () => { + expect(longestPalindromeSubsequence('bbbab')).toBe(4) + }) + + it('expects to return 2 as longest palindromic subsequence', () => { + expect(longestPalindromeSubsequence('cbbd')).toBe(2) + }) + + it('expects to return 7 as longest palindromic subsequence', () => { + expect(longestPalindromeSubsequence('racexyzcxar')).toBe(7) + }) +}) diff --git a/Dynamic-Programming/tests/LongestValidParentheses.test.js b/Dynamic-Programming/tests/LongestValidParentheses.test.js new file mode 100644 index 0000000000..94d9773a4e --- /dev/null +++ b/Dynamic-Programming/tests/LongestValidParentheses.test.js @@ -0,0 +1,19 @@ +import { longestValidParentheses } from '../LongestValidParentheses' + +describe('longestValidParentheses', () => { + it('expects to return 0 as longest valid parentheses substring', () => { + expect(longestValidParentheses('')).toBe(0) + }) + + it('expects to return 2 as longest valid parentheses substring', () => { + expect(longestValidParentheses('(()')).toBe(2) + }) + + it('expects to return 2 as longest valid parentheses substring', () => { + expect(longestValidParentheses(')()())')).toBe(4) + }) + + it('expects to return 2 as longest valid parentheses substring', () => { + expect(longestValidParentheses('(((')).toBe(0) + }) +}) diff --git a/Dynamic-Programming/tests/MaxProductOfThree.test.js b/Dynamic-Programming/tests/MaxProductOfThree.test.js new file mode 100644 index 0000000000..8ebc476398 --- /dev/null +++ b/Dynamic-Programming/tests/MaxProductOfThree.test.js @@ -0,0 +1,78 @@ +import { maxProductOfThree } from '../MaxProductOfThree' + +describe('MaxProductOfThree', () => { + it('expects to throw error for array with only 2 numbers', () => { + expect(() => { + maxProductOfThree([1, 3]) + }).toThrow('Triplet cannot exist with the given array') + }) + + it('expects to return 300 as the maximum product', () => { + expect(maxProductOfThree([10, 6, 5, 3, 1, -10])).toBe(300) + }) + + it('expects to return 300 as the maximum product', () => { + expect(maxProductOfThree([10, -6, 5, 3, 1, -10])).toBe(600) + }) +}) + +// Tests using random arrays of size 3 to 5, with values rangin from -4 to 4 +// The output is compared to a slower function that calculates all possible products of 3 numbers in the array and returns the largest one +describe('MaxProductOfThree, random arrays of size 3 to 5', () => { + // Slower function that operates in O(n^3), where n is the length of the input array. + // Calculates all possible products of 3 numbers in the array and returns the largest + function completeMaxThree(array) { + let maximumProduct = null + for (let i = 0; i < array.length - 2; i++) { + for (let j = i + 1; j < array.length - 1; j++) { + for (let k = j + 1; k < array.length; k++) { + const currentProduct = array[i] * array[j] * array[k] + if (maximumProduct === null || currentProduct > maximumProduct) { + maximumProduct = currentProduct + } + } + } + } + return maximumProduct + } + + // Set up consts for the tests + const maxValue = 4 + const minValue = -4 + const maxLength = 5 + const minLength = 3 + const numberOfRandomTests = 5000 + + // Run each test + for (let i = 0; i < numberOfRandomTests; i++) { + const arr = [] + // Randomize the length of the array in the current test + const length = Math.floor( + Math.random() * (maxLength - minLength) + minLength + ) + + // Fill the array with random values in the specified range + for (let j = 0; j < length + 1; j++) { + arr.push(Math.floor(Math.random() * (maxValue - minValue) + minValue)) + } + + // Calculate the actual max product, slow but completely + const expectedProduct = completeMaxThree(arr) + + // Set up the expectation + it( + 'Expect the array ' + + arr.toString() + + ' to return the maximum three product of ' + + expectedProduct, + () => { + // Calculate the max three product using the function being tested + const actualProduct = maxProductOfThree(arr) + + // Was unable to use expect().toBe(), since it sometimes compared 0 to -0, and that would not pass + // At the same time, standardjs forbid me from checking for === -0 to convert to 0 + expect(actualProduct === expectedProduct).toBeTruthy() + } + ) + } +}) diff --git a/Dynamic-Programming/tests/NumberOfSubsetEqualToGivenSum.test.js b/Dynamic-Programming/tests/NumberOfSubsetEqualToGivenSum.test.js new file mode 100644 index 0000000000..23eed33ebe --- /dev/null +++ b/Dynamic-Programming/tests/NumberOfSubsetEqualToGivenSum.test.js @@ -0,0 +1,25 @@ +import { NumberOfSubsetSum } from '../NumberOfSubsetEqualToGivenSum' + +describe('Testing NumberOfSubsetSum', () => { + it.each([ + [[], 0, 1], + [[], 1, 0], + [[1], 2, 0], + [[1, 2, 3, 4, 5], 0, 1], + [[1, 1, 1, 1, 1], 5, 1], + [[1, 1, 1, 1, 1], 4, 5], + [[1, 2, 3, 3], 6, 3], + [[10, 20, 30, 1], 31, 2], + [[1, 1, 2, 2, 3, 1, 1], 4, 18] + ])('check with %j and %i', (arr, sum, expected) => { + expect(NumberOfSubsetSum(arr, sum)).toBe(expected) + }) + + it.each([ + [[1, 2], -1], + [[0, 2], 2], + [[1, -1], 0] + ])('throws for %j and %i', (arr, sum) => { + expect(() => NumberOfSubsetSum(arr, sum)).toThrowError() + }) +}) diff --git a/Dynamic-Programming/tests/RodCutting.test.js b/Dynamic-Programming/tests/RodCutting.test.js new file mode 100644 index 0000000000..349ea29978 --- /dev/null +++ b/Dynamic-Programming/tests/RodCutting.test.js @@ -0,0 +1,21 @@ +import { rodCut } from '../RodCutting' + +test('Test Case 1', () => { + expect(rodCut([1, 5, 8, 9, 10, 17, 17, 20], 8)).toBe(22) +}) + +test('Test Case 2', () => { + expect(rodCut([1, 5, 4, 2, 1, 11, 19, 12], 8)).toBe(20) +}) + +test('Test Case 3', () => { + expect(rodCut([1, 2, 1], 3)).toBe(3) +}) + +test('Test Case 4', () => { + expect(rodCut([5, 4, 3, 2, 1], 5)).toBe(25) +}) + +test('Test Case 5', () => { + expect(rodCut([3, 5, 8, 8, 10, 16, 14, 19], 8)).toBe(24) +}) diff --git a/Dynamic-Programming/tests/SieveOfEratosthenes.test.js b/Dynamic-Programming/tests/SieveOfEratosthenes.test.js new file mode 100644 index 0000000000..b911440cc9 --- /dev/null +++ b/Dynamic-Programming/tests/SieveOfEratosthenes.test.js @@ -0,0 +1,25 @@ +import { sieveOfEratosthenes } from '../SieveOfEratosthenes' + +describe('SieveOfEratosthenes', () => { + it('Primes till 0', () => { + expect(sieveOfEratosthenes(0)).toEqual([]) + }) + + it('Primes till 1', () => { + expect(sieveOfEratosthenes(1)).toEqual([]) + }) + + it('Primes till 10', () => { + expect(sieveOfEratosthenes(10)).toEqual([2, 3, 5, 7]) + }) + + it('Primes till 23', () => { + expect(sieveOfEratosthenes(23)).toEqual([2, 3, 5, 7, 11, 13, 17, 19, 23]) + }) + + it('Primes till 70', () => { + expect(sieveOfEratosthenes(70)).toEqual([ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 + ]) + }) +}) diff --git a/Dynamic-Programming/tests/TrappingRainWater.test.js b/Dynamic-Programming/tests/TrappingRainWater.test.js new file mode 100644 index 0000000000..f290ea90ff --- /dev/null +++ b/Dynamic-Programming/tests/TrappingRainWater.test.js @@ -0,0 +1,11 @@ +import { trap } from '../TrappingRainWater' + +describe('TrappingRainWater', () => { + it('expects 6 units of rain water are being trapped', () => { + expect(trap([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1])).toBe(6) + }) + + it('expects 9 units of rain water are being trapped', () => { + expect(trap([4, 2, 0, 3, 2, 5])).toBe(9) + }) +}) diff --git a/Dynamic-Programming/tests/TribonacciNumber.test.js b/Dynamic-Programming/tests/TribonacciNumber.test.js new file mode 100644 index 0000000000..1ce012dfe8 --- /dev/null +++ b/Dynamic-Programming/tests/TribonacciNumber.test.js @@ -0,0 +1,23 @@ +import { tribonacci } from '../TribonacciNumber' + +describe('TribonacciNumber', () => { + it('tribonacci of 0', () => { + expect(tribonacci(0)).toBe(0) + }) + + it('tribonacci of 1', () => { + expect(tribonacci(1)).toBe(1) + }) + + it('tribonacci of 2', () => { + expect(tribonacci(2)).toBe(1) + }) + + it('tribonacci of 10', () => { + expect(tribonacci(10)).toBe(149) + }) + + it('tribonacci of 25', () => { + expect(tribonacci(25)).toBe(1389537) + }) +}) diff --git a/Dynamic-Programming/tests/UniquePaths.test.js b/Dynamic-Programming/tests/UniquePaths.test.js new file mode 100644 index 0000000000..eb6f8c74d4 --- /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 0000000000..2780fdd89a --- /dev/null +++ b/Dynamic-Programming/tests/UniquePaths2.test.js @@ -0,0 +1,43 @@ +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/Dynamic-Programming/tests/ZeroOneKnapsack.test.js b/Dynamic-Programming/tests/ZeroOneKnapsack.test.js new file mode 100644 index 0000000000..4f630d1eef --- /dev/null +++ b/Dynamic-Programming/tests/ZeroOneKnapsack.test.js @@ -0,0 +1,40 @@ +import { zeroOneKnapsack } from '../ZeroOneKnapsack' + +describe('ZeroOneKnapsack', () => { + it('zeroOneKnapsack when capacity is 4 and 5 items', () => { + expect( + zeroOneKnapsack( + [ + [1, 8], + [2, 4], + [3, 0], + [2, 5], + [2, 3] + ], + 5, + 4, + [ + [-1, -1, -1, -1, -1], + [-1, -1, -1, -1, -1], + [-1, -1, -1, -1, -1], + [-1, -1, -1, -1, -1], + [-1, -1, -1, -1, -1], + [-1, -1, -1, -1, -1] + ] + ) + ).toBe(13) + }) + + it('zeroOneKnapsack when capacity is 1 and 1 items', () => { + expect( + zeroOneKnapsack([[1, 80]], 1, 1, [ + [-1, -1], + [-1, -1] + ]) + ).toBe(80) + }) + + it('zeroOneKnapsack when capacity is 0 and 1 items', () => { + expect(zeroOneKnapsack([[1, 80]], 1, 0, [[-1], [-1]])).toBe(0) + }) +}) diff --git a/Geometry/Circle.js b/Geometry/Circle.js new file mode 100644 index 0000000000..3e3a3e03f4 --- /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 circle. + */ +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 0000000000..8820cfe0ae --- /dev/null +++ b/Geometry/Cone.js @@ -0,0 +1,30 @@ +/** + * 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/ConvexHullGraham.js b/Geometry/ConvexHullGraham.js new file mode 100644 index 0000000000..0d400242dd --- /dev/null +++ b/Geometry/ConvexHullGraham.js @@ -0,0 +1,103 @@ +/** + * Author: Arnab Ray + * ConvexHull using Graham Scan + * Wikipedia: https://en.wikipedia.org/wiki/Graham_scan + * Given a set of points in the plane. The Convex hull of the set is the smallest + * convex polygon that contains all the points of it. + */ + +function compare(a, b) { + // Compare Function to Sort the points, a and b are points to compare + if (a.x < b.x) return -1 + if (a.x === b.x && a.y < b.y) return -1 + return 1 +} +function orientation(a, b, c) { + // Check orientation of Line(a,b) and Line(b,c) + const alpha = (b.y - a.y) / (b.x - a.x) + const beta = (c.y - b.y) / (c.x - b.x) + + // Clockwise + if (alpha > beta) return 1 + // Anticlockwise + else if (beta > alpha) return -1 + // Colinear + return 0 +} + +function convexHull(points) { + const pointsLen = points.length + if (pointsLen <= 2) { + throw new Error('Minimum of 3 points is required to form closed polygon!') + } + + points.sort(compare) + const p1 = points[0] + const p2 = points[pointsLen - 1] + + // Divide Hull in two halves + const upperPoints = [] + const lowerPoints = [] + + upperPoints.push(p1) + lowerPoints.push(p1) + + for (let i = 1; i < pointsLen; i++) { + if (i === pointsLen - 1 || orientation(p1, points[i], p2) !== -1) { + let upLen = upperPoints.length + + while ( + upLen >= 2 && + orientation( + upperPoints[upLen - 2], + upperPoints[upLen - 1], + points[i] + ) === -1 + ) { + upperPoints.pop() + upLen = upperPoints.length + } + upperPoints.push(points[i]) + } + if (i === pointsLen - 1 || orientation(p1, points[i], p2) !== 1) { + let lowLen = lowerPoints.length + while ( + lowLen >= 2 && + orientation( + lowerPoints[lowLen - 2], + lowerPoints[lowLen - 1], + points[i] + ) === 1 + ) { + lowerPoints.pop() + lowLen = lowerPoints.length + } + lowerPoints.push(points[i]) + } + } + const hull = [] + for (let i = 1; i < upperPoints.length - 1; i++) { + hull.push(upperPoints[i]) + } + for (let i = lowerPoints.length - 1; i >= 0; i--) { + hull.push(lowerPoints[i]) + } + + return hull +} + +export { convexHull } + +// Example + +// const points = [ +// { x: 0, y: 3 }, +// { x: 1, y: 1 }, +// { x: 2, y: 2 }, +// { x: 4, y: 4 }, +// { x: 0, y: 0 }, +// { x: 1, y: 2 }, +// { x: 3, y: 1 }, +// { x: 3, y: 3 }] + +// convexHull(points) diff --git a/Geometry/Pyramid.js b/Geometry/Pyramid.js new file mode 100644 index 0000000000..294f43c873 --- /dev/null +++ b/Geometry/Pyramid.js @@ -0,0 +1,29 @@ +/** + * 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 0000000000..28c9448237 --- /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 0000000000..b2332df43d --- /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 0000000000..3ab0010396 --- /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/ConvexHullGraham.test.js b/Geometry/Test/ConvexHullGraham.test.js new file mode 100644 index 0000000000..ba6c794984 --- /dev/null +++ b/Geometry/Test/ConvexHullGraham.test.js @@ -0,0 +1,41 @@ +import { convexHull } from '../ConvexHullGraham' + +test('The ConvexHull of the following points is [{x: 0, y: 3}, {x: 4, y: 4}, {x: 3, y: 1}, {x: 0, y: 0}]', () => { + const points = [ + { x: 0, y: 3 }, + { x: 1, y: 1 }, + { x: 2, y: 2 }, + { x: 4, y: 4 }, + { x: 0, y: 0 }, + { x: 1, y: 2 }, + { x: 3, y: 1 }, + { x: 3, y: 3 } + ] + const res = convexHull(points) + expect(res).toEqual([ + { x: 0, y: 3 }, + { x: 4, y: 4 }, + { x: 3, y: 1 }, + { x: 0, y: 0 } + ]) +}) + +test('The ConvexHull of the following points is [{x: 1, y: 4}, {x: 9, y: 6}, {x: 7, y: 0}, {x: 0, y: 0}]', () => { + const points = [ + { x: 4, y: 3 }, + { x: 1, y: 4 }, + { x: 2, y: 4 }, + { x: 0, y: 0 }, + { x: 9, y: 6 }, + { x: 1, y: 3 }, + { x: 4, y: 1 }, + { x: 7, y: 0 } + ] + const res = convexHull(points) + expect(res).toEqual([ + { x: 1, y: 4 }, + { x: 9, y: 6 }, + { x: 7, y: 0 }, + { x: 0, y: 0 } + ]) +}) diff --git a/Geometry/Test/Pyramid.test.js b/Geometry/Test/Pyramid.test.js new file mode 100644 index 0000000000..a6d2b16d1a --- /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 0000000000..18f8333a7c --- /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/BellmanFord.js b/Graphs/BellmanFord.js new file mode 100644 index 0000000000..a324bc00b5 --- /dev/null +++ b/Graphs/BellmanFord.js @@ -0,0 +1,58 @@ +/* +The Bellmanโ€“Ford algorithm is an algorithm that computes shortest paths +from a single source vertex to all of the other vertices in a weighted digraph. +It also detects negative weight cycle. + +Complexity: + Worst-case performance O(VE) + Best-case performance O(E) + Worst-case space complexity O(V) + +Reference: + https://en.wikipedia.org/wiki/Bellmanโ€“Ford_algorithm + https://cp-algorithms.com/graph/bellman_ford.html + +*/ + +/** + * + * @param graph Graph in the format (u, v, w) where + * the edge is from vertex u to v. And weight + * of the edge is w. + * @param V Number of vertices in graph + * @param E Number of edges in graph + * @param src Starting node + * @param dest Destination node + * @returns Shortest distance from source to destination + */ +function BellmanFord(graph, V, E, src, dest) { + // Initialize distance of all vertices as infinite. + const dis = Array(V).fill(Infinity) + // initialize distance of source as 0 + dis[src] = 0 + + // Relax all edges |V| - 1 times. A simple + // shortest path from src to any other + // vertex can have at-most |V| - 1 edges + for (let i = 0; i < V - 1; i++) { + for (let j = 0; j < E; j++) { + if (dis[graph[j][0]] + graph[j][2] < dis[graph[j][1]]) { + dis[graph[j][1]] = dis[graph[j][0]] + graph[j][2] + } + } + } + // check for negative-weight cycles. + for (let i = 0; i < E; i++) { + const x = graph[i][0] + const y = graph[i][1] + const weight = graph[i][2] + if (dis[x] !== Infinity && dis[x] + weight < dis[y]) { + return null + } + } + for (let i = 0; i < V; i++) { + if (i === dest) return dis[i] + } +} + +export { BellmanFord } diff --git a/Graphs/BinaryLifting.js b/Graphs/BinaryLifting.js new file mode 100644 index 0000000000..b9a0116abc --- /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 technique 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 new file mode 100644 index 0000000000..c77ee0e923 --- /dev/null +++ b/Graphs/BreadthFirstSearch.js @@ -0,0 +1,37 @@ +import Queue from '../Data-Structures/Queue/Queue' + +/** + * Breadth-first search is an algorithm for traversing a graph. + * + * It discovers all nodes reachable from the starting position by exploring all of the neighbor nodes at the present + * depth prior to moving on to the nodes at the next depth level. + * + * (description adapted from https://en.wikipedia.org/wiki/Breadth-first_search) + * @see https://www.koderdojo.com/blog/breadth-first-search-and-shortest-path-in-csharp-and-net-core + */ +export function breadthFirstSearch(graph, startingNode) { + // visited keeps track of all nodes visited + const visited = new Set() + + // queue contains the nodes to be explored in the future + const queue = new Queue() + queue.enqueue(startingNode) + + while (!queue.isEmpty()) { + // start with the queue's first node + const node = queue.dequeue() + + if (!visited.has(node)) { + // mark the node as visited + visited.add(node) + const neighbors = graph[node] + + // put all its neighbors into the queue + for (let i = 0; i < neighbors.length; i++) { + queue.enqueue(neighbors[i]) + } + } + } + + return visited +} diff --git a/Graphs/BreadthFirstShortestPath.js b/Graphs/BreadthFirstShortestPath.js new file mode 100644 index 0000000000..14b74dedb7 --- /dev/null +++ b/Graphs/BreadthFirstShortestPath.js @@ -0,0 +1,54 @@ +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. + * + * It searches the target node among all neighbors of the starting node, then the process is repeated on the level of + * the neighbors of the neighbors and so on. + * + * @see https://en.wikipedia.org/wiki/Breadth-first_search + * @see https://www.koderdojo.com/blog/breadth-first-search-and-shortest-path-in-csharp-and-net-core + */ +export function breadthFirstShortestPath(graph, startNode, targetNode) { + // check if startNode & targetNode are identical + if (startNode === targetNode) { + return [startNode] + } + + // visited keeps track of all nodes visited + const visited = new Set() + + // queue contains the paths to be explored in the future + const initialPath = [startNode] + const queue = new Queue() + queue.enqueue(initialPath) + + while (!queue.isEmpty()) { + // start with the queue's first path + const path = queue.dequeue() + const node = path[path.length - 1] + + // explore this node if it hasn't been visited yet + if (!visited.has(node)) { + // mark the node as visited + visited.add(node) + + const neighbors = graph[node] + + // create a new path in the queue for each neighbor + for (let i = 0; i < neighbors.length; i++) { + const newPath = path.concat([neighbors[i]]) + + // the first path to contain the target node is the shortest path + if (neighbors[i] === targetNode) { + return newPath + } + + // queue the new path + queue.enqueue(newPath) + } + } + } + + // the target node was not reachable + return [] +} diff --git a/Graphs/ConnectedComponents.js b/Graphs/ConnectedComponents.js index 842a2eedcb..2605a3f280 100644 --- a/Graphs/ConnectedComponents.js +++ b/Graphs/ConnectedComponents.js @@ -1,23 +1,27 @@ class GraphUnweightedUndirectedAdjacencyList { // Unweighted Undirected Graph class - constructor () { + constructor() { this.connections = {} } - addNode (node) { + addNode(node) { // Function to add a node to the graph (connection represented by set) this.connections[node] = new Set() } - addEdge (node1, node2) { + 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)) { this.addNode(node1) } - if (!(node2 in this.connections)) { this.addNode(node2) } + if (!(node1 in this.connections)) { + this.addNode(node1) + } + if (!(node2 in this.connections)) { + this.addNode(node2) + } this.connections[node1].add(node2) this.connections[node2].add(node1) } - DFSComponent (components, node, visited) { + DFSComponent(components, node, visited) { // Helper function to populate the visited set with the nodes in each component // adding the first visited node in the component to the array @@ -28,29 +32,33 @@ class GraphUnweightedUndirectedAdjacencyList { const curr = stack.pop() visited.add(curr.toString()) for (const neighbour of this.connections[curr].keys()) { - if (!visited.has(neighbour.toString())) { stack.push(neighbour) } + if (!visited.has(neighbour.toString())) { + stack.push(neighbour) + } } } } - connectedComponents () { + connectedComponents() { // Function to generate the Connected Components // Result is an array containing 1 node from each component const visited = new Set() const components = [] for (const node of Object.keys(this.connections)) { - if (!visited.has(node.toString())) { this.DFSComponent(components, node, visited) } + if (!visited.has(node.toString())) { + this.DFSComponent(components, node, visited) + } } return components } } -function main () { - const graph = new GraphUnweightedUndirectedAdjacencyList() - graph.addEdge(1, 2) // Component 1 - graph.addEdge(3, 4) // Component 2 - graph.addEdge(3, 5) // Component 2 - console.log(graph.connectedComponents()) -} +export { GraphUnweightedUndirectedAdjacencyList } + +// Example -main() +// const graph = new GraphUnweightedUndirectedAdjacencyList() +// graph.addEdge(1, 2) // Component 1 +// graph.addEdge(3, 4) // Component 2 +// graph.addEdge(3, 5) // Component 2 +// const components = graph.connectedComponents() diff --git a/Graphs/Density.js b/Graphs/Density.js new file mode 100644 index 0000000000..b37c95f77c --- /dev/null +++ b/Graphs/Density.js @@ -0,0 +1,11 @@ +/* +The density of a network is a measure of how many edges exist proportional to +how many edges would exist in a complete network (where all possible edges). +https://networkx.org/documentation/networkx-1.9/reference/generated/networkx.classes.function.density.html +*/ +function density(numberOfNodes, numberOfEdges, isDirected = false) { + const multi = isDirected ? 1 : 2 + return (multi * numberOfEdges) / (numberOfNodes * (numberOfNodes - 1)) +} + +export { density } diff --git a/Graphs/DepthFirstSearchIterative.js b/Graphs/DepthFirstSearchIterative.js index 3eb9c1f832..a033c0bcad 100644 --- a/Graphs/DepthFirstSearchIterative.js +++ b/Graphs/DepthFirstSearchIterative.js @@ -1,30 +1,36 @@ class GraphUnweightedUndirected { // Unweighted Undirected Graph class - constructor () { + constructor() { this.connections = {} } - addNode (node) { + addNode(node) { // Function to add a node to the graph (connection represented by set) this.connections[node] = new Set() } - addEdge (node1, node2) { + 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)) { this.addNode(node1) } - if (!(node2 in this.connections)) { this.addNode(node2) } + if (!(node1 in this.connections)) { + this.addNode(node1) + } + if (!(node2 in this.connections)) { + this.addNode(node2) + } this.connections[node1].add(node2) this.connections[node2].add(node1) } - DFSIterative (node, value) { + DFSIterative(node, value) { // DFS Function to search if a node with the given value is present in the graph const stack = [node] const visited = new Set() while (stack.length > 0) { const currNode = stack.pop() // if the current node contains the value being searched for, true is returned - if (currNode === value) { return true } + if (currNode === value) { + return true + } // adding the current node to the visited set visited.add(currNode) // adding neighbours in the stack @@ -38,14 +44,14 @@ class GraphUnweightedUndirected { } } -function main () { - const graph = new GraphUnweightedUndirected() - graph.addEdge(1, 2) - graph.addEdge(2, 3) - graph.addEdge(2, 4) - graph.addEdge(3, 5) - console.log(graph.DFSIterative(5, 1)) - console.log(graph.DFSIterative(5, 100)) -} +export { GraphUnweightedUndirected } + +// Example -main() +// const graph = new GraphUnweightedUndirected() +// graph.addEdge(1, 2) +// graph.addEdge(2, 3) +// graph.addEdge(2, 4) +// graph.addEdge(3, 5) +// graph.DFSIterative(5, 1) +// graph.DFSIterative(5, 100) diff --git a/Graphs/DepthFirstSearchRecursive.js b/Graphs/DepthFirstSearchRecursive.js index 6522e9d15f..b7916b7ed5 100644 --- a/Graphs/DepthFirstSearchRecursive.js +++ b/Graphs/DepthFirstSearchRecursive.js @@ -1,46 +1,52 @@ class GraphUnweightedUndirected { // Unweighted Undirected Graph class - constructor () { + constructor() { this.connections = {} } - addNode (node) { + addNode(node) { // Function to add a node to the graph (connection represented by set) this.connections[node] = new Set() } - addEdge (node1, node2) { + 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)) { this.addNode(node1) } - if (!(node2 in this.connections)) { this.addNode(node2) } + if (!(node1 in this.connections)) { + this.addNode(node1) + } + if (!(node2 in this.connections)) { + this.addNode(node2) + } this.connections[node1].add(node2) this.connections[node2].add(node1) } - DFSRecursive (node, value, visited = new Set()) { + DFSRecursive(node, value, visited = new Set()) { // DFS Function to search if a node with the given value is present in the graph // checking if the searching node has been found - if (node === value) { return true } + if (node === value) { + return true + } // adding the current node to the visited set visited.add(node) - // calling the helper function recursivly for all unvisited nodes + // calling the helper function recursively for all unvisited nodes for (const neighbour of this.connections[node]) { if (!visited.has(neighbour)) { - if (this.DFSRecursive(neighbour, value, visited)) { return true } + if (this.DFSRecursive(neighbour, value, visited)) { + return true + } } } return false } } -function main () { - const graph = new GraphUnweightedUndirected() - graph.addEdge(1, 2) - graph.addEdge(2, 3) - graph.addEdge(2, 4) - graph.addEdge(3, 5) - console.log(graph.DFSRecursive(5, 1)) - console.log(graph.DFSRecursive(5, 100)) -} +export { GraphUnweightedUndirected } -main() +// const graph = new GraphUnweightedUndirected() +// graph.addEdge(1, 2) +// graph.addEdge(2, 3) +// graph.addEdge(2, 4) +// graph.addEdge(3, 5) +// graph.DFSRecursive(5, 1) +// graph.DFSRecursive(5, 100) diff --git a/Graphs/Dijkstra.js b/Graphs/Dijkstra.js index 159b19ad05..a836df0ff1 100644 --- a/Graphs/Dijkstra.js +++ b/Graphs/Dijkstra.js @@ -2,11 +2,11 @@ * Author: Samarth Jain * Dijkstra's Algorithm implementation in JavaScript * Dijkstra's Algorithm calculates the minimum distance between two nodes. - * It is used to find the shortes path. + * It is used to find the shortest path. * It uses graph data structure. */ -function createGraph (V, E) { +function createGraph(V, E) { // V - Number of vertices in graph // E - Number of edges in graph (u,v,w) const adjList = [] // Adjacency list @@ -20,7 +20,7 @@ function createGraph (V, E) { return adjList } -function djikstra (graph, V, src) { +function djikstra(graph, V, src) { const vis = Array(V).fill(0) const dist = [] for (let i = 0; i < V; i++) dist.push([10000, -1]) @@ -47,30 +47,30 @@ function djikstra (graph, V, src) { return dist } -const V = 9 -const E = [ - [0, 1, 4], - [0, 7, 8], - [1, 7, 11], - [1, 2, 8], - [7, 8, 7], - [6, 7, 1], - [2, 8, 2], - [6, 8, 6], - [5, 6, 2], - [2, 5, 4], - [2, 3, 7], - [3, 5, 14], - [3, 4, 9], - [4, 5, 10] -] +export { createGraph, djikstra } -const graph = createGraph(V, E) -const distances = djikstra(graph, V, 0) +// const V = 9 +// const E = [ +// [0, 1, 4], +// [0, 7, 8], +// [1, 7, 11], +// [1, 2, 8], +// [7, 8, 7], +// [6, 7, 1], +// [2, 8, 2], +// [6, 8, 6], +// [5, 6, 2], +// [2, 5, 4], +// [2, 3, 7], +// [3, 5, 14], +// [3, 4, 9], +// [4, 5, 10] +// ] + +// const graph = createGraph(V, E) +// const distances = djikstra(graph, V, 0) /** * The first value in the array determines the minimum distance and the * second value represents the parent node from which the minimum distance has been calculated */ - -console.log(distances) diff --git a/Graphs/DijkstraSmallestPath.js b/Graphs/DijkstraSmallestPath.js index 25571ed725..9765f1c679 100644 --- a/Graphs/DijkstraSmallestPath.js +++ b/Graphs/DijkstraSmallestPath.js @@ -1,23 +1,27 @@ // starting at s -function solve (graph, s) { - var solutions = {} +function solve(graph, s) { + const solutions = {} solutions[s] = [] solutions[s].dist = 0 while (true) { - var p = null - var neighbor = null - var dist = Infinity + let p = null + let neighbor = null + let dist = Infinity - for (var n in solutions) { - if (!solutions[n]) { continue } - var ndist = solutions[n].dist - var adj = graph[n] + for (const n in solutions) { + if (!solutions[n]) { + continue + } + const ndist = solutions[n].dist + const adj = graph[n] - for (var a in adj) { - if (solutions[a]) { continue } + for (const a in adj) { + if (solutions[a]) { + continue + } - var d = adj[a] + ndist + const d = adj[a] + ndist if (d < dist) { p = solutions[n] neighbor = a @@ -39,73 +43,5 @@ function solve (graph, s) { return solutions } -// create graph -var graph = {} - -var layout = { - R: ['2'], - 2: ['3', '4'], - 3: ['4', '6', '13'], - 4: ['5', '8'], - 5: ['7', '11'], - 6: ['13', '15'], - 7: ['10'], - 8: ['11', '13'], - 9: ['14'], - 10: [], - 11: ['12'], - 12: [], - 13: ['14'], - 14: [], - 15: [] -} - -// convert uni-directional to bi-directional graph -// var graph = { -// a: {e:1, b:1, g:3}, -// b: {a:1, c:1}, -// c: {b:1, d:1}, -// d: {c:1, e:1}, -// e: {d:1, a:1}, -// f: {g:1, h:1}, -// g: {a:3, f:1}, -// h: {f:1} -// }; - -for (var id in layout) { - if (!graph[id]) { graph[id] = {} } - layout[id].forEach(function (aid) { - graph[id][aid] = 1 - if (!graph[aid]) { graph[aid] = {} } - graph[aid][id] = 1 - }) -} - -// choose start node -var start = '10' -// get all solutions -var solutions = solve(graph, start) - -console.log("From '" + start + "' to") -// display solutions -for (var s in solutions) { - if (!solutions[s]) continue - console.log(' -> ' + s + ': [' + solutions[s].join(', ') + '] (dist:' + solutions[s].dist + ')') -} -// From '10' to -// -> 2: [7, 5, 4, 2] (dist:4) -// -> 3: [7, 5, 4, 3] (dist:4) -// -> 4: [7, 5, 4] (dist:3) -// -> 5: [7, 5] (dist:2) -// -> 6: [7, 5, 4, 3, 6] (dist:5) -// -> 7: [7] (dist:1) -// -> 8: [7, 5, 4, 8] (dist:4) -// -> 9: [7, 5, 4, 3, 13, 14, 9] (dist:7) -// -> 10: [] (dist:0) -// -> 11: [7, 5, 11] (dist:3) -// -> 12: [7, 5, 11, 12] (dist:4) -// -> 13: [7, 5, 4, 3, 13] (dist:5) -// -> 14: [7, 5, 4, 3, 13, 14] (dist:6) -// -> 15: [7, 5, 4, 3, 6, 15] (dist:6) -// -> R: [7, 5, 4, 2, R] (dist:5) +export { solve } diff --git a/Graphs/FloydWarshall.js b/Graphs/FloydWarshall.js new file mode 100644 index 0000000000..4552d4c602 --- /dev/null +++ b/Graphs/FloydWarshall.js @@ -0,0 +1,47 @@ +/* + Source: + https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm + + Complexity: + O(|V|^3) where V is the set of vertices +*/ + +const FloydWarshall = (dist) => { + // Input:- dist: 2D Array where dist[i][j] = edge weight b/w i and j + // Output:- dist: 2D Array where dist[i][j] = shortest dist b/w i and j + const n = dist.length + for (let k = 0; k < n; k++) { + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + if (dist[i][j] > dist[i][k] + dist[k][j]) { + // dist from i to j via k is lesser than the current distance + dist[i][j] = dist[i][k] + dist[k][j] + } + } + } + } + return dist +} + +export { FloydWarshall } + +// For the following graph (edge weights are shown in brackets) +// 4 1 dist[1][2] = dist[2][1] = 1 +// \ (2)/ \ dist[1][3] = dist[3][1] = 2 +// \ / \(1) dist[1][4] = dist[4][1] = Infinity +// (1)\ / \ dist[3][4] = dist[4][3] = 1 +// 3 2 dist[2][4] = dist[4][2] = Infinity +// dist[2][3] = dist[3][2] = Infinity +// Output should be: +// [ [0, 1, 2, 3], +// [1, 0, 3, 4], +// [2, 3, 0, 1], +// [3, 4, 1, 0] ] + +// FloydWarshall( +// [[0, 1, 2, Infinity], +// [1, 0, Infinity, Infinity], +// [2, Infinity, 0, 1], +// [Infinity, Infinity, 1, 0] +// ] +// ) diff --git a/Graphs/Kosaraju.js b/Graphs/Kosaraju.js new file mode 100644 index 0000000000..2f22a300b2 --- /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/KruskalMST.js b/Graphs/KruskalMST.js index 0a09c631ba..c2f749c68d 100644 --- a/Graphs/KruskalMST.js +++ b/Graphs/KruskalMST.js @@ -1,6 +1,6 @@ class DisjointSetTreeNode { // Disjoint Set Node to store the parent and rank - constructor (key) { + constructor(key) { this.key = key this.parent = this this.rank = 0 @@ -9,17 +9,17 @@ class DisjointSetTreeNode { class DisjointSetTree { // Disjoint Set DataStructure - constructor () { + constructor() { // map to from node name to the node object this.map = {} } - makeSet (x) { + makeSet(x) { // Function to create a new set with x as its member this.map[x] = new DisjointSetTreeNode(x) } - findSet (x) { + findSet(x) { // Function to find the set x belongs to (with path-compression) if (this.map[x] !== this.map[x].parent) { this.map[x].parent = this.findSet(this.map[x].parent.key) @@ -27,12 +27,12 @@ class DisjointSetTree { return this.map[x].parent } - union (x, y) { + union(x, y) { // Function to merge 2 disjoint sets this.link(this.findSet(x), this.findSet(y)) } - link (x, y) { + link(x, y) { // Helper function for union operation if (x.rank > y.rank) { y.parent = x @@ -47,26 +47,30 @@ class DisjointSetTree { class GraphWeightedUndirectedAdjacencyList { // Weighted Undirected Graph class - constructor () { + constructor() { this.connections = {} this.nodes = 0 } - addNode (node) { + addNode(node) { // Function to add a node to the graph (connection represented by set) this.connections[node] = {} this.nodes += 1 } - addEdge (node1, node2, weight) { + addEdge(node1, node2, weight) { // Function to add an edge (adds the node too if they are not present in the graph) - if (!(node1 in this.connections)) { this.addNode(node1) } - if (!(node2 in this.connections)) { this.addNode(node2) } + if (!(node1 in this.connections)) { + this.addNode(node1) + } + if (!(node2 in this.connections)) { + this.addNode(node2) + } this.connections[node1][node2] = weight this.connections[node2][node1] = weight } - KruskalMST () { + KruskalMST() { // Kruskal's Algorithm to generate a Minimum Spanning Tree (MST) of a graph // Details: https://en.wikipedia.org/wiki/Kruskal%27s_algorithm // getting the edges in ascending order of weights @@ -83,7 +87,7 @@ class GraphWeightedUndirectedAdjacencyList { edges.sort((a, b) => a[2] - b[2]) // creating the disjoint set const disjointSet = new DisjointSetTree() - Object.keys(this.connections).forEach(node => disjointSet.makeSet(node)) + Object.keys(this.connections).forEach((node) => disjointSet.makeSet(node)) // MST generation const graph = new GraphWeightedUndirectedAdjacencyList() let numEdges = 0 @@ -101,15 +105,12 @@ class GraphWeightedUndirectedAdjacencyList { } } -function main () { - const graph = new GraphWeightedUndirectedAdjacencyList() - graph.addEdge(1, 2, 1) - graph.addEdge(2, 3, 2) - graph.addEdge(3, 4, 1) - graph.addEdge(3, 5, 100) // Removed in MST - graph.addEdge(4, 5, 5) - console.log(graph) - console.log(graph.KruskalMST()) -} +export { GraphWeightedUndirectedAdjacencyList } -main() +// const graph = new GraphWeightedUndirectedAdjacencyList() +// graph.addEdge(1, 2, 1) +// graph.addEdge(2, 3, 2) +// graph.addEdge(3, 4, 1) +// graph.addEdge(3, 5, 100) // Removed in MST +// graph.addEdge(4, 5, 5) +// graph.KruskalMST() diff --git a/Graphs/LCABinaryLifting.js b/Graphs/LCABinaryLifting.js new file mode 100644 index 0000000000..507f4a31b1 --- /dev/null +++ b/Graphs/LCABinaryLifting.js @@ -0,0 +1,61 @@ +/** + * Author: Adrito Mukherjee + * Finding 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/NodeNeighbors.js b/Graphs/NodeNeighbors.js new file mode 100644 index 0000000000..312f20101f --- /dev/null +++ b/Graphs/NodeNeighbors.js @@ -0,0 +1,40 @@ +// https://en.wikipedia.org/wiki/Neighbourhood_(graph_theory) + +class Graph { + // Generic graph: the algorithm works regardless of direction or weight + constructor() { + this.edges = [] + } + + addEdge(node1, node2) { + // Adding edges to the graph + this.edges.push({ + node1, + node2 + }) + } + + nodeNeighbors(node) { + // Returns an array with all of the node neighbors + const neighbors = new Set() + for (const edge of this.edges) { + // Checks if they have an edge between them and if the neighbor is not + // already in the neighbors array + if (edge.node1 === node && !neighbors.has(edge.node2)) { + neighbors.add(edge.node2) + } else if (edge.node2 === node && !neighbors.has(edge.node1)) { + neighbors.add(edge.node1) + } + } + return neighbors + } +} + +export { Graph } + +// const graph = new Graph() +// graph.addEdge(1, 2) +// graph.addEdge(2, 3) +// graph.addEdge(3, 5) +// graph.addEdge(1, 5) +// graph.nodeNeighbors(1) diff --git a/Graphs/NumberOfIslands.js b/Graphs/NumberOfIslands.js new file mode 100644 index 0000000000..62891a4814 --- /dev/null +++ b/Graphs/NumberOfIslands.js @@ -0,0 +1,81 @@ +/* Number of Islands +https://dev.to/rattanakchea/amazons-interview-question-count-island-21h6 +Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. + +a two dimensional grid map +each element is going to represent a piece of land +1 is land, +0 is water +output a number which is the number of islands + +Example 1: + Input: + 11110 + 11010 + 11000 + 00000 + + Output: 1 + +Example 2: + Input: + 11000 + 11000 + 00100 + 00011 + + Output: 3 + +I: two dimensional array +O: a single integer; total number of islands + +Pseudocode: + OUTER FUNCTION + set count to 0 + + INNER FUNCTION - flood (col, row) + if the tile is water + return + make tile water(flood tile) + invoke flood on the neighbor coordinates + + iterate over the matrix (col, row) + if the current element is a 1 + increment count + invoke flood (coordinates for col and row) + + Return the count +*/ + +const islands = (matrixGrid) => { + const matrix = matrixGrid + let counter = 0 + + const flood = (row, col) => { + if (row < 0 || col < 0) return // Off the map above or left + if (row >= matrix.length || col >= matrix[row].length) return // Off the map below or right + + const tile = matrix[row][col] + if (tile !== '1') return + + matrix[row][col] = '0' + + flood(row + 1, col) // Down + flood(row - 1, col) // Up + flood(row, col + 1) // Right + flood(row, col - 1) // Left + } + + for (let row = 0; row < matrix.length; row += 1) { + for (let col = 0; col < matrix[row].length; col += 1) { + const current = matrix[row][col] + if (current === '1') { + flood(row, col) + counter += 1 + } + } + } + return counter +} + +export { islands } diff --git a/Graphs/PrimMST.js b/Graphs/PrimMST.js index be1e18c0d0..ea19cadb35 100644 --- a/Graphs/PrimMST.js +++ b/Graphs/PrimMST.js @@ -1,185 +1,50 @@ -// Priority Queue Helper functions -function getParentPosition (position) { - // Get the parent node of the current node - return Math.floor((position - 1) / 2) -} -function getChildrenPosition (position) { - // Get the children nodes of the current node - return [2 * position + 1, 2 * position + 2] -} - -class PriorityQueue { - // Priority Queue class using Minimum Binary Heap - constructor () { - this._heap = [] - this.keys = {} - } - - isEmpty () { - // Checking if the heap is empty - return this._heap.length === 0 - } - - push (key, priority) { - // Adding element to the queue (equivalent to add) - this._heap.push([key, priority]) - this.keys[key] = this._heap.length - 1 - this._shiftUp(this.keys[key]) - } - - pop () { - // Removing the element with least priority (equivalent to extractMin) - this._swap(0, this._heap.length - 1) - const [key] = this._heap.pop() - delete this.keys[key] - this._shiftDown(0) - return key - } - - contains (key) { - // Check if a given key is present in the queue - return (key in this.keys) - } - - update (key, priority) { - // Update the priority of the given element (equivalent to decreaseKey) - const currPos = this.keys[key] - this._heap[currPos][1] = priority - const parentPos = getParentPosition(currPos) - const currPriority = this._heap[currPos][1] - let parentPriority = Infinity - if (parentPos >= 0) { - parentPriority = this._heap[parentPos][1] - } - const [child1Pos, child2Pos] = getChildrenPosition(currPos) - let [child1Priority, child2Priority] = [Infinity, Infinity] - if (child1Pos < this._heap.length) { - child1Priority = this._heap[child1Pos][1] - } - if (child2Pos < this._heap.length) { - child2Priority = this._heap[child2Pos][1] - } - - if (parentPos >= 0 && parentPriority > currPriority) { - this._shiftUp(currPos) - } else if (child2Pos < this._heap.length && - (child1Priority < currPriority || child2Priority < currPriority)) { - this._shiftDown(currPos) - } - } - - _shiftUp (position) { - // Helper function to shift up a node to proper position (equivalent to bubbleUp) - let currPos = position - let parentPos = getParentPosition(currPos) - let currPriority = this._heap[currPos][1] - let parentPriority = Infinity - if (parentPos >= 0) { - parentPriority = this._heap[parentPos][1] - } - - while (parentPos >= 0 && parentPriority > currPriority) { - this._swap(currPos, parentPos) - currPos = parentPos - parentPos = getParentPosition(currPos) - currPriority = this._heap[currPos][1] - try { - parentPriority = this._heap[parentPos][1] - } catch (error) { - parentPriority = Infinity - } - } - this.keys[this._heap[currPos][0]] = currPos - } - - _shiftDown (position) { - // Helper function to shift down a node to proper position (equivalent to bubbleDown) - let currPos = position - let [child1Pos, child2Pos] = getChildrenPosition(currPos) - let [child1Priority, child2Priority] = [Infinity, Infinity] - if (child1Pos < this._heap.length) { - child1Priority = this._heap[child1Pos][1] - } - if (child2Pos < this._heap.length) { - child2Priority = this._heap[child2Pos][1] - } - let currPriority - try { - currPriority = this._heap[currPos][1] - } catch { - return - } - - while (child2Pos < this._heap.length && - (child1Priority < currPriority || child2Priority < currPriority)) { - if (child1Priority < currPriority && child1Priority < child2Priority) { - this._swap(child1Pos, currPos) - currPos = child1Pos - } else { - this._swap(child2Pos, currPos) - currPos = child2Pos - } - [child1Pos, child2Pos] = getChildrenPosition(currPos) - try { - [child1Priority, child2Priority] = [this._heap[child1Pos][1], this._heap[child2Pos][1]] - } catch (error) { - [child1Priority, child2Priority] = [Infinity, Infinity] - } - - currPriority = this._heap[currPos][1] - } - this.keys[this._heap[currPos][0]] = currPos - if (child1Pos < this._heap.length && child1Priority < currPriority) { - this._swap(child1Pos, currPos) - this.keys[this._heap[child1Pos][0]] = child1Pos - } - } - - _swap (position1, position2) { - // Helper function to swap 2 nodes - [this._heap[position1], this._heap[position2]] = [this._heap[position2], this._heap[position1]] - this.keys[this._heap[position1][0]] = position1 - this.keys[this._heap[position2][0]] = position2 - } -} - +import { KeyPriorityQueue } from '../Data-Structures/Heap/KeyPriorityQueue' class GraphWeightedUndirectedAdjacencyList { // Weighted Undirected Graph class - constructor () { + constructor() { this.connections = {} } - addNode (node) { + addNode(node) { // Function to add a node to the graph (connection represented by set) this.connections[node] = {} } - addEdge (node1, node2, weight) { + addEdge(node1, node2, weight) { // Function to add an edge (adds the node too if they are not present in the graph) - if (!(node1 in this.connections)) { this.addNode(node1) } - if (!(node2 in this.connections)) { this.addNode(node2) } + if (!(node1 in this.connections)) { + this.addNode(node1) + } + if (!(node2 in this.connections)) { + this.addNode(node2) + } this.connections[node1][node2] = weight this.connections[node2][node1] = weight } - PrimMST (start) { + PrimMST(start) { // Prim's Algorithm to generate a Minimum Spanning Tree (MST) of a graph // Details: https://en.wikipedia.org/wiki/Prim%27s_algorithm const distance = {} const parent = {} - const priorityQueue = new PriorityQueue() + const priorityQueue = new KeyPriorityQueue() // Initialization for (const node in this.connections) { - distance[node] = (node === start.toString() ? 0 : Infinity) + distance[node] = node === start.toString() ? 0 : Infinity parent[node] = null priorityQueue.push(node, distance[node]) } // Updating 'distance' object while (!priorityQueue.isEmpty()) { const node = priorityQueue.pop() - Object.keys(this.connections[node]).forEach(neighbour => { - if (priorityQueue.contains(neighbour) && distance[node] + this.connections[node][neighbour] < distance[neighbour]) { - distance[neighbour] = distance[node] + this.connections[node][neighbour] + Object.keys(this.connections[node]).forEach((neighbour) => { + if ( + priorityQueue.contains(neighbour) && + distance[node] + this.connections[node][neighbour] < + distance[neighbour] + ) { + distance[neighbour] = + distance[node] + this.connections[node][neighbour] parent[neighbour] = node priorityQueue.update(neighbour, distance[neighbour]) } @@ -188,7 +53,7 @@ class GraphWeightedUndirectedAdjacencyList { // MST Generation from the 'parent' object const graph = new GraphWeightedUndirectedAdjacencyList() - Object.keys(parent).forEach(node => { + Object.keys(parent).forEach((node) => { if (node && parent[node]) { graph.addEdge(node, parent[node], this.connections[node][parent[node]]) } @@ -197,14 +62,4 @@ class GraphWeightedUndirectedAdjacencyList { } } -function main () { - const graph = new GraphWeightedUndirectedAdjacencyList() - graph.addEdge(1, 2, 1) - graph.addEdge(2, 3, 2) - graph.addEdge(3, 4, 1) - graph.addEdge(3, 5, 100) // Removed in MST - graph.addEdge(4, 5, 5) - console.log(graph.PrimMST(1)) -} - -main() +export { GraphWeightedUndirectedAdjacencyList } diff --git a/Graphs/test/BellmanFord.test.js b/Graphs/test/BellmanFord.test.js new file mode 100644 index 0000000000..c7ad375c34 --- /dev/null +++ b/Graphs/test/BellmanFord.test.js @@ -0,0 +1,51 @@ +import { BellmanFord } from '../BellmanFord.js' + +test('Test Case 1', () => { + const V = 5 + const E = 8 + const destination = 3 + const graph = [ + [0, 1, -1], + [0, 2, 4], + [1, 2, 3], + [1, 3, 2], + [1, 4, 2], + [3, 2, 5], + [3, 1, 1], + [4, 3, -3] + ] + const dist = BellmanFord(graph, V, E, 0, destination) + expect(dist).toBe(-2) +}) +test('Test Case 2', () => { + const V = 6 + const E = 9 + const destination = 4 + const graph = [ + [0, 1, 3], + [0, 3, 6], + [0, 5, -1], + [1, 2, -3], + [1, 4, -2], + [5, 2, 5], + [2, 3, 1], + [4, 3, 5], + [5, 4, 2] + ] + const dist = BellmanFord(graph, V, E, 0, destination) + expect(dist).toBe(1) +}) +test('Test Case 3', () => { + const V = 4 + const E = 5 + const destination = 1 + const graph = [ + [0, 3, -1], + [0, 2, 4], + [3, 2, 2], + [3, 1, 5], + [2, 1, -1] + ] + const dist = BellmanFord(graph, V, E, 0, destination) + expect(dist).toBe(0) +}) diff --git a/Graphs/test/BinaryLifting.test.js b/Graphs/test/BinaryLifting.test.js new file mode 100644 index 0000000000..769b877e01 --- /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/BreadthFirstSearch.test.js b/Graphs/test/BreadthFirstSearch.test.js new file mode 100644 index 0000000000..5fc8db5cad --- /dev/null +++ b/Graphs/test/BreadthFirstSearch.test.js @@ -0,0 +1,39 @@ +import { breadthFirstSearch } from '../BreadthFirstSearch' + +describe('BreadthFirstSearch', () => { + const graph = { + A: ['B', 'D'], + B: ['E'], + C: ['D'], + D: ['A'], + E: ['D'], + F: ['G'], + G: [] + } + /* + A <-> B + สŒ | + | | + v v + C --> D <-- E + + F --> G + */ + + it('should return the visited nodes', () => { + expect(Array.from(breadthFirstSearch(graph, 'C'))).toEqual([ + 'C', + 'D', + 'A', + 'B', + 'E' + ]) + expect(Array.from(breadthFirstSearch(graph, 'A'))).toEqual([ + 'A', + 'B', + 'D', + 'E' + ]) + expect(Array.from(breadthFirstSearch(graph, 'F'))).toEqual(['F', 'G']) + }) +}) diff --git a/Graphs/test/BreadthFirstShortestPath.test.js b/Graphs/test/BreadthFirstShortestPath.test.js new file mode 100644 index 0000000000..007cac0d44 --- /dev/null +++ b/Graphs/test/BreadthFirstShortestPath.test.js @@ -0,0 +1,40 @@ +import { breadthFirstShortestPath } from '../BreadthFirstShortestPath' + +describe('BreadthFirstShortestPath', () => { + const graph = { + A: ['B', 'D'], + B: ['E'], + C: ['D'], + D: ['A'], + E: ['D'], + F: ['G'], + G: [] + } + /* + A <-> B + สŒ | + | | + v v + C --> D <-- E + + F --> G + */ + + it('should return the visited nodes', () => { + expect(breadthFirstShortestPath(graph, 'C', 'E')).toEqual([ + 'C', + 'D', + 'A', + 'B', + 'E' + ]) + expect(breadthFirstShortestPath(graph, 'E', 'B')).toEqual([ + 'E', + 'D', + 'A', + 'B' + ]) + expect(breadthFirstShortestPath(graph, 'F', 'G')).toEqual(['F', 'G']) + expect(breadthFirstShortestPath(graph, 'A', 'G')).toEqual([]) + }) +}) diff --git a/Graphs/test/Kosaraju.test.js b/Graphs/test/Kosaraju.test.js new file mode 100644 index 0000000000..a2da394db7 --- /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 0000000000..db77c91352 --- /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/Graphs/test/NumberOfIslands.test.js b/Graphs/test/NumberOfIslands.test.js new file mode 100644 index 0000000000..3beb1da75a --- /dev/null +++ b/Graphs/test/NumberOfIslands.test.js @@ -0,0 +1,31 @@ +import { islands } from '../NumberOfIslands' + +describe('Number of Islands', () => { + test('Graph with three islands', () => { + const graph = [ + ['1', '1', '0', '0', '0'], + ['1', '1', '0', '0', '0'], + ['0', '0', '1', '0', '0'], + ['0', '0', '0', '1', '1'] + ] + expect(islands(graph)).toBe(3) + }) + + test('Graph with only one island', () => { + const graph = [ + ['1', '1'], + ['1', '1'], + ['0', '0'], + ['0', '0'] + ] + expect(islands(graph)).toBe(1) + }) + + test('No islands', () => { + const graph = [ + ['0', '0'], + ['0', '0'] + ] + expect(islands(graph)).toBe(0) + }) +}) diff --git a/Graphs/test/PrimMST.test.js b/Graphs/test/PrimMST.test.js new file mode 100644 index 0000000000..82f3c033f1 --- /dev/null +++ b/Graphs/test/PrimMST.test.js @@ -0,0 +1,20 @@ +import { GraphWeightedUndirectedAdjacencyList } from '../PrimMST.js' + +test('Test Case PrimMST 1', () => { + // create graph to compute MST on + const graph = new GraphWeightedUndirectedAdjacencyList() + graph.addEdge(1, 2, 1) + graph.addEdge(2, 3, 2) + graph.addEdge(3, 4, 1) + graph.addEdge(3, 5, 100) // Removed in MST + graph.addEdge(4, 5, 5) + // create expected graph + const expectedGraph = new GraphWeightedUndirectedAdjacencyList() + expectedGraph.addEdge(1, 2, 1) + expectedGraph.addEdge(2, 3, 2) + expectedGraph.addEdge(3, 4, 1) + expectedGraph.addEdge(4, 5, 5) + // result from MST + const res = graph.PrimMST(1) + expect(res).toEqual(expectedGraph) +}) diff --git a/Hashes/MD5.js b/Hashes/MD5.js new file mode 100644 index 0000000000..42bef45cc5 --- /dev/null +++ b/Hashes/MD5.js @@ -0,0 +1,205 @@ +// Module that replicates the MD5 Cryptographic Hash +// function in Javascript. + +// main variables +const S = [ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, + 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, + 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, + 21 +] + +const K = [ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, + 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, + 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, + 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, + 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, + 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +] + +/** + * Separates an array into equal sized chunks + * + * @param {Array|string} array - array or string to separate into chunks + * @param {number} size - number of elements wanted in each chunk + * @return {Array} - array of original array split into chunks + * + * @example + * chunkify("this is a test", 2) + */ +function chunkify(array, size) { + const chunks = [] + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)) + } + return chunks +} + +/** + * Rotates the bits to the left + * + * @param {number} bits - 32 bit number + * @param {number} turns - number of rotations to make + * @return {number} - number after bits rotation + * + * @example + * rotateLeft(0b1011, 3); // 0b1011000 + */ +function rotateLeft(bits, turns) { + return (bits << turns) | (bits >>> (32 - turns)) +} + +/** + * Converts Uint8Array to Uint32Array + * + * @param {Uint8Array} u8Array Uint8Array to convert + * @returns {Uint32Array} - Required Uint32Array + */ +function u8ToU32(u8Array) { + const uint32Array = new Uint32Array(u8Array.length / 4) + + for (let i = 0; i < u8Array.length; i += 4) { + uint32Array[i / 4] = + (u8Array[i] | + (u8Array[i + 1] << 8) | + (u8Array[i + 2] << 16) | + (u8Array[i + 3] << 24)) >>> + 0 + } + + return uint32Array +} + +/** + * Converts Uint32Array to Uint8Array + * + * @param {Uint32Array} u32Array Uint32Array to convert + * @returns {Uint8Array} - Required Uint8Array + */ +function u32ToU8(u32Array) { + const uint8Array = new Uint8Array(u32Array.length * 4) + + for (let i = 0; i < u32Array.length; i++) { + uint8Array[i * 4] = u32Array[i] & 0xff + uint8Array[i * 4 + 1] = (u32Array[i] >> 8) & 0xff + uint8Array[i * 4 + 2] = (u32Array[i] >> 16) & 0xff + uint8Array[i * 4 + 3] = (u32Array[i] >> 24) & 0xff + } + + return uint8Array +} + +/** + * Adds padding to the end of the given array + * + * @param {Uint8Array} u8Array Array to pad + * @param {number} size Resulting size of the array + */ +function padEnd(u8Array, size) { + const result = new Uint8Array(size) + result.set(u8Array) + result.fill(0, u8Array.length) + + return result +} + +/** + * Pre-processes message to feed the algorithm loop + * + * @param {Uint8Array} message - message to pre-process + * @return {Uint32Array} - processed message + */ +function preProcess(message) { + // Extend message by adding '0' + // + // message.length + 1 is for adding '1' bit + // 56 - (length % 64) is for padding with '0's + // 8 is for appending 64 bit message length + let m = padEnd( + message, + message.length + 1 + (56 - ((message.length + 1) % 64)) + 8 + ) + + // Add '1' bit at the end of the message + m[message.length] = 1 << 7 + + // convert message to 32 bit uint array + m = u8ToU32(m) + + // Append the length of the message to the end + // (ml / 0x100000000) | 0 is equivalent to (ml >> 32) & 0xffffffff) in other languages + let ml = message.length * 8 + m[m.length - 2] = ml & 0xffffffff + m[m.length - 1] = (ml / 0x100000000) | 0 + + return m +} + +/** + * Hashes message using MD5 Cryptographic Hash Function + * + * @see + * For more info: https://en.wikipedia.org/wiki/MD5 + * + * @param {Uint8Array} message - message to hash + * @return {Uint8Array} - message digest (hash value) + */ +function MD5(message) { + // Initialize variables: + let [a0, b0, c0, d0] = [ + 0x67452301 >>> 0, + 0xefcdab89 >>> 0, + 0x98badcfe >>> 0, + 0x10325476 >>> 0 + ] + + // pre-process message and split into 512 bit chunks + const words = Array.from(preProcess(message)) + const chunks = chunkify(words, 16) + + chunks.forEach(function (chunk, _) { + // initialize variables for this chunk + let [A, B, C, D] = [a0, b0, c0, d0] + + for (let i = 0; i < 64; i++) { + let [F, g] = [0, 0] + + if (i <= 15) { + F = (B & C) | (~B & D) + g = i + } else if (i <= 31) { + F = (D & B) | (~D & C) + g = (5 * i + 1) % 16 + } else if (i <= 47) { + F = B ^ C ^ D + g = (3 * i + 5) % 16 + } else { + F = C ^ (B | ~D) + g = (7 * i) % 16 + } + + F = (F + A + K[i] + chunk[g]) >>> 0 + A = D + D = C + C = B + B = ((B + rotateLeft(F, S[i])) & 0xffffffff) >>> 0 + } + + // add values for this chunk to main hash variables (unsigned) + a0 = (a0 + A) >>> 0 + b0 = (b0 + B) >>> 0 + c0 = (c0 + C) >>> 0 + d0 = (d0 + D) >>> 0 + }) + + return u32ToU8([a0, b0, c0, d0]) +} + +// export MD5 function +export { MD5 } diff --git a/Hashes/SHA1.js b/Hashes/SHA1.js index b257a4301a..c3f7ecad2e 100644 --- a/Hashes/SHA1.js +++ b/Hashes/SHA1.js @@ -9,16 +9,16 @@ const CHAR_SIZE = 8 /** - * Adds padding to binary/hex string represention + * Adds padding to binary/hex string representation * - * @param {string} str - string represention (binary/hex) + * @param {string} str - string representation (binary/hex) * @param {int} bits - total number of bits wanted - * @return {string} - string represention padding with empty (0) bits + * @return {string} - string representation padding with empty (0) bits * * @example * pad("10011", 8); // "00010011" */ -function pad (str, bits) { +function pad(str, bits) { let res = str while (res.length % bits !== 0) { res = '0' + res @@ -34,9 +34,9 @@ function pad (str, bits) { * @return {array} - array of original string split into chunks * * @example - * chunkify("this is a test", 2); // ["th", "is", " i", "s ", "a ", "te", "st"] + * chunkify("this is a test", 2) */ -function chunkify (str, size) { +function chunkify(str, size) { const chunks = [] for (let i = 0; i < str.length; i += size) { chunks.push(str.slice(i, i + size)) @@ -54,7 +54,7 @@ function chunkify (str, size) { * @example * rotateLeft("1011", 3); // "1101" */ -function rotateLeft (bits, turns) { +function rotateLeft(bits, turns) { return bits.substr(turns) + bits.substr(0, turns) } @@ -64,14 +64,16 @@ function rotateLeft (bits, turns) { * @param {string} message - message to pre-process * @return {string} - processed message */ -function preProcess (message) { +function preProcess(message) { // convert message to binary representation padded to // 8 bits, and add 1 - let m = message.split('') - .map(e => e.charCodeAt(0)) - .map(e => e.toString(2)) - .map(e => pad(e, 8)) - .join('') + '1' + let m = + message + .split('') + .map((e) => e.charCodeAt(0)) + .map((e) => e.toString(2)) + .map((e) => pad(e, 8)) + .join('') + '1' // extend message by adding empty bits (0) while (m.length % 512 !== 448) { @@ -93,13 +95,13 @@ function preProcess (message) { * @param {string} message - message to hash * @return {string} - message digest (hash value) */ -function SHA1 (message) { +function SHA1(message) { // main variables let H0 = 0x67452301 - let H1 = 0xEFCDAB89 - let H2 = 0x98BADCFE + let H1 = 0xefcdab89 + let H2 = 0x98badcfe let H3 = 0x10325476 - let H4 = 0xC3D2E1F0 + let H4 = 0xc3d2e1f0 // pre-process message and split into 512 bit chunks const bits = preProcess(message) @@ -112,7 +114,7 @@ function SHA1 (message) { // extend 16 32-bit words to 80 32-bit words for (let i = 16; i < 80; i++) { const val = [words[i - 3], words[i - 8], words[i - 14], words[i - 16]] - .map(e => parseInt(e, 2)) + .map((e) => parseInt(e, 2)) .reduce((acc, curr) => curr ^ acc, 0) const bin = (val >>> 0).toString(2) const paddedBin = pad(bin, 32) @@ -127,16 +129,16 @@ function SHA1 (message) { let f, k if (i < 20) { f = (b & c) | (~b & d) - k = 0x5A827999 + k = 0x5a827999 } else if (i < 40) { f = b ^ c ^ d - k = 0x6ED9EBA1 + k = 0x6ed9eba1 } else if (i < 60) { f = (b & c) | (b & d) | (c & d) - k = 0x8F1BBCDC + k = 0x8f1bbcdc } else { f = b ^ c ^ d - k = 0xCA62C1D6 + k = 0xca62c1d6 } // make sure f is unsigned f >>>= 0 @@ -163,15 +165,12 @@ function SHA1 (message) { // combine hash values of main hash variables and return const HH = [H0, H1, H2, H3, H4] - .map(e => e.toString(16)) - .map(e => pad(e, 8)) + .map((e) => e.toString(16)) + .map((e) => pad(e, 8)) .join('') return HH } -console.log(SHA1('A Test')) -console.log(SHA1('A Test')) - // export SHA1 function -module.exports = SHA1 +export { SHA1 } diff --git a/Hashes/SHA256.js b/Hashes/SHA256.js index 9820981b8e..25e1a865c9 100644 --- a/Hashes/SHA256.js +++ b/Hashes/SHA256.js @@ -9,27 +9,30 @@ const CHAR_SIZE = 8 const K = [ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ] /** - * Adds padding to binary/hex string represention + * Adds padding to binary/hex string representation * - * @param {string} str - string represention (binary/hex) + * @param {string} str - string representation (binary/hex) * @param {int} bits - total number of bits wanted - * @return {string} - string represention padding with empty (0) bits + * @return {string} - string representation padding with empty (0) bits * * @example * pad("10011", 8); // "00010011" */ -function pad (str, bits) { +function pad(str, bits) { let res = str while (res.length % bits !== 0) { res = '0' + res @@ -45,9 +48,9 @@ function pad (str, bits) { * @return {array} - array of original string split into chunks * * @example - * chunkify("this is a test", 2); // ["th", "is", " i", "s ", "a ", "te", "st"] + * chunkify("this is a test", 2) */ -function chunkify (str, size) { +function chunkify(str, size) { const chunks = [] for (let i = 0; i < str.length; i += size) { chunks.push(str.slice(i, i + size)) @@ -56,16 +59,16 @@ function chunkify (str, size) { } /** - * Rotates string represention of bits to th left + * Rotates string representation of bits to the right * * @param {string} bits - string representation of bits * @param {int} turns - number of rotations to make * @return {string} - string representation of bits after rotation * * @example - * rotateLeft("1011", 3); // "1101" + * rotateRight("1011", 3); // "1101" */ -function rotateRight (bits, turns) { +function rotateRight(bits, turns) { return bits.substr(bits.length - turns) + bits.substr(0, bits.length - turns) } @@ -75,14 +78,16 @@ function rotateRight (bits, turns) { * @param {string} message - message to pre-process * @return {string} - processed message */ -function preProcess (message) { - // covert message to binary representation padded to +function preProcess(message) { + // convert message to binary representation padded to // 8 bits, and add 1 - let m = message.split('') - .map(e => e.charCodeAt(0)) - .map(e => e.toString(2)) - .map(e => pad(e, 8)) - .join('') + '1' + let m = + message + .split('') + .map((e) => e.charCodeAt(0)) + .map((e) => e.toString(2)) + .map((e) => pad(e, 8)) + .join('') + '1' // extend message by adding empty bits (0) while (m.length % 512 !== 448) { @@ -104,7 +109,7 @@ function preProcess (message) { * @param {string} message - message to hash * @return {string} - message digest (hash value) */ -function SHA256 (message) { +function SHA256(message) { // initial hash variables let H0 = 0x6a09e667 let H1 = 0xbb67ae85 @@ -133,7 +138,8 @@ function SHA256 (message) { const R4 = rotateRight(W2, 19) const S0 = parseInt(R1, 2) ^ parseInt(R2, 2) ^ (parseInt(W1, 2) >>> 3) const S1 = parseInt(R3, 2) ^ parseInt(R4, 2) ^ (parseInt(W2, 2) >>> 10) - const val = parseInt(words[i - 16], 2) + S0 + parseInt(words[i - 7], 2) + S1 + const val = + parseInt(words[i - 16], 2) + S0 + parseInt(words[i - 7], 2) + S1 words[i] = pad((val >>> 0).toString(2), 32) } @@ -141,16 +147,18 @@ function SHA256 (message) { let [a, b, c, d, e, f, g, h] = [H0, H1, H2, H3, H4, H5, H6, H7] for (let i = 0; i < 64; i++) { - const S1 = [6, 11, 25] - .map(turns => rotateRight(pad(e.toString(2), 32), turns)) - .map(bitstring => parseInt(bitstring, 2)) - .reduce((acc, curr) => acc ^ curr, 0) >>> 0 + const S1 = + [6, 11, 25] + .map((turns) => rotateRight(pad(e.toString(2), 32), turns)) + .map((bitstring) => parseInt(bitstring, 2)) + .reduce((acc, curr) => acc ^ curr, 0) >>> 0 const CH = ((e & f) ^ (~e & g)) >>> 0 const temp1 = (h + S1 + CH + K[i] + parseInt(words[i], 2)) >>> 0 - const S0 = [2, 13, 22] - .map(turns => rotateRight(pad(a.toString(2), 32), turns)) - .map(bitstring => parseInt(bitstring, 2)) - .reduce((acc, curr) => acc ^ curr, 0) >>> 0 + const S0 = + [2, 13, 22] + .map((turns) => rotateRight(pad(a.toString(2), 32), turns)) + .map((bitstring) => parseInt(bitstring, 2)) + .reduce((acc, curr) => acc ^ curr, 0) >>> 0 const maj = ((a & b) ^ (a & c) ^ (b & c)) >>> 0 const temp2 = (S0 + maj) >>> 0 @@ -177,12 +185,12 @@ function SHA256 (message) { // combine hash values of main hash variables and return const HH = [H0, H1, H2, H3, H4, H5, H6, H7] - .map(e => e.toString(16)) - .map(e => pad(e, 8)) + .map((e) => e.toString(16)) + .map((e) => pad(e, 8)) .join('') return HH } // export SHA256 function -module.exports = SHA256 +export { SHA256 } diff --git a/Hashes/tests/MD5.test.js b/Hashes/tests/MD5.test.js new file mode 100644 index 0000000000..5c44c7a57c --- /dev/null +++ b/Hashes/tests/MD5.test.js @@ -0,0 +1,38 @@ +import { MD5 } from '../MD5' + +/** + * Returns the MD5 hash of the given message as a hexadecimal string + * + * @param {Uint8Array} message - message to hash + * @return {string} - hash as a hexadecimal string + */ +function hexMD5(message) { + return Array.from(MD5(message), (byte) => + byte.toString(16).padStart(2, '0') + ).join('') +} + +describe('Testing MD5 function', () => { + it('should return the correct hash for "The quick brown fox jumps over the lazy dog"', () => { + const input = new TextEncoder().encode( + 'The quick brown fox jumps over the lazy dog' + ) + const hash = hexMD5(input) + + expect(hash).toBe('9e107d9d372bb6826bd81d3542a419d6') + }) + + it('should return the correct hash for "JavaScript!"', () => { + const input = new TextEncoder().encode('JavaScript!') + const hash = hexMD5(input) + + expect(hash).toBe('209eddd6b61af0643907a8e069a08fb8') + }) + + it('should correctly hash an empty string', () => { + const input = new TextEncoder().encode('') + const hash = hexMD5(input) + + expect(hash).toBe('d41d8cd98f00b204e9800998ecf8427e') + }) +}) diff --git a/Hashes/tests/SHA1.test.js b/Hashes/tests/SHA1.test.js new file mode 100644 index 0000000000..8032e29908 --- /dev/null +++ b/Hashes/tests/SHA1.test.js @@ -0,0 +1,25 @@ +import { describe, test } from 'vitest' +import { SHA1 } from '../SHA1' + +describe('Testing SHA1 function', () => { + it.each([ + ['', 'da39a3ee5e6b4b0d3255bfef95601890afd80709'], + [ + 'The quick brown fox jumps over the lazy dog', + '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12' + ], + [ + 'The quick brown fox jumps over the lazy cog', + 'de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3' + ], + ['a', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8'], + ['Today is 29.01.2024!', 'ae829b60d11fb5ab527d5db2501e06da3402718d'], + ['Have a nice day.', 'ed51dd3909281c25db5e1d8b1ce6fc701fda20ab'], + [ + '12345678901234567890123456789012345678901234567890123456789012345678901234567890', + '50abf5706a150990a08b2c5ea40fa0e585554732' + ] + ])('check with %j', (input, expected) => { + expect(SHA1(input)).toBe(expected) + }) +}) diff --git a/Hashes/tests/SHA256.test.js b/Hashes/tests/SHA256.test.js new file mode 100644 index 0000000000..1ffb236cea --- /dev/null +++ b/Hashes/tests/SHA256.test.js @@ -0,0 +1,27 @@ +import { describe, test } from 'vitest' +import { SHA256 } from '../SHA256' + +describe('Testing SHA256 function', () => { + it.each([ + ['', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'], + [ + 'The quick brown fox jumps over the lazy dog', + 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592' + ], + [ + 'The quick brown fox jumps over the lazy cog', + 'e4c4d8f3bf76b692de791a173e05321150f7a345b46484fe427f6acc7ecc81be' + ], + [ + 'This was added by vil02 on 01.02.2024. Have a nice day!', + '476025d91db754ab6ac0c124367afd7c108d041b2f497006a214d5035769ed5d' + ], + [ + '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', + '14582b3f153941891dca966b036a5b1de65fa3b7a2540095a31614da1de0feaf' + ], + ['a', 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb'] + ])('check with %j', (input, expected) => { + expect(SHA256(input)).toBe(expected) + }) +}) diff --git a/Linear-Algebra/README.md b/Linear-Algebra/README.md deleted file mode 100644 index 8c0fd79ab8..0000000000 --- a/Linear-Algebra/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# Linear algebra library for JavaScript - -This library contains some useful classes and functions for dealing with linear algebra in JavaScript. -The library was orginal written in **TypeScript** and then compiles into pure JavaScript. - ---- - -## Overview - -- class Vector : This class represents a vector of arbitray size and operations on it. - - constructor Vector(N) : creates a zero vector of size N - - constructor Vector(N, components) : creates a vector of size N with the given components. - - createUnitBasis(pos) : converts this vector in a unit basis vector and returns it. - - component(pos) : returns the specified component (indexing at 0) - - changeComponent(pos, value) : change the specified component. - - toString() : returns a string representation of this vector. - - size() : returns the size of the vector. (not the eulidean length!) - - eulideanLength() : computes the eulidean length of this vector. - - add(other) : vector addition, returns the rersult. - - sub(other) : vector subtraction, returns the rersult. - - dot(other) : computes the dot-product and returns it. - - scalar(s) : scalar (s) multiplication. returns the result. - - norm() : normalizes this vector and returns it. - - equal(other) : returns true if the vectors are equal, otherwise false. - -- function unitBasisVector(N,pos) : returns a unit basis vector of size N with a One on position 'pos' -- function randomVectorInt(N,a,b) : returns a random vector with integer components (between 'a' and 'b') of size N. -- function randomVectorFloat(N,a,b) : returns a random vector with floating point components (between 'a' and 'b') of size N. - -- class Matrix : This class represents a matrix of arbitrary size and operations on it. - - constructor(rows, cols) : creates a zero matrix of dimension rows x cols. - - constructor(rows, cols, components) : creates a matrix with fix numbers of dimension rows x cols. - - component(x,y) : returns the specified component. - - changeComponent(x,y,value) : changes the specified component with the new value 'value'. - - toString() : returns a string representation of this matrix. - - dimension() : returns the dimension of this matrix as number arras [rows,cols]. - - add(other) : returns the result of the matrix addition. - - equal(other) : returns true if the matrices are equal, otherwise false. - - scalar(c) : returns the result of the matrix-scalar multiplication. ---- - -## Documentation - -The module is well documented in its source code. Look in the TypeScript file ```la_lib.ts```. - ---- - -## Usage - -You will find the library in the **src** directory its called ```la_lib.js```. You simply need to -include this library in your project **(you don't install anything)**. After that: - -```js - var x = LinearAlgebra.Vector(...); -``` - -The namespace LinearAlgebra contains useful classes and functions for dealing with linear algebra under JavaScript. - -Some examples: - -```js -// ---------------------------- Examples ------------------------------------------ - -// creates vectors -var x = new LinearAlgebra.Vector(5, [1, 2, 3, 4, 5]); -var y = new LinearAlgebra.Vector(5, [1, 2, 3, 4, 5]); - -// prints size of the vector -console.log(x.size()); // ==> 5 - -// changes the 2-th component with 7 -//x.changeComponent(2,7); - -// print the 2-th component. -console.log(x.component(2)); // ==> 3 - -// prints the full vector as string. -console.log(x.toString()); // ==> (1,2,3,4,5) - -// vector addition -console.log(x.add(y).toString()); // ==> (2,3,6,8,10) - -//console.log(x.createUnitBasis(1).toString()); - -// computes the dot-product -console.log(x.dot(y)); // ==> 55 - -// computes and prints the scalar-product -console.log(x.scalar(5).toString()); // ==> (5,10,15,20,25) - -// creates a unit basis vector -console.log(LinearAlgebra.unitBasisVector(3, 0).toString()); // ==> (1,0,0) - -// creates random vectors -console.log(LinearAlgebra.randomVectorInt(3, 0, 5).toString()); -console.log(LinearAlgebra.randomVectorFloat(3, 0, 5).toString()); -``` - ---- - -## Tests - -Go in the directory of the project and type in: -```npm install``` -```npm test``` -The test-suite use the JavaScript test-framework **mocha**. - ---- - -## Contributing - -You can contribute to this project. Feel free and pull request some new features or documention. -**TODO:** Global functions for special matrices. -**TODO:** Documention of the classes and functions. \ No newline at end of file diff --git a/Linear-Algebra/package-lock.json b/Linear-Algebra/package-lock.json deleted file mode 100644 index 1492a09eed..0000000000 --- a/Linear-Algebra/package-lock.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - "name": "linear-algebra-javascript", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" - }, - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==" - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.2.tgz", - "integrity": "sha512-nmlYKMRpJZLxgzk0bRhcvlpjSisbi0x1JiRl7kctadOMPmecUie7WwCZmcyth+PzX5txKbpcMIvDZCAlx9ISxg==", - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", - "mkdirp": "0.5.1", - "supports-color": "4.4.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", - "requires": { - "has-flag": "2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } - } -} diff --git a/Linear-Algebra/package.json b/Linear-Algebra/package.json deleted file mode 100644 index 1631a3f9a7..0000000000 --- a/Linear-Algebra/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "linear-algebra-javascript", - "version": "1.0.0", - "description": "simple linear algebra library for JavaScript", - "main": "index.js", - "directories": { - "test": "test" - }, - "scripts": { - "test": "mocha" - }, - "author": "Christian Bender", - "license": "MIT", - "dependencies": { - "mocha": "^5.0.2" - } -} diff --git a/Linear-Algebra/src/la_lib.js b/Linear-Algebra/src/la_lib.js deleted file mode 100644 index be9f507def..0000000000 --- a/Linear-Algebra/src/la_lib.js +++ /dev/null @@ -1,311 +0,0 @@ -/* - author: Christian Bender - license: MIT-license - - The namespace LinearAlgebra contains useful classes and functions for dealing with - linear algebra under JavaScript. -*/ -var LinearAlgebra; -(function (LinearAlgebra) { - /* - class: Vector - This class represents a vector of arbitrary size and operations on it. - */ - var Vector = /** @class */ (function () { - // constructor - function Vector (N, comps) { - if (comps === undefined) { - comps = [] - } - this.components = new Array(N) - if (comps.length === 0) { - for (var i = 0; i < N; i++) { - this.components[i] = 0.0 - } - } else { - if (N === comps.length) { - this.components = comps - } else { - throw new Error('Vector: invalide size!') - } - } - } // end of constructor - // returns the size of this vector. - // not the eulidean length! - Vector.prototype.size = function () { - return this.components.length - } - // computes the eulidean length. - Vector.prototype.eulideanLength = function () { - var sum = 0 - for (var i = 0; i < this.components.length; i++) { - sum += this.components[i] * this.components[i] - } - return Math.sqrt(sum) - } - // getter for the components of the vector. - // returns a specified component (index) - Vector.prototype.component = function (index) { - return this.components[index] - } - // setter for a specified component of this vector. - Vector.prototype.changeComponent = function (index, value) { - if (index >= 0 && index < this.components.length) { - this.components[index] = value - } else { - throw new Error('changeComponent: index out of bounds!') - } - } - // vector addition - Vector.prototype.add = function (other) { - if (this.size() === other.size()) { - var SIZE = this.size() - var ans = new Vector(SIZE) - for (var i = 0; i < SIZE; i++) { - ans.changeComponent(i, (this.components[i] + other.component(i))) - } - return ans - } else { - throw new Error('add: vector must have same size!') - } - } - // vector subtraction - Vector.prototype.sub = function (other) { - if (this.size() === other.size()) { - var SIZE = this.size() - var ans = new Vector(SIZE) - for (var i = 0; i < SIZE; i++) { - ans.changeComponent(i, (this.components[i] - other.component(i))) - } - return ans - } else { - throw new Error('add: vector must have same size!') - } - } - // dot-product - Vector.prototype.dot = function (other) { - var sum = 0 - if (other.size() === this.size()) { - var SIZE = other.size() - for (var i = 0; i < SIZE; i++) { - sum += this.components[i] * other.component(i) - } - return sum - } else { - throw new Error('dot: vectors must have same size!') - } - } - // scalar multiplication - Vector.prototype.scalar = function (s) { - var SIZE = this.size() - var ans = new Vector(SIZE) - for (var i = 0; i < SIZE; i++) { - ans.changeComponent(i, (this.components[i] * s)) - } - return ans - } - // returns a string representation of this vector. - Vector.prototype.toString = function () { - var ans = '(' - var SIZE = this.components.length - for (var i = 0; i < SIZE; i++) { - if (i < SIZE - 1) { - ans += this.components[i] + ',' - } else { - ans += this.components[i] + ')' - } - } - return ans - } - // converts this vector in a unit basis vector and returns it. - // the One is on position 'pos' - Vector.prototype.createUnitBasis = function (pos) { - if (pos >= 0 && pos < this.components.length) { - for (var i = 0; i < this.components.length; i++) { - if (i === pos) { - this.components[i] = 1.0 - } else { - this.components[i] = 0.0 - } - } - } else { - throw new Error('createUnitBasis: index out of bounds') - } - return this - } - // normalizes this vector and returns it. - Vector.prototype.norm = function () { - var SIZE = this.size() - var quotient = 1.0 / this.eulideanLength() - for (var i = 0; i < SIZE; i++) { - this.components[i] = this.components[i] * quotient - } - return this - } - // returns true if the vectors are equal otherwise false. - Vector.prototype.equal = function (other) { - var ans = true - var SIZE = this.size() - var EPSILON = 0.001 - if (SIZE === other.size()) { - for (var i = 0; i < SIZE; i++) { - if (Math.abs(this.components[i] - other.component(i)) > EPSILON) { - ans = false - } - } - } else { - ans = false - } - return ans - } - return Vector - }()) // end of class Vector - LinearAlgebra.Vector = Vector - // -------------- global functions --------------------------------- - // returns a unit basis vector of size N with a One on position 'pos' - function unitBasisVector (N, pos) { - var ans = new Vector(N) - for (var i = 0; i < N; i++) { - if (i === pos) { - ans.changeComponent(i, 1.0) - } else { - ans.changeComponent(i, 0) - } - } - return ans - } - LinearAlgebra.unitBasisVector = unitBasisVector - // returns a random vector with integer components (between 'a' and 'b') of size N. - function randomVectorInt (N, a, b) { - var ans = new Vector(N) - for (var i = 0; i < N; i++) { - ans.changeComponent(i, (Math.floor((Math.random() * b) + a))) - } - return ans - } - LinearAlgebra.randomVectorInt = randomVectorInt - // returns a random vector with floating point components (between 'a' and 'b') of size N. - function randomVectorFloat (N, a, b) { - var ans = new Vector(N) - for (var i = 0; i < N; i++) { - ans.changeComponent(i, ((Math.random() * b) + a)) - } - return ans - } - LinearAlgebra.randomVectorFloat = randomVectorFloat - // ------------------ end of global functions ----------------------------- - /* - class: Matrix - This class represents a matrix of arbitrary size and operations on it. - */ - var Matrix = /** @class */ (function () { - // constructor for zero-matrix or fix number matrix. - function Matrix (row, col, comps) { - if (comps === undefined) { - comps = [] - } - if (comps.length === 0) { - this.matrix = [] - var rowVector = [] - for (var i = 0; i < row; i++) { - for (var j = 0; j < col; j++) { - rowVector[j] = 0 - } - this.matrix[i] = rowVector - rowVector = [] - } - } else { - this.matrix = comps - } - this.rows = row - this.cols = col - } - // returns the specified component. - Matrix.prototype.component = function (x, y) { - if (x >= 0 && x < this.rows && y >= 0 && y < this.cols) { - return this.matrix[x][y] - } else { - throw new Error('component: index out of bounds') - } - } - // changes the specified component with value 'value'. - Matrix.prototype.changeComponent = function (x, y, value) { - if (x >= 0 && x < this.rows && y >= 0 && y < this.cols) { - this.matrix[x][y] = value - } else { - throw new Error('changeComponent: index out of bounds') - } - } - // returns a string representation of this matrix. - Matrix.prototype.toString = function () { - var ans = '' - for (var i = 0; i < this.rows; i++) { - ans += '|' - for (var j = 0; j < this.cols; j++) { - if (j < this.cols - 1) { - ans += this.matrix[i][j] + ',' - } else { - if (i < this.rows - 1) { - ans += this.matrix[i][j] + '|\n' - } else { - ans += this.matrix[i][j] + '|' - } - } - } - } - return ans - } - // returns the dimension rows x cols as number array - Matrix.prototype.dimension = function () { - var ans = [] - ans[0] = this.rows - ans[1] = this.cols - return ans - } - // matrix addition. returns the result. - Matrix.prototype.add = function (other) { - if (this.rows === other.dimension()[0] && - this.cols === other.dimension()[1]) { - var ans = new Matrix(this.rows, this.cols) - for (var i = 0; i < this.rows; i++) { - for (var j = 0; j < this.cols; j++) { - ans.changeComponent(i, j, (this.matrix[i][j] + other.component(i, j))) - } - } - return ans - } else { - throw new Error('add: matrices must have same dimension!') - } - } - // returns true if the matrices are equal, otherwise false. - Matrix.prototype.equal = function (other) { - var ans = true - var EPSILON = 0.001 - if (this.rows === other.dimension()[0] && - this.cols === other.dimension()[1]) { - for (var i = 0; i < this.rows; i++) { - for (var j = 0; j < this.cols; j++) { - if (Math.abs(this.matrix[i][j] - other.component(i, j)) > EPSILON) { - ans = false - } - } - } - } else { - ans = false - } - return ans - } - // matrix-scalar multiplication - Matrix.prototype.scalar = function (c) { - var ans = new Matrix(this.rows, this.cols) - for (var i = 0; i < this.rows; i++) { - for (var j = 0; j < this.cols; j++) { - ans.changeComponent(i, j, (this.matrix[i][j] * c)) - } - } - return ans - } - return Matrix - }()) // end of class Matrix - LinearAlgebra.Matrix = Matrix -})(LinearAlgebra || (LinearAlgebra = {})) // end of namespace LinearAlgebra diff --git a/Linear-Algebra/test/test.js b/Linear-Algebra/test/test.js deleted file mode 100644 index 5f1892db89..0000000000 --- a/Linear-Algebra/test/test.js +++ /dev/null @@ -1,213 +0,0 @@ -/* - author: Christian Bender - license: MIT-license - - This file contains the test-suite for the linear algebra library. - The tests use javascript test-framework mocha -*/ -/* eslint-disable */ - -var assert = require('assert') -var fs = require('fs') - -// file is included here -eval(fs.readFileSync('src/la_lib.js') + '') -// Tests goes here - -// creating some vectors -describe('Create Vectors', function () { - describe('#toString()', function () { - it('should return a string representation', function () { - assert.strictEqual((new LinearAlgebra.Vector(3, [1, 2, 3])).toString(), '(1,2,3)') - }) - }) - describe('#unitBasisVector()', function () { - it('should return a unit basis vector', function () { - assert.strictEqual(LinearAlgebra.unitBasisVector(3, 1).toString(), '(0,1,0)') - }) - }) -}) - -// operations on it. -describe('Vector operations', function () { - describe('#add()', function () { - it('should return vector (2,4,6)', function () { - var x = new LinearAlgebra.Vector(3, [1, 2, 3]) - var y = new LinearAlgebra.Vector(3, [1, 2, 3]) - assert.strictEqual((x.add(y)).toString(), '(2,4,6)') - }) - }) - describe('#sub()', function () { - it('should return vector (0,0,0)', function () { - var x = new LinearAlgebra.Vector(3, [1, 2, 3]) - var y = new LinearAlgebra.Vector(3, [1, 2, 3]) - assert.strictEqual((x.sub(y)).toString(), '(0,0,0)') - }) - }) - describe('#dot()', function () { - it('should return the dot-product', function () { - var x = new LinearAlgebra.Vector(3, [1, 2, 3]) - var y = new LinearAlgebra.Vector(3, [5, 6, 7]) - assert.strictEqual(x.dot(y), 38) - }) - }) - describe('#scalar()', function () { - it('should return the scalar product', function () { - var x = new LinearAlgebra.Vector(3, [1, 2, 3]) - assert.strictEqual(x.scalar(2).toString(), '(2,4,6)') - }) - }) - describe('#norm()', function () { - it('should return the normalizes vector', function () { - var x = new LinearAlgebra.Vector(4, [9, 0, 3, 1]) - var y = x.norm() - assert.ok(Math.abs(y.component(0) - (9.0 / Math.sqrt(91))) <= 0.01) - }) - }) - describe('#eulideanLength()', function () { - it('should return the eulidean length of the vector', function () { - var x = new LinearAlgebra.Vector(3, [1, 2, 2]) - assert.ok(Math.abs(x.eulideanLength() - 3) <= 0.001) - }) - }) - describe('#size()', function () { - it('should return the size (not eulidean length!) of the vector', function () { - var x = LinearAlgebra.randomVectorInt(10, 1, 5) - assert.strictEqual(x.size(), 10) - }) - }) - describe('#equal()', function () { - it('should compares two vectors', function () { - var x = new LinearAlgebra.Vector(3, [1, 2, 2]) - var y = new LinearAlgebra.Vector(3, [1, 2, 3]) - assert.ok(x.equal(x)) - assert.ok(!x.equal(y)) - }) - }) -}) - -describe('Methods on vectors', function () { - describe('#component()', function () { - it('should return the specified component', function () { - var x = new LinearAlgebra.Vector(3, [1, 2, 2]) - assert.strictEqual(x.component(1), 2) - }) - }) - describe('#changeComponent()', function () { - it('should return the changed vector', function () { - var x = new LinearAlgebra.Vector(3, [1, 2, 2]) - x.changeComponent(1, 5) - assert.strictEqual(x.toString(), '(1,5,2)') - }) - }) - describe('#toString()', function () { - it('should return a string representation of the vector', function () { - var x = new LinearAlgebra.Vector(4, [9, 0, 3, 1]) - assert.strictEqual(x.toString(), '(9,0,3,1)') - }) - }) -}) - -describe('class Matrix', function () { - describe('#component()', function () { - it('should return the specified component', function () { - var A = new LinearAlgebra.Matrix(2, 2) - assert.strictEqual(A.component(0, 1), 0) - var B = new LinearAlgebra.Matrix(2, 2, [ - [1, 2], - [3, 4] - ]) - assert.strictEqual(B.component(1, 0), 3) - }) - }) - describe('#toString()', function () { - it('should return a string representation of the matrix', function () { - var A = new LinearAlgebra.Matrix(2, 2, [ - [1, 2], - [3, 4] - ]) - assert.strictEqual(A.toString(), '|1,2|\n|3,4|') - }) - }) - describe('#dimension()', function () { - it('should return the dimension of the matrix as number array', function () { - var A = new LinearAlgebra.Matrix(3, 2, [ - [1, 2], - [3, 4], - [5, 6] - ]) - assert.strictEqual(A.dimension()[0], 3) - assert.strictEqual(A.dimension()[1], 2) - }) - }) - describe('#changeComponent()', function () { - it('should change the specified component of the matrix', function () { - var A = new LinearAlgebra.Matrix(3, 2, [ - [1, 2], - [3, 4], - [5, 6] - ]) - A.changeComponent(1, 0, 5) - assert.strictEqual(A.component(1, 0), 5) - }) - }) - describe('#equal()', function () { - it('should compares the matrices', function () { - var A = new LinearAlgebra.Matrix(3, 2, [ - [1, 2], - [3, 4], - [5, 6] - ]) - var B = new LinearAlgebra.Matrix(3, 2, [ - [1, 2], - [3, 4], - [5, 6] - ]) - var C = new LinearAlgebra.Matrix(2, 2, [ - [1, 2], - [3, 4] - ]) - var D = new LinearAlgebra.Matrix(2, 2, [ - [1, 2], - [5, 4] - ]) - assert.ok(A.equal(B)) - assert.ok(!A.equal(C)) - assert.ok(!C.equal(D)) - }) - }) - describe('#add()', function () { - it('should return the result of the matrix addition', function () { - var A = new LinearAlgebra.Matrix(3, 2, [ - [1, 2], - [3, 4], - [5, 6] - ]) - var B = new LinearAlgebra.Matrix(3, 2, [ - [1, 2], - [3, 4], - [5, 6] - ]) - var C = A.add(B) - assert.strictEqual(C.component(1, 0), 6) - assert.strictEqual(C.component(1, 1), 8) - assert.strictEqual(C.component(0, 0), 2) - }) - }) - describe('#scalar()', function () { - it('should return the result of the matrix-scalar multiplication', function () { - var A = new LinearAlgebra.Matrix(3, 2, [ - [1, 2], - [3, 4], - [5, 6] - ]) - var B = A.scalar(2) - var C = new LinearAlgebra.Matrix(3, 2, [ - [2, 4], - [6, 8], - [10, 12] - ]) - assert.ok(B.equal(C)) - }) - }) -}) \ No newline at end of file diff --git a/Maths/Abs.js b/Maths/Abs.js index 454e4073a6..53c353fb8b 100644 --- a/Maths/Abs.js +++ b/Maths/Abs.js @@ -1,26 +1,22 @@ -/* - author: PatOnTheBack - license: GPL-3.0 or later +/** + * @function abs + * @description This script will find the absolute value of a number. + * @param {number} num - The input integer + * @return {number} - Absolute number of num. + * @see https://en.wikipedia.org/wiki/Absolute_value + * @example abs(-10) = 10 + * @example abs(50) = 50 + * @example abs(0) = 0 + */ - Modified from: - https://github.com/TheAlgorithms/Python/blob/master/maths/abs.py +const abs = (num) => { + const validNumber = +num // converted to number, also can use - Number(num) - This script will find the absolute value of a number. - - More about absolute values: - https://en.wikipedia.org/wiki/Absolute_value -*/ - -function absVal (num) { - // Find absolute value of `num`. - 'use strict' - if (num < 0) { - return -num + if (Number.isNaN(validNumber) || typeof num === 'object') { + throw new TypeError('Argument is NaN - Not a Number') } - // Executes if condition is not met. - return num + + return validNumber < 0 ? -validNumber : validNumber // if number is less than zero mean negative then it converted to positive. i.e -> n = -2 = -(-2) = 2 } -// Run `abs` function to find absolute value of two numbers. -console.log('The absolute value of -34 is ' + absVal(-34)) -console.log('The absolute value of 34 is ' + absVal(34)) +export { abs } diff --git a/Maths/AliquotSum.js b/Maths/AliquotSum.js new file mode 100644 index 0000000000..92b47502e2 --- /dev/null +++ b/Maths/AliquotSum.js @@ -0,0 +1,33 @@ +/* + A program to calculate the Aliquot Sum of a number. + The aliquot sum of a number n, is the sum of all the proper divisors of n apart from n itself + For example, for the number 6 + The divisors are 1, 2, 3 (we don't consider 6), so its aliquot sum is 1 + 2 + 3 = 6 + 1 is the only number whose aliquot sum is 0 (since its only divisor is 1 and aliquot sum of a number couldn't have itself) + For all prime numbers, the aliquot sum is 1, since their only divisor apart from themselves is 1 + Article on Aliquot Sum: https://en.wikipedia.org/wiki/Aliquot_sum + */ + +/** + * @param {Number} input The number whose aliquot sum you want to calculate + */ +function aliquotSum(input) { + // input can't be negative + if (input < 0) throw new TypeError('Input cannot be Negative') + + // input can't be a decimal + if (Math.floor(input) !== input) + throw new TypeError('Input cannot be a Decimal') + + // Dealing with 1, which isn't a prime + if (input === 1) return 0 + + let sum = 0 + for (let i = 1; i <= input / 2; i++) { + if (input % i === 0) sum += i + } + + return sum +} + +export { aliquotSum } diff --git a/Maths/Area.js b/Maths/Area.js new file mode 100644 index 0000000000..a047b92223 --- /dev/null +++ b/Maths/Area.js @@ -0,0 +1,184 @@ +/* + Calculate the area of various shapes +*/ + +/** + * @function surfaceAreaCube + * @description Calculate the Surface Area of a Cube. + * @param {Integer} side - Integer + * @return {Integer} - 6 * side ** 2 + * @see [surfaceAreaCube](https://en.wikipedia.org/wiki/Area#Surface_area) + * @example surfaceAreaCube(1) = 6 + */ +const surfaceAreaCube = (side) => { + validateNumericParam(side, 'side') + return 6 * side ** 2 +} + +/** + * @function surfaceAreaSphere + * @description Calculate the Surface Area of a Sphere. + * @param {Integer} radius - Integer + * @return {Integer} - 4 * pi * r^2 + * @see [surfaceAreaSphere](https://en.wikipedia.org/wiki/Sphere) + * @example surfaceAreaSphere(5) = 314.1592653589793 + */ +const surfaceAreaSphere = (radius) => { + validateNumericParam(radius, 'radius') + return 4.0 * Math.PI * radius ** 2.0 +} + +/** + * @function areaRectangle + * @description Calculate the area of a rectangle. + * @param {Integer} length - Integer + * @param {Integer} width - Integer + * @return {Integer} - width * length + * @see [areaRectangle](https://en.wikipedia.org/wiki/Area#Quadrilateral_area) + * @example areaRectangle(4) = 16 + */ +const areaRectangle = (length, width) => { + validateNumericParam(length, 'Length') + validateNumericParam(width, 'Width') + return width * length +} + +/** + * @function areaSquare + * @description Calculate the area of a square. + * @param {Integer} side - Integer + * @return {Integer} - side ** 2. + * @see [areaSquare](https://en.wikipedia.org/wiki/Square) + * @example areaSquare(4) = 16 + */ +const areaSquare = (side) => { + validateNumericParam(side, 'square side') + return side ** 2 +} + +/** + * @function areaTriangle + * @description Calculate the area of a triangle. + * @param {Integer} base - Integer + * @param {Integer} height - Integer + * @return {Integer} - base * height / 2. + * @see [areaTriangle](https://en.wikipedia.org/wiki/Area#Triangle_area) + * @example areaTriangle(1.66, 3.44) = 2.8552 + */ +const areaTriangle = (base, height) => { + validateNumericParam(base, 'Base') + validateNumericParam(height, 'Height') + return (base * height) / 2.0 +} + +/** + * @function areaTriangleWithAllThreeSides + * @description Calculate the area of a triangle with the all three sides given. + * @param {Integer} side1 - Integer + * @param {Integer} side2 - Integer + * @param {Integer} side3 - Integer + * @return {Integer} - area of triangle. + * @see [areaTriangleWithAllThreeSides](https://en.wikipedia.org/wiki/Heron%27s_formula) + * @example areaTriangleWithAllThreeSides(5, 6, 7) = 14.7 + */ +const areaTriangleWithAllThreeSides = (side1, side2, side3) => { + validateNumericParam(side1, 'side1') + validateNumericParam(side2, 'side2') + validateNumericParam(side3, 'side3') + if ( + side1 + side2 <= side3 || + side1 + side3 <= side2 || + side2 + side3 <= side1 + ) { + throw new TypeError('Invalid Triangle sides.') + } + // Finding Semi perimeter of the triangle using formula + const semi = (side1 + side2 + side3) / 2 + + // Calculating the area of the triangle + const area = Math.sqrt( + semi * (semi - side1) * (semi - side2) * (semi - side3) + ) + return Number(area.toFixed(2)) +} + +/** + * @function areaParallelogram + * @description Calculate the area of a parallelogram. + * @param {Integer} base - Integer + * @param {Integer} height - Integer + * @return {Integer} - base * height + * @see [areaParallelogram](https://en.wikipedia.org/wiki/Area#Dissection,_parallelograms,_and_triangles) + * @example areaParallelogram(5, 6) = 24 + */ +const areaParallelogram = (base, height) => { + validateNumericParam(base, 'Base') + validateNumericParam(height, 'Height') + return base * height +} + +/** + * @function areaTrapezium + * @description Calculate the area of a trapezium. + * @param {Integer} base1 - Integer + * @param {Integer} base2 - Integer + * @param {Integer} height - Integer + * @return {Integer} - (1 / 2) * (base1 + base2) * height + * @see [areaTrapezium](https://en.wikipedia.org/wiki/Trapezoid) + * @example areaTrapezium(5, 12, 10) = 85 + */ +const areaTrapezium = (base1, base2, height) => { + validateNumericParam(base1, 'Base One') + validateNumericParam(base2, 'Base Two') + validateNumericParam(height, 'Height') + return (1 / 2) * (base1 + base2) * height +} + +/** + * @function areaCircle + * @description Calculate the area of a circle. + * @param {Integer} radius - Integer + * @return {Integer} - Math.PI * radius ** 2 + * @see [areaCircle](https://en.wikipedia.org/wiki/Area_of_a_circle) + * @example areaCircle(5, 12, 10) = 85 + */ +const areaCircle = (radius) => { + validateNumericParam(radius, 'Radius') + return Math.PI * radius ** 2 +} + +/** + * @function areaRhombus + * @description Calculate the area of a rhombus. + * @param {Integer} diagonal1 - Integer + * @param {Integer} diagonal2 - Integer + * @return {Integer} - (1 / 2) * diagonal1 * diagonal2 + * @see [areaRhombus](https://en.wikipedia.org/wiki/Rhombus) + * @example areaRhombus(12, 10) = 60 + */ +const areaRhombus = (diagonal1, diagonal2) => { + validateNumericParam(diagonal1, 'diagonal one') + validateNumericParam(diagonal2, 'diagonal two') + return (1 / 2) * diagonal1 * diagonal2 +} + +const validateNumericParam = (param, paramName = 'param') => { + if (typeof param !== 'number') { + throw new TypeError('The ' + paramName + ' should be type Number') + } else if (param < 0) { + throw new Error('The ' + paramName + ' only accepts non-negative values') + } +} + +export { + surfaceAreaCube, + surfaceAreaSphere, + areaRectangle, + areaSquare, + areaTriangle, + areaParallelogram, + areaTrapezium, + areaCircle, + areaRhombus, + areaTriangleWithAllThreeSides +} diff --git a/Maths/ArithmeticGeometricMean.js b/Maths/ArithmeticGeometricMean.js new file mode 100644 index 0000000000..5e989eca67 --- /dev/null +++ b/Maths/ArithmeticGeometricMean.js @@ -0,0 +1,28 @@ +/** + * @function agm + * @description This finds the Arithmetic-Geometric Mean between any 2 numbers. + * @param {Number} a - 1st number, also used to store Arithmetic Mean. + * @param {Number} g - 2nd number, also used to store Geometric Mean. + * @return {Number} - AGM of both numbers. + * @see [AGM](https://en.wikipedia.org/wiki/Arithmetic%E2%80%93geometric_mean) + */ + +export const agm = (a, g) => { + if (a === Infinity && g === 0) return NaN + if (Object.is(a, -0) && !Object.is(g, -0)) return 0 + if (a === g) return a // avoid rounding errors, and increase efficiency + let x // temp var + do { + ;[a, g, x] = [(a + g) / 2, Math.sqrt(a * g), a] + } while (a !== x && !isNaN(a)) + /* + `x !== a` ensures the return value has full precision, + and prevents infinite loops caused by rounding differences between `div` and `sqrt` (no need for "epsilon"). + If we were to compare `a` with `g`, some input combinations (not all) can cause an infinite loop, + because the rounding mode never changes at runtime. + Precision is not the same as accuracy, but they're related. + This function isn't always 100% accurate (round-errors), but at least is more than 95% accurate. + `!isNaN(x)` prevents infinite loops caused by invalid inputs like: negatives, NaNs and Infinities. + */ + return a +} diff --git a/Maths/ArmstrongNumber.js b/Maths/ArmstrongNumber.js new file mode 100644 index 0000000000..8f861d8c15 --- /dev/null +++ b/Maths/ArmstrongNumber.js @@ -0,0 +1,21 @@ +/** + * Author: dephraiim + * License: GPL-3.0 or later + * + * An Armstrong number is equal to the sum of its own digits each raised to the power of the number of digits. + * For example, 370 is an Armstrong number because 3*3*3 + 7*7*7 + 0*0*0 = 370. + * An Armstrong number is often called Narcissistic number. + * + */ + +const armstrongNumber = (num) => { + if (typeof num !== 'number' || num < 0) return false + const numStr = num.toString() + const sum = [...numStr].reduce( + (acc, digit) => acc + parseInt(digit) ** numStr.length, + 0 + ) + return sum === num +} + +export { armstrongNumber } diff --git a/Maths/AutomorphicNumber.js b/Maths/AutomorphicNumber.js new file mode 100644 index 0000000000..ba008271fc --- /dev/null +++ b/Maths/AutomorphicNumber.js @@ -0,0 +1,40 @@ +/** + * @function isAutomorphic + * @author [SilverDragonOfR] (https://github.com/SilverDragonOfR) + * + * @see [Automorphic] (https://en.wikipedia.org/wiki/Automorphic_number) + * @description This script will check whether a number is Automorphic or not + * @description A number n is said to be a Automorphic number if the square of n ends in the same digits as n itself. + * + * @param {Integer} n - the n for nth Catalan Number + * @return {Integer} - the nth Catalan Number + * @complexity Time: O(log10(n)) , Space: O(1) + * + * @convention We define Automorphic only for whole number integers. For negetive integer we return False. For float or String we show error. + * @examples 0, 1, 5, 6, 25, 76, 376, 625, 9376 are some Automorphic numbers + */ + +// n is the number to be checked +export const isAutomorphic = (n) => { + if (typeof n !== 'number') { + throw new Error('Type of n must be number') + } + if (!Number.isInteger(n)) { + throw new Error('n cannot be a floating point number') + } + if (n < 0) { + return false + } + + // now n is a whole number integer >= 0 + let n_sq = n * n + while (n > 0) { + if (n % 10 !== n_sq % 10) { + return false + } + n = Math.floor(n / 10) + n_sq = Math.floor(n_sq / 10) + } + + return true +} diff --git a/Maths/AverageMean.js b/Maths/AverageMean.js index d0dc833c54..8ae3b55992 100644 --- a/Maths/AverageMean.js +++ b/Maths/AverageMean.js @@ -1,30 +1,19 @@ -/* - author: PatOnTheBack - license: GPL-3.0 or later +/** + * @function mean + * @description This script will find the mean value of a array of numbers. + * @param {Integer[]} nums - Array of integer + * @return {Integer} - mean of nums. + * @see [Mean](https://en.wikipedia.org/wiki/Mean) + * @example mean([1, 2, 4, 5]) = 3 + * @example mean([10, 40, 100, 20]) = 42.5 + */ - Modified from: - https://github.com/TheAlgorithms/Python/blob/master/maths/average.py +const mean = (nums) => { + if (!Array.isArray(nums)) { + throw new TypeError('Invalid Input') + } - This script will find the average (mean) of an array of numbers. - - More about mean: - https://en.wikipedia.org/wiki/Mean -*/ - -function mean (nums) { - 'use strict' - var sum = 0 - var avg - - // This loop sums all values in the 'nums' array. - nums.forEach(function (current) { - sum += current - }) - - // Divide sum by the length of the 'nums' array. - avg = sum / nums.length - return avg + return nums.reduce((sum, cur) => sum + cur, 0) / nums.length } -// Run `mean` Function to find average of a list of numbers. -console.log(mean([2, 4, 6, 8, 20, 50, 70])) +export { mean } diff --git a/Maths/AverageMedian.js b/Maths/AverageMedian.js new file mode 100644 index 0000000000..f8f6c64b14 --- /dev/null +++ b/Maths/AverageMedian.js @@ -0,0 +1,22 @@ +/* + * Median: https://en.wikipedia.org/wiki/Median + * + * function averageMedian + * to find the median value of an array of numbers + * the numbers in an array will be sorted in ascending order by the function sortNumbers + * if the length of the array is even number, the median value will be the average of the two middle numbers + * else if the length of the array is odd number, the median value will be the middle number in the array + */ + +const averageMedian = (sourceArrayOfNumbers) => { + const numbers = [...sourceArrayOfNumbers].sort(sortNumbers) + const numLength = numbers.length + + return numLength % 2 === 0 + ? (numbers[numLength / 2 - 1] + numbers[numLength / 2]) / 2 + : numbers[Math.floor(numLength / 2)] +} + +const sortNumbers = (num1, num2) => num1 - num2 + +export { averageMedian } diff --git a/Maths/BinaryConvert.js b/Maths/BinaryConvert.js new file mode 100644 index 0000000000..b1b6ce7fcc --- /dev/null +++ b/Maths/BinaryConvert.js @@ -0,0 +1,25 @@ +/** + * @function BinaryConvert + * @description Convert the decimal to binary. + * @param {Integer} num - The input integer + * @return {Integer} - Binary of num. + * @see [BinaryConvert](https://www.programiz.com/javascript/examples/decimal-binary) + * @example BinaryConvert(12) = 1100 + * @example BinaryConvert(12 + 2) = 1110 + */ + +const BinaryConvert = (num) => { + let power = 1 + let binary = 0 + + while (num) { + const rem = num % 2 + num = Math.floor(num / 2) + binary = rem * power + binary + power *= 10 + } + + return binary +} + +export { BinaryConvert } diff --git a/Maths/BinaryExponentiationIterative.js b/Maths/BinaryExponentiationIterative.js new file mode 100644 index 0000000000..9c1a23f215 --- /dev/null +++ b/Maths/BinaryExponentiationIterative.js @@ -0,0 +1,24 @@ +// To calculate x^n i.e. exponent(x, n) in O(log n) time in iterative way +// n is an integer and n >= 0 + +// Explanation: https://en.wikipedia.org/wiki/Exponentiation_by_squaring + +// Examples: +// 2^3 = 8 +// 5^0 = 1 + +// Uses the fact that +// exponent(x, n) +// = exponent(x*x, floor(n/2)) ; if n is odd +// = x*exponent(x*x, floor(n/2)) ; if n is even +const exponent = (x, n) => { + let answer = 1 + while (n > 0) { + if (n % 2 !== 0) answer *= x + n = Math.floor(n / 2) + if (n > 0) x *= x + } + return answer +} + +export { exponent } diff --git a/Maths/BinaryExponentiationRecursive.js b/Maths/BinaryExponentiationRecursive.js new file mode 100644 index 0000000000..a030e5ba9a --- /dev/null +++ b/Maths/BinaryExponentiationRecursive.js @@ -0,0 +1,20 @@ +/* + Modified from: + https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py + + Explanation: + https://en.wikipedia.org/wiki/Exponentiation_by_squaring +*/ + +export const binaryExponentiation = (a, n) => { + // input: a: int, n: int + // returns: a^n: int + if (n === 0) { + return 1 + } else if (n % 2 === 1) { + return binaryExponentiation(a, n - 1) * a + } else { + const b = binaryExponentiation(a, n / 2) + return b * b + } +} diff --git a/Maths/BinomialCoefficient.js b/Maths/BinomialCoefficient.js new file mode 100644 index 0000000000..65c52dd15d --- /dev/null +++ b/Maths/BinomialCoefficient.js @@ -0,0 +1,31 @@ +/* + * Author: Akshay Dubey (https://github.com/itsAkshayDubey) + * Binomial Coefficient: https://en.wikipedia.org/wiki/Binomial_coefficient + * function to find binomial coefficient of numbers n and k. + * return binomial coefficient of n,k + */ + +/** + * @function findBinomialCoefficient + * @description -> this function returns binomial coefficient + * of two numbers n & k given by n!/((n-k)!k!) + * @param {number} n + * @param {number} k + * @returns {number} + */ + +import { calcFactorial } from './Factorial' + +export const findBinomialCoefficient = (n, k) => { + if (typeof n !== 'number' || typeof k !== 'number') { + throw Error('Type of arguments must be number.') + } + if (n < 0 || k < 0) { + throw Error('Arguments must be greater than zero.') + } + let product = 1 + for (let i = n; i > k; i--) { + product *= i + } + return product / calcFactorial(n - k) +} diff --git a/Maths/BisectionMethod.js b/Maths/BisectionMethod.js new file mode 100644 index 0000000000..4539e6d466 --- /dev/null +++ b/Maths/BisectionMethod.js @@ -0,0 +1,53 @@ +/** + * + * @file + * @brief Find real roots of a function in a specified interval [a, b], where f(a)*f(b) < 0 + * + * @details Given a function f(x) and an interval [a, b], where f(a) * f(b) < 0, find an approximation of the root + * by calculating the middle m = (a + b) / 2, checking f(m) * f(a) and f(m) * f(b) and then by choosing the + * negative product that means Bolzano's theorem is applied,, define the new interval with these points. Repeat until + * we get the precision we want [Wikipedia](https://en.wikipedia.org/wiki/Bisection_method) + * + * @author [ggkogkou](https://github.com/ggkogkou) + * + */ + +const findRoot = (a, b, func, numberOfIterations) => { + // Check if a given real value belongs to the function's domain + const belongsToDomain = (x, f) => { + const res = f(x) + return !Number.isNaN(res) + } + if (!belongsToDomain(a, func) || !belongsToDomain(b, func)) + throw Error("Given interval is not a valid subset of function's domain") + + // Bolzano theorem + const hasRoot = (a, b, func) => { + return func(a) * func(b) <= 0 + } + if (hasRoot(a, b, func) === false) { + throw Error( + 'Product f(a)*f(b) has to be negative so that Bolzano theorem is applied' + ) + } + + // Declare m + const m = (a + b) / 2 + + // Recursion terminal condition + if (numberOfIterations === 0) { + return m + } + + // Find the products of f(m) and f(a), f(b) + const fm = func(m) + const prod1 = fm * func(a) + const prod2 = fm * func(b) + + // Depending on the sign of the products above, decide which position will m fill (a's or b's) + if (prod2 <= 0) return findRoot(m, b, func, --numberOfIterations) + + return findRoot(a, m, func, --numberOfIterations) +} + +export { findRoot } diff --git a/Maths/CheckKishnamurthyNumber.js b/Maths/CheckKishnamurthyNumber.js new file mode 100644 index 0000000000..5098b0fa7f --- /dev/null +++ b/Maths/CheckKishnamurthyNumber.js @@ -0,0 +1,47 @@ +/* + Problem statement and Explanation : https://www.geeksforgeeks.org/check-if-a-number-is-a-krishnamurthy-number-or-not-2/ + + krishnamurthy number is a number the sum of the all factorial of the all dights is equal to the number itself. + 145 => 1! + 4! + 5! = 1 + 24 + 120 = 145 +*/ + +// factorial utility method. +const factorial = (n) => { + let fact = 1 + while (n !== 0) { + fact = fact * n + n-- + } + return fact +} + +/** + * krishnamurthy number is a number the sum of the factorial of the all dights is equal to the number itself. + * @param {Number} number a number for checking is krishnamurthy number or not. + * @returns return correspond boolean value, if the number is krishnamurthy number return `true` else return `false`. + * @example 145 => 1! + 4! + 5! = 1 + 24 + 120 = 145 + */ +const CheckKishnamurthyNumber = (number) => { + // firstly, check that input is a number or not. + if (typeof number !== 'number') { + throw new TypeError('Argument is not a number.') + } + if (number === 0) { + return false + } + // create a variable to store the sum of all digits factorial. + let sumOfAllDigitFactorial = 0 + // convert the number to string for convenience. + let newNumber = number + // Extract number digits using the remainder method. + while (newNumber > 0) { + const lastDigit = newNumber % 10 + // calculate each digit factorial. + sumOfAllDigitFactorial += factorial(lastDigit) + newNumber = Math.floor(newNumber / 10) + } + // if the sumOfAllDigitFactorial is equal to the given number it means the number is a Krishnamurthy number. + return sumOfAllDigitFactorial === number +} + +export { CheckKishnamurthyNumber } diff --git a/Maths/CircularArc.js b/Maths/CircularArc.js new file mode 100644 index 0000000000..b1d819323c --- /dev/null +++ b/Maths/CircularArc.js @@ -0,0 +1,28 @@ +import { degreeToRadian } from './DegreeToRadian.js' + +/** + * @function circularArcLength + * @description calculate the length of a circular arc + * @param {Integer} radius + * @param {Integer} degrees + * @returns {Integer} radius * angle_in_radians + * @see https://en.wikipedia.org/wiki/Circular_arc + * @example circularArcLength(3, 45) = 2.356194490192345 + */ +function circularArcLength(radius, degrees) { + return radius * degreeToRadian(degrees) +} +/** + * @function circularArcArea + * @description calculate the area of the sector formed by an arc + * @param {Integer} radius + * @param {Integer} degrees + * @returns {Integer} 0.5 * r * r * angle_in_radians + * @see https://en.wikipedia.org/wiki/Circular_arc + * @example circularArcArea(3,45) = 3.5342917352885173 + */ +function circularArcArea(radius, degrees) { + return (Math.pow(radius, 2) * degreeToRadian(degrees)) / 2 +} + +export { circularArcLength, circularArcArea } diff --git a/Maths/CoPrimeCheck.js b/Maths/CoPrimeCheck.js new file mode 100644 index 0000000000..6f0437eaac --- /dev/null +++ b/Maths/CoPrimeCheck.js @@ -0,0 +1,29 @@ +/* + Problem statement and Explanation : https://en.wikipedia.org/wiki/Coprime_integers + + In number theory, two integers a and b are coprime, relatively prime or + mutually prime if the only positive integer that is a divisor of both + of them is Consequently, any prime number that divides one of a + or b does not divide the other. This is equivalent to their greatest + common divisor (gcd) being. One says also a is prime to b or a + is coprime with b. +*/ + +import { GetEuclidGCD } from './GetEuclidGCD' + +// CoPrimeCheck function return the boolean in respect of the given number is co-prime or not. +/** + * CoPrimeCheck function return the boolean in respect of the given number is co-prime or not. + * @param {Number} firstNumber first number for checking is prime or not. + * @param {Number} secondNumber second number for checking is prime or not. + * @returns return correspond boolean value, if both number are co-prime return `true`, else return `false`. + */ +const CoPrimeCheck = (firstNumber, secondNumber) => { + /* + This is the most efficient algorithm for checking co-primes + if the GCD of both the numbers is 1 that means they are co-primes. + */ + return GetEuclidGCD(firstNumber, secondNumber) === 1 +} + +export { CoPrimeCheck } diff --git a/Maths/CollatzSequence.js b/Maths/CollatzSequence.js new file mode 100644 index 0000000000..608fc99a0d --- /dev/null +++ b/Maths/CollatzSequence.js @@ -0,0 +1,30 @@ +/** + * @function collatz + * @description Applies the Collatz Sequence on a specified number. + * The Collatz Sequence states that every natural number will always fall in a 1, 2, 4 loop when iterated under the following function: + * If the number is even, divide by 2, and if its odd, multiply it by 3 and add 1. + * + * @parama {Integer} n The number to apply the Collatz Sequence to. + * + * @return An array of steps and the final result.. + * + * @see [Collatz Conjecture](https://en.wikipedia.org/wiki/Collatz_conjecture) + * + * @example collatz(1) = { result: 1, steps: [] } + * @example collatz(5) = { result: 1, steps: [16, 8, 4, 2, 1] } + */ +export function collatz(n) { + const steps = [] + + while (n !== 1) { + if (n % 2 === 0) { + n = n / 2 + } else { + n = 3 * n + 1 + } + + steps.push(n) + } + + return { result: n, steps } +} diff --git a/Maths/Coordinate.js b/Maths/Coordinate.js new file mode 100644 index 0000000000..b2276baa37 --- /dev/null +++ b/Maths/Coordinate.js @@ -0,0 +1,19 @@ +/* + Calculate the mathematical properties involving coordinates + Calculate the Distance Between 2 Points on a 2 Dimensional Plane + Example: coorDistance(2,2,14,11) will return 15 + Wikipedia reference: https://en.wikipedia.org/wiki/Geographical_distance#Flat-surface_formulae +*/ +const euclideanDistance = (longitude1, latitude1, longitude2, latitude2) => { + const width = longitude2 - longitude1 + const height = latitude2 - latitude1 + return Math.sqrt(width * width + height * height) +} + +const manhattanDistance = (longitude1, latitude1, longitude2, latitude2) => { + const width = Math.abs(longitude2 - longitude1) + const height = Math.abs(latitude2 - latitude1) + return width + height +} + +export { euclideanDistance, manhattanDistance } diff --git a/Maths/CountNumbersDivisible.js b/Maths/CountNumbersDivisible.js new file mode 100644 index 0000000000..632011f107 --- /dev/null +++ b/Maths/CountNumbersDivisible.js @@ -0,0 +1,63 @@ +/** + * Count the numbers divisible by โ€˜Mโ€™ in a given range + * + * @see {@link https://www.tutorialspoint.com/count-the-numbers-divisible-by-m-in-a-given-range-in-cplusplus} + * + * We have 3 numbers A, B, M as inputs, A and B defines the numbers range [A, B] + * Count the total number of divisibles in that range by number M + * + * @author Chetan07j + */ + +/** + * Function to find total divisibles in given range + * + * @param {number} num1 + * @param {number} num2 + * @param {number} divider + * + * @returns {number} count of total number of divisibles + */ +const countNumbersDivisible = (num1, num2, divider) => { + if ( + typeof num1 !== 'number' || + typeof num2 !== 'number' || + typeof divider !== 'number' + ) { + throw new Error('Invalid input, please pass only numbers') + } + + // Valid number range is num1 < num2, otherwise throw error + if (num1 > num2) { + throw new Error( + 'Invalid number range, please provide numbers such that num1 < num2' + ) + } + + // if divider is out of range then return 0 + // as in such case no divisible exists + if (divider > num2) { + return 0 + } + + // Find the number of multiples of divider for num1 and num2 + // integer division part + const num1Multiplier = num1 / divider + const num2Multiplier = num2 / divider + + // The count of numbers divisibles by divider between num1 and num2 + let divisibleCount = num2Multiplier - num1Multiplier + + // If num1 is divisible by divider then, edge case for num1 is ignored + // which results in 1 less count + // to fix that we add +1 in this case + if (num1 % divider === 0) { + divisibleCount++ + } + + // As it includes integer division meaning floating values + // to get exact count Math.round() is added + return Math.round(divisibleCount) +} + +export { countNumbersDivisible } diff --git a/Maths/DecimalExpansion.js b/Maths/DecimalExpansion.js new file mode 100644 index 0000000000..4cd8303d6b --- /dev/null +++ b/Maths/DecimalExpansion.js @@ -0,0 +1,67 @@ +/** + * @author Eric Lavault + * + * Represents the decimal (or binary, octal, any base from 2 to 10) expansion + * of a/b using euclidean division. + * + * Because this function is recursive, it may throw an error when reaching the + * maximum call stack size. + * + * Returns an array containing : [ + * 0: integer part of the division + * 1: array of decimals (if any, or an empty array) + * 2: indexOf 1st cycle digit in decimals array if a/b is periodic, or undef. + * ] + * + * @see https://mathworld.wolfram.com/DecimalExpansion.html + * + * @param {number} a + * @param {number} b + * @param {number} [base=10] + * @returns {array} + */ +export function decExp(a, b, base = 10, exp = [], d = {}, dlen = 0) { + if (base < 2 || base > 10) { + throw new RangeError('Unsupported base. Must be in range [2, 10]') + } + + if (a === 0) { + return [0, [], undefined] + } + + if (a === b && dlen === 0) { + return [1, [], undefined] + } + + // d contains the dividends used so far and the corresponding index of its + // euclidean division by b in the expansion array. + d[a] = dlen++ + + if (a < b) { + exp.push(0) + return decExp(a * base, b, base, exp, d, dlen) + } + + // Euclid's division lemma : a = bq + r + const r = a % b + const q = (a - r) / b + + // Decimal expansion (1st element is the integer part) + exp.push(+q.toString(base)) + + if (r === 0) { + // got a regular number (division terminates) + return [exp[0], exp.slice(1), undefined] + } + + // For the next iteration + a = r * base + + // Check if `a` has already been used as a dividend, in which case it means + // the expansion is periodic. + if (a in d) { + return [exp[0], exp.slice(1), d[a] - 1] + } + + return decExp(a, b, base, exp, d, dlen) +} diff --git a/Maths/DecimalIsolate.js b/Maths/DecimalIsolate.js new file mode 100644 index 0000000000..ac9f81a076 --- /dev/null +++ b/Maths/DecimalIsolate.js @@ -0,0 +1,10 @@ +/* + * function isolates the decimal part of a number. + * Take the number and subtract it from the floored number. + * Return the result. + */ + +export const decimalIsolate = (number) => { + const answer = parseFloat((number + '').replace(/^[-\d]+./, '.')) + return isNaN(answer) === true ? 0 : answer +} diff --git a/Maths/DegreeToRadian.js b/Maths/DegreeToRadian.js new file mode 100644 index 0000000000..60e663574c --- /dev/null +++ b/Maths/DegreeToRadian.js @@ -0,0 +1,23 @@ +/* + * Radian : https://en.wikipedia.org/wiki/Radian + * Degree : https://en.wikipedia.org/wiki/Degree_(angle) + * + * Angle in Radian = ( Angle in Degree ) x ( pi / 180 ) + * + * Example : + * Question : Convert 90 degree to radian + * So, Angle in Degree = 90 + * + * Solution : + * Angle in Radian = ( 90 ) x ( pi / 180 ) = pi / 2 + * + * So, 90 degree is equal to pi / 2 radian + */ + +/** + * @param {number} degree + * @return {number} + */ +export const degreeToRadian = (degree) => { + return degree * (Math.PI / 180) +} diff --git a/Maths/Determinant.js b/Maths/Determinant.js new file mode 100644 index 0000000000..d218a6ee98 --- /dev/null +++ b/Maths/Determinant.js @@ -0,0 +1,78 @@ +/** + * Given a square matrix, find its determinant using Laplace Expansion. + * Time Complexity : O(n!) + * + * For more info: https://en.wikipedia.org/wiki/Determinant + * + * @param {number[[]]} matrix - Two dimensional array of integers. + * @returns {number} - An integer equal to the determinant. + * + * @example + * const squareMatrix = [ + * [2,3,4,6], + * [5,8,9,0], + * [7,4,3,9], + * [4,0,2,1] + * ]; + * + * const result = determinant(squareMatrix); + * // The function should return 858 as the resultant determinant. + */ + +const subMatrix = (matrix, i, j) => { + let matrixSize = matrix[0].length + if (matrixSize === 1) { + return matrix[0][0] + } + let subMatrix = [] + for (let x = 0; x < matrixSize; x++) { + if (x === i) { + continue + } + subMatrix.push([]) + for (let y = 0; y < matrixSize; y++) { + if (y === j) { + continue + } + subMatrix[subMatrix.length - 1].push(matrix[x][y]) + } + } + return subMatrix +} + +const isMatrixSquare = (matrix) => { + let numRows = matrix.length + for (let i = 0; i < numRows; i++) { + if (numRows !== matrix[i].length) { + return false + } + } + return true +} + +const determinant = (matrix) => { + if ( + !Array.isArray(matrix) || + matrix.length === 0 || + !Array.isArray(matrix[0]) + ) { + throw new Error('Input is not a valid 2D matrix.') + } + if (!isMatrixSquare(matrix)) { + throw new Error('Square matrix is required.') + } + let numCols = matrix[0].length + if (numCols === 1) { + return matrix[0][0] + } + let result = 0 + let setIndex = 0 + for (let i = 0; i < numCols; i++) { + result += + Math.pow(-1, i) * + matrix[setIndex][i] * + determinant(subMatrix(matrix, setIndex, i)) + } + return result +} +export { determinant } diff --git a/Maths/EuclideanDistance.js b/Maths/EuclideanDistance.js new file mode 100644 index 0000000000..0cded84ebb --- /dev/null +++ b/Maths/EuclideanDistance.js @@ -0,0 +1,19 @@ +/** + * @see [Wikipedia](https://en.wikipedia.org/wiki/Euclidean_distance) + * Calculate the Euclidean distance between two vectors. + * @param {number[]} vector1 - The first vector. + * @param {number[]} vector2 - The second vector. + * @returns {number} The Euclidean distance between the two vectors. + */ + +const EuclideanDistance = (vector1, vector2) => { + let sumOfSquares = 0 + + for (let i = 0; i < vector1.length; i++) { + sumOfSquares += Math.pow(vector1[i] - vector2[i], 2) + } + + return Math.sqrt(sumOfSquares) +} + +export { EuclideanDistance } diff --git a/Maths/EulerMethod.js b/Maths/EulerMethod.js new file mode 100644 index 0000000000..619f2c8a67 --- /dev/null +++ b/Maths/EulerMethod.js @@ -0,0 +1,36 @@ +/** + * In mathematics and computational science, the Euler method (also called forward Euler method) is a first-order + * numerical procedure for solving ordinary differential equations (ODEs) with a given initial value. It is the most + * basic explicit method for numerical integration of ordinary differential equations. The method proceeds in a series + * of steps. At each step the y-value is calculated by evaluating the differential equation at the previous step, + * multiplying the result with the step-size and adding it to the last y-value: y_n+1 = y_n + stepSize * f(x_n, y_n). + * + * (description adapted from https://en.wikipedia.org/wiki/Euler_method) + * @see https://www.geeksforgeeks.org/euler-method-solving-differential-equation/ + */ +export function eulerStep(xCurrent, stepSize, yCurrent, differentialEquation) { + // calculates the next y-value based on the current value of x, y and the stepSize + return yCurrent + stepSize * differentialEquation(xCurrent, yCurrent) +} + +export function eulerFull( + xStart, + xEnd, + stepSize, + yStart, + differentialEquation +) { + // loops through all the steps until xEnd is reached, adds a point for each step and then returns all the points + const points = [{ x: xStart, y: yStart }] + let yCurrent = yStart + let xCurrent = xStart + + while (xCurrent < xEnd) { + // Euler method for next step + yCurrent = eulerStep(xCurrent, stepSize, yCurrent, differentialEquation) + xCurrent += stepSize + points.push({ x: xCurrent, y: yCurrent }) + } + + return points +} diff --git a/Maths/EulersTotient.js b/Maths/EulersTotient.js new file mode 100644 index 0000000000..09d05377af --- /dev/null +++ b/Maths/EulersTotient.js @@ -0,0 +1,29 @@ +/* + Source: + https://en.wikipedia.org/wiki/Euler%27s_totient_function + + EulersTotient(n) = n * product(1 - 1/p for all prime p dividing n) + + Complexity: + O(sqrt(n)) +*/ + +export const EulersTotient = (n) => { + // input: n: int + // output: phi(n): count of numbers b/w 1 and n that are coprime to n + let res = n + for (let i = 2; i * i <= n; i++) { + if (n % i === 0) { + while (n % i === 0) { + n = Math.floor(n / i) + } + // i is a prime diving n, multiply res by 1 - 1/i + // res = res * (1 - 1/i) = res - (res / i) + res = res - Math.floor(res / i) + } + } + if (n > 1) { + res = res - Math.floor(res / n) + } + return res +} diff --git a/Maths/EulersTotientFunction.js b/Maths/EulersTotientFunction.js new file mode 100644 index 0000000000..387a93fa2b --- /dev/null +++ b/Maths/EulersTotientFunction.js @@ -0,0 +1,28 @@ +/* + author sandyboypraper + + Here is the EulerTotientFunction. + it is also represented by phi + + so EulersTotientFunction(n) (or phi(n)) is the count of numbers in {1,2,3,....,n} that are relatively + prime to n, i.e., the numbers whose GCD (Greatest Common Divisor) with n is 1. +*/ + +const gcdOfTwoNumbers = (x, y) => { + // x is smaller than y + // let gcd of x and y is gcdXY + // so it divides x and y completely + // so gcdXY should also divide y%x (y = gcdXY*a and x = gcdXY*b and y%x = y - x*k so y%x = gcdXY(a - b*k)) + // and gcd(x,y) is equal to gcd(y%x , x) + return x === 0 ? y : gcdOfTwoNumbers(y % x, x) +} + +const eulersTotientFunction = (n) => { + let countOfRelativelyPrimeNumbers = 1 + for (let iterator = 2; iterator <= n; iterator++) { + if (gcdOfTwoNumbers(iterator, n) === 1) countOfRelativelyPrimeNumbers++ + } + return countOfRelativelyPrimeNumbers +} + +export { eulersTotientFunction } diff --git a/Maths/ExponentialFunction.js b/Maths/ExponentialFunction.js new file mode 100644 index 0000000000..11da977575 --- /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/ExtendedEuclideanGCD.js b/Maths/ExtendedEuclideanGCD.js new file mode 100644 index 0000000000..82317668de --- /dev/null +++ b/Maths/ExtendedEuclideanGCD.js @@ -0,0 +1,72 @@ +/** + * Problem statement and explanation: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm + * + * This algorithm plays an important role for modular arithmetic, and by extension for cryptography algorithms + * + * Basic explanation: + * The Extended Euclidean algorithm is a modification of the standard Euclidean GCD algorithm. + * It allows to calculate coefficients x and y for the equation: + * ax + by = gcd(a,b) + * + * This is called Bรฉzout's identity and the coefficients are called Bรฉzout coefficients + * + * The algorithm uses the Euclidean method of getting remainder: + * r_i+1 = r_i-1 - qi*ri + * and applies it to series s and t (with same quotient q at each stage) + * When r_n reaches 0, the value r_n-1 gives the gcd, and s_n-1 and t_n-1 give the coefficients + * + * This implementation uses an iterative approach to calculate the values + */ + +/** + * + * @param {Number} arg1 first argument + * @param {Number} arg2 second argument + * @returns Array with GCD and first and second Bรฉzout coefficients + */ +const extendedEuclideanGCD = (arg1, arg2) => { + if (typeof arg1 !== 'number' || typeof arg2 !== 'number') + throw new TypeError('Not a Number') + if (arg1 < 1 || arg2 < 1) throw new TypeError('Must be positive numbers') + + // Make the order of coefficients correct, as the algorithm assumes r0 > r1 + if (arg1 < arg2) { + const res = extendedEuclideanGCD(arg2, arg1) + const temp = res[1] + res[1] = res[2] + res[2] = temp + return res + } + + // At this point arg1 > arg2 + + // Remainder values + let r0 = arg1 + let r1 = arg2 + + // Coefficient1 values + let s0 = 1 + let s1 = 0 + + // Coefficient 2 values + let t0 = 0 + let t1 = 1 + + while (r1 !== 0) { + const q = Math.floor(r0 / r1) + + const r2 = r0 - r1 * q + const s2 = s0 - s1 * q + const t2 = t0 - t1 * q + + r0 = r1 + r1 = r2 + s0 = s1 + s1 = s2 + t0 = t1 + t1 = t2 + } + return [r0, s0, t0] +} + +export { extendedEuclideanGCD } diff --git a/Maths/Factorial.js b/Maths/Factorial.js index c13878d905..7dc6fd6d6f 100644 --- a/Maths/Factorial.js +++ b/Maths/Factorial.js @@ -13,41 +13,27 @@ 'use strict' -function calcRange (num) { - // Generate a range of numbers from 1 to `num`. - var i = 1 - var range = [] - while (i <= num) { - range.push(i) - i += 1 - } - return range +const calcRange = (num) => { + return [...Array(num).keys()].map((i) => i + 1) } -function calcFactorial (num) { - var factorial - var range = calcRange(num) - - // Check if the number is negative, positive, null, undefined, or zero - if (num < 0) { - return 'Sorry, factorial does not exist for negative numbers.' +const calcFactorial = (num) => { + if (num === 0) { + return 1 } - if (num === null || num === undefined) { - return 'Sorry, factorial does not exist for null or undefined numbers.' + if (num < 0) { + throw Error('Sorry, factorial does not exist for negative numbers.') } - if (num === 0) { - return 'The factorial of 0 is 1.' + if (!num) { + throw Error( + 'Sorry, factorial does not exist for null or undefined numbers.' + ) } if (num > 0) { - factorial = 1 - range.forEach(function (i) { - factorial = factorial * i - }) - return 'The factorial of ' + num + ' is ' + factorial + const range = calcRange(num) + const factorial = range.reduce((a, c) => a * c, 1) + return factorial } } -// Run `factorial` Function to find average of a list of numbers. - -var num = console.log('Enter a number: ') -console.log(calcFactorial(num)) +export { calcFactorial } diff --git a/Maths/Factors.js b/Maths/Factors.js new file mode 100644 index 0000000000..68bbde6d23 --- /dev/null +++ b/Maths/Factors.js @@ -0,0 +1,16 @@ +/** + * Author: dephraiim + * License: GPL-3.0 or later + * + * More on Factors: + * https://www.mathsisfun.com/definitions/factor.html + * + */ + +const factorsOfANumber = (number = 0) => { + return Array.from(Array(number + 1).keys()).filter( + (num) => number % num === 0 + ) +} + +export { factorsOfANumber } diff --git a/Maths/FareyApproximation.js b/Maths/FareyApproximation.js new file mode 100644 index 0000000000..3667879d42 --- /dev/null +++ b/Maths/FareyApproximation.js @@ -0,0 +1,50 @@ +/* + * Reference: https://en.wikipedia.org/wiki/Farey_sequence + * Inspiration: https://www.youtube.com/watch?v=7LKy3lrkTRA + * + * Farey Approximation algorithm is an algorithm to + * approximate a reduced fraction value for a certain + * decimal number x where 0 < x < 1. + * + * The algorithm works by keeping two fractional upper and + * lower bounds which start at 0 / 1 and 1 / 1. These values + * are then used to find the "mediate" which is a value between + * the two fractions. + * + * For any two fractions a / b and c / d, + * mediate = a + c / b + d + * + * Then it is checked if the decimal is greater than or less + * than the mediate and then the lower or the upper value is + * set to be the mediate respectively. + * + * This is repeated for n times and then the mediate is + * returned. + * + * This is explained in a greater detail in the "Inspiration" + * link. + */ + +function fareyApproximation(decimal, repeat = 20) { + let a = 0 + let b = 1 + let c = 1 + let d = 1 + let numerator + let denominator + + for (let i = 0; i < repeat; i++) { + numerator = a + c + denominator = b + d + + if (decimal > numerator / denominator) { + ;[a, b] = [numerator, denominator] + } else { + ;[c, d] = [numerator, denominator] + } + } + + return { numerator, denominator } +} + +export { fareyApproximation } diff --git a/Maths/FermatPrimalityTest.js b/Maths/FermatPrimalityTest.js new file mode 100644 index 0000000000..331c168eea --- /dev/null +++ b/Maths/FermatPrimalityTest.js @@ -0,0 +1,97 @@ +/* + * The Fermat primality test is a probabilistic test to determine whether a number + * is a probable prime. + * + * It relies on Fermat's Little Theorem, which states that if p is prime and a + * is not divisible by p, then + * + * a^(p - 1) % p = 1 + * + * However, there are certain numbers (so called Fermat Liars) that screw things up; + * if a is one of these liars the equation will hold even though p is composite. + * + * But not everything is lost! It's been proven that at least half of all integers + * aren't Fermat Liars (these ones called Fermat Witnesses). Thus, if we keep + * testing the primality with random integers, we can achieve higher reliability. + * + * The interesting about all of this is that since half of all integers are + * Fermat Witnesses, the precision gets really high really fast! Suppose that we + * make the test 50 times: the chance of getting only Fermat Liars in all runs is + * + * 1 / 2^50 = 8.8 * 10^-16 (a pretty small number) + * + * For comparison, the probability of a cosmic ray causing an error to your + * infallible program is around 1.4 * 10^-15. An order of magnitude below! + * + * But because nothing is perfect, there's a major flaw to this algorithm, and + * the cause are the so called Carmichael Numbers. These are composite numbers n + * that hold the equality from Fermat's Little Theorem for every a < n (excluding + * is factors). In other words, if we are trying to determine if a Carmichael Number + * is prime or not, the chances of getting a wrong answer are pretty high! Because + * of that, the Fermat Primality Test is not used is serious applications. :( + * + * You can find more about the Fermat primality test and its flaws here: + * https://en.wikipedia.org/wiki/Fermat_primality_test + * + * And about Carmichael Numbers here: + * https://primes.utm.edu/glossary/xpage/CarmichaelNumber.html + */ + +/** + * Faster exponentiation that capitalize on the fact that we are only interested + * in the modulus of the exponentiation. + * + * Find out more about it here: https://en.wikipedia.org/wiki/Modular_exponentiation + * + * @param {number} base + * @param {number} exponent + * @param {number} modulus + */ +const modularExponentiation = (base, exponent, modulus) => { + if (modulus === 1) return 0 // after all, any x % 1 = 0 + + let result = 1 + base %= modulus // make sure that base < modulus + + while (exponent > 0) { + // if exponent is odd, multiply the result by the base + if (exponent % 2 === 1) { + result = (result * base) % modulus + exponent-- + } else { + exponent = exponent / 2 // exponent is even for sure + base = (base * base) % modulus + } + } + + return result +} + +/** + * Test if a given number n is prime or not. + * + * @param {number} n The number to check for primality + * @param {number} numberOfIterations The number of times to apply Fermat's Little Theorem + * @returns True if prime, false otherwise + */ +const fermatPrimeCheck = (n, numberOfIterations = 50) => { + // first check for edge cases + if (n <= 1 || n === 4) return false + if (n <= 3) return true // 2 and 3 are included here + + for (let i = 0; i < numberOfIterations; i++) { + // pick a random number a, with 2 <= a < n - 2 + const randomNumber = Math.floor(Math.random() * (n - 2) + 2) + + // if a^(n - 1) % n is different than 1, n is composite + if (modularExponentiation(randomNumber, n - 1, n) !== 1) { + return false + } + } + + // if we arrived here without finding a Fermat Witness, this is almost guaranteed + // to be a prime number (or a Carmichael number, if you are unlucky) + return true +} + +export { modularExponentiation, fermatPrimeCheck } diff --git a/Maths/Fibonacci.js b/Maths/Fibonacci.js index 9a4361781d..2b6881cc3b 100644 --- a/Maths/Fibonacci.js +++ b/Maths/Fibonacci.js @@ -1,51 +1,69 @@ -const list = [] - -const FibonacciIterative = (nth) => { - const sequence = [] - - if (nth >= 1) sequence.push(1) - if (nth >= 2) sequence.push(1) - - for (let i = 2; i < nth; i++) { - sequence.push(sequence[i - 1] + sequence[i - 2]) +// https://en.wikipedia.org/wiki/Generalizations_of_Fibonacci_numbers#Extension_to_negative_integers +const FibonacciIterative = (num) => { + const isNeg = num < 0 + if (isNeg) num *= -1 + const sequence = [0] + + if (num >= 1) sequence.push(1) + if (num >= 2) sequence.push(isNeg ? -1 : 1) + + for (let i = 2; i < num; i++) { + sequence.push( + isNeg ? sequence[i - 1] - sequence[i] : sequence[i] + sequence[i - 1] + ) } return sequence } -const FibonacciRecursive = (number) => { +const FibonacciGenerator = function* (neg) { + let a = 0 + let b = 1 + yield a + while (true) { + yield b + ;[a, b] = neg ? [b, a - b] : [b, a + b] + } +} + +const list = [] +const FibonacciRecursive = (num) => { + const isNeg = num < 0 + if (isNeg) num *= -1 return (() => { switch (list.length) { case 0: - list.push(1) - return FibonacciRecursive(number) + list.push(0) + return FibonacciRecursive(num) case 1: list.push(1) - return FibonacciRecursive(number) - case number: + return FibonacciRecursive(num) + case num + 1: return list default: - list.push(list[list.length - 1] + list[list.length - 2]) - return FibonacciRecursive(number) + list.push(list.at(-1) + list.at(-2)) + return FibonacciRecursive(num) } - })() + })().map((fib, i) => fib * (isNeg ? (-1) ** (i + 1) : 1)) } const dict = new Map() - const FibonacciRecursiveDP = (stairs) => { - if (stairs <= 0) return 0 - if (stairs === 1) return 1 + const isNeg = stairs < 0 + if (isNeg) stairs *= -1 + + if (stairs <= 1) return stairs // Memoize stair count - if (dict.has(stairs)) return dict.get(stairs) + if (dict.has(stairs)) + return (isNeg ? (-1) ** (stairs + 1) : 1) * dict.get(stairs) const res = FibonacciRecursiveDP(stairs - 1) + FibonacciRecursiveDP(stairs - 2) dict.set(stairs, res) - return res + return (isNeg ? (-1) ** (stairs + 1) : 1) * res } // Algorithms @@ -59,25 +77,133 @@ const FibonacciRecursiveDP = (stairs) => { // a function of the number of input bits // @Satzyakiz -const FibonacciDpWithoutRecursion = (number) => { - const table = [] - table.push(1) +const FibonacciDpWithoutRecursion = (num) => { + const isNeg = num < 0 + if (isNeg) num *= -1 + const table = [0] table.push(1) - for (var i = 2; i < number; ++i) { - table.push(table[i - 1] + table[i - 2]) + table.push(isNeg ? -1 : 1) + for (let i = 2; i < num; ++i) { + table.push(isNeg ? table[i - 1] - table[i] : table[i] + table[i - 1]) + } + return table +} + +// Using Matrix exponentiation to find n-th fibonacci in O(log n) time + +const copyMatrix = (A) => { + return A.map((row) => row.map((cell) => cell)) +} + +const Identity = (size) => { + const isBigInt = typeof size === 'bigint' + const ZERO = isBigInt ? 0n : 0 + const ONE = isBigInt ? 1n : 1 + size = Number(size) + const I = Array(size) + .fill(null) + .map(() => Array(size).fill()) + return I.map((row, rowIdx) => + row.map((_col, colIdx) => { + return rowIdx === colIdx ? ONE : ZERO + }) + ) +} + +// A of size (l x m) and B of size (m x n) +// product C will be of size (l x n). +// both matrices must have same-type numeric values +// either both BigInt or both Number +const matrixMultiply = (A, B) => { + A = copyMatrix(A) + B = copyMatrix(B) + const isBigInt = typeof A[0][0] === 'bigint' + const l = A.length + const m = B.length + const n = B[0].length // Assuming non-empty matrices + const C = Array(l) + .fill(null) + .map(() => Array(n).fill()) + for (let i = 0; i < l; i++) { + for (let j = 0; j < n; j++) { + C[i][j] = isBigInt ? 0n : 0 + for (let k = 0; k < m; k++) { + C[i][j] += A[i][k] * B[k][j] + } + } + } + return C +} + +/** + * Computes A raised to the power n i.e. pow(A, n) where A is a square matrix + * @param {*} A the square matrix + * @param {*} n the exponent + */ +// A is a square matrix +const matrixExpo = (A, n) => { + A = copyMatrix(A) + const isBigInt = typeof n === 'bigint' + const ZERO = isBigInt ? 0n : 0 + const TWO = isBigInt ? 2n : 2 + + // Just like Binary exponentiation mentioned in ./BinaryExponentiationIterative.js + let result = Identity((isBigInt ? BigInt : Number)(A.length)) // Identity matrix + while (n > ZERO) { + if (n % TWO !== ZERO) result = matrixMultiply(result, A) + n /= TWO + if (!isBigInt) n = Math.floor(n) + if (n > ZERO) A = matrixMultiply(A, A) } - return (table) + return result +} + +const FibonacciMatrixExpo = (num) => { + const isBigInt = typeof num === 'bigint' + const ZERO = isBigInt ? 0n : 0 + const ONE = isBigInt ? 1n : 1 + // F(0) = 0, F(1) = 1 + // F(n) = F(n-1) + F(n-2) + // Consider below matrix multiplication: + + // | F(n) | |1 1| |F(n-1)| + // | | = | | * | | + // |F(n-1)| |1 0| |F(n-2)| + + // Let's rewrite it as F(n, n-1) = A * F(n-1, n-2) + // or F(n, n-1) = A * A * F(n-2, n-3) + // or F(n, n-1) = pow(A, n-1) * F(1, 0) + + if (num === ZERO) return num + + const isNeg = num < 0 + if (isNeg) num *= -ONE + + const A = [ + [ONE, ONE], + [ONE, ZERO] + ] + + const poweredA = matrixExpo(A, num - ONE) // A raised to the power n-1 + let F = [[ONE], [ZERO]] + F = matrixMultiply(poweredA, F) + return F[0][0] * (isNeg ? (-ONE) ** (num + ONE) : ONE) } -// testing +/* + Resource : https://math.hmc.edu/funfacts/fibonacci-number-formula/ +*/ -console.log(FibonacciIterative(5)) -// Output: [ 1, 1, 2, 3, 5 ] -console.log(FibonacciRecursive(5)) -// Output: [ 1, 1, 2, 3, 5 ] +const sqrt5 = Math.sqrt(5) +const phi = (1 + sqrt5) / 2 +const psi = (1 - sqrt5) / 2 -console.log(FibonacciRecursiveDP(5)) -// Output: 5 +const FibonacciUsingFormula = (n) => Math.round((phi ** n - psi ** n) / sqrt5) -console.log(FibonacciDpWithoutRecursion(5)) -// Output: [ 1, 1, 2, 3, 5 ] +export { FibonacciDpWithoutRecursion } +export { FibonacciIterative } +export { FibonacciGenerator } +export { FibonacciRecursive } +export { FibonacciRecursiveDP } +export { FibonacciMatrixExpo } +export { FibonacciUsingFormula } diff --git a/Maths/FigurateNumber.js b/Maths/FigurateNumber.js new file mode 100644 index 0000000000..058972dee6 --- /dev/null +++ b/Maths/FigurateNumber.js @@ -0,0 +1,75 @@ +/** + Problem Statement and Explanation : + Triangular => https://en.wikipedia.org/wiki/Triangular_number + Tetrahedral => https://en.wikipedia.org/wiki/Tetrahedral_number + Pentatope => https://en.wikipedia.org/wiki/Pentatope_number + + Example: + Triangular => (0, 1, 3, 6, 10, 15, 21, 28, 36, 45) + Tetrahedral => (1, 4, 10, 20, 35, 56, 84, 120, 165,) + Pentatope => (1, 5, 15, 35, 70, 126, 210, 330, 495) + */ + +/** + * + * @param {*} number + * @returns + */ +const isTriangular = (number) => { + for (let i = 0; i <= number; i++) { + if ((i * (i + 1)) / 2 === number) { + return true + } else if ((i * (i + 1)) / 2 > number) { + return false + } + } + return false +} + +/** + * + * @param {*} number + * @returns + */ +const isTetrahedral = (number) => { + for (let i = 1; i <= number; i++) { + if ((i * (i + 1) * (i + 2)) / 6 === number) { + return true + } else if ((i * (i + 1) * (i + 2)) / 6 > number) { + return false + } + } + return false +} +/** + * + * @param {*} number + * @returns + */ +const isPentatope = (number) => { + for (let i = 1; i <= number; i++) { + if ((i * (i + 1) * (i + 2) * (i + 3)) / 24 === number) { + return true + } else if ((i * (i + 1) * (i + 2) * (i + 3)) / 24 > number) { + return false + } + } + return false +} + +/** + * + * @param {*} number + * @returns + */ +const checkAll = (number) => { + return { + isTriangular: isTriangular(number), + isTetrahedral: isTetrahedral(number), + isPentatope: isPentatope(number) + } +} +export { isTriangular } +export { isTetrahedral } +export { isPentatope } +export { checkAll } diff --git a/Maths/FindHcf.js b/Maths/FindHcf.js index f6b696219e..19105b5aa6 100644 --- a/Maths/FindHcf.js +++ b/Maths/FindHcf.js @@ -4,7 +4,7 @@ https://en.wikipedia.org/wiki/Greatest_common_divisor */ -function findHCF (x, y) { +const findHCF = (x, y) => { // If the input numbers are less than 1 return an error message. if (x < 1 || y < 1) { return 'Please enter values greater than zero.' @@ -27,4 +27,5 @@ function findHCF (x, y) { // When the while loop finishes the minimum of x and y is the HCF. return Math.min(x, y) } -console.log(findHCF(27, 36)) + +export { findHCF } diff --git a/Maths/FindLcm.js b/Maths/FindLcm.js index 60470e37ee..95ae2dc7f5 100644 --- a/Maths/FindLcm.js +++ b/Maths/FindLcm.js @@ -11,28 +11,43 @@ 'use strict' +import { findHCF } from './FindHcf' + // Find the LCM of two numbers. -function findLcm (num1, num2) { - var maxNum - var lcm - // Check to see whether num1 or num2 is larger. - if (num1 > num2) { - maxNum = num1 - } else { - maxNum = num2 +const findLcm = (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.') } - lcm = maxNum + + // Get the larger number between the two + const maxNum = Math.max(num1, num2) + let lcm = maxNum while (true) { - if ((lcm % num1 === 0) && (lcm % num2 === 0)) { - break - } + if (lcm % num1 === 0 && lcm % num2 === 0) return lcm lcm += maxNum } - return lcm } -// Run `findLcm` Function -var num1 = 12 -var num2 = 76 -console.log(findLcm(num1, num2)) +// 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/FindMaxRecursion.js b/Maths/FindMaxRecursion.js new file mode 100644 index 0000000000..9ef2982fed --- /dev/null +++ b/Maths/FindMaxRecursion.js @@ -0,0 +1,42 @@ +/** + * @function findMaxRecursion + * @description This algorithm will find the maximum value of a array of numbers. + * + * @param {Integer[]} arr Array of numbers + * @param {Integer} left Index of the first element + * @param {Integer} right Index of the last element + * + * @return {Integer} Maximum value of the array + * + * @see [Maximum value](https://en.wikipedia.org/wiki/Maximum_value) + * + * @example findMaxRecursion([1, 2, 4, 5]) = 5 + * @example findMaxRecursion([10, 40, 100, 20]) = 100 + * @example findMaxRecursion([-1, -2, -4, -5]) = -1 + */ +function findMaxRecursion(arr, left, right) { + const len = arr.length + + if (len === 0 || !arr) { + return undefined + } + + if (left >= len || left < -len || right >= len || right < -len) { + throw new Error('Index out of range') + } + + if (left === right) { + return arr[left] + } + + // n >> m is equivalent to floor(n / pow(2, m)), floor(n / 2) in this case, which is the mid index + const mid = (left + right) >> 1 + + const leftMax = findMaxRecursion(arr, left, mid) + const rightMax = findMaxRecursion(arr, mid + 1, right) + + // Return the maximum + return Math.max(leftMax, rightMax) +} + +export { findMaxRecursion } diff --git a/Maths/FindMin.js b/Maths/FindMin.js new file mode 100644 index 0000000000..9eee78dc7e --- /dev/null +++ b/Maths/FindMin.js @@ -0,0 +1,23 @@ +/** + * @function FindMin + * @description Function to find the minimum number given in an array of integers. + * @param {Integer[]} nums - Array of Integers + * @return {Integer} - The minimum number of the array. + */ + +const findMin = (...nums) => { + if (nums.length === 0) { + throw new TypeError('Array is empty') + } + + let min = nums[0] + for (let i = 1; i < nums.length; i++) { + if (nums[i] < min) { + min = nums[i] + } + } + + return min +} + +export { findMin } diff --git a/Maths/FindMinIterator.js b/Maths/FindMinIterator.js new file mode 100644 index 0000000000..658ac9a5cb --- /dev/null +++ b/Maths/FindMinIterator.js @@ -0,0 +1,44 @@ +/** + * @function FindMinIterator + * @description Function to find the minimum number given in an array. + */ + +const FindMinIterator = (_iterable, _selector = undefined) => { + let min + + const iterator = _iterable[Symbol.iterator]() + if (!_selector) { + let current = iterator.next() + if (current.done) { + return undefined + } + min = current.value + + current = iterator.next() + while (!current.done) { + const x = current.value + if (x < min) { + min = x + } + current = iterator.next() + } + } else { + let current = iterator.next() + if (current.done) { + return undefined + } + min = _selector(current.value) + + current = iterator.next() + while (!current.done) { + const x = _selector(current.value) + if (x < min) { + min = x + } + current = iterator.next() + } + } + return min +} + +export { FindMinIterator } diff --git a/Maths/FriendlyNumbers.js b/Maths/FriendlyNumbers.js new file mode 100644 index 0000000000..f7440384b4 --- /dev/null +++ b/Maths/FriendlyNumbers.js @@ -0,0 +1,38 @@ +/* + '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/GetEuclidGCD.js b/Maths/GetEuclidGCD.js new file mode 100644 index 0000000000..42a30a6042 --- /dev/null +++ b/Maths/GetEuclidGCD.js @@ -0,0 +1,39 @@ +function CheckInput(a, b) { + if (typeof a !== 'number' || typeof b !== 'number') { + throw new TypeError('Arguments must be numbers') + } +} + +/** + * GetEuclidGCD Euclidean algorithm to determine the GCD of two numbers + * @param {Number} a integer (may be negative) + * @param {Number} b integer (may be negative) + * @returns {Number} Greatest Common Divisor gcd(a, b) + */ +export function GetEuclidGCD(a, b) { + CheckInput(a, b) + a = Math.abs(a) + b = Math.abs(b) + while (b !== 0) { + const rem = a % b + a = b + b = rem + } + return a +} + +/** + * Recursive version of GetEuclidGCD + * @param {Number} a integer (may be negative) + * @param {Number} b integer (may be negative) + * @returns {Number} Greatest Common Divisor gcd(a, b) + */ +export function GetEuclidGCDRecursive(a, b) { + CheckInput(a, b) + a = Math.abs(a) + b = Math.abs(b) + if (b == 0) { + return a + } + return GetEuclidGCDRecursive(b, a % b) +} diff --git a/Maths/GridGet.js b/Maths/GridGet.js index 69ae502efd..44ae9a1931 100644 --- a/Maths/GridGet.js +++ b/Maths/GridGet.js @@ -40,17 +40,14 @@ */ const gridGetX = (columns, index) => { - while ((index + 1) > columns) { + while (index + 1 > columns) { index = index - columns } - return (index + 1) + return index + 1 } const gridGetY = (columns, index) => { - return (Math.floor(index / columns)) + 1 + return Math.floor(index / columns) + 1 } -console.log(`If a square array has 400 elements, then the value of x for the 27th element is ${gridGetX(Math.sqrt(400), 27)}`) -console.log(`If an array has 7 columns and 3 rows, then the value of x for the 11th element is ${gridGetX(7, 11)}`) -console.log(`If a square array has 400 elements, then the value of y for the 27th element is ${gridGetY(Math.sqrt(400), 27)}`) -console.log(`If an array has 7 columns and 3 rows, then the value of y for the 11th element is ${gridGetY(7, 11)}`) +export { gridGetX, gridGetY } diff --git a/Maths/HexagonalNumber.js b/Maths/HexagonalNumber.js new file mode 100644 index 0000000000..31fac7ea75 --- /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 0000000000..6ec8a0bab0 --- /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/IsDivisible.js b/Maths/IsDivisible.js new file mode 100644 index 0000000000..62c9f2e0a0 --- /dev/null +++ b/Maths/IsDivisible.js @@ -0,0 +1,15 @@ +// Checks if a number is divisible by another number. + +export const isDivisible = (num1, num2) => { + if (!Number.isFinite(num1) || !Number.isFinite(num2)) { + throw new TypeError('Expected a valid real number') + } + if (num2 === 0) { + return false + } + return num1 % num2 === 0 +} + +// isDivisible(10, 5) // returns true +// isDivisible(123498175, 5) // returns true +// isDivisible(99, 5) // returns false diff --git a/Maths/IsEven.js b/Maths/IsEven.js new file mode 100644 index 0000000000..d12a4dd335 --- /dev/null +++ b/Maths/IsEven.js @@ -0,0 +1,48 @@ +/* + * Even Number: https://simple.wikipedia.org/wiki/Even_number + * + * function to check if number is even + * return true if number is even + * else false + */ + +/** + * @function isEven + * @description - Checking if number is even using divisibility by 2 + * + * If number is divisible by 2 i.e remainder = 0, then it is even + * therefore, the function will return true + * + * If number is not divisible by 2 i.e remainder != 0, then it is not even i.e odd + * therefore, the function will return false + * @param {number} number + * @return {boolean} + */ +export const isEven = (number) => number % 2 === 0 + +/** + * @function isEvenBitwise + * @description - Checking if number is even using bitwise operator + * Bitwise AND (&) compares the bits of the 32 + * bit binary representations of the number and + * returns a number after comparing each bit: + * + * 0 & 0 -> 0 + * 0 & 1 -> 0 + * 1 & 0 -> 0 + * 1 & 1 -> 1 + * + * For odd numbers, the last binary bit will be 1 + * and for even numbers, the last binary bit will + * be 0. + * + * As the number is compared with one, all the + * other bits except the last will become 0. The + * last bit will be 0 for even numbers and 1 for + * odd numbers, which is checked with the use + * of the equality operator. + * @param {number} number + * @returns {boolean} + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND + */ +export const isEvenBitwise = (number) => (number & 1) === 0 diff --git a/Maths/IsOdd.js b/Maths/IsOdd.js new file mode 100644 index 0000000000..f68e2a6077 --- /dev/null +++ b/Maths/IsOdd.js @@ -0,0 +1,45 @@ +/* + * Odd Number: https://simple.wikipedia.org/wiki/Odd_number + * function to check if number is odd. + * return true if number is odd. + * else false + */ + +/** + * @function isOdd + * @description -> Checking if number is odd using not divisibility by 2 + * If number is not divisible by 2 i.e remainder = 1, then it is odd + * therefore, the function will return true + * + * If number is divisible by 2 i.e remainder != 1, then it is even + * therefore, the function will return false + * @param {number} number + * @returns {boolean} + */ +const isOdd = (number) => Boolean(number % 2) // 1 -> true, 0 -> false +/** + * @function isOddBitwise + * @description -> Checking if number is even using bitwise operator + * Bitwise AND (&) compares the bits of the 32 + * bit binary representations of the number and + * returns a number after comparing each bit: + * + * 0 & 0 -> 0 + * 0 & 1 -> 0 + * 1 & 0 -> 0 + * 1 & 1 -> 1 + * + * For every odd numbers, the last binary bit will be 1 + * and for even numbers, the last binary bit will be 0. + * + * As the number is compared with one, all the + * other bits except the last will become 0. The + * last bit will be 0 for even numbers and 1 for + * odd numbers. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND + * @param {number} number + * @returns {boolean} + */ +const isOddBitwise = (number) => Boolean(number & 1) // 1 -> true, 0 -> false + +export { isOdd, isOddBitwise } diff --git a/Maths/IsPronic.js b/Maths/IsPronic.js new file mode 100644 index 0000000000..878cdbc80e --- /dev/null +++ b/Maths/IsPronic.js @@ -0,0 +1,27 @@ +/* + * Author: Akshay Dubey (https://github.com/itsAkshayDubey) + * Pronic Number: https://en.wikipedia.org/wiki/Pronic_number + * function to check if number is pronic. + * return true if number is pronic. + * else false + */ + +/** + * @function isPronic + * @description -> Checking if number is pronic using product of two consecutive numbers + * If number is a product of two consecutive numbers, then it is pronic + * therefore, the function will return true + * + * If number is not a product of two consecutive numbers, then it is not pronic + * therefore, the function will return false + * @param {number} number + * @returns {boolean} + */ + +export const isPronic = (number) => { + if (number === 0) { + return true + } + const sqrt = Math.sqrt(number) + return sqrt % 1 !== 0 && Math.ceil(sqrt) * Math.floor(sqrt) === number +} diff --git a/Maths/IsSquareFree.js b/Maths/IsSquareFree.js new file mode 100644 index 0000000000..8235f91e83 --- /dev/null +++ b/Maths/IsSquareFree.js @@ -0,0 +1,23 @@ +/* + * Author: Akshay Dubey (https://github.com/itsAkshayDubey) + * Square free integer: https://en.wikipedia.org/wiki/Square-free_integer + * function to check if an integer has repeated prime factors. + * return false if the number as repeated prime factors. + * else true + */ + +/** + * @function isSquareFree + * @description -> Checking if number is square free using prime factorization + * @param {number} number + * @returns {boolean} true if the number has unique prime factors, otherwise false + */ + +import { PrimeFactors } from './PrimeFactors.js' +export const isSquareFree = (number) => { + const primeFactorsArray = PrimeFactors(number) + if (number <= 0) { + throw new Error('Number must be greater than zero.') + } + return primeFactorsArray.length === new Set(primeFactorsArray).size +} diff --git a/Maths/JugglerSequence.js b/Maths/JugglerSequence.js new file mode 100644 index 0000000000..7c56f3b6dd --- /dev/null +++ b/Maths/JugglerSequence.js @@ -0,0 +1,24 @@ +/* + * Juggler Sequence: https://en.wikipedia.org/wiki/Juggler_sequence + * function jugglerSequence + * Juggler Sequence is a series of integer number in which the first term starts with a positive integer number n + * and the remaining terms are generated from the immediate previous term using the recurrence relation + * Produce Juggler Sequence using number n as the first term of the sequence and store in an array + * Reference: https://www.geeksforgeeks.org/juggler-sequence/ + * jugglerSequence(3) // returns [3, 5, 11, 36, 6, 2, 1 ] + * jugglerSequence(9) // returns [9, 27, 140, 11, 36, 6, 2, 1] + * jugglerSequence(15) // returns [15, 58, 7, 18, 4, 2, 1] + */ + +function jugglerSequence(n) { + const sequence = [] + sequence.push(n) + // Calculate terms until last term is not 1 + while (n !== 1) { + n = Math.floor(n ** ((n % 2) + 0.5)) + sequence.push(n) + } + return sequence +} + +export { jugglerSequence } diff --git a/Maths/LeapYear.js b/Maths/LeapYear.js new file mode 100644 index 0000000000..fb53f12001 --- /dev/null +++ b/Maths/LeapYear.js @@ -0,0 +1,18 @@ +/** + * isLeapYear :: Number -> Boolean + * + * Check if a year is a leap year or not. A leap year is a year which has 366 days. + * For the extra +1 day the February month contains 29 days instead of 28 days. + * + * The logic behind the leap year is- + * 1. If the year is divisible by 400 then it is a leap year. + * 2. If it is not divisible by 400 but divisible by 100 then it is not a leap year. + * 3. If the year is not divisible by both 400 and 100 but divisible by 4 then a leap year. + * 4. Other cases except the describing ones are not a leap year. + * + * @param {number} year + * @returns {boolean} true if this is a leap year, false otherwise. + */ +export const isLeapYear = (year) => { + return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0) +} diff --git a/Maths/LinearSieve.js b/Maths/LinearSieve.js new file mode 100644 index 0000000000..8092c9ebe4 --- /dev/null +++ b/Maths/LinearSieve.js @@ -0,0 +1,24 @@ +const LinearSieve = (n) => { + /* + * Calculates prime numbers till a number n + * Time Complexity: O(n) + * Explanation: https://cp-algorithms.com/algebra/prime-sieve-linear.html + * :param n: Number up to which to calculate primes + * :return: A list containing only primes + */ + const isnPrime = new Array(n + 1) + isnPrime[0] = isnPrime[1] = true + const primes = [] + for (let i = 2; i <= n; i++) { + if (!isnPrime[i]) primes.push(i) + for (const p of primes) { + const k = i * p + if (k > n) break + isnPrime[k] = true + if (i % p === 0) break + } + } + return primes +} + +export { LinearSieve } diff --git a/Maths/LiouvilleFunction.js b/Maths/LiouvilleFunction.js new file mode 100644 index 0000000000..f13916d55d --- /dev/null +++ b/Maths/LiouvilleFunction.js @@ -0,0 +1,25 @@ +/* + * Author: Akshay Dubey (https://github.com/itsAkshayDubey) + * Liouville Function: https://en.wikipedia.org/wiki/Liouville_function + * For any positive integer n, define ฮป(n) as the sum of the primitive nth roots of unity. + * It has values in {โˆ’1, 1} depending on the factorization of n into prime factors: + * ฮป(n) = +1 if n positive integer with an even number of prime factors. + * ฮป(n) = โˆ’1 if n positive integer with an odd number of prime factors. + */ + +/** + * @function liouvilleFunction + * @description -> This method returns ฮป(n) of given number n + * returns 1 when number has even number of prime factors + * returns -1 when number has odd number of prime factors + * @param {Integer} number + * @returns {Integer} 1|-1 + */ + +import { PrimeFactors } from './PrimeFactors.js' +export const liouvilleFunction = (number) => { + if (number <= 0) { + throw new Error('Number must be greater than zero.') + } + return PrimeFactors(number).length % 2 === 0 ? 1 : -1 +} diff --git a/Maths/LucasSeries.js b/Maths/LucasSeries.js new file mode 100644 index 0000000000..e645872e2d --- /dev/null +++ b/Maths/LucasSeries.js @@ -0,0 +1,34 @@ +/* + Program to get the Nth Lucas Number + Article on Lucas Number: https://en.wikipedia.org/wiki/Lucas_number + Examples: + > loopLucas(1) + 1 + > loopLucas(20) + 15127 + > loopLucas(100) + 792070839848372100000 +*/ + +/** + * @param {Number} index The position of the number you want to get from the Lucas Series + */ +function lucas(index) { + // index can't be negative + if (index < 0) throw new TypeError('Index cannot be Negative') + + // index can't be a decimal + if (Math.floor(index) !== index) + throw new TypeError('Index cannot be a Decimal') + + let a = 2 + let b = 1 + for (let i = 0; i < index; i++) { + const temp = a + b + a = b + b = temp + } + return a +} + +export { lucas } diff --git a/Maths/Mandelbrot.js b/Maths/Mandelbrot.js new file mode 100644 index 0000000000..9b9f311c9b --- /dev/null +++ b/Maths/Mandelbrot.js @@ -0,0 +1,157 @@ +/** + * Method to generate the image of the Mandelbrot set. + * + * Two types of coordinates are used: image-coordinates that refer to the pixels and figure-coordinates that refer to + * the complex numbers inside and outside the Mandelbrot set. The figure-coordinates in the arguments of this method + * determine which section of the Mandelbrot set is viewed. The main area of the Mandelbrot set is roughly between + * "-1.5 < x < 0.5" and "-1 < y < 1" in the figure-coordinates. + * + * The Mandelbrot set is the set of complex numbers "c" for which the series "z_(n+1) = z_n * z_n + c" does not diverge, + * i.e. remains bounded. Thus, a complex number "c" is a member of the Mandelbrot set if, when starting with "z_0 = 0" + * and applying the iteration repeatedly, the absolute value of "z_n" remains bounded for all "n > 0". Complex numbers + * can be written as "a + b*i": "a" is the real component, usually drawn on the x-axis, and "b*i" is the imaginary + * component, usually drawn on the y-axis. Most visualizations of the Mandelbrot set use a color-coding to indicate + * after how many steps in the series the numbers outside the set cross the divergence threshold. Images of the + * Mandelbrot set exhibit an elaborate and infinitely complicated boundary that reveals progressively ever-finer + * recursive detail at increasing magnifications, making the boundary of the Mandelbrot set a fractal curve. + * + * (description adapted from https://en.wikipedia.org/wiki/Mandelbrot_set) + * @see https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set + * + * @param {number} imageWidth The width of the rendered image. + * @param {number} imageHeight The height of the rendered image. + * @param {number} figureCenterX The x-coordinate of the center of the figure. + * @param {number} figureCenterY The y-coordinate of the center of the figure. + * @param {number} figureWidth The width of the figure. + * @param {number} maxStep Maximum number of steps to check for divergent behavior. + * @param {boolean} useDistanceColorCoding Render in color or black and white. + * @return {object} The RGB-data of the rendered Mandelbrot set. + */ +export function getRGBData( + imageWidth = 800, + imageHeight = 600, + figureCenterX = -0.6, + figureCenterY = 0, + figureWidth = 3.2, + maxStep = 50, + useDistanceColorCoding = true +) { + if (imageWidth <= 0) { + throw new Error('imageWidth should be greater than zero') + } + + if (imageHeight <= 0) { + throw new Error('imageHeight should be greater than zero') + } + + if (maxStep <= 0) { + throw new Error('maxStep should be greater than zero') + } + + const rgbData = [] + const figureHeight = (figureWidth / imageWidth) * imageHeight + + // loop through the image-coordinates + for (let imageX = 0; imageX < imageWidth; imageX++) { + rgbData[imageX] = [] + for (let imageY = 0; imageY < imageHeight; imageY++) { + // determine the figure-coordinates based on the image-coordinates + const figureX = figureCenterX + (imageX / imageWidth - 0.5) * figureWidth + const figureY = + figureCenterY + (imageY / imageHeight - 0.5) * figureHeight + + const distance = getDistance(figureX, figureY, maxStep) + + // color the corresponding pixel based on the selected coloring-function + rgbData[imageX][imageY] = useDistanceColorCoding + ? colorCodedColorMap(distance) + : blackAndWhiteColorMap(distance) + } + } + + return rgbData +} + +/** + * Black and white color-coding that ignores the relative distance. + * + * The Mandelbrot set is black, everything else is white. + * + * @param {number} distance Distance until divergence threshold + * @return {object} The RGB-value corresponding to the distance. + */ +function blackAndWhiteColorMap(distance) { + return distance >= 1 ? [0, 0, 0] : [255, 255, 255] +} + +/** + * Color-coding taking the relative distance into account. + * + * The Mandelbrot set is black. + * + * @param {number} distance Distance until divergence threshold + * @return {object} The RGB-value corresponding to the distance. + */ +function colorCodedColorMap(distance) { + if (distance >= 1) { + return [0, 0, 0] + } else { + // simplified transformation of HSV to RGB + // distance determines hue + const hue = 360 * distance + const saturation = 1 + const val = 255 + const hi = Math.floor(hue / 60) % 6 + const f = hue / 60 - Math.floor(hue / 60) + + const v = val + const p = 0 + const q = Math.floor(val * (1 - f * saturation)) + const t = Math.floor(val * (1 - (1 - f) * saturation)) + + switch (hi) { + case 0: + return [v, t, p] + case 1: + return [q, v, p] + case 2: + return [p, v, t] + case 3: + return [p, q, v] + case 4: + return [t, p, v] + default: + return [v, p, q] + } + } +} + +/** + * Return the relative distance (ratio of steps taken to maxStep) after which the complex number + * constituted by this x-y-pair diverges. + * + * Members of the Mandelbrot set do not diverge so their distance is 1. + * + * @param {number} figureX The x-coordinate within the figure. + * @param {number} figureY The y-coordinate within the figure. + * @param {number} maxStep Maximum number of steps to check for divergent behavior. + * @return {number} The relative distance as the ratio of steps taken to maxStep. + */ +function getDistance(figureX, figureY, maxStep) { + let a = figureX + let b = figureY + let currentStep = 0 + for (let step = 0; step < maxStep; step++) { + currentStep = step + const aNew = a * a - b * b + figureX + b = 2 * a * b + figureY + a = aNew + + // divergence happens for all complex number with an absolute value + // greater than 4 (= divergence threshold) + if (a * a + b * b > 4) { + break + } + } + return currentStep / (maxStep - 1) +} diff --git a/Maths/MatrixExponentiationRecursive.js b/Maths/MatrixExponentiationRecursive.js new file mode 100644 index 0000000000..e86ceebe29 --- /dev/null +++ b/Maths/MatrixExponentiationRecursive.js @@ -0,0 +1,80 @@ +/* + Source: + https://en.wikipedia.org/wiki/Exponentiation_by_squaring + + Complexity: + O(d^3 log n) + where: d is the dimension of the square matrix + n is the power the matrix is raised to +*/ + +const Identity = (n) => { + // Input: n: int + // Output: res: Identity matrix of size n x n + // Complexity: O(n^2) + const res = [] + for (let i = 0; i < n; i++) { + res[i] = [] + for (let j = 0; j < n; j++) { + res[i][j] = i === j ? 1 : 0 + } + } + return res +} + +const MatMult = (matrixA, matrixB) => { + // Input: matrixA: 2D Array of Numbers of size n x n + // matrixB: 2D Array of Numbers of size n x n + // Output: matrixA x matrixB: 2D Array of Numbers of size n x n + // Complexity: O(n^3) + const n = matrixA.length + const matrixC = [] + for (let i = 0; i < n; i++) { + matrixC[i] = [] + for (let j = 0; j < n; j++) { + matrixC[i][j] = 0 + } + } + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + for (let k = 0; k < n; k++) { + matrixC[i][j] += matrixA[i][k] * matrixB[k][j] + } + } + } + return matrixC +} + +export const MatrixExponentiationRecursive = (mat, m) => { + // Input: mat: 2D Array of Numbers of size n x n + // Output: mat^n: 2D Array of Numbers of size n x n + // Complexity: O(n^3 log m) + if (m === 0) { + // return identity matrix of size n x n + return Identity(mat.length) + } else if (m % 2 === 1) { + // tmp = mat ^ m-1 + const tmp = MatrixExponentiationRecursive(mat, m - 1) + /// return tmp * mat = (mat ^ m-1) * mat = mat ^ m + return MatMult(tmp, mat) + } else { + // tmp = mat ^ m/2 + const tmp = MatrixExponentiationRecursive(mat, m >> 1) + // return tmp * tmp = (mat ^ m/2) ^ 2 = mat ^ m + return MatMult(tmp, tmp) + } +} + +// const mat = [[1, 0, 2], [2, 1, 0], [0, 2, 1]] + +// // mat ^ 0 = [ [ 1, 0, 0 ], [ 0, 1, 0 ], [ 0, 0, 1 ] ] +// MatrixExponentiationRecursive(mat, 0) + +// // mat ^ 1 = [ [ 1, 0, 2 ], [ 2, 1, 0 ], [ 0, 2, 1 ] ] +// MatrixExponentiationRecursive(mat, 1) + +// // mat ^ 2 = [ [ 1, 4, 4 ], [ 4, 1, 4 ], [ 4, 4, 1 ] ] +// MatrixExponentiationRecursive(mat, 2) + +// // mat ^ 5 = [ [ 1, 4, 4 ], [ 4, 1, 4 ], [ 4, 4, 1 ] ] +// MatrixExponentiationRecursive(mat, 5) diff --git a/Maths/MatrixMultiplication.js b/Maths/MatrixMultiplication.js new file mode 100644 index 0000000000..b6626d0190 --- /dev/null +++ b/Maths/MatrixMultiplication.js @@ -0,0 +1,95 @@ +// Wikipedia URL for General Matrix Multiplication Concepts: https://en.wikipedia.org/wiki/Matrix_multiplication + +// This algorithm has multiple functions that ultimately check if the inputs are actually matrices and if two Matrices (that can be different sizes) can be multiplied together. +// matrices that are of the same size [2x2]x[2x2], and the second is the multiplication of two matrices that are not the same size [2x3]x[3x2]. + +// MatrixCheck tests to see if all of the rows of the matrix inputted have similar size columns +const matrixCheck = (matrix) => { + let columnNumb + for (let index = 0; index < matrix.length; index++) { + if (index === 0) { + columnNumb = matrix[index].length + } else if (matrix[index].length !== columnNumb) { + // The columns in this array are not equal + } else { + return columnNumb + } + } +} + +// tests to see if the matrices have a like side, i.e. the row length on the first matrix matches the column length on the second matrix, or vice versa. +const twoMatricesCheck = (first, second) => { + const [firstRowLength, secondRowLength, firstColLength, secondColLength] = [ + first.length, + second.length, + matrixCheck(first), + matrixCheck(second) + ] + + // These matrices do not have a common side + return ( + firstRowLength === secondColLength && secondRowLength === firstColLength + ) +} + +// returns an empty array that has the same number of rows as the left matrix being multiplied. +// Uses Array.prototype.map() to loop over the first (or left) matrix and returns an empty array on each iteration. +const initiateEmptyArray = (first, second) => { + if (twoMatricesCheck(first, second)) { + const emptyArray = first.map(() => { + return [''] + }) + return emptyArray + } else { + return false + } +} + +// Finally, `matrixMult` uses `Array.prototype.push()`, multiple layers of nested `for` loops, the addition assignment `+=` operator and multiplication operator `*` to perform the dot product between two matrices of differing sizes. +// Dot product, takes the row of the first matrix and multiplies it by the column of the second matrix, the `twoMatricesCheck` tested to see if they were the same size already. +// The dot product for each iteration is then saved to its respective index into `multMatrix`. +export const matrixMult = (firstArray, secondArray) => { + const multMatrix = initiateEmptyArray(firstArray, secondArray) + for (let rm = 0; rm < firstArray.length; rm++) { + const rowMult = [] + for (let col = 0; col < firstArray[0].length; col++) { + rowMult.push(firstArray[rm][col]) + } + for (let cm = 0; cm < firstArray.length; cm++) { + const colMult = [] + for (let row = 0; row < secondArray.length; row++) { + colMult.push(secondArray[row][cm]) + } + let newNumb = 0 + for (let index = 0; index < rowMult.length; index++) { + newNumb += rowMult[index] * colMult[index] + } + multMatrix[rm][cm] = newNumb + } + } + return multMatrix +} + +// const firstMatrix = [ +// [1, 2], +// [3, 4] +// ] + +// const secondMatrix = [ +// [5, 6], +// [7, 8] +// ] + +// matrixMult(firstMatrix, secondMatrix) // [ [ 19, 22 ], [ 43, 50 ] ] + +// const thirdMatrix = [ +// [-1, 4, 1], +// [7, -6, 2] +// ] +// const fourthMatrix = [ +// [2, -2], +// [5, 3], +// [3, 2] +// ] + +// matrixMult(thirdMatrix, fourthMatrix) // [ [ 21, 16 ], [ -10, -28 ] ] diff --git a/Maths/MeanAbsoluteDeviation.js b/Maths/MeanAbsoluteDeviation.js new file mode 100644 index 0000000000..55585592b6 --- /dev/null +++ b/Maths/MeanAbsoluteDeviation.js @@ -0,0 +1,21 @@ +import { mean } from './AverageMean.js' +/** + *@function meanAbsoluteDeviation + *@description Calculates the mean absolute deviation of list of numbers + * @param {Integer} data + * @returns meanAbsoluteDeviation([2,34,5,0,-2]) = 10.480 + * @url https://en.wikipedia.org/wiki/Average_absolute_deviation + */ +function meanAbsoluteDeviation(data) { + if (!Array.isArray(data)) { + throw new TypeError('Invalid Input') + } + let absoluteSum = 0 + const meanValue = mean(data) + for (const dataPoint of data) { + absoluteSum += Math.abs(dataPoint - meanValue) + } + return absoluteSum / data.length +} + +export { meanAbsoluteDeviation } diff --git a/Maths/MeanSquareError.js b/Maths/MeanSquareError.js new file mode 100644 index 0000000000..edcd3e6993 --- /dev/null +++ b/Maths/MeanSquareError.js @@ -0,0 +1,21 @@ +// Wikipedia: https://en.wikipedia.org/wiki/Mean_squared_error + +const meanSquaredError = (predicted, expected) => { + if (!Array.isArray(predicted) || !Array.isArray(expected)) { + throw new TypeError('Argument must be an Array') + } + + if (predicted.length !== expected.length) { + throw new TypeError('The two lists must be of equal length') + } + + let err = 0 + + for (let i = 0; i < expected.length; i++) { + err += (expected[i] - predicted[i]) ** 2 + } + + return err / expected.length +} + +export { meanSquaredError } diff --git a/Maths/MidpointIntegration.js b/Maths/MidpointIntegration.js new file mode 100644 index 0000000000..08bfeba954 --- /dev/null +++ b/Maths/MidpointIntegration.js @@ -0,0 +1,63 @@ +/** + * + * @title Midpoint rule for definite integral evaluation + * @author [ggkogkou](https://github.com/ggkogkou) + * @brief Calculate definite integrals with midpoint method + * + * @details The idea is to split the interval in a number N of intervals and use as interpolation points the xi + * for which it applies that xi = x0 + i*h, where h is a step defined as h = (b-a)/N where a and b are the + * first and last points of the interval of the integration [a, b]. + * + * We create a table of the xi and their corresponding f(xi) values and we evaluate the integral by the formula: + * I = h * {f(x0+h/2) + f(x1+h/2) + ... + f(xN-1+h/2)} + * + * N must be > 0 and a= 2') + } // check if N > 0 + if (a > b) { + throw Error('a must be less or equal than b') + } // Check if a < b + if (a === b) return 0 // If a === b integral is zero + + // Calculate the step h + const h = (b - a) / N + + // Find interpolation points + let xi = a // initialize xi = x0 + const pointsArray = [] + + // Find the sum {f(x0+h/2) + f(x1+h/2) + ... + f(xN-1+h/2)} + let temp + for (let i = 0; i < N; i++) { + temp = func(xi + h / 2) + pointsArray.push(temp) + xi += h + } + + // Calculate the integral + let result = h + temp = pointsArray.reduce((acc, currValue) => acc + currValue, 0) + + result *= temp + + if (Number.isNaN(result)) { + throw Error( + 'Result is NaN. The input interval does not belong to the functions domain' + ) + } + + return result +} + +export { integralEvaluation } diff --git a/Maths/MobiusFunction.js b/Maths/MobiusFunction.js new file mode 100644 index 0000000000..bd268b8bbd --- /dev/null +++ b/Maths/MobiusFunction.js @@ -0,0 +1,33 @@ +/* + * Author: Akshay Dubey (https://github.com/itsAkshayDubey) + * Mobius Function: https://en.wikipedia.org/wiki/M%C3%B6bius_function + * For any positive integer n, define ฮผ(n) as the sum of the primitive nth roots of unity. + * It has values in {โˆ’1, 0, 1} depending on the factorization of n into prime factors: + * ฮผ(n) = +1 if n is a square-free positive integer with an even number of prime factors. + * ฮผ(n) = โˆ’1 if n is a square-free positive integer with an odd number of prime factors. + * ฮผ(n) = 0 if n has a squared prime factor. + */ + +/** + * @function mobiusFunction + * @description -> This method returns ฮผ(n) of given number n + * returns 1 when number is less than or equals 1 + * or number has even number of prime factors + * returns 0 when number has repeated prime factor + * returns -1 when number has odd number of prime factors + * @param {Integer} number + * @returns {Integer} + */ + +import { PrimeFactors } from './PrimeFactors.js' +export const mobiusFunction = (number) => { + const primeFactorsArray = PrimeFactors(number) + if (number <= 0) { + throw new Error('Number must be greater than zero.') + } + return primeFactorsArray.length !== new Set(primeFactorsArray).size + ? 0 + : primeFactorsArray.length % 2 === 0 + ? 1 + : -1 +} diff --git a/Maths/ModularArithmetic.js b/Maths/ModularArithmetic.js new file mode 100644 index 0000000000..e0570d8b3c --- /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/ModularBinaryExponentiationRecursive.js b/Maths/ModularBinaryExponentiationRecursive.js new file mode 100644 index 0000000000..54665a3142 --- /dev/null +++ b/Maths/ModularBinaryExponentiationRecursive.js @@ -0,0 +1,22 @@ +/* + Modified from: + https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exp_mod.py + + Explanation: + https://en.wikipedia.org/wiki/Exponentiation_by_squaring +*/ + +const modularBinaryExponentiation = (a, n, m) => { + // input: a: int, n: int, m: int + // returns: (a^n) % m: int + if (n === 0) { + return 1 + } else if (n % 2 === 1) { + return (modularBinaryExponentiation(a, n - 1, m) * a) % m + } else { + const b = modularBinaryExponentiation(a, n / 2, m) + return (b * b) % m + } +} + +export { modularBinaryExponentiation } diff --git a/Maths/NumberOfDigits.js b/Maths/NumberOfDigits.js new file mode 100644 index 0000000000..8a3e2b3c6a --- /dev/null +++ b/Maths/NumberOfDigits.js @@ -0,0 +1,23 @@ +/** + * + * Author: dephraiim + * License: GPL-3.0 or later + * + * Returns the number of digits of a given integer + * + */ + +const numberOfDigit = (n) => Math.abs(n).toString().length + +/** + * Returns the number of digits of a given integer. + * + * @param {number} n - The integer for which to count digits. + * @returns {number} The number of digits in the integer. + * @see https://math.stackexchange.com/questions/2145480/how-does-the-logarithm-returns-the-number-of-digits-of-a-number + * @author dev-madhurendra + */ +const numberOfDigitsUsingLog = (n) => + n === 0 ? 1 : Math.floor(Math.log10(Math.abs(n))) + 1 + +export { numberOfDigit, numberOfDigitsUsingLog } diff --git a/Maths/Palindrome.js b/Maths/Palindrome.js index 4abc8d9976..094faf9606 100644 --- a/Maths/Palindrome.js +++ b/Maths/Palindrome.js @@ -14,7 +14,7 @@ * @complexity: O(n) */ -function PalindromeRecursive (string) { +const PalindromeRecursive = (string) => { // Base case if (string.length < 2) return true @@ -26,7 +26,7 @@ function PalindromeRecursive (string) { return PalindromeRecursive(string.slice(1, string.length - 1)) } -function PalindromeIterative (string) { +const PalindromeIterative = (string) => { const _string = string .toLowerCase() .replace(/ /g, '') @@ -45,7 +45,18 @@ function PalindromeIterative (string) { return true } -// testing +/** + * + * Checks if a string is a palindrome. + * @author dev-madhurendra + * @param {string} str - The string to check. + * @returns {boolean} True if the string is a palindrome, false otherwise. + * + * @example + * const isPalindrome = checkPalindrome('racecar'); // Returns true + * const isNotPalindrome = checkPalindrome('hello'); // Returns false + */ +const checkPalindrome = (str) => + str.replace(/\s/g, '') === str.replace(/\s/g, '').split('').reverse().join('') -console.log(PalindromeRecursive('Javascript Community')) -console.log(PalindromeIterative('mom')) +export { PalindromeIterative, PalindromeRecursive, checkPalindrome } diff --git a/Maths/ParityOutlier.js b/Maths/ParityOutlier.js new file mode 100644 index 0000000000..8afb427e0c --- /dev/null +++ b/Maths/ParityOutlier.js @@ -0,0 +1,36 @@ +/** + * @author mrmagic2020 + * @description The function will find the parity outlier from an array of integers. + * @see https://en.wikipedia.org/wiki/Parity_(mathematics) + * @param {number[]} integers - An array of integers. + * @returns {number} - The parity outlier. + * @example parityOutlier([1, 3, 5, 8, 9]) = 8 + */ +const parityOutlier = (integers) => { + let oddsCount = 0 // define counter for odd number(s) + let evensCount = 0 // define counter for even number(s) + let odd, even + + for (const e of integers) { + if (!Number.isInteger(e)) { + // detect non-integer elements + return null + } + if (e % 2 === 0) { + // an even number + even = e + evensCount++ + } else { + // an odd number + odd = e + oddsCount++ + } + } + + if (oddsCount === 0 || evensCount === 0) return null // array has only odd/even number(s) + if (oddsCount > 1 && evensCount > 1) return null // array has more than one even and odd number + + return oddsCount === 1 ? odd : even +} + +export { parityOutlier } diff --git a/Maths/PascalTriangle.js b/Maths/PascalTriangle.js index eaf3f2a9e0..71d782cd61 100644 --- a/Maths/PascalTriangle.js +++ b/Maths/PascalTriangle.js @@ -1,14 +1,22 @@ -const numRows = 5 +const addRow = (triangle) => { + const previous = triangle[triangle.length - 1] + const newRow = [1] + for (let i = 0; i < previous.length - 1; i++) { + const current = previous[i] + const next = previous[i + 1] + newRow.push(current + next) + } + newRow.push(1) + return triangle.push(newRow) +} -var generate = function (numRows) { +const generate = (numRows) => { const triangle = [[1], [1, 1]] if (numRows === 0) { return [] } else if (numRows === 1) { return [[1]] - } else if (numRows === 2) { - return [[1], [1, 1]] } else { for (let i = 2; i < numRows; i++) { addRow(triangle) @@ -16,16 +24,5 @@ var generate = function (numRows) { } return triangle } -var addRow = function (triangle) { - const previous = triangle[triangle.length - 1] - const newRow = [1] - for (let i = 0; i < previous.length - 1; i++) { - const current = previous[i] - const next = previous[i + 1] - newRow.push(current + next) - } - newRow.push(1) - return triangle.push(newRow) -} -generate(numRows) +export { generate } diff --git a/Maths/PerfectCube.js b/Maths/PerfectCube.js new file mode 100644 index 0000000000..202cddafa5 --- /dev/null +++ b/Maths/PerfectCube.js @@ -0,0 +1,11 @@ +/** + * Author: dephraiim + * License: GPL-3.0 or later + * + * This uses `round` instead of `floor` or `trunc`, to guard against potential `cbrt` accuracy errors + */ + +const perfectCube = (num) => + Number.isFinite(num) && Math.round(Math.cbrt(num)) ** 3 === num + +export { perfectCube } diff --git a/Maths/PerfectNumber.js b/Maths/PerfectNumber.js new file mode 100644 index 0000000000..ce8adefb6c --- /dev/null +++ b/Maths/PerfectNumber.js @@ -0,0 +1,30 @@ +/** + * Author: dephraiim + * License: GPL-3.0 or later + * + * == Perfect Number == + * In number theory, a perfect number is a positive integer that is equal to the sum of + * its positive divisors(factors), excluding the number itself. + * For example: 6 ==> divisors[1, 2, 3, 6] + * Excluding 6, the sum(divisors) is 1 + 2 + 3 = 6 + * So, 6 is a Perfect Number + * Other examples of Perfect Numbers: 28, 486, ... + * + * More on Perfect Number: + * https://en.wikipedia.org/wiki/Perfect_number + * + */ + +const factorsExcludingNumber = (n) => { + return [...Array(n).keys()].filter((num) => n % num === 0) +} + +const perfectNumber = (n) => { + const factorSum = factorsExcludingNumber(n).reduce((num, initialValue) => { + return num + initialValue + }, 0) + + return factorSum === n +} + +export { perfectNumber } diff --git a/Maths/PerfectSquare.js b/Maths/PerfectSquare.js new file mode 100644 index 0000000000..4d26323939 --- /dev/null +++ b/Maths/PerfectSquare.js @@ -0,0 +1,11 @@ +/** + * Author: dephraiim + * License: GPL-3.0 or later + * + * This uses `round` instead of `floor` or `trunc`, to guard against potential `sqrt` accuracy errors + */ + +const perfectSquare = (num) => + Number.isFinite(num) && Math.round(Math.sqrt(num)) ** 2 === num + +export { perfectSquare } diff --git a/Maths/PermutationAndCombination.js b/Maths/PermutationAndCombination.js new file mode 100644 index 0000000000..ba99888ce0 --- /dev/null +++ b/Maths/PermutationAndCombination.js @@ -0,0 +1,50 @@ +/** + * @details Calculates the number of permutations and combinations. + * @external_link (Permutation And Combinations)[https://www.geeksforgeeks.org/permutation-and-combination/] + */ + +/** + * @brief Calculates the factorial of the given number. + * @param num: integer + * @details Factorial of n = n * (n - 1) * (n - 2) * ... * 1 + * @returns integer: Factorial of the number. + NaN: if negative number is provided. + */ +const factorial = (n) => { + if (n >= 0) { + if (n === 0) { + return 1 + } else { + return n * factorial(n - 1) + } + } else { + return NaN + } +} + +/** + * @brief Calculates the number of Permutations from the given data. + * @param + * n: integer -> number of items. + * r: integer -> number of times n is taken. + * @returns integer: The number of permutations. + NaN: if negative number is provided. + */ +const permutation = (n, r) => { + return factorial(n) / factorial(n - r) +} + +/** + * @brief Calculates the number of Combinations from the given data. + * @param + * n -> number of items. + * r -> number of times n is taken. + * @returns integer: The number of combinations. + NaN: if negative number is provided. + */ +const combination = (n, r) => { + return factorial(n) / (factorial(r) * factorial(n - r)) +} + +// Exports the functions to be used in other files. +export { factorial, permutation, combination } diff --git a/Maths/PiApproximationMonteCarlo.js b/Maths/PiApproximationMonteCarlo.js index a4b3d8b81f..882818601d 100644 --- a/Maths/PiApproximationMonteCarlo.js +++ b/Maths/PiApproximationMonteCarlo.js @@ -1,7 +1,7 @@ // Wikipedia: https://en.wikipedia.org/wiki/Monte_Carlo_method -// Video Explaination: https://www.youtube.com/watch?v=ELetCV_wX_c +// Video Explanation: https://www.youtube.com/watch?v=ELetCV_wX_c -function piEstimation (iterations = 100000) { +const piEstimation = (iterations = 100000) => { let circleCounter = 0 for (let i = 0; i < iterations; i++) { @@ -13,13 +13,9 @@ function piEstimation (iterations = 100000) { if (radius < 1) circleCounter += 1 } - // fomula for pi = (ratio of number inside circle and total iteration) x 4 + // formula for pi = (ratio of number inside circle and total iteration) x 4 const pi = (circleCounter / iterations) * 4 return pi } -function main () { - console.log(piEstimation()) -} - -main() +export { piEstimation } diff --git a/Maths/Polynomial.js b/Maths/Polynomial.js new file mode 100644 index 0000000000..00f96761c6 --- /dev/null +++ b/Maths/Polynomial.js @@ -0,0 +1,59 @@ +/** + * Polynomials are algebraic expressions consisting of two or more algebraic terms. + * Terms of a polynomial are: + * 1. Coefficients e.g. 5, 4 in 5x^0, 4x^3 respectively + * 2. Variables e.g. y in 3y^2 + * 3. Exponents e.g. 5 in y^5 + * + * Class Polynomial constructs the polynomial using Array as an argument. + * The members of array are coefficients and their indexes as exponents. + */ +class Polynomial { + constructor(array) { + this.coefficientArray = array // array of coefficients + this.polynomial = '' // in terms of x e.g. (2x) + (1) + this.construct() + } + + /** + * Function to construct the polynomial in terms of x using the coefficientArray + */ + construct() { + this.polynomial = this.coefficientArray + .map((coefficient, exponent) => { + if (coefficient === 0) { + return '0' + } + if (exponent === 0) { + return `(${coefficient})` + } else if (exponent === 1) { + return `(${coefficient}x)` + } else { + return `(${coefficient}x^${exponent})` + } + }) + .filter((x) => x !== '0') + .reverse() + .join(' + ') + } + + /** + * Function to display polynomial in terms of x + * @returns {String} of polynomial representation in terms of x + */ + display() { + return this.polynomial + } + + /** + * Function to calculate the value of the polynomial by substituting variable x + * @param {Number} value + */ + evaluate(value) { + return this.coefficientArray.reduce((result, coefficient, exponent) => { + return result + coefficient * Math.pow(value, exponent) + }, 0) + } +} + +export { Polynomial } diff --git a/Maths/Pow.js b/Maths/Pow.js new file mode 100644 index 0000000000..992c68a19d --- /dev/null +++ b/Maths/Pow.js @@ -0,0 +1,65 @@ +/** + * @function powLinear + * @description - The powLinear function is a power function with Linear O(n) complexity + * @param {number} base + * @param {number} exponent + * @returns {number} + * @example - powLinear(2, 2) => 4 --> 2 * 2 + * @example - powLinear(3, 3) => 27 --> 3 * 3 * 3 + */ +const powLinear = (base, exponent) => { + if (exponent < 0) { + base = 1 / base + exponent = -exponent + } + + let result = 1 + + while (exponent--) { + // Break the execution while the exponent will 0 + result *= base + } + + return result +} + +/** + * @function powFaster + * @description - The powFaster function is a power function with O(logN) complexity + * @param {number} base + * @param {number} exponent + * @returns {number} + * @example - powFaster(2, 2) => 4 --> 2 * 2 + * @example - powFaster(3, 3) => 27 --> 3 * 3 * 3 + */ +const powFaster = (base, exponent) => { + if (exponent < 2) { + // explanation below - 1 + return base && ([1, base][exponent] || powFaster(1 / base, -exponent)) + } + + if (exponent & 1) { + // if the existing exponent is odd + return base * powFaster(base * base, exponent >> 1) // explanation below - 2 + } + + return powFaster(base * base, exponent / 2) +} + +/** + * 1 - Magic of short circuit evaluation (&&, ||) + * if the base is 0 then it returns 0 cause 0 is falsy + * if the base is not 0 then it's must be truthy. after that, it will be executed the right portion of the && (AND) operator + * Now it checks the exponent by the help array index, is it 0 or 1. + * if the exponent is not 0 or 1 it's definitely less than 0, and a negative number is not a valid index number so it returns "undefined" + * if the expression is undefined mean -> falsy, the || (OR) operator evaluates the right portion that is a recursive function. + */ + +/** + * 2 - Play with right shift bitwise operator (>>) + * right shift with any odd numbers it returns the floor number instead of float. + * E.g. if the number is 5, after right shifting with 1 it's will give us 2, not 2.5 + * cause the right shift formula is --> x >> y = |x| / 2^y + */ + +export { powLinear, powFaster } diff --git a/Maths/PowLogarithmic.js b/Maths/PowLogarithmic.js new file mode 100644 index 0000000000..ff57d14ef7 --- /dev/null +++ b/Maths/PowLogarithmic.js @@ -0,0 +1,26 @@ +import { isEven } from './IsEven' + +/** + * This algorithm is divide the n by 2 every time and pass this to recursive call to find the result of smaller result. + * why? Because + * x^n => [if n is even] x^(n / 2) * x^(n / 2) (example : 7^4 => 7^2 * 7^2) + * [if n is odd] x^(n / 2) * x^(n / 2) * x (example : 7^5 => 7^2 * 7^2 * 7) + * and repeat the above step until we reach to the base case. + * + * @function PowLogarithmic + * @description Given two integers x and n, return x^n in logarithmic complexity. + * @param {Integer} x - The input integer + * @param {Integer} n - The input integer + * @return {Integer} - Returns x^n. + * @see [Pow-Logarithmic](https://www.geeksforgeeks.org/write-a-c-program-to-calculate-powxn/) + */ +const powLogarithmic = (x, n) => { + if (n === 0) return 1 + const result = powLogarithmic(x, Math.floor(n / 2)) + if (isEven(n)) { + return result * result + } + return result * result * x +} + +export { powLogarithmic } diff --git a/Maths/PrimeCheck.js b/Maths/PrimeCheck.js new file mode 100644 index 0000000000..1d0b18ed43 --- /dev/null +++ b/Maths/PrimeCheck.js @@ -0,0 +1,25 @@ +/* + Modified from: + https://github.com/TheAlgorithms/Python/blob/master/maths/prime_check.py + + Complexity: + O(sqrt(n)) +*/ + +const PrimeCheck = (n) => { + // input: n: int + // output: boolean + if (n === 1) return false + if (n === 0) return false + if (n === 2) return true + if (n % 2 === 0) return false + + for (let i = 3; i * i <= n; i += 2) { + if (n % i === 0) { + return false + } + } + return true +} + +export { PrimeCheck } diff --git a/Maths/PrimeFactors.js b/Maths/PrimeFactors.js new file mode 100644 index 0000000000..a593826b36 --- /dev/null +++ b/Maths/PrimeFactors.js @@ -0,0 +1,20 @@ +/* + Modified from: + https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py +*/ + +export const PrimeFactors = (n) => { + // input: n: int + // output: primeFactors: Array of all prime factors of n + const primeFactors = [] + for (let i = 2; i * i <= n; i++) { + while (n % i === 0) { + primeFactors.push(i) + n = Math.floor(n / i) + } + } + if (n > 1) { + primeFactors.push(n) + } + return primeFactors +} diff --git a/Maths/QuadraticRoots.js b/Maths/QuadraticRoots.js new file mode 100644 index 0000000000..a6b52cf1a9 --- /dev/null +++ b/Maths/QuadraticRoots.js @@ -0,0 +1,37 @@ +/** + * @see https://www.cuemath.com/algebra/roots-of-quadratic-equation/ + * @author Dibya Debayan Dash + * Calculates the roots of a quadratic equation of the form ax^2 + bx + c = 0. + * + * @param {number} a - Coefficient of x^2. + * @param {number} b - Coefficient of x. + * @param {number} c - Constant term. + * @returns {number[]} - An array containing the roots if they are real, + * or an empty array indicating no real roots. + * + * @example + * // Find the roots of the quadratic equation: 2x^2 - 4x + 2 = 0 + * const roots = quadraticRoots(2, -4, 2); + * // Expected output: [1] + */ +const quadraticRoots = (a, b, c) => { + // Calculate the discriminant + const discriminant = b * b - 4 * a * c + + // Check if roots are real + if (discriminant < 0) { + return [] + } else if (discriminant === 0) { + // One real root + return [-b / (2 * a)] + } else { + // Two real roots + const sqrtDiscriminant = Math.sqrt(discriminant) + return [ + (-b + sqrtDiscriminant) / (2 * a), + (-b - sqrtDiscriminant) / (2 * a) + ] + } +} + +export { quadraticRoots } diff --git a/Maths/RadianToDegree.js b/Maths/RadianToDegree.js new file mode 100644 index 0000000000..22429ea56c --- /dev/null +++ b/Maths/RadianToDegree.js @@ -0,0 +1,23 @@ +/* + * Radian : https://en.wikipedia.org/wiki/Radian + * Degree : https://en.wikipedia.org/wiki/Degree_(angle) + * + * Angle in Degree = ( Angle in Radian ) x ( 180 / pi ) + * + * Example : + * Question : Convert pi / 2 degree to radian + * So, Angle in Radian = pi / 2 + * + * Solution : + * Angle in Degree = ( pi / 2 ) x ( 180 / pi ) = 90 + * + * So, pi / 2 radian is equal to 90 degree + */ + +/** + * @param {number} radian + * @return {number} + */ +export const radianToDegree = (radian) => { + return radian * (180 / Math.PI) +} diff --git a/Maths/ReverseNumber.js b/Maths/ReverseNumber.js new file mode 100644 index 0000000000..4874cb3a86 --- /dev/null +++ b/Maths/ReverseNumber.js @@ -0,0 +1,29 @@ +/* + Problem statement and Explanation : https://medium.com/@ManBearPigCode/how-to-reverse-a-number-mathematically-97c556626ec6 +*/ + +/** + * ReverseNumber return the reversed value of the given number. + * @param {Number} n any digit number. + * @returns `Number` n reverse in reverse. + */ +const ReverseNumber = (number) => { + // firstly, check that input is a number or not. + if (typeof number !== 'number') { + throw new TypeError('Argument is not a number.') + } + // A variable for storing the reversed number. + let reverseNumber = 0 + // Iterate the process until getting the number is 0. + while (number > 0) { + // get the last digit of the number + const lastDigit = number % 10 + // add to the last digit to in reverseNumber + reverseNumber = reverseNumber * 10 + lastDigit + // reduce the actual number. + number = Math.floor(number / 10) + } + return reverseNumber +} + +export { ReverseNumber } diff --git a/Maths/ReversePolishNotation.js b/Maths/ReversePolishNotation.js new file mode 100644 index 0000000000..efe6240dc0 --- /dev/null +++ b/Maths/ReversePolishNotation.js @@ -0,0 +1,33 @@ +// Wikipedia: https://en.wikipedia.org/wiki/Reverse_Polish_notation + +const calcRPN = (expression) => { + const operators = { + '+': (a, b) => a + b, + '-': (a, b) => a - b, + '*': (a, b) => a * b, + '/': (a, b) => b / a + } + + const tokens = expression.split(' ') + + const stack = [] + + tokens.forEach((token) => { + const operator = operators[token] + + if (typeof operator === 'function') { + const a = stack.pop() + const b = stack.pop() + + const result = operator(a, b) + + stack.push(result) + } else { + stack.push(parseFloat(token)) + } + }) + + return stack.pop() +} + +export { calcRPN } diff --git a/Maths/RowEchelon.js b/Maths/RowEchelon.js new file mode 100644 index 0000000000..c773bb80a9 --- /dev/null +++ b/Maths/RowEchelon.js @@ -0,0 +1,150 @@ +/** + * Given a two dimensional matrix, find its row echelon form. + * + * For more info: https://en.wikipedia.org/wiki/Row_echelon_form + * + * @param {number[[]]} matrix - Two dimensional array of rational numbers. + * @returns {number[[]]} - Two dimensional array of rational numbers (row echelon form). + * + * @example + * const matrix = [ + * [2,3,4,5,7], + * [9,8,4,0,9], + * [5,7,4,3,9], + * [3,4,0,2,1] + * ] + * + * const result = rowEchelon(matrix) + * + * // The function returns the corresponding row echelon form: + * // result: + * // [ + * // [1, 1.5, 2, 2.5, 3.5], + * // [0, 1, 2.54545, 4.09091, 4.09091], + * // [0, 0, 1, 1.57692, 1.36539], + * // [0, 0, 0, 1, -0.25] + * // ] + */ + +// Set a tolerance value for floating-point comparisons +const tolerance = 0.000001 + +// Check if all the rows have same length of elements +const isMatrixValid = (matrix) => { + let numRows = matrix.length + let numCols = matrix[0].length + for (let i = 0; i < numRows; i++) { + if (numCols !== matrix[i].length) { + return false + } + } + + // Check for input other than a 2D matrix + if ( + !Array.isArray(matrix) || + matrix.length === 0 || + !Array.isArray(matrix[0]) + ) { + return false + } + return true +} + +const checkNonZero = (currentRow, currentCol, matrix) => { + let numRows = matrix.length + for (let i = currentRow; i < numRows; i++) { + // Checks if the current element is not very near to zero. + if (!isTolerant(0, matrix[i][currentCol], tolerance)) { + return true + } + } + return false +} + +const swapRows = (currentRow, withRow, matrix) => { + let numCols = matrix[0].length + let tempValue = 0 + for (let j = 0; j < numCols; j++) { + tempValue = matrix[currentRow][j] + matrix[currentRow][j] = matrix[withRow][j] + matrix[withRow][j] = tempValue + } +} + +// Select a pivot element in the current column to facilitate row operations. +// Pivot element is the first non-zero element found from the current row +// down to the last row. +const selectPivot = (currentRow, currentCol, matrix) => { + let numRows = matrix.length + for (let i = currentRow; i < numRows; i++) { + if (matrix[i][currentCol] !== 0) { + swapRows(currentRow, i, matrix) + return + } + } +} + +// Multiply each element of the given row with a factor. +const scalarMultiplication = (currentRow, factor, matrix) => { + let numCols = matrix[0].length + for (let j = 0; j < numCols; j++) { + matrix[currentRow][j] *= factor + } +} + +// Subtract one row from another row +const subtractRow = (currentRow, fromRow, matrix) => { + let numCols = matrix[0].length + for (let j = 0; j < numCols; j++) { + matrix[fromRow][j] -= matrix[currentRow][j] + } +} + +// Check if two numbers are equal within a given tolerance +const isTolerant = (a, b, tolerance) => { + const absoluteDifference = Math.abs(a - b) + return absoluteDifference <= tolerance +} + +const rowEchelon = (matrix) => { + // Check if the input matrix is valid; if not, throw an error. + if (!isMatrixValid(matrix)) { + throw new Error('Input is not a valid 2D matrix.') + } + + let numRows = matrix.length + let numCols = matrix[0].length + let result = matrix + + // Iterate through the rows (i) and columns (j) of the matrix. + for (let i = 0, j = 0; i < numRows && j < numCols; ) { + // If the current column has all zero elements below the current row, + // move to the next column. + if (!checkNonZero(i, j, result)) { + j++ + continue + } + + // Select a pivot element and normalize the current row. + selectPivot(i, j, result) + let factor = 1 / result[i][j] + scalarMultiplication(i, factor, result) + + // Make elements below the pivot element zero by performing + // row operations on subsequent rows. + for (let x = i + 1; x < numRows; x++) { + factor = result[x][j] + if (isTolerant(0, factor, tolerance)) { + continue + } + scalarMultiplication(i, factor, result) + subtractRow(i, x, result) + factor = 1 / factor + scalarMultiplication(i, factor, result) + } + i++ + } + return result +} + +export { rowEchelon } diff --git a/Maths/ShorsAlgorithm.js b/Maths/ShorsAlgorithm.js new file mode 100644 index 0000000000..302a7ac644 --- /dev/null +++ b/Maths/ShorsAlgorithm.js @@ -0,0 +1,98 @@ +/** + * @function ShorsAlgorithm + * @description Classical implementation of Shor's Algorithm. + * @param {Integer} num - Find a non-trivial factor of this number. + * @returns {Integer} - A non-trivial factor of num. + * @see https://en.wikipedia.org/wiki/Shor%27s_algorithm + * @see https://www.youtube.com/watch?v=lvTqbM5Dq4Q + * + * Shor's algorithm is a quantum algorithm for integer factorization. This + * function implements a version of the algorithm which is computable using + * a classical computer, but is not as efficient as the quantum algorithm. + * + * The algorithm basically consists of guessing a number g which may share + * factors with our target number N, and then use Euclid's GCD algorithm to + * find the common factor. + * + * The algorithm starts with a random guess for g, and then improves the + * guess by using the fact that for two coprimes A and B, A^p = mB + 1. + * For our purposes, this means that g^p = mN + 1. This mathematical + * identity can be rearranged into (g^(p/2) + 1)(g^(p/2) - 1) = mN. + * Provided that p/2 is an integer, and neither g^(p/2) + 1 nor g^(p/2) - 1 + * are a multiple of N, either g^(p/2) + 1 or g^(p/2) - 1 must share a + * factor with N, which can then be found using Euclid's GCD algorithm. + */ +function ShorsAlgorithm(num) { + const N = BigInt(num) + + while (true) { + // generate random g such that 1 < g < N + const g = BigInt(Math.floor(Math.random() * (num - 1)) + 2) + + // check if g shares a factor with N + // if it does, find and return the factor + let K = gcd(g, N) + if (K !== 1) return K + + // find p such that g^p = mN + 1 + const p = findP(g, N) + + // p needs to be even for it's half to be an integer + if (p % 2n === 1n) continue + + const base = g ** (p / 2n) // g^(p/2) + const upper = base + 1n // g^(p/2) + 1 + const lower = base - 1n // g^(p/2) - 1 + + // upper and lower can't be a multiple of N + if (upper % N === 0n || lower % N === 0n) continue + + // either upper or lower must share a factor with N + K = gcd(upper, N) + if (K !== 1) return K // upper shares a factor + return gcd(lower, N) // otherwise lower shares a factor + } +} + +/** + * @function findP + * @description Finds a value p such that A^p = mB + 1. + * @param {BigInt} A + * @param {BigInt} B + * @returns The value p. + */ +function findP(A, B) { + let p = 1n + while (!isValidP(A, B, p)) p++ + return p +} + +/** + * @function isValidP + * @description Checks if A, B, and p fulfill A^p = mB + 1. + * @param {BigInt} A + * @param {BigInt} B + * @param {BigInt} p + * @returns Whether A, B, and p fulfill A^p = mB + 1. + */ +function isValidP(A, B, p) { + // A^p = mB + 1 => A^p - 1 = 0 (mod B) + return (A ** p - 1n) % B === 0n +} + +/** + * @function gcd + * @description Euclid's GCD algorithm. + * @param {BigInt} A + * @param {BigInt} B + * @returns Greatest Common Divisor between A and B. + */ +function gcd(A, B) { + while (B !== 0n) { + ;[A, B] = [B, A % B] + } + + return Number(A) +} + +export { ShorsAlgorithm } diff --git a/Maths/SieveOfEratosthenes.js b/Maths/SieveOfEratosthenes.js index 1e0a2e2b21..681d8ba904 100644 --- a/Maths/SieveOfEratosthenes.js +++ b/Maths/SieveOfEratosthenes.js @@ -1,31 +1,30 @@ -function sieveOfEratosthenes (n) { - /* - * Calculates prime numbers till a number n - * :param n: Number upto which to calculate primes - * :return: A boolean list contaning only primes - */ - const primes = new Array(n + 1) - primes.fill(true) // set all as true initially - primes[0] = primes[1] = false // Handling case for 0 and 1 - const sqrtn = Math.ceil(Math.sqrt(n)) - for (let i = 2; i <= sqrtn; i++) { - if (primes[i]) { - for (let j = 2 * i; j <= n; j += i) { - primes[j] = false +/** + * @function sieveOfEratosthenes + * @description Function to get all the prime numbers below a given number using sieve of eratosthenes algorithm + * @param {Number} max The limit below which all the primes are required to be + * @returns {Number[]} An array of all the prime numbers below max + * @see [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) + * @example + * sieveOfEratosthenes(1) // ====> [] + * @example + * sieveOfEratosthenes(20) // ====> [2, 3, 5, 7, 11, 13, 17, 19] + * + */ +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 } -function main () { - const n = 69 // number till where we wish to find primes - const primes = sieveOfEratosthenes(n) - for (let i = 2; i <= n; i++) { - if (primes[i]) { - console.log(i) - } - } -} - -main() +export { sieveOfEratosthenes } diff --git a/Maths/Signum.js b/Maths/Signum.js new file mode 100644 index 0000000000..76f5521bff --- /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/SimpsonIntegration.js b/Maths/SimpsonIntegration.js new file mode 100644 index 0000000000..e7082a9212 --- /dev/null +++ b/Maths/SimpsonIntegration.js @@ -0,0 +1,78 @@ +/* + * + * @file + * @title Composite Simpson's rule for definite integral evaluation + * @author: [ggkogkou](https://github.com/ggkogkou) + * @brief Calculate definite integrals using composite Simpson's numerical method + * + * @details The idea is to split the interval in an EVEN number N of intervals and use as interpolation points the xi + * for which it applies that xi = x0 + i*h, where h is a step defined as h = (b-a)/N where a and b are the + * first and last points of the interval of the integration [a, b]. + * + * We create a table of the xi and their corresponding f(xi) values and we evaluate the integral by the formula: + * I = h/3 * {f(x0) + 4*f(x1) + 2*f(x2) + ... + 2*f(xN-2) + 4*f(xN-1) + f(xN)} + * + * That means that the first and last indexed i f(xi) are multiplied by 1, + * the odd indexed f(xi) by 4 and the even by 2. + * + * N must be even number and a= 2') + } + + // Check if a < b + if (a > b) { + throw Error('a must be less or equal than b') + } + if (a === b) return 0 + + // Calculate the step h + const h = (b - a) / N + + // Find interpolation points + let xi = a // initialize xi = x0 + const pointsArray = [] + + // Find the sum {f(x0) + 4*f(x1) + 2*f(x2) + ... + 2*f(xN-2) + 4*f(xN-1) + f(xN)} + let temp + for (let i = 0; i < N + 1; i++) { + if (i === 0 || i === N) temp = func(xi) + else if (i % 2 === 0) temp = 2 * func(xi) + else temp = 4 * func(xi) + + pointsArray.push(temp) + xi += h + } + + // Calculate the integral + let result = h / 3 + temp = pointsArray.reduce((acc, currValue) => acc + currValue, 0) + + result *= temp + + if (Number.isNaN(result)) { + throw Error( + "Result is NaN. The input interval doesn't belong to the functions domain" + ) + } + + return result +} + +export { integralEvaluation } diff --git a/Maths/Softmax.js b/Maths/Softmax.js new file mode 100644 index 0000000000..66363c4436 --- /dev/null +++ b/Maths/Softmax.js @@ -0,0 +1,13 @@ +// Wikipedia: https://en.wikipedia.org/wiki/Softmax_function + +const Softmax = (inputs) => { + const eulerExpOfAllInputs = inputs.map((input) => Math.exp(input)) + const sumOfEulerExpOfAllInputs = eulerExpOfAllInputs.reduce((a, b) => a + b) + + return inputs.map((input) => { + const eulerExpInputs = Math.exp(input) + return eulerExpInputs / sumOfEulerExpOfAllInputs + }) +} + +export { Softmax } diff --git a/Maths/SquareRoot.js b/Maths/SquareRoot.js new file mode 100644 index 0000000000..0c50d9d3d7 --- /dev/null +++ b/Maths/SquareRoot.js @@ -0,0 +1,23 @@ +/* + * Author: Rak Laptudirm + * + * https://en.wikipedia.org/wiki/Newton%27s_method + * + * Finding the square root of a number using Newton's method. + */ + +function sqrt(num, precision = 4) { + if (!Number.isFinite(num)) { + throw new TypeError(`Expected a number, received ${typeof num}`) + } + if (!Number.isFinite(precision)) { + throw new TypeError(`Expected a number, received ${typeof precision}`) + } + let sqrt = 1 + for (let i = 0; i < precision; i++) { + sqrt -= (sqrt * sqrt - num) / (2 * sqrt) + } + return sqrt +} + +export { sqrt } diff --git a/Maths/SquareRootLogarithmic.js b/Maths/SquareRootLogarithmic.js new file mode 100644 index 0000000000..e9b54aed37 --- /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/SumOfDigits.js b/Maths/SumOfDigits.js new file mode 100644 index 0000000000..9fedf0d2b8 --- /dev/null +++ b/Maths/SumOfDigits.js @@ -0,0 +1,52 @@ +/* + Gets the sum of the digits of the numbers inputted + sumOfDigits(10) will return 1 + 0 = 1 + sumOfDigits(255) will return 2 + 5 + 5 = 12 + Wikipedia: https://en.wikipedia.org/wiki/Digit_sum +*/ + +/* + The given input is converted to a string, split into an array of characters. + This array is reduced to a number using the method .reduce +*/ +function sumOfDigitsUsingString(number) { + if (number < 0) number = -number + + return +number + .toString() + .split('') + .reduce((a, b) => +a + +b) +} + +/* + The input is divided by 10 in each iteration, till the input is equal to 0 + The sum of all the digits is returned (The res variable acts as a collector, taking the remainders on each iteration) +*/ +function sumOfDigitsUsingLoop(number) { + if (number < 0) number = -number + let res = 0 + + while (number > 0) { + res += number % 10 + number = Math.floor(number / 10) + } + + return res +} + +/* + We use the fact that the sum of the digits of a one digit number is itself, and check whether the number is less than 10. If so, then we return the number. Else, we take the number divided by 10 and floored, and recursively call the function, while adding it with the number mod 10 +*/ +function sumOfDigitsUsingRecursion(number) { + if (number < 0) number = -number + + if (number < 10) return number + + return (number % 10) + sumOfDigitsUsingRecursion(Math.floor(number / 10)) +} + +export { + sumOfDigitsUsingRecursion, + sumOfDigitsUsingLoop, + sumOfDigitsUsingString +} diff --git a/Maths/SumOfGeometricProgression.js b/Maths/SumOfGeometricProgression.js new file mode 100644 index 0000000000..0b91412c45 --- /dev/null +++ b/Maths/SumOfGeometricProgression.js @@ -0,0 +1,38 @@ +/* + Returns the sum of a geometric progression + Article on Geometric Progression: https://en.wikipedia.org/wiki/Geometric_series + Examples: + > sumOfGeometricProgression(2, 0.5, 6) + 3.9375 + > sumOfGeometricProgression(0.5, 10, 3) + 55.5 + > sumOfGeometricProgression(0.5, 10, Infinity) + Error: The geometric progression is diverging, and its sum cannot be calculated +*/ + +/** + * + * @param {Number} firstTerm The first term of the geometric progression + * @param {Number} commonRatio The common ratio of the geometric progression + * @param {Number} numOfTerms The number of terms in the progression + */ +function sumOfGeometricProgression(firstTerm, commonRatio, numOfTerms) { + if (!Number.isFinite(numOfTerms)) { + /* + If the number of Terms is Infinity, the common ratio needs to be less than 1 to be a convergent geometric progression + Article on Convergent Series: https://en.wikipedia.org/wiki/Convergent_series + */ + if (Math.abs(commonRatio) < 1) return firstTerm / (1 - commonRatio) + throw new Error( + 'The geometric progression is diverging, and its sum cannot be calculated' + ) + } + + if (commonRatio === 1) return firstTerm * numOfTerms + + return ( + (firstTerm * (Math.pow(commonRatio, numOfTerms) - 1)) / (commonRatio - 1) + ) +} + +export { sumOfGeometricProgression } diff --git a/Maths/TwoSum.js b/Maths/TwoSum.js new file mode 100644 index 0000000000..8aaaea9d14 --- /dev/null +++ b/Maths/TwoSum.js @@ -0,0 +1,24 @@ +/** + * Given an array of integers, find two numbers that add up to a specific target. + * + * @param {number[]} nums - The array of integers. + * @param {number} target - The target sum. + * @returns {number[]} - An array containing the indices of the two numbers. + * + * @example + * const nums = [2, 7, 11, 15]; + * const target = 9; + * const result = twoSum(nums, target); + * // The function should return [0, 1] because nums[0] + nums[1] = 2 + 7 = 9. + */ + +const TwoSum = (nums, target) => { + const numIndicesMap = new Map() + for (let i = 0; i < nums.length; i++) { + const complement = target - nums[i] + if (numIndicesMap.has(complement)) return [numIndicesMap.get(complement), i] + numIndicesMap.set(nums[i], i) + } + return [] +} +export { TwoSum } diff --git a/Maths/Volume.js b/Maths/Volume.js new file mode 100644 index 0000000000..a043001f29 --- /dev/null +++ b/Maths/Volume.js @@ -0,0 +1,133 @@ +/* +Calculate the volume of the shapes + +Volume for Cuboid +Volume for Cube +Volume for Cone +Volume for Pyramid +Volume for Cylinder +Volume for Triangular Prism +Volume for Pentagonal Prism +Volume for Sphere +Volume for Hemisphere +*/ + +/* + Calculate the volume for a Cuboid + Reference: https://www.cuemath.com/measurement/volume-of-cuboid/ + return width * length * height +*/ +const volCuboid = (width, length, height) => { + isNumber(width, 'Width') + isNumber(length, 'Length') + isNumber(height, 'Height') + return width * length * height +} + +/* + Calculate the volume for a Cube + Reference: https://www.cuemath.com/measurement/volume-of-cube/ + return length * length * length +*/ +const volCube = (length) => { + isNumber(length, 'Length') + return length ** 3 +} + +/* + Calculate the volume for a Cone + Reference: https://www.cuemath.com/measurement/volume-of-cone/ + return PI * radius^2 * height/3 +*/ +const volCone = (radius, height) => { + isNumber(radius, 'Radius') + isNumber(height, 'Height') + return (Math.PI * radius ** 2 * height) / 3.0 +} + +/* + Calculate the volume for a Pyramid + Reference: https://www.cuemath.com/measurement/volume-of-pyramid/ + return (baseLength * baseWidth * height) / 3 +*/ +const volPyramid = (baseLength, baseWidth, height) => { + isNumber(baseLength, 'BaseLength') + isNumber(baseWidth, 'BaseWidth') + isNumber(height, 'Height') + return (baseLength * baseWidth * height) / 3.0 +} + +/* + Calculate the volume for a Cylinder + Reference: https://www.cuemath.com/measurement/volume-of-cylinder/ + return PI * radius^2 * height +*/ +const volCylinder = (radius, height) => { + isNumber(radius, 'Radius') + isNumber(height, 'Height') + return Math.PI * radius ** 2 * height +} + +/* + Calculate the volume for a Triangular Prism + Reference: http://lrd.kangan.edu.au/numbers/content/03_volume/04_page.htm + return 1 / 2 * baseLengthTriangle * heightTriangle * height +*/ +const volTriangularPrism = (baseLengthTriangle, heightTriangle, height) => { + isNumber(baseLengthTriangle, 'BaseLengthTriangle') + isNumber(heightTriangle, 'HeightTriangle') + isNumber(height, 'Height') + return (1 / 2) * baseLengthTriangle * heightTriangle * height +} + +/* + Calculate the volume for a Pentagonal Prism + Reference: https://www.cuemath.com/measurement/volume-of-pentagonal-prism/ + return 5/2 * pentagonalLength * pentagonalBaseLength * height +*/ +const volPentagonalPrism = (pentagonalLength, pentagonalBaseLength, height) => { + isNumber(pentagonalLength, 'PentagonalLength') + isNumber(pentagonalBaseLength, 'PentagonalBaseLength') + isNumber(height, 'Height') + return (5 / 2) * pentagonalLength * pentagonalBaseLength * height +} + +/* + Calculate the volume for a Sphere + Reference: https://www.cuemath.com/measurement/volume-of-sphere/ + return 4/3 * PI * radius^3 +*/ +const volSphere = (radius) => { + isNumber(radius, 'Radius') + return (4 / 3) * Math.PI * radius ** 3 +} + +/* + Calculate the volume for a Hemisphere + Reference: https://www.cuemath.com/measurement/volume-of-hemisphere/ + return (2 * PI * radius^3)/3 +*/ +const volHemisphere = (radius) => { + isNumber(radius, 'Radius') + return (2.0 * Math.PI * radius ** 3) / 3.0 +} + +const isNumber = (number, noName = 'number') => { + if (typeof number !== 'number') { + throw new TypeError('The ' + noName + ' should be Number type') + } else if (number < 0 || !Number.isFinite(number)) { + throw new Error('The ' + noName + ' only accepts positive values') + } +} + +export { + volCuboid, + volCube, + volCone, + volPyramid, + volCylinder, + volTriangularPrism, + volPentagonalPrism, + volSphere, + volHemisphere +} diff --git a/Maths/WhileLoopFactorial.js b/Maths/WhileLoopFactorial.js new file mode 100644 index 0000000000..943fabd8d6 --- /dev/null +++ b/Maths/WhileLoopFactorial.js @@ -0,0 +1,14 @@ +/* + author: Theepag, optimised by merelymyself + */ +export const factorialize = (num) => { + // Step 1. Handles cases where num is 0 or 1, by returning 1. + let result = 1 + // Step 2. WHILE loop + while (num > 1) { + result *= num // or result = result * num; + num-- // decrement 1 at each iteration + } + // Step 3. Return the factorial + return result +} diff --git a/Maths/ZellersCongruenceAlgorithm.js b/Maths/ZellersCongruenceAlgorithm.js new file mode 100644 index 0000000000..e188af7592 --- /dev/null +++ b/Maths/ZellersCongruenceAlgorithm.js @@ -0,0 +1,35 @@ +// Zeller's Congruence Algorithm finds the day of the week from the Gregorian Date. Wikipedia: https://en.wikipedia.org/wiki/Zeller%27s_congruence +export const zellersCongruenceAlgorithm = (day, month, year) => { + if ( + typeof day !== 'number' || + typeof month !== 'number' || + typeof year !== 'number' + ) { + throw new TypeError('Arguments are not all numbers.') + } + const q = day + let m = month + let y = year + if (month < 3) { + m += 12 + y -= 1 + } + day = + (q + + Math.floor((26 * (m + 1)) / 10) + + (y % 100) + + Math.floor((y % 100) / 4) + + Math.floor(Math.floor(y / 100) / 4) + + 5 * Math.floor(y / 100)) % + 7 + const days = [ + 'Saturday', + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday' + ] + return days[day] +} diff --git a/Maths/isPalindromeIntegerNumber.js b/Maths/isPalindromeIntegerNumber.js new file mode 100644 index 0000000000..2aceb3ed76 --- /dev/null +++ b/Maths/isPalindromeIntegerNumber.js @@ -0,0 +1,33 @@ +/** + * @function isPalindromeIntegerNumber + * @param { Number } x + * @returns {boolean} - input integer is palindrome or not + * + * time complexity : O(log_10(N)) + * space complexity : O(1) + */ +export function isPalindromeIntegerNumber(x) { + if (typeof x !== 'number') { + throw new TypeError('Input must be a integer number') + } + // check x is integer + if (!Number.isInteger(x)) { + return false + } + + // if it has '-' it cannot be palindrome + if (x < 0) return false + + // make x reverse + let reversed = 0 + let num = x + + while (num > 0) { + const lastDigit = num % 10 + reversed = reversed * 10 + lastDigit + num = Math.floor(num / 10) + } + + // compare origin x and reversed are same + return x === reversed +} diff --git a/Maths/test/Abs.test.js b/Maths/test/Abs.test.js new file mode 100644 index 0000000000..0749679647 --- /dev/null +++ b/Maths/test/Abs.test.js @@ -0,0 +1,40 @@ +import { abs } from '../Abs' + +describe('Testing abs function', () => { + it('Testing for invalid types', () => { + expect(() => abs('234a')).toThrow() + expect(() => abs({})).toThrow() + expect(() => abs([12, -32, -60])).toThrow() + expect(() => abs([])).toThrow() // coerces to 0 + }) + + it('Testing for number of string type', () => { + expect(abs('-345')).toBe(345) + expect(abs('-345.455645')).toBe(345.455645) + }) + + it('Testing for a boolean type', () => { + expect(abs(true)).toBe(1) + expect(abs(false)).toBe(0) + }) + + it('should return an absolute value of a negative number', () => { + const absOfNegativeNumber = abs(-34) + expect(absOfNegativeNumber).toBe(34) + }) + + it('should return an absolute value of a positive number', () => { + const absOfPositiveNumber = abs(50) + expect(absOfPositiveNumber).toBe(50) + }) + + it('should return an absolute value of a zero number', () => { + const absOfPositiveNumber = abs(0) + expect(absOfPositiveNumber).toBe(0) + }) + + it('should return an absolute value of any floating number', () => { + const absOfPositiveNumber = abs(-20.2034) + expect(absOfPositiveNumber).toBe(20.2034) + }) +}) diff --git a/Maths/test/AliquotSum.test.js b/Maths/test/AliquotSum.test.js new file mode 100644 index 0000000000..88a64299de --- /dev/null +++ b/Maths/test/AliquotSum.test.js @@ -0,0 +1,15 @@ +import { aliquotSum } from '../AliquotSum' + +describe('Aliquot Sum of a Number', () => { + it('Aliquot Sum of 6', () => { + expect(aliquotSum(6)).toBe(6) + }) + + it('Aliquot Sum of 1', () => { + expect(aliquotSum(1)).toBe(0) + }) + + it('Aliquot Sum of 28', () => { + expect(aliquotSum(28)).toBe(28) + }) +}) diff --git a/Maths/test/Area.test.js b/Maths/test/Area.test.js new file mode 100644 index 0000000000..a3c64ad11f --- /dev/null +++ b/Maths/test/Area.test.js @@ -0,0 +1,119 @@ +import * as area from '../Area' + +describe('Testing surfaceAreaCube calculations', () => { + it('with natural number', () => { + const surfaceAreaOfOne = area.surfaceAreaCube(1.2) + const surfaceAreaOfThree = area.surfaceAreaCube(3) + expect(surfaceAreaOfOne).toBe(8.64) + expect(surfaceAreaOfThree).toBe(54) + }) + it('with negative argument, expect throw', () => { + expect(() => area.surfaceAreaCube(-1)).toThrow() + }) + it('with non-numeric argument, expect throw', () => { + expect(() => area.surfaceAreaCube('199')).toThrow() + }) +}) + +describe('Testing surfaceAreaSphere calculations', () => { + it('with correct value', () => { + const calculateArea = area.surfaceAreaSphere(5) + const expected = 314.1592653589793 + expect(calculateArea).toBe(expected) + }) + it('with negative value, expect throw', () => { + expect(() => area.surfaceAreaSphere(-1)).toThrow() + }) +}) + +describe('Testing areaRectangle calculations', () => { + it('with correct args', () => { + const areaRectangle = area.areaRectangle(2.5, 2) + expect(areaRectangle).toBe(5.0) + }) + it('with incorrect args, expect throw', () => { + expect(() => area.areaRectangle(-1, 20)).toThrow() + expect(() => area.areaRectangle('1', 0)).toThrow() + expect(() => area.areaRectangle(23, -1)).toThrow() + expect(() => area.areaRectangle(23, 'zero')).toThrow() + }) +}) + +describe('Testing areaSquare calculations', () => { + it('with correct args', () => { + const areaSquare = area.areaSquare(2.5) + expect(areaSquare).toBe(6.25) + }) + it('with incorrect side length, expect throw', () => { + expect(() => area.areaSquare(-1)).toThrow() + expect(() => area.areaSquare('zero')).toThrow() + }) +}) + +describe('Testing areaTriangle calculations', () => { + it('with correct args', () => { + const areaTriangle = area.areaTriangle(1.66, 3.44) + expect(areaTriangle).toBe(2.8552) + }) + it('with incorrect base and height, expect throw', () => { + expect(() => area.areaTriangle(-1, 1)).toThrow() + expect(() => area.areaTriangle(9, 'zero')).toThrow() + }) +}) + +describe('Testing areaTriangleWithAllThreeSides calculations', () => { + it('with correct args', () => { + const areaTriangle = area.areaTriangleWithAllThreeSides(5, 6, 7) + expect(areaTriangle).toBe(14.7) + }) + it('with incorrect sides, expect throw', () => { + expect(() => area.areaTriangleWithAllThreeSides(-1, 1, 10)).toThrow() + expect(() => area.areaTriangleWithAllThreeSides(9, 'zero', 2)).toThrow() + expect(() => area.areaTriangleWithAllThreeSides(1, 10, 12)).toThrow() + }) +}) + +describe('Testing areaParallelogram calculations', () => { + it('with correct args', () => { + const areaParallelogram = area.areaParallelogram(1.66, 3.44) + expect(areaParallelogram).toBe(5.7104) + }) + it('with incorrect base and height, expect throw', () => { + expect(() => area.areaParallelogram(-1, 1)).toThrow() + expect(() => area.areaParallelogram(9, 'zero')).toThrow() + }) +}) + +describe('Testing areaTrapezium calculations', () => { + it('with correct args', () => { + const areaTrapezium = area.areaTrapezium(1.66, 2.41, 4.1) + expect(areaTrapezium).toBe(8.3435) + }) + it('with incorrect bases and height, expect throw', () => { + expect(() => area.areaTrapezium(-1, 1, 0)).toThrow() + expect(() => area.areaTrapezium(9, 'zero', 2)).toThrow() + expect(() => area.areaTrapezium(9, 1, 'seven')).toThrow() + }) +}) + +describe('Testing areaCircle calculations', () => { + it('with correct args', () => { + const areaCircle = area.areaCircle(3.456) + expect(areaCircle).toBe(37.52298159254666) + }) + it('with incorrect diagonal, expect throw', () => { + expect(() => area.areaCircle(-1)).toThrow() + expect(() => area.areaCircle('zero')).toThrow() + }) +}) + +describe('Testing areaRhombus calculations', () => { + it('with correct args', () => { + const areaRhombus = area.areaRhombus(2.5, 2.0) + expect(areaRhombus).toBe(2.5) + }) + it('with incorrect diagonals, expect throw', () => { + expect(() => area.areaRhombus(7, -1)).toThrow() + expect(() => area.areaRhombus('zero', 2)).toThrow() + }) +}) diff --git a/Maths/test/ArithmeticGeometricMean.test.js b/Maths/test/ArithmeticGeometricMean.test.js new file mode 100644 index 0000000000..b0f9361c27 --- /dev/null +++ b/Maths/test/ArithmeticGeometricMean.test.js @@ -0,0 +1,56 @@ +import { agm } from '../ArithmeticGeometricMean.js' + +describe('Tests for AGM', () => { + it('should be a function', () => { + expect(typeof agm).toEqual('function') + }) + + it('number of parameters should be 2', () => { + expect(agm.length).toEqual(2) + }) + + const m = 0x100 // scale for rand + + it('should return NaN if any or all params has a negative argument', () => { + // I multiplied by minus one, because the sign inversion is more clearly visible + expect(agm(-1 * Math.random() * m, Math.random() * m)).toBe(NaN) + expect(agm(Math.random() * m, -1 * Math.random() * m)).toBe(NaN) + expect(agm(-1 * Math.random() * m, -1 * Math.random() * m)).toBe(NaN) + }) + + it('should return Infinity if any arg is Infinity and the other is not 0', () => { + expect(agm(Math.random() * m + 1, Infinity)).toEqual(Infinity) + expect(agm(Infinity, Math.random() * m + 1)).toEqual(Infinity) + expect(agm(Infinity, Infinity)).toEqual(Infinity) + }) + + it('should return NaN if some arg is Infinity and the other is 0', () => { + expect(agm(0, Infinity)).toBe(NaN) + expect(agm(Infinity, 0)).toBe(NaN) + }) + + it('should return +0 if any or all args are +0 or -0, and return -0 if all are -0', () => { + expect(agm(Math.random() * m, 0)).toBe(0) + expect(agm(0, Math.random() * m)).toBe(0) + expect(agm(Math.random() * m, -0)).toBe(0) + expect(agm(-0, Math.random() * m)).toBe(0) + expect(agm(0, -0)).toBe(0) + expect(agm(-0, 0)).toBe(0) + expect(agm(-0, -0)).toBe(-0) + }) + + it('should return NaN if any or all args are NaN', () => { + expect(agm(Math.random() * m, NaN)).toBe(NaN) + expect(agm(NaN, Math.random() * m)).toBe(NaN) + expect(agm(NaN, NaN)).toBe(NaN) + }) + + it('should return an accurate approximation of the AGM between 2 valid input args', () => { + // all the constants are provided by WolframAlpha + expect(agm(1, 2)).toBeCloseTo(1.4567910310469068) + expect(agm(2, 256)).toBeCloseTo(64.45940719438667) + expect(agm(55555, 34)).toBeCloseTo(9933.4047239552) + // test "unsafe" numbers + expect(agm(2 ** 48, 3 ** 27)).toBeCloseTo(88506556379265.7) + }) +}) diff --git a/Maths/test/ArmstrongNumber.test.js b/Maths/test/ArmstrongNumber.test.js new file mode 100644 index 0000000000..01da1631da --- /dev/null +++ b/Maths/test/ArmstrongNumber.test.js @@ -0,0 +1,14 @@ +import { armstrongNumber } from '../ArmstrongNumber' + +describe('ArmstrongNumber', () => { + it('should return true for an armstrong number', () => { + expect(armstrongNumber(371)).toBeTruthy() + }) + + it('should return false for a non-armstrong number', () => { + expect(armstrongNumber(300)).toBeFalsy() + }) + it('should return false for negative values', () => { + expect(armstrongNumber(-2)).toBeFalsy() + }) +}) diff --git a/Maths/test/AutomorphicNumber.test.js b/Maths/test/AutomorphicNumber.test.js new file mode 100644 index 0000000000..57f40d27ee --- /dev/null +++ b/Maths/test/AutomorphicNumber.test.js @@ -0,0 +1,28 @@ +import { isAutomorphic } from '../AutomorphicNumber' + +describe('AutomorphicNumber', () => { + it('should throw Error when n is String', () => { + expect(() => isAutomorphic('qwerty')).toThrow() + }) + it('should throw Error when n is floating point', () => { + expect(() => isAutomorphic(13.6)).toThrow() + }) + + test.each([ + { n: -3, expected: false }, + { n: -25, expected: false } + ])('should return false when n is negetive', ({ n, expected }) => { + expect(isAutomorphic(n)).toBe(false) + }) + + test.each([ + { n: 7, expected: false }, + { n: 83, expected: false }, + { n: 0, expected: true }, + { n: 1, expected: true }, + { n: 376, expected: true }, + { n: 90625, expected: true } + ])('should return $expected when n is $n', ({ n, expected }) => { + expect(isAutomorphic(n)).toBe(expected) + }) +}) diff --git a/Maths/test/AverageMean.test.js b/Maths/test/AverageMean.test.js new file mode 100644 index 0000000000..7d0bd3a6ac --- /dev/null +++ b/Maths/test/AverageMean.test.js @@ -0,0 +1,21 @@ +import { mean } from '../AverageMean' + +describe('Tests for average mean', () => { + it('should be a function', () => { + expect(typeof mean).toEqual('function') + }) + + it('should throw error for invalid input', () => { + expect(() => mean(123)).toThrow() + }) + + it('should return the mean of an array of numbers', () => { + const meanFunction = mean([1, 2, 4, 5]) + expect(meanFunction).toBe(3) + }) + + it('should return the mean of an array of numbers', () => { + const meanFunction = mean([10, 40, 100, 20]) + expect(meanFunction).toBe(42.5) + }) +}) diff --git a/Maths/test/AverageMedian.test.js b/Maths/test/AverageMedian.test.js new file mode 100644 index 0000000000..7e525fa5f1 --- /dev/null +++ b/Maths/test/AverageMedian.test.js @@ -0,0 +1,21 @@ +import { averageMedian } from '../AverageMedian' + +test('should return the median of an array of numbers:', () => { + const medianValue = averageMedian([1, 2, 6, 4, 5]) + expect(medianValue).toBe(4) +}) + +test('should return the median of an array of numbers:', () => { + const medianValue = averageMedian([8, 9, 1, 2, 5, 10, 11]) + expect(medianValue).toBe(8) +}) + +test('should return the median of an array of numbers:', () => { + const medianValue = averageMedian([15, 18, 3, 9, 13, 5]) + expect(medianValue).toBe(11) +}) + +test('should return the median of an array of numbers:', () => { + const medianValue = averageMedian([1, 2, 3, 4, 6, 8]) + expect(medianValue).toBe(3.5) +}) diff --git a/Maths/test/BInaryConvert.test.js b/Maths/test/BInaryConvert.test.js new file mode 100644 index 0000000000..53f18e5ffa --- /dev/null +++ b/Maths/test/BInaryConvert.test.js @@ -0,0 +1,22 @@ +import { BinaryConvert } from '../BinaryConvert' + +describe('BinaryConvert', () => { + it('should return the correct value', () => { + expect(BinaryConvert(4)).toBe(100) + }) + it('should return the correct value', () => { + expect(BinaryConvert(12)).toBe(1100) + }) + it('should return the correct value of the sum from two number', () => { + expect(BinaryConvert(12 + 2)).toBe(1110) + }) + it('should return the correct value of the subtract from two number', () => { + expect(BinaryConvert(245 - 56)).toBe(10111101) + }) + it('should return the correct value', () => { + expect(BinaryConvert(254)).toBe(11111110) + }) + it('should return the correct value', () => { + expect(BinaryConvert(63483)).toBe(1111011111111011) + }) +}) diff --git a/Maths/test/BinaryExponentiationIterative.test.js b/Maths/test/BinaryExponentiationIterative.test.js new file mode 100644 index 0000000000..6e6fff8bd8 --- /dev/null +++ b/Maths/test/BinaryExponentiationIterative.test.js @@ -0,0 +1,15 @@ +import { exponent } from '../BinaryExponentiationIterative' + +describe('exponent', () => { + it('should return 1 when power is 0', () => { + expect(exponent(5, 0)).toBe(1) + }) + + it('should return 0 when base is 0', () => { + expect(exponent(0, 7)).toBe(0) + }) + + it('should return the value of a base raised to a power', () => { + expect(exponent(3, 5)).toBe(243) + }) +}) diff --git a/Maths/test/BinaryExponentiationRecursive.test.js b/Maths/test/BinaryExponentiationRecursive.test.js new file mode 100644 index 0000000000..dc48d22d59 --- /dev/null +++ b/Maths/test/BinaryExponentiationRecursive.test.js @@ -0,0 +1,11 @@ +import { binaryExponentiation } from '../BinaryExponentiationRecursive' + +describe('BinaryExponentiationRecursive', () => { + it('should calculate 2 to the power of 10 correctly', () => { + expect(binaryExponentiation(2, 10)).toBe(1024) + }) + + it('should calculate 3 to the power of 9 correctly', () => { + expect(binaryExponentiation(3, 9)).toBe(19683) + }) +}) diff --git a/Maths/test/BinomialCoefficient.test.js b/Maths/test/BinomialCoefficient.test.js new file mode 100644 index 0000000000..26960b6ab4 --- /dev/null +++ b/Maths/test/BinomialCoefficient.test.js @@ -0,0 +1,37 @@ +import { findBinomialCoefficient } from '../BinomialCoefficient.js' + +describe('Testing findBinomialCoefficient function', () => { + it('should return 56', () => { + const binomialCoefficient = findBinomialCoefficient(8, 3) + expect(binomialCoefficient).toBe(56) + }) + + it('should return 10', () => { + const binomialCoefficient = findBinomialCoefficient(5, 2) + expect(binomialCoefficient).toBe(10) + }) + + it('should throw error when supplied arguments other than number', () => { + expect(() => { + findBinomialCoefficient('eight', 'three') + }).toThrow(Error) + }) + + it('should throw error when n is less than zero', () => { + expect(() => { + findBinomialCoefficient(-1, 3) + }).toThrow(Error) + }) + + it('should throw error when k is less than zero', () => { + expect(() => { + findBinomialCoefficient(1, -3) + }).toThrow(Error) + }) + + it('should throw error when n and k are less than zero', () => { + expect(() => { + findBinomialCoefficient(-1, -3) + }).toThrow(Error) + }) +}) diff --git a/Maths/test/BisectionMethod.test.js b/Maths/test/BisectionMethod.test.js new file mode 100644 index 0000000000..4a49e8f6a4 --- /dev/null +++ b/Maths/test/BisectionMethod.test.js @@ -0,0 +1,39 @@ +import { findRoot } from '../BisectionMethod' + +test('Equation f(x) = x^2 - 3*x + 2 = 0, has root x = 1 in [a, b] = [0, 1.5]', () => { + const root = findRoot(0, 1.5, (x) => x ** 2 - 3 * x + 2, 8) + expect(root).toBe(0.9990234375) +}) + +test('Equation f(x) = ln(x) + sqrt(x) + ฯ€*x^2 = 0, has root x = 0.36247037 in [a, b] = [0, 10]', () => { + const root = findRoot( + 0, + 10, + (x) => { + return Math.log(x) + Math.sqrt(x) + Math.PI * Math.pow(x, 2) + }, + 32 + ) + expect(Number(Number(root).toPrecision(8))).toBe(0.36247037) +}) + +test('Equation f(x) = sqrt(x) + e^(2*x) - 8*x = 0, has root x = 0.93945851 in [a, b] = [0.5, 100]', () => { + const root = findRoot( + 0.5, + 100, + (x) => { + return Math.exp(2 * x) + Math.sqrt(x) - 8 * x + }, + 32 + ) + expect(Number(Number(root).toPrecision(8))).toBe(0.93945851) +}) + +test('Equation f(x) = x^3 = 0, has root x = 0.0 in [a, b] = [-1.0, 1.0]', () => { + const root = findRoot(-1.0, 1.0, (x) => x ** 3, 32) + expect(root).toBeCloseTo(0.0, 5) +}) + +test('Throws an error when function does not change sign', () => { + expect(() => findRoot(-1.0, 1.0, (x) => x ** 2, 10)).toThrowError() +}) diff --git a/Maths/test/CheckKishnamurthyNumber.test.js b/Maths/test/CheckKishnamurthyNumber.test.js new file mode 100644 index 0000000000..4f2e0db952 --- /dev/null +++ b/Maths/test/CheckKishnamurthyNumber.test.js @@ -0,0 +1,18 @@ +import { CheckKishnamurthyNumber } from '../CheckKishnamurthyNumber' + +describe('CheckKishnamurthyNumber', () => { + it.each([1, 2, 145, 40585])('returns true for %i', (num) => { + expect(CheckKishnamurthyNumber(num)).toBe(true) + }) + + it.each([0, 3, 4, 5, 100, 146, 1019823, -1])( + 'returns false for %i', + (num) => { + expect(CheckKishnamurthyNumber(num)).toBe(false) + } + ) + + it('should throw when input is not a number', () => { + expect(() => CheckKishnamurthyNumber('2')).toThrowError() + }) +}) diff --git a/Maths/test/CircularArc.test.js b/Maths/test/CircularArc.test.js new file mode 100644 index 0000000000..1819d30eed --- /dev/null +++ b/Maths/test/CircularArc.test.js @@ -0,0 +1,19 @@ +import { circularArcLength, circularArcArea } from '../CircularArc' + +describe('circularArcLength', () => { + it('with natural number', () => { + const arcLengthOfOneThirty = circularArcLength(1, 30) + const arcLengthOfThreeSixty = circularArcLength(3, 60) + expect(arcLengthOfOneThirty).toBe(0.5235987755982988) + expect(arcLengthOfThreeSixty).toBe(3.141592653589793) + }) +}) + +describe('circularArcArea', () => { + it('with natural number', () => { + const arcAreaOfOneThirty = circularArcArea(1, 30) + const arcAreaOfThreeSixty = circularArcArea(3, 60) + expect(arcAreaOfOneThirty).toBe(0.2617993877991494) + expect(arcAreaOfThreeSixty).toBe(4.71238898038469) + }) +}) diff --git a/Maths/test/CoPrimeCheck.test.js b/Maths/test/CoPrimeCheck.test.js new file mode 100644 index 0000000000..7a46bcf6cf --- /dev/null +++ b/Maths/test/CoPrimeCheck.test.js @@ -0,0 +1,33 @@ +import { CoPrimeCheck } from '../CoPrimeCheck' + +describe('CoPrimeCheck', () => { + it.each([ + [1, 1], + [1, 2], + [1, 3], + [1, 7], + [20, 21], + [5, 7], + [-5, -7], + [1, 0], + [-1, 0] + ])('returns true for %j and %i', (inputA, inputB) => { + expect(CoPrimeCheck(inputA, inputB)).toBe(true) + expect(CoPrimeCheck(inputB, inputA)).toBe(true) + }) + + it.each([ + [5, 15], + [13 * 17 * 19, 17 * 23 * 29], + [2, 0], + [0, 0] + ])('returns false for %j and %i', (inputA, inputB) => { + expect(CoPrimeCheck(inputA, inputB)).toBe(false) + expect(CoPrimeCheck(inputB, inputA)).toBe(false) + }) + + it('should throw when any of the inputs is not a number', () => { + expect(() => CoPrimeCheck('1', 2)).toThrowError() + expect(() => CoPrimeCheck(1, '2')).toThrowError() + }) +}) diff --git a/Maths/test/CollatzSequence.test.js b/Maths/test/CollatzSequence.test.js new file mode 100644 index 0000000000..f837bd90d7 --- /dev/null +++ b/Maths/test/CollatzSequence.test.js @@ -0,0 +1,8 @@ +import { collatz } from '../CollatzSequence' + +describe('The Collatz Sequence', () => { + it('Should be 1', () => { + expect(collatz(1)).toStrictEqual({ result: 1, steps: [] }) + expect(collatz(5)).toStrictEqual({ result: 1, steps: [16, 8, 4, 2, 1] }) + }) +}) diff --git a/Maths/test/Coordinate.test.js b/Maths/test/Coordinate.test.js new file mode 100644 index 0000000000..48c0e57bcc --- /dev/null +++ b/Maths/test/Coordinate.test.js @@ -0,0 +1,53 @@ +import * as coordinate from '../Coordinate' + +describe('Testing euclideanDistance calculations', () => { + it('Should give a numeric output (distance between 2 points) with 4 numeric arguments', () => { + const euclideanDistance = coordinate.euclideanDistance(2, 2, -10, -7) + expect(euclideanDistance).toBe(15) + }) + it('Should not give any output given non-numeric argument', () => { + const euclideanDistance = coordinate.euclideanDistance( + 'ABC', + '123', + '', + '###' + ) + expect(euclideanDistance).toBeNaN() + }) + it('Should not give any output given any number of numeric arguments less than 4', () => { + const euclideanDistance3arg = coordinate.euclideanDistance(2, 2, -10) + const euclideanDistance2arg = coordinate.euclideanDistance(2, 2) + const euclideanDistance1arg = coordinate.euclideanDistance(2) + const euclideanDistance0arg = coordinate.euclideanDistance() + expect(euclideanDistance3arg).toBeNaN() + expect(euclideanDistance2arg).toBeNaN() + expect(euclideanDistance1arg).toBeNaN() + expect(euclideanDistance0arg).toBeNaN() + }) +}) + +describe('Testing manhattanDistance calculations', () => { + it('Should give a numeric output (distance between 2 points) with 4 numeric arguments', () => { + const manhattanDistance = coordinate.manhattanDistance(2, 2, -10, -7) + expect(manhattanDistance).toBe(21) + }) + it('Should not give any output given non-numeric argument', () => { + const manhattanDistance = coordinate.manhattanDistance( + 'ABC', + '123', + '', + '###' + ) + expect(manhattanDistance).toBeNaN() + }) + it('Should not give any output given any number of numeric arguments less than 4', () => { + const manhattanDistance3arg = coordinate.manhattanDistance(2, 2, -10) + const manhattanDistance2arg = coordinate.manhattanDistance(2, 2) + const manhattanDistance1arg = coordinate.manhattanDistance(2) + const manhattanDistance0arg = coordinate.manhattanDistance() + expect(manhattanDistance3arg).toBeNaN() + expect(manhattanDistance2arg).toBeNaN() + expect(manhattanDistance1arg).toBeNaN() + expect(manhattanDistance0arg).toBeNaN() + }) +}) diff --git a/Maths/test/CountNumbersDivisible.test.js b/Maths/test/CountNumbersDivisible.test.js new file mode 100644 index 0000000000..f497cfe726 --- /dev/null +++ b/Maths/test/CountNumbersDivisible.test.js @@ -0,0 +1,31 @@ +import { countNumbersDivisible } from '../CountNumbersDivisible' + +describe('Count the numbers divisible', () => { + test.each([ + [1, 20, 6, 3], + [6, 15, 3, 4], + [25, 100, 30, 3], + [25, 70, 10, 5], + [1, 23, 30, 0] + ])( + 'Total number(s) divisible between %i to %i by %i is/are %i', + (n1, n2, m, expected) => { + expect(countNumbersDivisible(n1, n2, m)).toBe(expected) + } + ) + + test.each([ + ['test', 23, 10, 'Invalid input, please pass only numbers'], + [ + 44, + 30, + 10, + 'Invalid number range, please provide numbers such that num1 < num2' + ] + ])( + 'Should throw an error for input %i, %i, %i, %i', + (n1, n2, m, expected) => { + expect(() => countNumbersDivisible(n1, n2, m)).toThrowError(expected) + } + ) +}) diff --git a/Maths/test/DecimalExpansion.test.js b/Maths/test/DecimalExpansion.test.js new file mode 100644 index 0000000000..afc7243ba4 --- /dev/null +++ b/Maths/test/DecimalExpansion.test.js @@ -0,0 +1,192 @@ +import { decExp } from '../DecimalExpansion' + +/** + * Decimal + */ + +describe('Finite Decimal Expansion', () => { + it('1/2 = 0.5', () => { + const [integer, decimals, cycleIndex] = decExp(1, 2) + expect(integer).toBe(0) + expect(decimals).toEqual([5]) + expect(cycleIndex).toBeUndefined() + }) + + it('1/5 = 0.2', () => { + const [integer, decimals, cycleIndex] = decExp(1, 5) + expect(integer).toBe(0) + expect(decimals).toEqual([2]) + expect(cycleIndex).toBeUndefined() + }) + + it('1/8 = 0.125', () => { + const [integer, decimals, cycleIndex] = decExp(1, 8) + expect(integer).toBe(0) + expect(decimals).toEqual([1, 2, 5]) + expect(cycleIndex).toBeUndefined() + }) + + it('255/40 = 6.375', () => { + const [integer, decimals, cycleIndex] = decExp(255, 40) + expect(integer).toBe(6) + expect(decimals).toEqual([3, 7, 5]) + expect(cycleIndex).toBeUndefined() + }) +}) + +describe('Repeating Decimal Expansion', () => { + it('1/3 = 0.(3)', () => { + expect(decExp(1, 3)).toStrictEqual([0, [3], 0]) + }) + + it('1/6 = 0.1(6)', () => { + expect(decExp(1, 6)).toStrictEqual([0, [1, 6], 1]) + }) + + it('1/7 = 0.(142857)', () => { + expect(decExp(1, 7)).toStrictEqual([0, [1, 4, 2, 8, 5, 7], 0]) + }) +}) + +/** + * Binary + */ + +describe('Finite Binary Expansion', () => { + it('1/2 = 0.1โ‚‚', () => { + const [integer, decimals, cycleIndex] = decExp(1, 2, 2) + expect(integer).toBe(0) + expect(decimals).toEqual([1]) + expect(cycleIndex).toBeUndefined() + }) + + it('1/8 = 0.001โ‚‚', () => { + const [integer, decimals, cycleIndex] = decExp(1, 8, 2) + expect(integer).toBe(0) + expect(decimals).toEqual([0, 0, 1]) + expect(cycleIndex).toBeUndefined() + }) + + it('255/40 = 110.011โ‚‚', () => { + const [integer, decimals, cycleIndex] = decExp(255, 40, 2) + expect(integer).toBe(110) + expect(decimals).toEqual([0, 1, 1]) + expect(cycleIndex).toBeUndefined() + }) +}) + +describe('Repeating Binary Expansion', () => { + it('1/3 = 0.(01)โ‚‚', () => { + expect(decExp(1, 3, 2)).toStrictEqual([0, [0, 1], 0]) + }) + + it('1/5 = 0.(0011)โ‚‚', () => { + expect(decExp(1, 5, 2)).toStrictEqual([0, [0, 0, 1, 1], 0]) + }) + + it('1/6 = 0.0(01)โ‚‚', () => { + expect(decExp(1, 6, 2)).toStrictEqual([0, [0, 0, 1], 1]) + }) + + it('1/7 = 0.(001)โ‚‚', () => { + expect(decExp(1, 7, 2)).toStrictEqual([0, [0, 0, 1], 0]) + }) +}) + +/** + * Octal + */ + +describe('Finite Octal Expansion', () => { + it('1/2 = 0.4โ‚ˆ', () => { + const [integer, decimals, cycleIndex] = decExp(1, 2, 8) + expect(integer).toBe(0) + expect(decimals).toEqual([4]) + expect(cycleIndex).toBeUndefined() + }) + + it('1/8 = 0.1โ‚ˆ', () => { + const [integer, decimals, cycleIndex] = decExp(1, 8, 8) + expect(integer).toBe(0) + expect(decimals).toEqual([1]) + expect(cycleIndex).toBeUndefined() + }) + + it('255/40 = 6.3โ‚ˆ', () => { + const [integer, decimals, cycleIndex] = decExp(255, 40, 8) + expect(integer).toBe(6) + expect(decimals).toEqual([3]) + expect(cycleIndex).toBeUndefined() + }) +}) + +describe('Repeating Octal Expansion', () => { + it('1/3 = 0.(25)โ‚ˆ', () => { + expect(decExp(1, 3, 8)).toStrictEqual([0, [2, 5], 0]) + }) + + it('1/5 = 0.(1463)โ‚ˆ', () => { + expect(decExp(1, 5, 8)).toStrictEqual([0, [1, 4, 6, 3], 0]) + }) + + it('1/6 = 0.1(25)โ‚ˆ', () => { + expect(decExp(1, 6, 8)).toStrictEqual([0, [1, 2, 5], 1]) + }) + + it('1/7 = 0.(1)โ‚ˆ', () => { + expect(decExp(1, 7, 8)).toStrictEqual([0, [1], 0]) + }) +}) + +/** + * Integers + */ + +describe('Integers', () => { + it('1/1 = 1', () => { + const [integer, decimals, cycleIndex] = decExp(1, 1) + expect(integer).toBe(1) + expect(decimals).toStrictEqual([]) + expect(cycleIndex).toBeUndefined() + }) + + it('5/5 = 1', () => { + const [integer, decimals, cycleIndex] = decExp(5, 5) + expect(integer).toBe(1) + expect(decimals).toStrictEqual([]) + expect(cycleIndex).toBeUndefined() + }) + + it('2/1 = 2', () => { + const [integer, decimals, cycleIndex] = decExp(2, 1) + expect(integer).toBe(2) + expect(decimals).toStrictEqual([]) + expect(cycleIndex).toBeUndefined() + }) + + it('9/3 = 3', () => { + const [integer, decimals, cycleIndex] = decExp(9, 3) + expect(integer).toBe(3) + expect(decimals).toStrictEqual([]) + expect(cycleIndex).toBeUndefined() + }) +}) + +/** + * Unsupported base + */ + +describe('Throws on unsupported base', () => { + it('negative base', () => { + expect(() => decExp(1, 1, -2)).toThrow(RangeError) + }) + it('base 0', () => { + expect(() => decExp(1, 1, 0)).toThrow(RangeError) + }) + it('base 1', () => { + expect(() => decExp(1, 1, 1)).toThrow(RangeError) + }) + it('base 11', () => { + expect(() => decExp(1, 1, 11)).toThrow(RangeError) + }) +}) diff --git a/Maths/test/DegreeToRadian.test.js b/Maths/test/DegreeToRadian.test.js new file mode 100644 index 0000000000..678fbc88b4 --- /dev/null +++ b/Maths/test/DegreeToRadian.test.js @@ -0,0 +1,21 @@ +import { degreeToRadian } from '../DegreeToRadian' + +test('should convert degree to radian:', () => { + const radianEqual = degreeToRadian(0) + expect(radianEqual).toBe(0) +}) + +test('should convert degree to radian:', () => { + const radianEqual = degreeToRadian(45) + expect(radianEqual).toBe(Math.PI / 4) +}) + +test('should convert degree to radian:', () => { + const radianEqual = degreeToRadian(90) + expect(radianEqual).toBe(Math.PI / 2) +}) + +test('should convert degree to radian:', () => { + const radianEqual = degreeToRadian(180) + expect(radianEqual).toBe(Math.PI) +}) diff --git a/Maths/test/Determinant.test.js b/Maths/test/Determinant.test.js new file mode 100644 index 0000000000..f6cba8241c --- /dev/null +++ b/Maths/test/Determinant.test.js @@ -0,0 +1,60 @@ +import { expect } from 'vitest' +import { determinant } from '../Determinant' +describe('Determinant', () => { + test.each([ + [ + [ + [8, 1, 6], + [1, 2, 3], + [4, 7, 5] + ], + -87 + ], + [ + [ + [2, 1, 0, 2], + [1, 2, 4, 3], + [0, 4, 7, 5], + [4, 7, 9, 8] + ], + 25 + ], + [ + [ + [5, 9], + [3, 7] + ], + 8 + ], + [ + [ + [7, 5, 1, 4, 3], + [6, 8, 7, 9, 6], + [9, 8, 0, 4, 7], + [0, 3, 4, 7, 9], + [3, 6, 2, 8, 8] + ], + 2476 + ], + [[[23]], 23] + ])( + 'Should return the determinant of the square matrix.', + (matrix, expected) => { + expect(determinant(matrix)).toEqual(expected) + } + ) + + test.each([ + [ + [ + [1, 6], + [1, 2, 3], + [4, 7, 5] + ], + 'Square matrix is required.' + ], + [[1, 3, 2, [5, 8, 6], 3], 'Input is not a valid 2D matrix.'] + ])('Should return the error message.', (matrix, expected) => { + expect(() => determinant(matrix)).toThrowError(expected) + }) +}) diff --git a/Maths/test/EuclideanDistance.test.js b/Maths/test/EuclideanDistance.test.js new file mode 100644 index 0000000000..717ea2e6a0 --- /dev/null +++ b/Maths/test/EuclideanDistance.test.js @@ -0,0 +1,25 @@ +import { EuclideanDistance } from '../EuclideanDistance.js' + +describe('EuclideanDistance', () => { + it('should calculate the distance correctly for 2D vectors', () => { + expect(EuclideanDistance([0, 0], [2, 2])).toBeCloseTo( + 2.8284271247461903, + 10 + ) + }) + + it('should calculate the distance correctly for 3D vectors', () => { + expect(EuclideanDistance([0, 0, 0], [2, 2, 2])).toBeCloseTo( + 3.4641016151377544, + 10 + ) + }) + + it('should calculate the distance correctly for 4D vectors', () => { + expect(EuclideanDistance([1, 2, 3, 4], [5, 6, 7, 8])).toBeCloseTo(8.0, 10) + }) + + it('should calculate the distance correctly for different 2D vectors', () => { + expect(EuclideanDistance([1, 2], [4, 6])).toBeCloseTo(5.0, 10) + }) +}) diff --git a/Maths/test/EulerMethod.manual-test.js b/Maths/test/EulerMethod.manual-test.js new file mode 100644 index 0000000000..e1959280a5 --- /dev/null +++ b/Maths/test/EulerMethod.manual-test.js @@ -0,0 +1,68 @@ +import { eulerFull } from '../EulerMethod' + +function plotLine(label, points, width, height) { + // utility function to plot the results + + // container needed to control the size of the canvas + const container = document.createElement('div') + container.style.width = width + 'px' + container.style.height = height + 'px' + document.body.append(container) + + // the canvas for plotting + const canvas = document.createElement('canvas') + container.append(canvas) + + // Chart-class from chartjs + const chart = new Chart(canvas, { + type: 'scatter', + data: { + datasets: [ + { + label, + data: points, + showLine: true, + fill: false, + tension: 0, + borderColor: 'black' + } + ] + }, + options: { + maintainAspectRatio: false, + responsive: true + } + }) +} + +function exampleEquation1(x, y) { + return x +} + +// example from https://en.wikipedia.org/wiki/Euler_method +function exampleEquation2(x, y) { + return y +} + +// example from https://www.geeksforgeeks.org/euler-method-solving-differential-equation/ +function exampleEquation3(x, y) { + return x + y + x * y +} + +// plot the results if the script is executed in a browser with a window-object +if (typeof window !== 'undefined') { + const points1 = eulerFull(0, 4, 0.1, 0, exampleEquation1) + const points2 = eulerFull(0, 4, 0.1, 1, exampleEquation2) + const points3 = eulerFull(0, 0.1, 0.025, 1, exampleEquation3) + + const script = document.createElement('script') + + // using chartjs + script.src = 'https://www.chartjs.org/dist/2.9.4/Chart.min.js' + script.onload = function () { + plotLine('example 1: dy/dx = x', points1, 600, 400) + plotLine('example 2: dy/dx = y', points2, 600, 400) + plotLine('example 3: dy/dx = x + y + x * y', points3, 600, 400) + } + document.body.append(script) +} diff --git a/Maths/test/EulerMethod.test.js b/Maths/test/EulerMethod.test.js new file mode 100644 index 0000000000..47bd68b2af --- /dev/null +++ b/Maths/test/EulerMethod.test.js @@ -0,0 +1,41 @@ +import { eulerFull, eulerStep } from '../EulerMethod' + +describe('eulerStep', () => { + it('should calculate the next y value correctly', () => { + expect( + eulerStep(0, 0.1, 0, function (x, y) { + return x + }) + ).toBe(0) + expect( + eulerStep(2, 1, 1, function (x, y) { + return x * x + }) + ).toBe(5) + }) +}) + +describe('eulerFull', () => { + it('should return all the points found', () => { + expect( + eulerFull(0, 3, 1, 0, function (x, y) { + return x + }) + ).toEqual([ + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 2, y: 1 }, + { x: 3, y: 3 } + ]) + + expect( + eulerFull(3, 4, 0.5, 1, function (x, y) { + return x * x + }) + ).toEqual([ + { x: 3, y: 1 }, + { x: 3.5, y: 5.5 }, + { x: 4, y: 11.625 } + ]) + }) +}) diff --git a/Maths/test/EulersTotient.test.js b/Maths/test/EulersTotient.test.js new file mode 100644 index 0000000000..84893b0906 --- /dev/null +++ b/Maths/test/EulersTotient.test.js @@ -0,0 +1,11 @@ +import { EulersTotient } from '../EulersTotient' + +describe('EulersTotient', () => { + it('should return 6 as 1, 2, 4, 5, 7, and 8 are coprime to 9', () => { + expect(EulersTotient(9)).toBe(6) + }) + + it('should return 4 as 1, 3, 7, and 9 are coprime to 10', () => { + expect(EulersTotient(10)).toBe(4) + }) +}) diff --git a/Maths/test/EulersTotientFunction.test.js b/Maths/test/EulersTotientFunction.test.js new file mode 100644 index 0000000000..2b8d6c4758 --- /dev/null +++ b/Maths/test/EulersTotientFunction.test.js @@ -0,0 +1,11 @@ +import { eulersTotientFunction } from '../EulersTotientFunction' + +describe('eulersTotientFunction', () => { + it('is a function', () => { + expect(typeof eulersTotientFunction).toEqual('function') + }) + it('should return the phi of a given number', () => { + const phiOfNumber = eulersTotientFunction(10) + expect(phiOfNumber).toBe(4) + }) +}) diff --git a/Maths/test/ExponentialFunction.test.js b/Maths/test/ExponentialFunction.test.js new file mode 100644 index 0000000000..d1eed4895f --- /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/ExtendedEuclideanGCD.test.js b/Maths/test/ExtendedEuclideanGCD.test.js new file mode 100644 index 0000000000..c962c31c84 --- /dev/null +++ b/Maths/test/ExtendedEuclideanGCD.test.js @@ -0,0 +1,24 @@ +import { extendedEuclideanGCD } from '../ExtendedEuclideanGCD' + +describe('extendedEuclideanGCD', () => { + it('should return valid values in order for positive arguments', () => { + expect(extendedEuclideanGCD(240, 46)).toMatchObject([2, -9, 47]) + expect(extendedEuclideanGCD(46, 240)).toMatchObject([2, 47, -9]) + }) + it('should give error on non-positive arguments', () => { + expect(() => extendedEuclideanGCD(0, 240)).toThrowError( + new TypeError('Must be positive numbers') + ) + expect(() => extendedEuclideanGCD(46, -240)).toThrowError( + new TypeError('Must be positive numbers') + ) + }) + it('should give error on non-numeric arguments', () => { + expect(() => extendedEuclideanGCD('240', 46)).toThrowError( + new TypeError('Not a Number') + ) + expect(() => extendedEuclideanGCD([240, 46])).toThrowError( + new TypeError('Not a Number') + ) + }) +}) diff --git a/Maths/test/Factorial.test.js b/Maths/test/Factorial.test.js new file mode 100644 index 0000000000..5126b2fcb0 --- /dev/null +++ b/Maths/test/Factorial.test.js @@ -0,0 +1,27 @@ +import { calcFactorial } from '../Factorial' + +describe('calcFactorial', () => { + it('should return a statement for value "0"', () => { + expect(calcFactorial(0)).toBe(1) + }) + + it('should throw error for "null" and "undefined"', () => { + expect(() => { + calcFactorial(null) + }).toThrow(Error) + expect(() => { + calcFactorial(undefined) + }).toThrow(Error) + }) + + it('should throw error for negative numbers', () => { + expect(() => { + calcFactorial(-1) + }).toThrow(Error) + }) + + it('should return the factorial of a positive number', () => { + const positiveFactorial = calcFactorial(3) + expect(positiveFactorial).toBe(6) + }) +}) diff --git a/Maths/test/Factors.test.js b/Maths/test/Factors.test.js new file mode 100644 index 0000000000..1ad60132d3 --- /dev/null +++ b/Maths/test/Factors.test.js @@ -0,0 +1,10 @@ +import { factorsOfANumber } from '../Factors' + +describe('Factors', () => { + factorsOfANumber(50).forEach((num) => { + it(`${num} is a factor of 50`, () => { + const isFactor = 50 % num === 0 + expect(isFactor).toBeTruthy() + }) + }) +}) diff --git a/Maths/test/FareyApproximation.test.js b/Maths/test/FareyApproximation.test.js new file mode 100644 index 0000000000..f94024bb9d --- /dev/null +++ b/Maths/test/FareyApproximation.test.js @@ -0,0 +1,13 @@ +import { fareyApproximation } from '../FareyApproximation' + +describe('fareyApproximation', () => { + it('Return Farey Approximation of 0.7538385', () => { + const approx = fareyApproximation(0.7538385) + expect(approx).toStrictEqual({ numerator: 52, denominator: 69 }) + }) + + it('Return Farey Approximation of 0.23584936', () => { + const approx = fareyApproximation(0.23584936) + expect(approx).toStrictEqual({ numerator: 196, denominator: 831 }) + }) +}) diff --git a/Maths/test/FermatPrimalityTest.test.js b/Maths/test/FermatPrimalityTest.test.js new file mode 100644 index 0000000000..92fad4bcd6 --- /dev/null +++ b/Maths/test/FermatPrimalityTest.test.js @@ -0,0 +1,19 @@ +import { fermatPrimeCheck, modularExponentiation } from '../FermatPrimalityTest' + +describe('modularExponentiation', () => { + it('should give the correct output for all exponentiations', () => { + expect(modularExponentiation(38, 220, 221)).toBe(1) + expect(modularExponentiation(24, 220, 221)).toBe(81) + }) +}) + +describe('fermatPrimeCheck', () => { + it('should give the correct output for prime and composite numbers', () => { + expect(fermatPrimeCheck(2, 35)).toBe(true) + expect(fermatPrimeCheck(10, 30)).toBe(false) + expect(fermatPrimeCheck(94286167)).toBe(true) + expect(fermatPrimeCheck(83165867)).toBe(true) + expect(fermatPrimeCheck(13268774)).toBe(false) + expect(fermatPrimeCheck(13233852)).toBe(false) + }) +}) diff --git a/Maths/test/Fibonacci.test.js b/Maths/test/Fibonacci.test.js new file mode 100644 index 0000000000..3ebb8b8c9b --- /dev/null +++ b/Maths/test/Fibonacci.test.js @@ -0,0 +1,109 @@ +import { + FibonacciDpWithoutRecursion, + FibonacciRecursiveDP, + FibonacciIterative, + FibonacciGenerator, + FibonacciRecursive, + FibonacciMatrixExpo, + FibonacciUsingFormula +} from '../Fibonacci' + +describe('Fibonacci', () => { + it('should return an array of numbers for FibonacciIterative', () => { + expect(FibonacciIterative(6)).toEqual( + expect.arrayContaining([0, 1, 1, 2, 3, 5, 8]) + ) + expect(FibonacciIterative(-6)).toEqual( + expect.arrayContaining([0, 1, -1, 2, -3, 5, -8]) + ) + }) + + it('should return number for FibonacciGenerator', () => { + const positive = FibonacciGenerator() + expect(positive.next().value).toBe(0) + expect(positive.next().value).toBe(1) + expect(positive.next().value).toBe(1) + expect(positive.next().value).toBe(2) + expect(positive.next().value).toBe(3) + expect(positive.next().value).toBe(5) + expect(positive.next().value).toBe(8) + + const negative = FibonacciGenerator(true) + expect(negative.next().value).toBe(0) + expect(negative.next().value).toBe(1) + expect(negative.next().value).toBe(-1) + expect(negative.next().value).toBe(2) + expect(negative.next().value).toBe(-3) + expect(negative.next().value).toBe(5) + expect(negative.next().value).toBe(-8) + }) + + it('should return an array of numbers for FibonacciRecursive', () => { + expect(FibonacciRecursive(6)).toEqual( + expect.arrayContaining([0, 1, 1, 2, 3, 5, 8]) + ) + expect(FibonacciRecursive(-6)).toEqual( + expect.arrayContaining([-0, 1, -1, 2, -3, 5, -8]) + ) + }) + + it('should return number for FibonacciRecursiveDP', () => { + expect(FibonacciRecursiveDP(6)).toBe(8) + expect(FibonacciRecursiveDP(-6)).toBe(-8) + }) + + it('should return an array of numbers for FibonacciDpWithoutRecursion', () => { + expect(FibonacciDpWithoutRecursion(6)).toEqual( + expect.arrayContaining([0, 1, 1, 2, 3, 5, 8]) + ) + expect(FibonacciDpWithoutRecursion(-6)).toEqual( + expect.arrayContaining([0, 1, -1, 2, -3, 5, -8]) + ) + }) + + it('should return number for FibonacciMatrixExpo', () => { + expect(FibonacciMatrixExpo(0)).toBe(0) + expect(FibonacciMatrixExpo(1)).toBe(1) + expect(FibonacciMatrixExpo(2)).toBe(1) + expect(FibonacciMatrixExpo(3)).toBe(2) + expect(FibonacciMatrixExpo(4)).toBe(3) + expect(FibonacciMatrixExpo(5)).toBe(5) + expect(FibonacciMatrixExpo(6)).toBe(8) + + expect(FibonacciMatrixExpo(-0)).toBe(-0) + expect(FibonacciMatrixExpo(-1)).toBe(1) + expect(FibonacciMatrixExpo(-2)).toBe(-1) + expect(FibonacciMatrixExpo(-3)).toBe(2) + expect(FibonacciMatrixExpo(-4)).toBe(-3) + expect(FibonacciMatrixExpo(-5)).toBe(5) + expect(FibonacciMatrixExpo(-6)).toBe(-8) + }) + + it('should return bigint for FibonacciMatrixExpo', () => { + expect(FibonacciMatrixExpo(0n)).toBe(0n) + expect(FibonacciMatrixExpo(1n)).toBe(1n) + expect(FibonacciMatrixExpo(2n)).toBe(1n) + expect(FibonacciMatrixExpo(3n)).toBe(2n) + expect(FibonacciMatrixExpo(4n)).toBe(3n) + expect(FibonacciMatrixExpo(5n)).toBe(5n) + expect(FibonacciMatrixExpo(6n)).toBe(8n) + + expect(FibonacciMatrixExpo(-0n)).toBe(0n) + expect(FibonacciMatrixExpo(-1n)).toBe(1n) + expect(FibonacciMatrixExpo(-2n)).toBe(-1n) + expect(FibonacciMatrixExpo(-3n)).toBe(2n) + expect(FibonacciMatrixExpo(-4n)).toBe(-3n) + expect(FibonacciMatrixExpo(-5n)).toBe(5n) + expect(FibonacciMatrixExpo(-6n)).toBe(-8n) + }) + it.each([ + [0, 0], + [1, 1], + [15, 610] + ])( + 'should calculate the correct Fibonacci number for n = %i', + (n, expected) => { + expect(FibonacciUsingFormula(n)).toBe(expected) + } + ) +}) diff --git a/Maths/test/FigurateNumber.test.js b/Maths/test/FigurateNumber.test.js new file mode 100644 index 0000000000..7c00551619 --- /dev/null +++ b/Maths/test/FigurateNumber.test.js @@ -0,0 +1,72 @@ +import { + isTriangular, + isTetrahedral, + isPentatope, + checkAll +} from '../FigurateNumber' + +describe('FigurateNumber', () => { + it('Triangular : should return true', () => { + expect(isTriangular(1)).toEqual(true) + }) + it('Triangular : should return true', () => { + expect(isTriangular(3)).toEqual(true) + }) + + it('Triangular : should return false', () => { + expect(isTriangular(5)).toEqual(false) + }) + + it('Triangular : should return true', () => { + expect(isTriangular(171)).toEqual(true) + }) + /** End */ + + it('Tetrahedral : should return true', () => { + expect(isTetrahedral(1)).toEqual(true) + }) + it('Tetrahedral : should return true', () => { + expect(isTetrahedral(4)).toEqual(true) + }) + + it('Tetrahedral : should return false', () => { + expect(isTetrahedral(3)).toEqual(false) + }) + + it('Tetrahedral : should return true', () => { + expect(isTetrahedral(165)).toEqual(true) + }) + + /** End */ + it('Pentatope : should return true', () => { + expect(isPentatope(1)).toEqual(true) + }) + it('Pentatope : should return true', () => { + expect(isPentatope(5)).toEqual(true) + }) + + it('Pentatope : should return false', () => { + expect(isPentatope(3)).toEqual(false) + }) + + it('Pentatope : should return true', () => { + expect(isPentatope(1001)).toEqual(true) + }) + /** End */ + + it('Check All : should return all true', () => { + expect(checkAll(1)).toEqual({ + isTriangular: true, + isTetrahedral: true, + isPentatope: true + }) + }) + + it('Check All : should return all true,true,false', () => { + expect(checkAll(15)).toEqual({ + isTriangular: true, + isTetrahedral: false, + isPentatope: true + }) + }) +}) diff --git a/Maths/test/FindHcf.test.js b/Maths/test/FindHcf.test.js new file mode 100644 index 0000000000..ccb5c30459 --- /dev/null +++ b/Maths/test/FindHcf.test.js @@ -0,0 +1,20 @@ +import { findHCF } from '../FindHcf' + +describe('findHCF', () => { + it('should throw a statement for values less than 1', () => { + expect(findHCF(0, 0)).toBe('Please enter values greater than zero.') + }) + + it('should throw a statement for one value less than 1', () => { + expect(findHCF(0, 1)).toBe('Please enter values greater than zero.') + expect(findHCF(1, 0)).toBe('Please enter values greater than zero.') + }) + + it('should return an error for values non-integer values', () => { + expect(findHCF(2.24, 4.35)).toBe('Please enter whole numbers.') + }) + + it('should return the HCF of two given integers', () => { + expect(findHCF(27, 36)).toBe(9) + }) +}) diff --git a/Maths/test/FindLcm.test.js b/Maths/test/FindLcm.test.js new file mode 100644 index 0000000000..4c6c80779a --- /dev/null +++ b/Maths/test/FindLcm.test.js @@ -0,0 +1,55 @@ +import { findLcm, findLcmWithHcf } from '../FindLcm' + +describe('findLcm', () => { + it('should throw a statement for values less than 1', () => { + expect(() => { + findLcm(0, 0) + }).toThrow(Error) + }) + + it('should throw a statement for one value less than 1', () => { + 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) + }).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/FindMaxRecursion.test.js b/Maths/test/FindMaxRecursion.test.js new file mode 100644 index 0000000000..fec40bd281 --- /dev/null +++ b/Maths/test/FindMaxRecursion.test.js @@ -0,0 +1,58 @@ +import { findMaxRecursion } from '../FindMaxRecursion' + +describe('Test findMaxRecursion function', () => { + const positiveAndNegativeArray = [1, 2, 4, 5, -1, -2, -4, -5] + const positiveAndNegativeArray1 = [10, 40, 100, 20, -10, -40, -100, -20] + + const positiveArray = [1, 2, 4, 5] + const positiveArray1 = [10, 40, 100, 20] + + const negativeArray = [-1, -2, -4, -5] + const negativeArray1 = [-10, -40, -100, -20] + + const zeroArray = [0, 0, 0, 0] + const emptyArray = [] + + it('Testing with positive arrays', () => { + expect(findMaxRecursion(positiveArray, 0, positiveArray.length - 1)).toBe(5) + expect(findMaxRecursion(positiveArray1, 0, positiveArray1.length - 1)).toBe( + 100 + ) + }) + + it('Testing with negative arrays', () => { + expect(findMaxRecursion(negativeArray, 0, negativeArray.length - 1)).toBe( + -1 + ) + expect(findMaxRecursion(negativeArray1, 0, negativeArray1.length - 1)).toBe( + -10 + ) + }) + + it('Testing with positive and negative arrays', () => { + expect( + findMaxRecursion( + positiveAndNegativeArray, + 0, + positiveAndNegativeArray.length - 1 + ) + ).toBe(5) + expect( + findMaxRecursion( + positiveAndNegativeArray1, + 0, + positiveAndNegativeArray1.length - 1 + ) + ).toBe(100) + }) + + it('Testing with zero arrays', () => { + expect(findMaxRecursion(zeroArray, 0, zeroArray.length - 1)).toBe(0) + }) + + it('Testing with empty arrays', () => { + expect(findMaxRecursion(emptyArray, 0, emptyArray.length - 1)).toBe( + undefined + ) + }) +}) diff --git a/Maths/test/FindMin.test.js b/Maths/test/FindMin.test.js new file mode 100644 index 0000000000..85381f80e8 --- /dev/null +++ b/Maths/test/FindMin.test.js @@ -0,0 +1,18 @@ +import { findMin } from '../FindMin' + +describe('FindMin', () => { + test('Should return the minimum number in the array', () => { + const min = findMin(2, 5, 1, 12, 43, 1, 9) + expect(min).toBe(1) + }) + + test('Should return the minimum number in the array', () => { + const min = findMin(21, 513, 6) + expect(min).toBe(6) + }) + + test('Should throw error', () => { + const min = () => findMin() + expect(min).toThrow('Array is empty') + }) +}) diff --git a/Maths/test/FindMinIterator.test.js b/Maths/test/FindMinIterator.test.js new file mode 100644 index 0000000000..792cb7695e --- /dev/null +++ b/Maths/test/FindMinIterator.test.js @@ -0,0 +1,56 @@ +import { FindMinIterator } from '../FindMinIterator' + +describe('FindMinIterator', () => { + test('given empty array then min is undefined', () => { + expect(FindMinIterator([])).toBeUndefined() + }) + + test('given single value array then min is found', () => { + expect(FindMinIterator([1])).toBe(1) + expect(FindMinIterator([-1])).toBe(-1) + expect(FindMinIterator([0])).toBe(0) + }) + + test('given array then min is found', () => { + expect(FindMinIterator([1, 2])).toBe(1) + expect(FindMinIterator([-1, 10])).toBe(-1) + expect(FindMinIterator([0, 100])).toBe(0) + expect(FindMinIterator([100, 0])).toBe(0) + expect( + FindMinIterator([100, 50, 20, 0, -100, 0, 2, 30, 45, 99, 104, 23]) + ).toBe(-100) + }) + + test('given empty generator then min is undefined', () => { + const src = function* () {} + expect(FindMinIterator(src())).toBeUndefined() + }) + + test('given generator then min is found', () => { + const src = function* () { + yield 1 + yield -1 + yield 0 + } + expect(FindMinIterator(src())).toBe(-1) + }) + + test('given string generator then min string length is found', () => { + const src = function* () { + yield 'abc' + yield 'de' + yield 'qwerty' + } + expect(FindMinIterator(src(), (_x) => _x.length)).toBe(2) + }) + + test('given array of objects then min accessor is found', () => { + const array = [ + { name: 'Item #1', price: 1.0 }, + { name: 'Item #2', price: 0.0 }, + { name: 'Item #3', price: -1.0 } + ] + expect(FindMinIterator(array, (_x) => _x.price)).toBe(-1) + expect(FindMinIterator(array, (_x) => _x.name)).toBe('Item #1') + }) +}) diff --git a/Maths/test/GetEuclidGCD.test.js b/Maths/test/GetEuclidGCD.test.js new file mode 100644 index 0000000000..92f888acea --- /dev/null +++ b/Maths/test/GetEuclidGCD.test.js @@ -0,0 +1,22 @@ +import { GetEuclidGCD, GetEuclidGCDRecursive } from '../GetEuclidGCD' + +describe.each([GetEuclidGCD, GetEuclidGCDRecursive])('%o', (gcdFunction) => { + it.each([ + [5, 20, 5], + [109, 902, 1], + [290, 780, 10], + [104, 156, 52], + [0, 100, 100], + [-5, 50, 5], + [0, 0, 0], + [1, 1234567, 1] + ])('returns correct result for %i and %j', (inputA, inputB, expected) => { + expect(gcdFunction(inputA, inputB)).toBe(expected) + expect(gcdFunction(inputB, inputA)).toBe(expected) + }) + + it('should throw when any of the inputs is not a number', () => { + expect(() => gcdFunction('1', 2)).toThrowError() + expect(() => gcdFunction(1, '2')).toThrowError() + }) +}) diff --git a/Maths/test/GridGet.test.js b/Maths/test/GridGet.test.js new file mode 100644 index 0000000000..eef51fc6f0 --- /dev/null +++ b/Maths/test/GridGet.test.js @@ -0,0 +1,16 @@ +import { gridGetX, gridGetY } from '../GridGet' + +describe('GridGet', () => { + it('should have a value of x for the 27th element if the square array has 400 elements', () => { + expect(gridGetX(Math.sqrt(400), 27)).toEqual(8) + }) + it('should have a value of x for the 11th element if the square array has 7 columns and 3 rows', () => { + expect(gridGetX(7, 11)).toEqual(5) + }) + it('should have a value of y for the 27th element if the square array has 400 elements', () => { + expect(gridGetY(Math.sqrt(400), 27)).toEqual(2) + }) + it('should have a value of y for the 11th element if the square array has 7 columns and 3 rows ', () => { + expect(gridGetX(7, 11)).toEqual(5) + }) +}) diff --git a/Maths/test/HexagonalNumber.test.js b/Maths/test/HexagonalNumber.test.js new file mode 100644 index 0000000000..eab2ea0043 --- /dev/null +++ b/Maths/test/HexagonalNumber.test.js @@ -0,0 +1,29 @@ +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 0000000000..9ebcbbf480 --- /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/IsDivisible.test.js b/Maths/test/IsDivisible.test.js new file mode 100644 index 0000000000..76e6769958 --- /dev/null +++ b/Maths/test/IsDivisible.test.js @@ -0,0 +1,45 @@ +import { isDivisible } from '../IsDivisible' + +describe('isDivisible', () => { + const testCases = [ + [0, 1, true], + [0, 2, true], + [1, 1, true], + [1, 2, false], + [2, 1, true], + [4, 4, true], + [16, 4, true], + [36978235, 5, true], + [36978235, 4, false], + [4.5, 1.5, true], + [4.5, 1.2, false], + [5, 0, false], + [5, -0, false] + ] + + test.each(testCases)( + 'if parameters are (%i, %i) it returns %p', + (dividend, divisor, expected) => { + expect(isDivisible(dividend, divisor)).toBe(expected) + } + ) + + const errorCases = [ + [NaN, NaN], + [NaN, 1], + [1, NaN], + ['1', 1], + [1, '1'], + [1, true], + [false, 2] + ] + + test.each(errorCases)( + 'throws an error if parameters are (%p, %p)', + (dividend, divisor) => { + expect(() => { + isDivisible(dividend, divisor) + }).toThrow() + } + ) +}) diff --git a/Maths/test/IsEven.test.js b/Maths/test/IsEven.test.js new file mode 100644 index 0000000000..804b7bf481 --- /dev/null +++ b/Maths/test/IsEven.test.js @@ -0,0 +1,25 @@ +import { isEven, isEvenBitwise } from '../IsEven' + +describe('Testing isEven function', () => { + it('should return if the number is even or not', () => { + const isEvenNumber = isEven(4) + expect(isEvenNumber).toBe(true) + }) + + it('should return if the number is even or not', () => { + const isEvenNumber = isEven(7) + expect(isEvenNumber).toBe(false) + }) +}) + +describe('Testing isEvenBitwise function', () => { + it('should return if the number is even or not', () => { + const isEvenNumber = isEvenBitwise(6) + expect(isEvenNumber).toBe(true) + }) + + it('should return if the number is even or not', () => { + const isEvenNumber = isEvenBitwise(3) + expect(isEvenNumber).toBe(false) + }) +}) diff --git a/Maths/test/IsOdd.test.js b/Maths/test/IsOdd.test.js new file mode 100644 index 0000000000..477044b75a --- /dev/null +++ b/Maths/test/IsOdd.test.js @@ -0,0 +1,25 @@ +import { isOdd, isOddBitwise } from '../IsOdd' + +describe('Testing the isOdd function', () => { + it('should return true, if the number is odd', () => { + const isOddNumber = isOdd(4) + expect(isOddNumber).toBe(false) + }) + + it('should return true, if the number is odd', () => { + const isOddNumber = isOdd(7) + expect(isOddNumber).toBe(true) + }) +}) + +describe('Testing the isOddBitwise function', () => { + it('should return true, if the number is odd', () => { + const isOddNumber = isOddBitwise(6) + expect(isOddNumber).toBe(false) + }) + + it('should return true, if the number is odd', () => { + const isOddNumber = isOddBitwise(3) + expect(isOddNumber).toBe(true) + }) +}) diff --git a/Maths/test/IsPronic.test.js b/Maths/test/IsPronic.test.js new file mode 100644 index 0000000000..f46aef69f1 --- /dev/null +++ b/Maths/test/IsPronic.test.js @@ -0,0 +1,17 @@ +import { isPronic } from '../IsPronic' + +const pronicNumbers = [ + 0, 2, 6, 12, 20, 30, 42, 56, 72, 90, 110, 132, 156, 182, 210, 240, 272, 306, + 342, 380, 420, 462, 506, 552, 600, 650, 702, 756, 812, 870, 930, 992, 1056, + 1122, 1190, 1260, 1332, 1406, 1482, 1560, 1640, 1722, 1806, 1892, 1980, 2070, + 2162, 2256, 2352, 2450, 2550 +] + +describe('Testing isPronic function', () => { + for (let i = 0; i <= 2500; i++) { + it('should return true', () => { + const isPronicNumber = isPronic(i) + expect(isPronicNumber).toBe(pronicNumbers.includes(i)) + }) + } +}) diff --git a/Maths/test/IsSquareFree.test.js b/Maths/test/IsSquareFree.test.js new file mode 100644 index 0000000000..17bc67018c --- /dev/null +++ b/Maths/test/IsSquareFree.test.js @@ -0,0 +1,135 @@ +import { isSquareFree } from '../IsSquareFree' + +const squareFreeNumbers = [ + 1, 2, 3, 5, 6, 7, 10, 11, 13, 14, 15, 17, 19, 21, 22, 23, 26, 29, 30, 31, 33, + 34, 35, 37, 38, 39, 41, 42, 43, 46, 47, 51, 53, 55, 57, 58, 59, 61, 62, 65, + 66, 67, 69, 70, 71, 73, 74, 77, 78, 79, 82, 83, 85, 86, 87, 89, 91, 93, 94, + 95, 97, 101, 102, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115, 118, 119, + 122, 123, 127, 129, 130, 131, 133, 134, 137, 138, 139, 141, 142, 143, 145, + 146, 149, 151, 154, 155, 157, 158, 159, 161, 163, 165, 166, 167, 170, 173, + 174, 177, 178, 179, 181, 182, 183, 185, 186, 187, 190, 191, 193, 194, 195, + 197, 199, 201, 202, 203, 205, 206, 209, 210, 211, 213, 214, 215, 217, 218, + 219, 221, 222, 223, 226, 227, 229, 230, 231, 233, 235, 237, 238, 239, 241, + 246, 247, 249, 251, 253, 254, 255, 257, 258, 259, 262, 263, 265, 266, 267, + 269, 271, 273, 274, 277, 278, 281, 282, 283, 285, 286, 287, 290, 291, 293, + 295, 298, 299, 301, 302, 303, 305, 307, 309, 310, 311, 313, 314, 317, 318, + 319, 321, 322, 323, 326, 327, 329, 330, 331, 334, 335, 337, 339, 341, 345, + 346, 347, 349, 353, 354, 355, 357, 358, 359, 362, 365, 366, 367, 370, 371, + 373, 374, 377, 379, 381, 382, 383, 385, 386, 389, 390, 391, 393, 394, 395, + 397, 398, 399, 401, 402, 403, 406, 407, 409, 410, 411, 413, 415, 417, 418, + 419, 421, 422, 426, 427, 429, 430, 431, 433, 434, 435, 437, 438, 439, 442, + 443, 445, 446, 447, 449, 451, 453, 454, 455, 457, 458, 461, 462, 463, 465, + 466, 467, 469, 470, 471, 473, 474, 478, 479, 481, 482, 483, 485, 487, 489, + 491, 493, 494, 497, 498, 499, 501, 502, 503, 505, 506, 509, 510, 511, 514, + 515, 517, 518, 519, 521, 523, 526, 527, 530, 533, 534, 535, 537, 538, 541, + 542, 543, 545, 546, 547, 551, 553, 554, 555, 557, 559, 561, 562, 563, 565, + 566, 569, 570, 571, 573, 574, 577, 579, 581, 582, 583, 586, 587, 589, 590, + 591, 593, 595, 597, 598, 599, 601, 602, 606, 607, 609, 610, 611, 613, 614, + 615, 617, 618, 619, 622, 623, 626, 627, 629, 631, 633, 634, 635, 638, 641, + 642, 643, 645, 646, 647, 649, 651, 653, 654, 655, 658, 659, 661, 662, 663, + 665, 667, 669, 670, 671, 673, 674, 677, 678, 679, 681, 682, 683, 685, 687, + 689, 690, 691, 694, 695, 697, 698, 699, 701, 703, 705, 706, 707, 709, 710, + 713, 714, 715, 717, 718, 719, 721, 723, 727, 730, 731, 733, 734, 737, 739, + 741, 742, 743, 745, 746, 749, 751, 753, 754, 755, 757, 758, 759, 761, 762, + 763, 766, 767, 769, 770, 771, 773, 777, 778, 779, 781, 782, 785, 786, 787, + 789, 790, 791, 793, 794, 795, 797, 798, 799, 802, 803, 805, 806, 807, 809, + 811, 813, 814, 815, 817, 818, 821, 822, 823, 826, 827, 829, 830, 831, 834, + 835, 838, 839, 842, 843, 849, 851, 853, 854, 857, 858, 859, 861, 862, 863, + 865, 866, 869, 870, 871, 874, 877, 878, 879, 881, 883, 885, 886, 887, 889, + 890, 893, 894, 895, 897, 898, 899, 901, 902, 903, 905, 906, 907, 910, 911, + 913, 914, 915, 917, 919, 921, 922, 923, 926, 929, 930, 933, 934, 935, 937, + 938, 939, 941, 942, 943, 946, 947, 949, 951, 953, 955, 957, 958, 959, 962, + 965, 966, 967, 969, 970, 971, 973, 974, 977, 978, 979, 982, 983, 985, 986, + 987, 989, 991, 993, 994, 995, 997, 998, 1001, 1002, 1003, 1005, 1006, 1007, + 1009, 1010, 1011, 1013, 1015, 1018, 1019, 1021, 1022, 1023, 1027, 1030, 1031, + 1033, 1034, 1037, 1038, 1039, 1041, 1042, 1043, 1045, 1046, 1047, 1049, 1051, + 1054, 1055, 1057, 1059, 1061, 1063, 1065, 1066, 1067, 1069, 1070, 1073, 1074, + 1077, 1079, 1081, 1082, 1085, 1086, 1087, 1090, 1091, 1093, 1094, 1095, 1097, + 1099, 1101, 1102, 1103, 1105, 1106, 1109, 1110, 1111, 1113, 1114, 1115, 1117, + 1118, 1119, 1121, 1122, 1123, 1126, 1129, 1130, 1131, 1133, 1135, 1137, 1138, + 1139, 1141, 1142, 1145, 1146, 1147, 1149, 1151, 1153, 1154, 1155, 1157, 1158, + 1159, 1162, 1163, 1165, 1166, 1167, 1169, 1171, 1173, 1174, 1177, 1178, 1181, + 1182, 1185, 1186, 1187, 1189, 1190, 1191, 1193, 1194, 1195, 1198, 1199, 1201, + 1202, 1203, 1205, 1207, 1209, 1211, 1213, 1214, 1217, 1218, 1219, 1221, 1222, + 1223, 1226, 1227, 1229, 1230, 1231, 1234, 1235, 1237, 1238, 1239, 1241, 1243, + 1245, 1246, 1247, 1249, 1253, 1254, 1255, 1257, 1258, 1259, 1261, 1262, 1263, + 1265, 1266, 1267, 1270, 1271, 1273, 1277, 1279, 1281, 1282, 1283, 1285, 1286, + 1289, 1290, 1291, 1293, 1294, 1295, 1297, 1298, 1299, 1301, 1302, 1303, 1306, + 1307, 1309, 1310, 1311, 1313, 1315, 1317, 1318, 1319, 1321, 1322, 1326, 1327, + 1329, 1330, 1333, 1334, 1335, 1337, 1338, 1339, 1342, 1343, 1345, 1346, 1347, + 1349, 1351, 1353, 1354, 1355, 1357, 1358, 1361, 1362, 1363, 1365, 1366, 1367, + 1370, 1371, 1373, 1374, 1378, 1379, 1381, 1382, 1383, 1385, 1387, 1389, 1390, + 1391, 1393, 1394, 1397, 1398, 1399, 1401, 1402, 1403, 1405, 1406, 1407, 1409, + 1410, 1411, 1414, 1415, 1417, 1418, 1419, 1423, 1426, 1427, 1429, 1430, 1433, + 1434, 1435, 1437, 1438, 1439, 1441, 1442, 1443, 1446, 1447, 1451, 1453, 1454, + 1455, 1457, 1459, 1461, 1462, 1463, 1465, 1466, 1469, 1471, 1473, 1474, 1477, + 1478, 1479, 1481, 1482, 1483, 1486, 1487, 1489, 1490, 1491, 1493, 1495, 1497, + 1498, 1499, 1501, 1502, 1505, 1506, 1507, 1509, 1510, 1511, 1513, 1514, 1515, + 1517, 1518, 1522, 1523, 1526, 1527, 1529, 1531, 1533, 1534, 1535, 1537, 1538, + 1541, 1542, 1543, 1545, 1546, 1547, 1549, 1551, 1553, 1554, 1555, 1558, 1559, + 1561, 1562, 1563, 1565, 1567, 1569, 1570, 1571, 1574, 1577, 1578, 1579, 1581, + 1582, 1583, 1585, 1586, 1589, 1590, 1591, 1594, 1595, 1597, 1598, 1599, 1601, + 1603, 1605, 1606, 1607, 1609, 1610, 1613, 1614, 1615, 1618, 1619, 1621, 1622, + 1623, 1626, 1627, 1630, 1631, 1633, 1634, 1635, 1637, 1639, 1641, 1642, 1643, + 1645, 1646, 1649, 1651, 1653, 1654, 1655, 1657, 1658, 1659, 1661, 1662, 1663, + 1667, 1669, 1670, 1671, 1673, 1677, 1678, 1679, 1685, 1686, 1687, 1689, 1691, + 1693, 1695, 1697, 1698, 1699, 1702, 1703, 1705, 1706, 1707, 1709, 1711, 1713, + 1714, 1717, 1718, 1721, 1722, 1723, 1726, 1727, 1729, 1730, 1731, 1733, 1735, + 1738, 1739, 1741, 1742, 1743, 1745, 1747, 1749, 1751, 1753, 1754, 1757, 1758, + 1759, 1761, 1762, 1763, 1765, 1766, 1767, 1769, 1770, 1771, 1774, 1777, 1778, + 1779, 1781, 1783, 1785, 1786, 1787, 1789, 1790, 1793, 1794, 1795, 1797, 1798, + 1799, 1801, 1802, 1803, 1806, 1807, 1810, 1811, 1814, 1817, 1819, 1821, 1822, + 1823, 1826, 1829, 1830, 1831, 1833, 1834, 1835, 1837, 1838, 1839, 1841, 1842, + 1843, 1846, 1847, 1851, 1853, 1855, 1857, 1858, 1861, 1865, 1866, 1867, 1869, + 1870, 1871, 1873, 1874, 1877, 1878, 1879, 1882, 1883, 1885, 1886, 1887, 1889, + 1891, 1893, 1894, 1895, 1897, 1898, 1901, 1902, 1903, 1905, 1906, 1907, 1909, + 1910, 1913, 1914, 1915, 1918, 1919, 1921, 1923, 1927, 1929, 1930, 1931, 1933, + 1934, 1937, 1938, 1939, 1941, 1942, 1943, 1945, 1946, 1947, 1949, 1951, 1954, + 1955, 1957, 1958, 1959, 1961, 1963, 1965, 1966, 1967, 1969, 1970, 1973, 1974, + 1977, 1978, 1979, 1981, 1982, 1983, 1985, 1986, 1987, 1990, 1991, 1993, 1994, + 1995, 1997, 1999, 2001, 2002, 2003, 2005, 2006, 2010, 2011, 2013, 2014, 2015, + 2017, 2018, 2019, 2021, 2022, 2026, 2027, 2029, 2030, 2031, 2033, 2035, 2037, + 2038, 2039, 2041, 2042, 2045, 2046, 2047, 2049, 2051, 2053, 2054, 2055, 2059, + 2062, 2063, 2065, 2066, 2067, 2069, 2071, 2073, 2074, 2077, 2078, 2081, 2082, + 2083, 2085, 2086, 2087, 2089, 2090, 2091, 2093, 2094, 2095, 2098, 2099, 2101, + 2102, 2103, 2105, 2109, 2110, 2111, 2113, 2114, 2117, 2118, 2119, 2121, 2122, + 2123, 2126, 2127, 2129, 2130, 2131, 2134, 2135, 2137, 2138, 2139, 2141, 2143, + 2145, 2146, 2147, 2149, 2153, 2154, 2155, 2157, 2158, 2159, 2161, 2162, 2163, + 2165, 2167, 2170, 2171, 2173, 2174, 2177, 2179, 2181, 2182, 2183, 2185, 2186, + 2189, 2190, 2191, 2193, 2194, 2195, 2198, 2199, 2201, 2202, 2203, 2206, 2207, + 2210, 2211, 2213, 2215, 2217, 2218, 2219, 2221, 2222, 2226, 2227, 2229, 2230, + 2231, 2233, 2234, 2235, 2237, 2238, 2239, 2242, 2243, 2245, 2246, 2247, 2249, + 2251, 2253, 2255, 2257, 2258, 2261, 2262, 2263, 2265, 2266, 2267, 2269, 2270, + 2271, 2273, 2274, 2278, 2279, 2281, 2282, 2283, 2285, 2287, 2289, 2290, 2291, + 2293, 2294, 2297, 2298, 2301, 2302, 2305, 2306, 2307, 2309, 2310, 2311, 2314, + 2315, 2317, 2318, 2319, 2321, 2323, 2326, 2327, 2329, 2330, 2333, 2334, 2335, + 2337, 2338, 2339, 2341, 2342, 2343, 2345, 2346, 2347, 2351, 2353, 2354, 2355, + 2357, 2359, 2361, 2362, 2363, 2365, 2369, 2370, 2371, 2373, 2374, 2377, 2378, + 2379, 2381, 2382, 2383, 2386, 2387, 2389, 2390, 2391, 2393, 2395, 2397, 2398, + 2399, 2402, 2405, 2406, 2407, 2409, 2410, 2411, 2413, 2414, 2415, 2417, 2418, + 2419, 2422, 2423, 2426, 2427, 2429, 2431, 2433, 2434, 2435, 2437, 2438, 2441, + 2442, 2443, 2445, 2446, 2447, 2449, 2451, 2453, 2454, 2455, 2458, 2459, 2461, + 2462, 2463, 2465, 2467, 2469, 2470, 2471, 2473, 2474, 2477, 2478, 2479, 2481, + 2482, 2483, 2485, 2486, 2487, 2489, 2490, 2491, 2494, 2495, 2497, 2498 +] + +describe('Testing isSquareFree function', () => { + for (let i = 1; i <= 2500; i++) { + it('should return true or false', () => { + const isSquareFreeNumber = isSquareFree(i) + expect(isSquareFreeNumber).toBe(squareFreeNumbers.includes(i)) + }) + } + + it('should throw error when supplied negative numbers', () => { + expect(() => { + isSquareFree(-1) + }).toThrow(Error) + }) + + it('should throw error when supplied zero', () => { + expect(() => { + isSquareFree(0) + }).toThrow(Error) + }) +}) diff --git a/Maths/test/JugglerSequence.test.js b/Maths/test/JugglerSequence.test.js new file mode 100644 index 0000000000..392047a95b --- /dev/null +++ b/Maths/test/JugglerSequence.test.js @@ -0,0 +1,21 @@ +import { jugglerSequence } from '../JugglerSequence' + +describe('Testing jugglerSequence function', () => { + it('should return [3, 5, 11, 36, 6, 2, 1 ] if the number is 3', () => { + expect(jugglerSequence(3)).toEqual( + expect.arrayContaining([3, 5, 11, 36, 6, 2, 1]) + ) + }) + + it('should return [9, 27, 140, 11, 36, 6, 2, 1] if the number is 9', () => { + expect(jugglerSequence(9)).toEqual( + expect.arrayContaining([9, 27, 140, 11, 36, 6, 2, 1]) + ) + }) + + it('should return [15, 58, 7, 18, 4, 2, 1] if the number is 15', () => { + expect(jugglerSequence(15)).toEqual( + expect.arrayContaining([15, 58, 7, 18, 4, 2, 1]) + ) + }) +}) diff --git a/Maths/test/LeapYear.test.js b/Maths/test/LeapYear.test.js new file mode 100644 index 0000000000..c30786c0e1 --- /dev/null +++ b/Maths/test/LeapYear.test.js @@ -0,0 +1,22 @@ +import { isLeapYear } from '../LeapYear' + +describe('Leap Year', () => { + it('Should return true on the year 2000', () => { + expect(isLeapYear(2000)).toBe(true) + }) + it('Should return false on the year 2001', () => { + expect(isLeapYear(2001)).toBe(false) + }) + it('Should return false on the year 2002', () => { + expect(isLeapYear(2002)).toBe(false) + }) + it('Should return false on the year 2003', () => { + expect(isLeapYear(2003)).toBe(false) + }) + it('Should return false on the year 2004', () => { + expect(isLeapYear(2004)).toBe(true) + }) + it('Should return false on the year 1900', () => { + expect(isLeapYear(1900)).toBe(false) + }) +}) diff --git a/Maths/test/LinearSieve.test.js b/Maths/test/LinearSieve.test.js new file mode 100644 index 0000000000..738d04d930 --- /dev/null +++ b/Maths/test/LinearSieve.test.js @@ -0,0 +1,19 @@ +import { LinearSieve } from '../LinearSieve' +import { PrimeCheck } from '../PrimeCheck' + +describe('LinearSieve', () => { + it('should return primes below 100', () => { + expect(LinearSieve(100)).toEqual([ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, + 71, 73, 79, 83, 89, 97 + ]) + }) + + it('should return primes only', () => { + const n = 100000 + const primes = LinearSieve(n) + for (const p of primes) { + expect(PrimeCheck(p)).toBeTruthy() + } + }) +}) diff --git a/Maths/test/LiouvilleFunction.test.js b/Maths/test/LiouvilleFunction.test.js new file mode 100644 index 0000000000..9de92d234b --- /dev/null +++ b/Maths/test/LiouvilleFunction.test.js @@ -0,0 +1,32 @@ +import { liouvilleFunction } from '../LiouvilleFunction' + +const expectedValuesArray = [ + 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, + -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, + -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, + -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, + -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1 +] + +describe('Testing liouville function', () => { + for (let i = 1; i <= 100; i++) { + it( + 'Testing for number = ' + i + ', should return ' + expectedValuesArray[i], + () => { + expect(liouvilleFunction(i)).toBe(expectedValuesArray[i - 1]) + } + ) + } + + it('should throw error when supplied negative numbers', () => { + expect(() => { + liouvilleFunction(-1) + }).toThrow(Error) + }) + + it('should throw error when supplied zero', () => { + expect(() => { + liouvilleFunction(0) + }).toThrow(Error) + }) +}) diff --git a/Maths/test/LucasSeries.test.js b/Maths/test/LucasSeries.test.js new file mode 100644 index 0000000000..21d6d09042 --- /dev/null +++ b/Maths/test/LucasSeries.test.js @@ -0,0 +1,15 @@ +import { lucas } from '../LucasSeries' + +describe('Nth Lucas Number', () => { + it('should return the 20th Lucas Number', () => { + expect(lucas(20)).toBe(15127) + }) + + it('should return the 20th Lucas Number', () => { + expect(lucas(0)).toBe(2) + }) + + it('should return the 20th Lucas Number', () => { + expect(lucas(100)).toBe(792070839848372100000) + }) +}) diff --git a/Maths/test/Mandelbrot.manual-test.js b/Maths/test/Mandelbrot.manual-test.js new file mode 100644 index 0000000000..a2b4f70779 --- /dev/null +++ b/Maths/test/Mandelbrot.manual-test.js @@ -0,0 +1,20 @@ +import { getRGBData } from '../Mandelbrot' + +// plot the results if the script is executed in a browser with a window-object +if (typeof window !== 'undefined') { + const rgbData = getRGBData() + const width = rgbData.length + const height = rgbData[0].length + const canvas = document.createElement('canvas') + canvas.width = width + canvas.height = height + const ctx = canvas.getContext('2d') + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + const rgb = rgbData[x][y] + ctx.fillStyle = 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')' + ctx.fillRect(x, y, 1, 1) + } + } + document.body.append(canvas) +} diff --git a/Maths/test/Mandelbrot.test.js b/Maths/test/Mandelbrot.test.js new file mode 100644 index 0000000000..dbbad41608 --- /dev/null +++ b/Maths/test/Mandelbrot.test.js @@ -0,0 +1,21 @@ +import { getRGBData } from '../Mandelbrot' + +describe('Mandelbrot', () => { + it('should produce black pixels inside the set', () => { + const blackAndWhite = getRGBData(800, 600, -0.6, 0, 3.2, 50, false) + expect(blackAndWhite[400][300]).toEqual([0, 0, 0]) // black + + const colorCoded = getRGBData(800, 600, -0.6, 0, 3.2, 50, true) + expect(colorCoded[400][300]).toEqual([0, 0, 0]) // black + }) + + it('should produce white pixels outside of the set', () => { + const blackAndWhite = getRGBData(800, 600, -0.6, 0, 3.2, 50, false) + expect(blackAndWhite[0][0]).toEqual([255, 255, 255]) // black + }) + + it('should produce colored pixels distant to the set', () => { + const colorCoded = getRGBData(800, 600, -0.6, 0, 3.2, 50, true) + expect(colorCoded[0][0]).toEqual([255, 0, 0]) // red + }) +}) diff --git a/Maths/test/MeanAbsoluteDeviation.test.js b/Maths/test/MeanAbsoluteDeviation.test.js new file mode 100644 index 0000000000..d44eb7d06e --- /dev/null +++ b/Maths/test/MeanAbsoluteDeviation.test.js @@ -0,0 +1,16 @@ +import { meanAbsoluteDeviation } from '../MeanAbsoluteDeviation.js' + +describe('tests for mean absolute deviation', () => { + it('should be a function', () => { + expect(typeof meanAbsoluteDeviation).toEqual('function') + }) + + it('should throw an invalid input error', () => { + expect(() => meanAbsoluteDeviation('fgh')).toThrow() + }) + + it('should return the mean absolute deviation of an array of numbers', () => { + const meanAbDev = meanAbsoluteDeviation([2, 34, 5, 0, -2]) + expect(meanAbDev).toBe(10.479999999999999) + }) +}) diff --git a/Maths/test/MeanSquareError.test.js b/Maths/test/MeanSquareError.test.js new file mode 100644 index 0000000000..ecd53de89a --- /dev/null +++ b/Maths/test/MeanSquareError.test.js @@ -0,0 +1,21 @@ +import { meanSquaredError } from '../MeanSquareError' + +describe('meanSquareError', () => { + it('should throw an error on non-array arguments', () => { + expect(() => meanSquaredError(1, 4)).toThrow('Argument must be an Array') + }) + + it('should throw an error on non equal length ', () => { + const firstArr = [1, 2, 3, 4, 5] + const secondArr = [1, 2, 3] + expect(() => meanSquaredError(firstArr, secondArr)).toThrow( + 'The two lists must be of equal length' + ) + }) + + it('should return the mean square error of two equal length arrays', () => { + const firstArr = [1, 2, 3, 4, 5] + const secondArr = [1, 3, 5, 6, 7] + expect(meanSquaredError(firstArr, secondArr)).toBe(2.6) + }) +}) diff --git a/Maths/test/MidpointIntegration.test.js b/Maths/test/MidpointIntegration.test.js new file mode 100644 index 0000000000..6bf9da28ea --- /dev/null +++ b/Maths/test/MidpointIntegration.test.js @@ -0,0 +1,22 @@ +import { integralEvaluation } from '../MidpointIntegration' + +test('Should return the integral of f(x) = sqrt(x) in [1, 3] to be equal 2.797434', () => { + const result = integralEvaluation(10000, 1, 3, (x) => { + return Math.sqrt(x) + }) + expect(Number(result.toPrecision(6))).toBe(2.79743) +}) + +test('Should return the integral of f(x) = sqrt(x) + x^2 in [1, 3] to be equal 11.46410161', () => { + const result = integralEvaluation(10000, 1, 3, (x) => { + return Math.sqrt(x) + Math.pow(x, 2) + }) + expect(Number(result.toPrecision(10))).toBe(11.46410161) +}) + +test('Should return the integral of f(x) = log(x) + Pi*x^3 in [5, 12] to be equal 15809.9141543', () => { + const result = integralEvaluation(20000, 5, 12, (x) => { + return Math.log(x) + Math.PI * Math.pow(x, 3) + }) + expect(Number(result.toPrecision(10))).toBe(15809.91415) +}) diff --git a/Maths/test/MobiusFunction.test.js b/Maths/test/MobiusFunction.test.js new file mode 100644 index 0000000000..11de678212 --- /dev/null +++ b/Maths/test/MobiusFunction.test.js @@ -0,0 +1,32 @@ +import { mobiusFunction } from '../MobiusFunction' + +const expectedValuesArray = [ + 1, -1, -1, 0, -1, 1, -1, 0, 0, 1, -1, 0, -1, 1, 1, 0, -1, 0, -1, 0, 1, 1, -1, + 0, 0, 1, 0, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, 1, 1, 0, -1, -1, -1, 0, 0, 1, + -1, 0, 0, 0, 1, 0, -1, 0, 1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 1, -1, -1, 0, 1, -1, + -1, 0, -1, 1, 0, 0, 1, -1, -1, 0, 0, 1, -1, 0, 1, 1, 1, 0, -1, 0, 1, 0, 1, 1, + 1, 0, -1, 0, 0, 0 +] + +describe('Testing mobius function', () => { + for (let i = 1; i <= 100; i++) { + it( + 'Testing for number = ' + i + ', should return ' + expectedValuesArray[i], + () => { + expect(mobiusFunction(i)).toBe(expectedValuesArray[i - 1]) + } + ) + } + + it('should throw error when supplied negative numbers', () => { + expect(() => { + mobiusFunction(-1) + }).toThrow(Error) + }) + + it('should throw error when supplied zero', () => { + expect(() => { + mobiusFunction(0) + }).toThrow(Error) + }) +}) diff --git a/Maths/test/ModularArithmetic.test.js b/Maths/test/ModularArithmetic.test.js new file mode 100644 index 0000000000..af42e243de --- /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/ModularBinaryExponentiationRecursive.test.js b/Maths/test/ModularBinaryExponentiationRecursive.test.js new file mode 100644 index 0000000000..9758d0ed1d --- /dev/null +++ b/Maths/test/ModularBinaryExponentiationRecursive.test.js @@ -0,0 +1,7 @@ +import { modularBinaryExponentiation } from '../ModularBinaryExponentiationRecursive' + +describe('modularBinaryExponentiation', () => { + it('should return the binary exponentiation', () => { + expect(modularBinaryExponentiation(2, 10, 17)).toBe(4) + }) +}) diff --git a/Maths/test/NumberOfDigits.test.js b/Maths/test/NumberOfDigits.test.js new file mode 100644 index 0000000000..e3495eb9ea --- /dev/null +++ b/Maths/test/NumberOfDigits.test.js @@ -0,0 +1,23 @@ +import { numberOfDigit, numberOfDigitsUsingLog } from '../NumberOfDigits' + +describe('NumberOfDigits', () => { + it('should return the correct number of digits for an integer', () => { + expect(numberOfDigit(1234000)).toBe(7) + }) + + it('should return the correct number of digits for a negative number', () => { + expect(numberOfDigit(-2346243)).toBe(7) + }) + + it.each([ + [0, 1], + [123423232, 9], + [-123423232, 9], + [9999, 4] + ])( + 'should return the correct number of digits in an integer', + (value, expected) => { + expect(numberOfDigitsUsingLog(value)).toBe(expected) + } + ) +}) diff --git a/Maths/test/Palindrome.test.js b/Maths/test/Palindrome.test.js new file mode 100644 index 0000000000..76e3ada66b --- /dev/null +++ b/Maths/test/Palindrome.test.js @@ -0,0 +1,29 @@ +import { + PalindromeRecursive, + PalindromeIterative, + checkPalindrome +} from '../Palindrome' + +describe('Palindrome', () => { + it('should return true for a palindrome for PalindromeRecursive', () => { + expect(PalindromeRecursive('mom')).toBeTruthy() + }) + it('should return true for a palindrome for PalindromeIterative', () => { + expect(PalindromeIterative('mom')).toBeTruthy() + }) + it('should return false for a non-palindrome for PalindromeRecursive', () => { + expect(PalindromeRecursive('Algorithms')).toBeFalsy() + }) + it('should return true for a non-palindrome for PalindromeIterative', () => { + expect(PalindromeIterative('JavaScript')).toBeFalsy() + }) + + it.each([ + ['radar', true], + ['hello', false], + ['r', true], + [' racecar ', true] + ])('should return value is palindrome or not', (value, expected) => { + expect(checkPalindrome(value)).toBe(expected) + }) +}) diff --git a/Maths/test/ParityOutlier.test.js b/Maths/test/ParityOutlier.test.js new file mode 100644 index 0000000000..f4de9162b1 --- /dev/null +++ b/Maths/test/ParityOutlier.test.js @@ -0,0 +1,25 @@ +import { parityOutlier } from '../ParityOutlier' + +describe('Testing parityOutlier function', () => { + it('should return the odd number in an array of even numbers', () => { + expect(parityOutlier([1, 2, 16, -8848, 5126])).toBe(1) + }) + + it('should return the even number in an array of odd numbers', () => { + expect(parityOutlier([177, 5, 76, 1919])).toBe(76) + }) + + it('should, if the given array has only an odd and an even number, return the odd outlier', () => { + expect(parityOutlier([1, 2])).toBe(1) + expect(parityOutlier([4, 3])).toBe(3) + }) + + it('should return null if the given array is empty, contains only one integer, contains non-interger elements or does not have a parity outlier', () => { + expect(parityOutlier([])).toBe(null) + expect(parityOutlier([2])).toBe(null) + expect(parityOutlier([2, {}, 5, 'GitHub'])).toBe(null) + expect(parityOutlier([1, 3, 5, 7, 9])).toBe(null) + expect(parityOutlier([0, 2, 4, 6, 8])).toBe(null) + expect(parityOutlier([1, 3, 5, 7, 2, 4, 6, 8])).toBe(null) + }) +}) diff --git a/Maths/test/PascalTriangle.test.js b/Maths/test/PascalTriangle.test.js new file mode 100644 index 0000000000..65c736f14f --- /dev/null +++ b/Maths/test/PascalTriangle.test.js @@ -0,0 +1,20 @@ +import { expect } from 'vitest' +import { generate } from '../PascalTriangle' + +describe('Pascals Triangle', () => { + it.each([ + [0, []], + [1, [[1]]], + [2, [[1], [1, 1]]], + [3, [[1], [1, 1], [1, 2, 1]]], + [4, [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]], + [5, [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]], [1, 4, 6, 4, 1]] + ])('check with %j', (input, expected) => { + const pascalsTriangle = generate(input) + expect(pascalsTriangle.length).toEqual(input) + pascalsTriangle.forEach((arr, index) => { + expect(arr.length).toEqual(index + 1) + }) + expect(pascalsTriangle).toEqual(expect.arrayContaining(expected)) + }) +}) diff --git a/Maths/test/PerfectCube.test.js b/Maths/test/PerfectCube.test.js new file mode 100644 index 0000000000..f3c4f219e6 --- /dev/null +++ b/Maths/test/PerfectCube.test.js @@ -0,0 +1,12 @@ +import { perfectCube } from '../PerfectCube' + +describe('PerfectCube', () => { + it('should return true for a perfect cube', () => { + expect(perfectCube(125)).toBeTruthy() + expect(perfectCube(-125)).toBeTruthy() + }) + it('should return false for a non perfect cube', () => { + expect(perfectCube(100)).toBeFalsy() + expect(perfectCube(Infinity)).toBeFalsy() + }) +}) diff --git a/Maths/test/PerfectNumber.test.js b/Maths/test/PerfectNumber.test.js new file mode 100644 index 0000000000..55b7d84269 --- /dev/null +++ b/Maths/test/PerfectNumber.test.js @@ -0,0 +1,10 @@ +import { perfectNumber } from '../PerfectNumber' + +describe('PerfectNumber', () => { + it('should return true for a perfect cube', () => { + expect(perfectNumber(28)).toBeTruthy() + }) + it('should return false for a non perfect cube', () => { + expect(perfectNumber(10)).toBeFalsy() + }) +}) diff --git a/Maths/test/PerfectSquare.test.js b/Maths/test/PerfectSquare.test.js new file mode 100644 index 0000000000..37fce248d4 --- /dev/null +++ b/Maths/test/PerfectSquare.test.js @@ -0,0 +1,11 @@ +import { perfectSquare } from '../PerfectSquare' + +describe('PerfectSquare', () => { + it('should return true for a perfect square', () => { + expect(perfectSquare(16)).toBeTruthy() + }) + it('should return false for a non perfect square', () => { + expect(perfectSquare(10)).toBeFalsy() + expect(perfectSquare(Infinity)).toBeFalsy() + }) +}) diff --git a/Maths/test/PermutationAndCombination.test.js b/Maths/test/PermutationAndCombination.test.js new file mode 100644 index 0000000000..0468366f36 --- /dev/null +++ b/Maths/test/PermutationAndCombination.test.js @@ -0,0 +1,23 @@ +import { + factorial, + permutation, + combination +} from '../PermutationAndCombination' + +describe('Factorial', () => { + it('factorial(5)', () => { + expect(factorial(5)).toBe(120) + }) +}) + +describe('Permutation', () => { + it('permutation(5, 2)', () => { + expect(permutation(5, 2)).toBe(20) + }) +}) + +describe('Combination', () => { + it('combination(5, 2)', () => { + expect(combination(5, 2)).toBe(10) + }) +}) diff --git a/Maths/test/PiApproximationMonteCarlo.test.js b/Maths/test/PiApproximationMonteCarlo.test.js new file mode 100644 index 0000000000..9727aa5788 --- /dev/null +++ b/Maths/test/PiApproximationMonteCarlo.test.js @@ -0,0 +1,9 @@ +import { piEstimation } from '../PiApproximationMonteCarlo' + +describe('PiApproximationMonteCarlo', () => { + it('should be between the range of 2 to 4', () => { + const pi = piEstimation() + const piRange = pi >= 2 && pi <= 4 + expect(piRange).toBeTruthy() + }) +}) diff --git a/Maths/test/Polynomial.test.js b/Maths/test/Polynomial.test.js new file mode 100644 index 0000000000..af5618ab3d --- /dev/null +++ b/Maths/test/Polynomial.test.js @@ -0,0 +1,37 @@ +import { Polynomial } from '../Polynomial' + +describe('Polynomial', () => { + it('should not return a expression for zero', () => { + const polynomial = new Polynomial([0]) + expect(polynomial.display()).toBe('') + }) + it('should not return an expression for zero values', () => { + const polynomial = new Polynomial([0, 0, 0, 0, 0]) + expect(polynomial.display()).toBe('') + }) + it('should return an expression for single a non zero value', () => { + const polynomial = new Polynomial([9]) + expect(polynomial.display()).toBe('(9)') + }) + it('should return an expression for two values', () => { + const polynomial = new Polynomial([3, 2]) + expect(polynomial.display()).toBe('(2x) + (3)') + }) + it('should return an expression for values including zero', () => { + const polynomial = new Polynomial([0, 2]) + expect(polynomial.display()).toBe('(2x)') + }) + it('should return an expression and evaluate it', () => { + const polynomial = new Polynomial([1, 2, 3, 4]) + expect(polynomial.display()).toBe('(4x^3) + (3x^2) + (2x) + (1)') + expect(polynomial.evaluate(2)).toEqual(49) + }) + it('should evaluate 0 for zero values', () => { + const polynomial = new Polynomial([0, 0, 0, 0]) + expect(polynomial.evaluate(5)).toEqual(0) + }) + it('should evaluate for negative values', () => { + const polynomial = new Polynomial([-1, -3, -4, -7]) + expect(polynomial.evaluate(-5)).toBe(789) + }) +}) diff --git a/Maths/test/Pow.test.js b/Maths/test/Pow.test.js new file mode 100644 index 0000000000..9ffb64e52d --- /dev/null +++ b/Maths/test/Pow.test.js @@ -0,0 +1,41 @@ +import { powLinear, powFaster } from '../Pow' + +describe('Testing powLinear function', () => { + it('should return 1 for numbers with exponent 0', () => { + expect(powLinear(2, 0)).toBe(1) + }) + + it('should return 0.5 for numbers with exponent -1', () => { + expect(powLinear(2, -1)).toBe(0.5) + }) + + it('should return 0 for numbers with base 0', () => { + expect(powLinear(0, 23)).toBe(0) + }) + + it('should return the base to the exponent power', () => { + expect(powLinear(24, 4)).toBe(331776) + }) +}) + +describe('Testing powFaster function', () => { + it('should return 1 for numbers with exponent 0', () => { + expect(powFaster(2, 0)).toBe(1) + }) + + it('should return 0.5 for numbers with exponent -1', () => { + expect(powFaster(2, -1)).toBe(0.5) + }) + + it('should return 0 for numbers with base 0', () => { + expect(powFaster(0, 23)).toBe(0) + }) + + it('should return the base to the exponent power', () => { + expect(powFaster(24, 4)).toBe(331776) + }) + + it('should return the result in O(lonN) complexity', () => { + expect(powFaster(2, 64)).toBe(18446744073709552000) // execution time Math.log2(64) -> 6 + }) +}) diff --git a/Maths/test/PowLogarithmic.test.js b/Maths/test/PowLogarithmic.test.js new file mode 100644 index 0000000000..e432213f6e --- /dev/null +++ b/Maths/test/PowLogarithmic.test.js @@ -0,0 +1,15 @@ +import { powLogarithmic } from '../PowLogarithmic' + +describe('PowLogarithmic', () => { + it('should return 1 for numbers with exponent 0', () => { + expect(powLogarithmic(2, 0)).toBe(1) + }) + + it('should return 0 for numbers with base 0', () => { + expect(powLogarithmic(0, 23)).toBe(0) + }) + + it('should return the base to the exponent power', () => { + expect(powLogarithmic(24, 4)).toBe(331776) + }) +}) diff --git a/Maths/test/PrimeCheck.test.js b/Maths/test/PrimeCheck.test.js new file mode 100644 index 0000000000..da1cd1b52f --- /dev/null +++ b/Maths/test/PrimeCheck.test.js @@ -0,0 +1,14 @@ +import { PrimeCheck } from '../PrimeCheck' + +describe('PrimeCheck', () => { + it('should return true for Prime Numbers', () => { + expect(PrimeCheck(1000003)).toBeTruthy() + }) + it('should return false for Non Prime Numbers', () => { + expect(PrimeCheck(1000001)).toBeFalsy() + }) + it('should return false for 1 and 0', () => { + expect(PrimeCheck(1)).toBeFalsy() + expect(PrimeCheck(0)).toBeFalsy() + }) +}) diff --git a/Maths/test/PrimeFactors.test.js b/Maths/test/PrimeFactors.test.js new file mode 100644 index 0000000000..69fa36b330 --- /dev/null +++ b/Maths/test/PrimeFactors.test.js @@ -0,0 +1,11 @@ +import { PrimeFactors } from '../PrimeFactors' + +describe('EulersTotient', () => { + it('should return the prime factors for 100', () => { + expect(PrimeFactors(100)).toEqual([2, 2, 5, 5]) + }) + + it('should return the prime factors for 2560', () => { + expect(PrimeFactors(2560)).toEqual([2, 2, 2, 2, 2, 2, 2, 2, 2, 5]) + }) +}) diff --git a/Maths/test/QuadraticRoots.test.js b/Maths/test/QuadraticRoots.test.js new file mode 100644 index 0000000000..bd0db43c15 --- /dev/null +++ b/Maths/test/QuadraticRoots.test.js @@ -0,0 +1,13 @@ +import { quadraticRoots } from '../QuadraticRoots.js' + +describe('quadratic roots', () => { + it('returns an array with two real roots when the discriminant is positive', () => { + expect(quadraticRoots(1, -3, 2)).toEqual([2, 1]) + }) + it('returns an array with one real root when the discriminant is zero', () => { + expect(quadraticRoots(1, -2, 1)).toEqual([1]) + }) + it('returns an empty array indicating no real roots when the discriminant is negative', () => { + expect(quadraticRoots(1, 2, 5)).toEqual([]) + }) +}) diff --git a/Maths/test/RadianToDegree.test.js b/Maths/test/RadianToDegree.test.js new file mode 100644 index 0000000000..507798fdf6 --- /dev/null +++ b/Maths/test/RadianToDegree.test.js @@ -0,0 +1,21 @@ +import { radianToDegree } from '../RadianToDegree' + +test('should convert radian to degree:', () => { + const degreeEqual = radianToDegree(0) + expect(degreeEqual).toBe(0) +}) + +test('should convert radian to degree:', () => { + const degreeEqual = radianToDegree(Math.PI / 4) + expect(degreeEqual).toBe(45) +}) + +test('should convert radian to degree:', () => { + const degreeEqual = radianToDegree(Math.PI / 2) + expect(degreeEqual).toBe(90) +}) + +test('should convert radian to degree:', () => { + const degreeEqual = radianToDegree(Math.PI) + expect(degreeEqual).toBe(180) +}) diff --git a/Maths/test/ReverseNumber.test.js b/Maths/test/ReverseNumber.test.js new file mode 100644 index 0000000000..64593cbe14 --- /dev/null +++ b/Maths/test/ReverseNumber.test.js @@ -0,0 +1,16 @@ +import { ReverseNumber } from '../ReverseNumber' + +describe('ReverseNumber', () => { + it.each([ + [0, 0], + [10, 1], + [123, 321], + [100001, 100001] + ])('check with %j', (input, expected) => { + expect(expected).toEqual(ReverseNumber(input)) + }) + + it('should throw when input is not a number', () => { + expect(() => ReverseNumber('100')).toThrowError() + }) +}) diff --git a/Maths/test/ReversePolishNotation.test.js b/Maths/test/ReversePolishNotation.test.js new file mode 100644 index 0000000000..8b880ee472 --- /dev/null +++ b/Maths/test/ReversePolishNotation.test.js @@ -0,0 +1,11 @@ +import { calcRPN } from '../ReversePolishNotation' + +describe('ReversePolishNotation', () => { + it('should evaluate correctly for two values', () => { + expect(calcRPN('2 3 +')).toEqual(5) + }) + it("should evaluate' for multiple values", () => { + expect(calcRPN('2 2 2 * +')).toEqual(6) + expect(calcRPN('6 9 7 + 2 / + 3 *')).toEqual(42) + }) +}) diff --git a/Maths/test/RowEchelon.test.js b/Maths/test/RowEchelon.test.js new file mode 100644 index 0000000000..5575bc6d39 --- /dev/null +++ b/Maths/test/RowEchelon.test.js @@ -0,0 +1,89 @@ +import { rowEchelon } from '../RowEchelon' +describe('Determinant', () => { + const tolerance = 0.000001 + test.each([ + [ + [ + [8, 1, 3, 5], + [4, 6, 8, 2], + [3, 5, 6, 8] + ], + [ + [1, 0.125, 0.375, 0.625], + [0, 1, 1.18182, -0.09091], + [0, 0, 1, -11.0769] + ] + ], + [ + [ + [6, 8, 1, 3, 5], + [1, 4, 6, 8, 2], + [0, 3, 5, 6, 8], + [2, 5, 9, 7, 8], + [5, 5, 7, 0, 1] + ], + [ + [1, 1.33333, 0.16667, 0.5, 0.83333], + [0, 1, 2.1875, 2.8125, 0.4375], + [0, 0, 1, 1.56, -4.28003], + [0, 0, 0, 1, -3.3595], + [0, 0, 0, 0, 1] + ] + ], + [ + [ + [1, 3, 5], + [6, 8, 2], + [5, 6, 8], + [7, 9, 9], + [5, 0, 6] + ], + [ + [1, 3, 5], + [0, 1, 2.8], + [0, 0, 1], + [0, 0, 0], + [0, 0, 0] + ] + ], + [ + [ + [0, 7, 8, 1, 3, 5], + [0, 6, 4, 6, 8, 2], + [0, 7, 3, 5, 6, 8], + [6, 8, 1, 0, 0, 4], + [3, 3, 5, 7, 3, 1], + [1, 2, 1, 0, 9, 7], + [8, 8, 0, 2, 3, 1] + ], + [ + [1, 1.33333, 0.16667, 0, 0, 0.66667], + [0, 1, 0.66667, 1, 1.33333, 0.33333], + [0, 0, 1, 1.2, 1.99999, -3.4], + [0, 0, 0, 1, 1.3, -1.4], + [0, 0, 0, 0, 1, -2.32854], + [0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0] + ] + ] + ])('Should return the matrix in row echelon form.', (matrix, expected) => { + for (let i = 0; i < matrix.length; i++) { + for (let j = 0; j < matrix[i].length; j++) { + expect(rowEchelon(matrix)[i][j]).toBeCloseTo(expected[i][j], tolerance) + } + } + }) + + test.each([ + [ + [ + [8, 1, 3, 5], + [4, 6, 8, 2, 7], + [3, 5, 6, 8] + ], + 'Input is not a valid 2D matrix.' + ] + ])('Should return the error message.', (matrix, expected) => { + expect(() => rowEchelon(matrix)).toThrowError(expected) + }) +}) diff --git a/Maths/test/ShorsAlgorithm.test.js b/Maths/test/ShorsAlgorithm.test.js new file mode 100644 index 0000000000..24c52ed37f --- /dev/null +++ b/Maths/test/ShorsAlgorithm.test.js @@ -0,0 +1,29 @@ +import { ShorsAlgorithm } from '../ShorsAlgorithm' +import { fermatPrimeCheck } from '../FermatPrimalityTest' + +describe("Shor's Algorithm", () => { + const N = 10 // number of tests + const max = 35000 // max value to factorize + const min = 1000 // min value to factorize + + for (let i = 0; i < N; i++) { + while (true) { + const num = Math.floor(Math.random() * max) + min + // num must be composite, don't care for false negatives + if (fermatPrimeCheck(num, 1)) continue + + it('should find a non-trivial factor of ' + num, () => { + const f = ShorsAlgorithm(num) + + // should not be trivial + expect(f).not.toEqual(1) + expect(f).not.toEqual(num) + + // should be a factor + expect(num % f).toEqual(0) + }) + + break + } + } +}) diff --git a/Maths/test/SieveOfEratosthenes.test.js b/Maths/test/SieveOfEratosthenes.test.js new file mode 100644 index 0000000000..1a10b8bc7f --- /dev/null +++ b/Maths/test/SieveOfEratosthenes.test.js @@ -0,0 +1,29 @@ +import { sieveOfEratosthenes } from '../SieveOfEratosthenes' + +describe('sieveOfEratosthenes', () => { + test('returns an empty array for max < 2', () => { + expect(sieveOfEratosthenes(1)).toEqual([]) + }) + + test('returns [2] for max = 2', () => { + expect(sieveOfEratosthenes(2)).toEqual([2]) + }) + + test('returns [2, 3] for max = 3', () => { + expect(sieveOfEratosthenes(3)).toEqual([2, 3]) + }) + + test('returns [2, 3, 5, 7] for max = 10', () => { + expect(sieveOfEratosthenes(10)).toEqual([2, 3, 5, 7]) + }) + + test('returns [2, 3, 5, 7, 11, 13, 17, 19] for max = 20', () => { + expect(sieveOfEratosthenes(20)).toEqual([2, 3, 5, 7, 11, 13, 17, 19]) + }) + + test('returns [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] for max = 30', () => { + expect(sieveOfEratosthenes(30)).toEqual([ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 + ]) + }) +}) diff --git a/Maths/test/Signum.test.js b/Maths/test/Signum.test.js new file mode 100644 index 0000000000..ac00676a00 --- /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/SimpsonIntegration.test.js b/Maths/test/SimpsonIntegration.test.js new file mode 100644 index 0000000000..97963614e2 --- /dev/null +++ b/Maths/test/SimpsonIntegration.test.js @@ -0,0 +1,22 @@ +import { integralEvaluation } from '../SimpsonIntegration' + +test('Should return the integral of f(x) = sqrt(x) in [1, 3] to be equal 2.797434', () => { + const result = integralEvaluation(16, 1, 3, (x) => { + return Math.sqrt(x) + }) + expect(Number(result.toPrecision(7))).toBe(2.797434) +}) + +test('Should return the integral of f(x) = sqrt(x) + x^2 in [1, 3] to be equal 11.46410161', () => { + const result = integralEvaluation(64, 1, 3, (x) => { + return Math.sqrt(x) + Math.pow(x, 2) + }) + expect(Number(result.toPrecision(10))).toBe(11.46410161) +}) + +test('Should return the integral of f(x) = log(x) + Pi*x^3 in [5, 12] to be equal 15809.9141543', () => { + const result = integralEvaluation(128, 5, 12, (x) => { + return Math.log(x) + Math.PI * Math.pow(x, 3) + }) + expect(Number(result.toPrecision(12))).toBe(15809.9141543) +}) diff --git a/Maths/test/Softmax.test.js b/Maths/test/Softmax.test.js new file mode 100644 index 0000000000..179697bcf7 --- /dev/null +++ b/Maths/test/Softmax.test.js @@ -0,0 +1,12 @@ +import { Softmax } from '../Softmax' + +describe('Softmax', () => { + it('should return equal distribution of 1 for equal input values', () => { + expect(Softmax([1, 1])).toEqual([0.5, 0.5]) + expect(Softmax([1, 1, 1, 1])).toEqual([0.25, 0.25, 0.25, 0.25]) + }) + + it('should return values which sum to the value of 1', () => { + expect(Softmax([1, 2, 3, 4]).reduce((a, b) => a + b, 0)).toEqual(1) + }) +}) diff --git a/Maths/test/SquareRoot.test.js b/Maths/test/SquareRoot.test.js new file mode 100644 index 0000000000..a85b3581b1 --- /dev/null +++ b/Maths/test/SquareRoot.test.js @@ -0,0 +1,36 @@ +import { sqrt } from '../SquareRoot' + +test('Check SquareRoot of 4 is 2', () => { + const res = sqrt(4, 10) + expect(res).toBeCloseTo(2) +}) + +test('Check SquareRoot of 2 is 1.4142135', () => { + const res = sqrt(2, 10) + expect(res).toBeCloseTo(1.4142135) +}) + +test('Check SquareRoot of 3.2 is 1.788854381999832', () => { + const res = sqrt(3.2, 10) + expect(res).toBeCloseTo(1.788854381999832) +}) + +test('Check SquareRoot of 1 is 1', () => { + const res = sqrt(1, 10) + expect(res).toBe(1) +}) + +test('Check SquareRoot of 144 is 12', () => { + const res = sqrt(144, 10) + expect(res).toBeCloseTo(12) +}) + +test('Check SquareRoot of 0 is 0', () => { + const res = sqrt(0, 10) + expect(res).toBeCloseTo(0) +}) + +test('Check SquareRoot of 1000 is 31.62277', () => { + const res = sqrt(1000, 10) + expect(res).toBeCloseTo(31.62277) +}) diff --git a/Maths/test/SquareRootLogarithmic.test.js b/Maths/test/SquareRootLogarithmic.test.js new file mode 100644 index 0000000000..6eec49d239 --- /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/Maths/test/SumOfDigits.test.js b/Maths/test/SumOfDigits.test.js new file mode 100644 index 0000000000..f73891d677 --- /dev/null +++ b/Maths/test/SumOfDigits.test.js @@ -0,0 +1,20 @@ +import { + sumOfDigitsUsingLoop, + sumOfDigitsUsingRecursion, + sumOfDigitsUsingString +} from '../SumOfDigits' + +test('Testing on sumOfDigitsUsingLoop', () => { + const sum = sumOfDigitsUsingLoop(123) + expect(sum).toBe(6) +}) + +test('Testing on sumOfDigitsUsingRecursion', () => { + const sum = sumOfDigitsUsingRecursion(123) + expect(sum).toBe(6) +}) + +test('Testing on sumOfDigitsUsingString', () => { + const sum = sumOfDigitsUsingString(123) + expect(sum).toBe(6) +}) diff --git a/Maths/test/SumOfGeometricProgression.test.js b/Maths/test/SumOfGeometricProgression.test.js new file mode 100644 index 0000000000..0edf2b9f8e --- /dev/null +++ b/Maths/test/SumOfGeometricProgression.test.js @@ -0,0 +1,15 @@ +import { sumOfGeometricProgression } from '../SumOfGeometricProgression' + +describe('Sum Of Geometric Progression', () => { + it('should return the sum of a finite GP', () => { + expect(sumOfGeometricProgression(100, 1.5, 4)).toBe(812.5) + }) + + it('should return the sum of an infinite GP', () => { + expect(sumOfGeometricProgression(2, 0.5, Infinity)).toBe(4) + }) + + it('should throw when series diverges', () => { + expect(() => sumOfGeometricProgression(1, 1, Infinity)).toThrowError() + }) +}) diff --git a/Maths/test/TwoSum.test.js b/Maths/test/TwoSum.test.js new file mode 100644 index 0000000000..851f180a98 --- /dev/null +++ b/Maths/test/TwoSum.test.js @@ -0,0 +1,28 @@ +import { TwoSum } from '../TwoSum.js' +describe('Two Sum', () => { + const testCasesWithoutSolution = [ + [[8], 8], + [[3, 3, 3, 3], 19] + ] + const testCasesWithSolution = [ + [[2, 7, 11, 15], 9, [0, 1]], + [[15, 2, 11, 7], 13, [1, 2]], + [[2, 7, 11, 15], 17, [0, 3]], + [[7, 15, 11, 2], 18, [0, 2]], + [[2, 7, 11, 15], 26, [2, 3]] + ] + + test.each(testCasesWithoutSolution)( + 'Should return an empty array if there is no solution', + (nums, target) => { + expect(TwoSum(nums, target)).toEqual([]) + } + ) + + test.each(testCasesWithSolution)( + 'Should return the indices of two numbers that add up to the target', + (nums, target, expected) => { + expect(TwoSum(nums, target)).toEqual(expected) + } + ) +}) diff --git a/Maths/test/Volume.test.js b/Maths/test/Volume.test.js new file mode 100644 index 0000000000..e45235eb6a --- /dev/null +++ b/Maths/test/Volume.test.js @@ -0,0 +1,46 @@ +import * as volume from '../Volume' + +test('Testing on volCuboid', () => { + const volCuboid = volume.volCuboid(2.0, 5.0, 3) + expect(volCuboid).toBe(30.0) +}) + +test('Testing on volCube', () => { + const volCube = volume.volCube(2.0) + expect(volCube).toBe(8.0) +}) + +test('Testing on volCone', () => { + const volCone = volume.volCone(3.0, 8.0) + expect(volCone).toBe(75.39822368615503) +}) + +test('Testing on volPyramid', () => { + const volPyramid = volume.volPyramid(2.0, 3.0, 8.0) + expect(volPyramid).toBe(16.0) +}) + +test('Testing on volCylinder', () => { + const volCylinder = volume.volCylinder(3.0, 8.0) + expect(volCylinder).toBe(226.1946710584651) +}) + +test('Testing on volTriangularPrism', () => { + const volTriangularPrism = volume.volTriangularPrism(3.0, 6.0, 8.0) + expect(volTriangularPrism).toBe(72.0) +}) + +test('Testing on volPentagonalPrism', () => { + const volPentagonalPrism = volume.volPentagonalPrism(1.0, 4.0, 8.0) + expect(volPentagonalPrism).toBe(80.0) +}) + +test('Testing on volSphere', () => { + const volSphere = volume.volSphere(4.0) + expect(volSphere).toBe(268.082573106329) +}) + +test('Testing on volHemisphere', () => { + const volHemisphere = volume.volHemisphere(4.0) + expect(volHemisphere).toBe(134.0412865531645) +}) diff --git a/Maths/test/WhileLoopFactorial.test.js b/Maths/test/WhileLoopFactorial.test.js new file mode 100644 index 0000000000..1f8c9a8749 --- /dev/null +++ b/Maths/test/WhileLoopFactorial.test.js @@ -0,0 +1,12 @@ +import { factorialize } from '../WhileLoopFactorial' + +function testFactorial(n, expected) { + test('Testing on ' + n + '!', () => { + expect(factorialize(n)).toBe(expected) + }) +} + +testFactorial(3, 6) +testFactorial(7, 5040) +testFactorial(0, 1) +testFactorial(12, 479001600) diff --git a/Maths/test/ZellersCongruenceAlgorithm.test.js b/Maths/test/ZellersCongruenceAlgorithm.test.js new file mode 100644 index 0000000000..0e0d30ec4b --- /dev/null +++ b/Maths/test/ZellersCongruenceAlgorithm.test.js @@ -0,0 +1,17 @@ +import { zellersCongruenceAlgorithm } from '../ZellersCongruenceAlgorithm' + +function testZeller(day, month, year, expected) { + test('Testing on ' + day + '/' + month + '/' + year, () => { + expect(zellersCongruenceAlgorithm(day, month, year)).toBe(expected) + }) +} + +test('Testing on this/should/throw', () => { + expect(() => { + zellersCongruenceAlgorithm('this', 'should', 'error') + }).toThrowError(new TypeError('Arguments are not all numbers.')) +}) +testZeller(25, 1, 2013, 'Friday') +testZeller(26, 1, 2013, 'Saturday') +testZeller(16, 4, 2022, 'Saturday') +testZeller(25, 4, 2022, 'Monday') diff --git a/Maths/test/isPalindromeIntegerNumber.test.js b/Maths/test/isPalindromeIntegerNumber.test.js new file mode 100644 index 0000000000..5efa350e48 --- /dev/null +++ b/Maths/test/isPalindromeIntegerNumber.test.js @@ -0,0 +1,32 @@ +import { isPalindromeIntegerNumber } from '../isPalindromeIntegerNumber' + +describe('isPalindromeIntegerNumber', () => { + it('expects to return true when length of input is 1', () => { + expect(isPalindromeIntegerNumber(6)).toEqual(true) + }) + + it('expects to return true when input is palindrome', () => { + expect(isPalindromeIntegerNumber(121)).toEqual(true) + expect(isPalindromeIntegerNumber(12321)).toEqual(true) + expect(isPalindromeIntegerNumber(1221)).toEqual(true) + }) + + it('expects to return false when input is not palindrome', () => { + expect(isPalindromeIntegerNumber(189)).toEqual(false) + }) + + it('expects to return false when input is minus', () => { + expect(isPalindromeIntegerNumber(-121)).toEqual(false) + expect(isPalindromeIntegerNumber(-12321)).toEqual(false) + }) + + it('expects to return false when input is not integer number', () => { + expect(isPalindromeIntegerNumber(123.456)).toEqual(false) + }) + + it('expects to throw error when input is not a number', () => { + expect(() => isPalindromeIntegerNumber(undefined)).toThrowError() + expect(() => isPalindromeIntegerNumber({ key: 'val' })).toThrowError() + expect(() => isPalindromeIntegerNumber([])).toThrowError() + }) +}) diff --git a/Navigation/Haversine.js b/Navigation/Haversine.js new file mode 100644 index 0000000000..0013f2098b --- /dev/null +++ b/Navigation/Haversine.js @@ -0,0 +1,44 @@ +/** + * @function HaversineDistance + * @description Calculate the distance between two coordinates using the haversine formula + * @param {Integer} latitude1 - The input integer + * @param {Integer} latitude2 - The input integer + * @param {Integer} longitude1 - The input integer + * @param {Integer} longitude2 - The input integer + * @return {Integer} Haversine Distance. + * @see [Haversine_Distance](https://pt.wikipedia.org/wiki/F%C3%B3rmula_de_Haversine) + */ +const haversineDistance = ( + latitude1 = 0, + longitude1 = 0, + latitude2 = 0, + longitude2 = 0 +) => { + validateLatOrLong(latitude1) + validateLatOrLong(latitude2) + validateLatOrLong(longitude1) + validateLatOrLong(longitude2) + const earthRadius = 6371e3 // 6,371km + const pi = Math.PI + const cos1 = (latitude1 * pi) / 180.0 + const cos2 = (latitude2 * pi) / 180.0 + const deltaLatitude = ((latitude2 - latitude1) * pi) / 180.0 + const deltaLongitude = ((longitude2 - longitude1) * pi) / 180.0 + + const alpha = + Math.sin(deltaLatitude / 2) * Math.sin(deltaLatitude / 2) + + Math.cos(cos1) * + Math.cos(cos2) * + Math.sin(deltaLongitude / 2) * + Math.sin(deltaLongitude / 2) + const constant = 2 * Math.atan2(Math.sqrt(alpha), Math.sqrt(1 - alpha)) + return earthRadius * constant +} + +const validateLatOrLong = (value) => { + if (typeof value !== 'number') { + throw new TypeError('The value of latitude or longitude should be a number') + } +} + +export { haversineDistance } diff --git a/Navigation/test/Haversine.test.js b/Navigation/test/Haversine.test.js new file mode 100644 index 0000000000..5448a6143a --- /dev/null +++ b/Navigation/test/Haversine.test.js @@ -0,0 +1,13 @@ +import { haversineDistance } from '../Haversine' + +describe('Testing the haversine distance calculator', () => { + it('Calculate distance', () => { + const distance = haversineDistance(64.1265, -21.8174, 40.7128, -74.006) + expect(distance).toBe(4208198.758424171) + }) + it('Test validation, expect throw', () => { + expect(() => + haversineDistance(64.1265, -21.8174, 40.7128, '74.0060') + ).toThrow() + }) +}) diff --git a/Project-Euler/Problem001.js b/Project-Euler/Problem001.js new file mode 100644 index 0000000000..a88f912603 --- /dev/null +++ b/Project-Euler/Problem001.js @@ -0,0 +1,23 @@ +// https://projecteuler.net/problem=1 +/* Multiples of 3 and 5 +If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6, and 9. The sum of these multiples is 23. +Find the sum of all the multiples of 3 or 5 below the provided parameter value number. +*/ + +// This method uses the nSum function to add the nSum for 3 and 5. However, it needs to subtract the nSum for 15 once to avoid double counting. +const multiplesThreeAndFive = (num) => { + if (num < 1) throw new Error('No natural numbers exist below 1') + num -= 1 + let sum = 0 + + // The nSum function calculates the sum of the first n numbers in the sequence with a common difference of num. + // Here, n is denoted as frequency. + const nSum = (num, frequency) => (frequency * (frequency + 1) * num) >> 1 + + sum += nSum(3, Math.floor(num / 3)) + sum += nSum(5, Math.floor(num / 5)) + sum -= nSum(15, Math.floor(num / 15)) + return sum +} + +export { multiplesThreeAndFive } diff --git a/Project-Euler/Problem002.js b/Project-Euler/Problem002.js new file mode 100644 index 0000000000..f1d4c3eee5 --- /dev/null +++ b/Project-Euler/Problem002.js @@ -0,0 +1,18 @@ +// https://projecteuler.net/problem=2 +const SQ5 = 5 ** 0.5 // Square root of 5 +const PHI = (1 + SQ5) / 2 // definition of PHI + +// theoretically it should take O(1) constant amount of time as long +// arithmetic calculations are considered to be in constant amount of time +export const EvenFibonacci = (limit) => { + if (limit < 1) + throw new Error("Fibonacci sequence limit can't be less than 1") + + const highestIndex = Math.floor(Math.log(limit * SQ5) / Math.log(PHI)) + const n = Math.floor(highestIndex / 3) + return Math.floor( + ((PHI ** (3 * n + 3) - 1) / (PHI ** 3 - 1) - + ((1 - PHI) ** (3 * n + 3) - 1) / ((1 - PHI) ** 3 - 1)) / + SQ5 + ) +} diff --git a/Project-Euler/Problem003.js b/Project-Euler/Problem003.js new file mode 100644 index 0000000000..d61c9ad5fb --- /dev/null +++ b/Project-Euler/Problem003.js @@ -0,0 +1,18 @@ +// https://projecteuler.net/problem=3 + +export const largestPrime = (num = 600851475143) => { + let newnum = num + let largestFact = 0 + let counter = 2 + while (counter * counter <= newnum) { + if (newnum % counter === 0) { + newnum = newnum / counter + } else { + counter++ + } + } + if (newnum > largestFact) { + largestFact = newnum + } + return largestFact +} diff --git a/Project-Euler/Problem004.js b/Project-Euler/Problem004.js new file mode 100644 index 0000000000..7c85dfdb85 --- /dev/null +++ b/Project-Euler/Problem004.js @@ -0,0 +1,44 @@ +// https://projecteuler.net/problem=4 +/* A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 ร— 99. + Find the largest palindrome made from the product of two 3-digit numbers. +*/ +export const largestPalindromic = (digits) => { + let i + let n + let m + let d + let limit + let number = 0 + + for (i = 1; i < digits; i++) { + number = 10 * number + 9 + } + const inf = number // highest (digits - 1) number, in this example highest 2 digit number + const sup = 10 * number + 9 // highest (digits) number, in this example highest 3 digit number + + const isPalindromic = (n) => { + let p = 0 + const q = n + let r + while (n > 0) { + r = n % 10 + p = 10 * p + r + n = Math.floor(n / 10) + } + return p === q // returning whether the number is palindromic or not + } + + for (n = sup * sup, m = inf * inf; n > m; n--) { + if (isPalindromic(n)) { + limit = Math.ceil(Math.sqrt(n)) + d = sup + while (d >= limit) { + if (n % d === 0 && n / d > inf) { + return n + } + d -= 1 + } + } + } + return NaN // returning not a number, if any such case arise +} diff --git a/Project-Euler/Problem005.js b/Project-Euler/Problem005.js new file mode 100644 index 0000000000..92f3df9b68 --- /dev/null +++ b/Project-Euler/Problem005.js @@ -0,0 +1,20 @@ +/* +Smallest multiple + +2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder. +What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20? +*/ + +export const findSmallestMultiple = (maxDivisor) => { + const divisors = Array.from({ length: maxDivisor }, (_, i) => i + 1) + let num = maxDivisor + 1 + let result + + while (!result) { + const isDivisibleByAll = divisors.every((divisor) => num % divisor === 0) + if (isDivisibleByAll) result = num + else num++ + } + + return result +} diff --git a/Project-Euler/Problem006.js b/Project-Euler/Problem006.js new file mode 100644 index 0000000000..804b165558 --- /dev/null +++ b/Project-Euler/Problem006.js @@ -0,0 +1,8 @@ +// https://projecteuler.net/problem=6 + +export const squareDifference = (num = 100) => { + let sumOfSquares = (num * (num + 1) * (2 * num + 1)) / 6 + let sums = (num * (num + 1)) / 2 + + return sums ** 2 - sumOfSquares // difference of square of the total sum and sum of squares +} diff --git a/Project-Euler/Problem007.js b/Project-Euler/Problem007.js new file mode 100644 index 0000000000..009c1a896e --- /dev/null +++ b/Project-Euler/Problem007.js @@ -0,0 +1,27 @@ +import { PrimeCheck } from '../Maths/PrimeCheck.js' + +/** + * Find nth Prime Number + * + * P.S.(Project Euler - 007): + * By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. + * What is the 10001st prime number? + * + * @param {Number} n + * @returns {Number} returns the nth prime number + */ +export const nthPrime = (n) => { + if (n < 1) { + throw new Error('Invalid Input') + } + + let count = 0 + let candidateValue = 1 + while (count < n) { + candidateValue++ + if (PrimeCheck(candidateValue)) { + count++ + } + } + return candidateValue +} diff --git a/Project-Euler/Problem008.js b/Project-Euler/Problem008.js new file mode 100644 index 0000000000..66c50d12b9 --- /dev/null +++ b/Project-Euler/Problem008.js @@ -0,0 +1,26 @@ +// Problem: https://projecteuler.net/problem=8 + +const largestAdjacentNumber = (grid, consecutive) => { + grid = grid.split('\n').join('') + const splitGrid = grid.split('\n') + let largestProd = 0 + + for (const row in splitGrid) { + const currentRow = splitGrid[row].split('').map((x) => Number(x)) + + for (let i = 0; i < currentRow.length - consecutive; i++) { + const combine = currentRow.slice(i, i + consecutive) + + if (!combine.includes(0)) { + const product = combine.reduce(function (a, b) { + return a * b + }) + + if (largestProd < product) largestProd = product + } + } + } + return largestProd +} + +export { largestAdjacentNumber } diff --git a/Project-Euler/Problem009.js b/Project-Euler/Problem009.js new file mode 100644 index 0000000000..0ab5ae61d3 --- /dev/null +++ b/Project-Euler/Problem009.js @@ -0,0 +1,26 @@ +/* +Special Pythagorean triplet + +A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, + +a^2 + b^2 = c^2 +For example, 32 + 42 = 9 + 16 = 25 = 52. + +There exists exactly one Pythagorean triplet for which a + b + c = 1000. +Find the product abc. +*/ + +const isPythagoreanTriplet = (a, b, c) => + Math.pow(a, 2) + Math.pow(b, 2) === Math.pow(c, 2) + +export const findSpecialPythagoreanTriplet = () => { + for (let a = 0; a < 1000; a++) { + for (let b = a + 1; b < 1000; b++) { + for (let c = b + 1; c < 1000; c++) { + if (isPythagoreanTriplet(a, b, c) && a + b + c === 1000) { + return a * b * c + } + } + } + } +} diff --git a/Project-Euler/Problem010.js b/Project-Euler/Problem010.js new file mode 100644 index 0000000000..9d90ee5c3f --- /dev/null +++ b/Project-Euler/Problem010.js @@ -0,0 +1,24 @@ +// https://projecteuler.net/problem=10 + +const isPrime = (number) => { + if (number === 2) return true + if (number % 2 === 0) return false + + for (let j = 3; j * j <= number; j += 2) { + if (number % j === 0) { + return false + } + } + return true +} + +const calculateSumOfPrimeNumbers = (maxNumber) => { + let sum = 0 + for (let i = maxNumber - 1; i >= 2; i--) { + if (isPrime(parseInt(i)) === true) { + sum += i + } + } + return sum +} +export { calculateSumOfPrimeNumbers } diff --git a/Project-Euler/Problem011.js b/Project-Euler/Problem011.js new file mode 100644 index 0000000000..3f6c177db4 --- /dev/null +++ b/Project-Euler/Problem011.js @@ -0,0 +1,59 @@ +/* + * + * In the 20ร—20 grid below, four numbers along a diagonal line have been marked in red. + * + * 08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08 + * 49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00 + * 81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65 + * 52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91 + * 22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80 + * 24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50 + * 32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70 + * 67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21 + * 24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72 + * 21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95 + * 78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92 + * 16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57 + * 86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58 + * 19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40 + * 04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66 + * 88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69 + * 04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36 + * 20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16 + * 20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54 + * 01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48 + * + * The product of these numbers is 26 ร— 63 ร— 78 ร— 14 = 1788696. + * + * What is the greatest product of four adjacent numbers in the + * same direction (up, down, left, right, or diagonally) in the 20ร—20 grid? + */ + +export function largestProductInAGrid(arr) { + let max = 0 + const k = 4 + + const dx = [1, 0, 1, -1] + const dy = [0, 1, 1, 1] + + for (let y = 0; y < arr.length; y++) { + for (let x = 0; x < arr[y].length; x++) { + for (let d = 0; d < 4; d++) { + let p = 1 + for (let i = 0; i < k; i++) { + p *= get(arr, y + i * dy[d], x + i * dx[d]) + } + max = Math.max(p, max) + } + } + } + return max +} + +function get(arr, y, x) { + if (y >= 0 && y < arr.length && x >= 0 && x < arr[y].length) { + return arr[y][x] + } + + return 0 +} diff --git a/Project-Euler/Problem012.js b/Project-Euler/Problem012.js new file mode 100644 index 0000000000..ec76bda4e4 --- /dev/null +++ b/Project-Euler/Problem012.js @@ -0,0 +1,64 @@ +/** + * Problem 12 - Highly divisible triangular number + * + * https://projecteuler.net/problem=11 + * + * The sequence of triangle numbers is generated by adding the natural numbers. + * So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. + * + * The first ten terms would be: 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ... + * Let us list the factors of the first seven triangle numbers: + * + * 1: 1 + * 3: 1,3 + * 6: 1,2,3,6 + * 10: 1,2,5,10 + * 15: 1,3,5,15 + * 21: 1,3,7,21 + * 28: 1,2,4,7,14,28 + * + * We can see that 28 is the first triangle number to have over five divisors. + * + * What is the value of the first triangle number to have over five hundred divisors? + */ + +/** + * Gets number of divisors of a given number + * @params num The number whose divisors to find + */ +const getNumOfDivisors = (num) => { + // initialize numberOfDivisors + let numberOfDivisors = 0 + + // if one divisor less than sqrt(num) exists + // then another divisor greater than sqrt(n) exists and its value is num/i + const sqrtNum = Math.sqrt(num) + for (let i = 0; i <= sqrtNum; i++) { + // check if i divides num + if (num % i === 0) { + if (i === sqrtNum) { + // if both divisors are equal, i.e., num is perfect square, then only 1 divisor + numberOfDivisors++ + } else { + // 2 divisors, one of them is less than sqrt(n), other greater than sqrt(n) + numberOfDivisors += 2 + } + } + } + return numberOfDivisors +} + +/** + * Loops till first triangular number with 500 divisors is found + */ +const firstTriangularWith500Divisors = () => { + let triangularNum + // loop forever until numOfDivisors becomes greater than or equal to 500 + for (let n = 1; ; n++) { + // nth triangular number is (1/2)*n*(n+1) by Arithmetic Progression + triangularNum = (1 / 2) * n * (n + 1) + if (getNumOfDivisors(triangularNum) >= 500) return triangularNum + } +} + +export { firstTriangularWith500Divisors } diff --git a/Project-Euler/Problem013.js b/Project-Euler/Problem013.js new file mode 100644 index 0000000000..8748a8ee17 --- /dev/null +++ b/Project-Euler/Problem013.js @@ -0,0 +1,27 @@ +/** + * Work out the first ten digits of the sum of the following one-hundred 50-digit numbers. + */ + +export function largeSum(bignum) { + const nums = [] + for (let i = 0; i < bignum.length; i += 50) { + nums.push(bignum.slice(i, i + 50)) + } + + let pos = nums[0].length + let ret = '' + let num = 0 + + while (pos--) { + for (let i = nums.length; i--; ) { + num += +nums[i].charAt(pos) + } + ret = (num % 10) + ret + num = (num / 10) | 0 + } + + if (num > 0) { + ret = num + ret + } + return ret.slice(0, 10) +} diff --git a/Project-Euler/Problem014.js b/Project-Euler/Problem014.js new file mode 100644 index 0000000000..10556c5cb6 --- /dev/null +++ b/Project-Euler/Problem014.js @@ -0,0 +1,45 @@ +/* +Longest Collatz sequence + +The following iterative sequence is defined for the set of positive integers: + +n โ†’ n/2 (n is even) +n โ†’ 3n + 1 (n is odd) + +Using the rule above and starting with 13, we generate the following sequence: + +13 โ†’ 40 โ†’ 20 โ†’ 10 โ†’ 5 โ†’ 16 โ†’ 8 โ†’ 4 โ†’ 2 โ†’ 1 +It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1. + +Which starting number, under one million, produces the longest chain? + +NOTE: Once the chain starts the terms are allowed to go above one million. +*/ + +const getCollatzSequenceLength = (num, seqLength) => { + if (num === 1) { + return seqLength + } else { + let newElement + if (num % 2 === 0) { + newElement = num / 2 + } else { + newElement = 3 * num + 1 + } + seqLength++ + return getCollatzSequenceLength(newElement, seqLength) + } +} + +export const findLongestCollatzSequence = (limit = 1000000) => { + let startingPointForLargestSequence = 1 + let largestSequenceLength = 1 + for (let i = 2; i < limit; i++) { + const currentSequenceLength = getCollatzSequenceLength(i, 1) + if (currentSequenceLength > largestSequenceLength) { + startingPointForLargestSequence = i + largestSequenceLength = currentSequenceLength + } + } + return startingPointForLargestSequence +} diff --git a/Project-Euler/Problem015.js b/Project-Euler/Problem015.js new file mode 100644 index 0000000000..9e63c75705 --- /dev/null +++ b/Project-Euler/Problem015.js @@ -0,0 +1,19 @@ +// https://projecteuler.net/problem=15 +/* Starting in the top left corner of a 2ร—2 grid, and only being able to move to +the right and down, there are exactly 6 routes to the bottom right corner. +How many such routes are there through a 20ร—20 grid? +*/ + +// A lattice path is composed of horizontal and vertical lines that pass through lattice points. + +export const latticePath = (gridSize) => { + let paths + for (let i = 1, paths = 1; i <= gridSize; i++) { + paths = (paths * (gridSize + i)) / i + } + // The total number of paths can be found using the binomial coefficient (b+a)/a. + return paths +} + +// > latticePath(20)) +// 137846528820 diff --git a/Project-Euler/Problem016.js b/Project-Euler/Problem016.js new file mode 100644 index 0000000000..d3c92f3eeb --- /dev/null +++ b/Project-Euler/Problem016.js @@ -0,0 +1,43 @@ +/** + * Problem 16 - Power digit sum + * + * @see {@link https://projecteuler.net/problem=16} + * + * 2ยนโต = 32768 and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26. + * + * What is the sum of the digits of the number 2ยนโฐโฐโฐ ? + */ + +/** + * Returns the power digit sum of n^pow. + * + * @param {number} [n=2] + * @param {number} [pow=1000] + * @returns {number} + */ +const powerDigitSum = function (n = 2, pow = 1000) { + // The idea is to consider each digit (d*10^exp) separately, right-to-left. + // digits = [units, tens, ...] + + const digits = [n] + let p = 1 + + while (++p <= pow) { + let carry = 0 + for (let exp = 0; exp < digits.length; exp++) { + const prod = digits[exp] * n + carry + carry = Math.floor(prod / 10) + digits[exp] = prod % 10 + } + while (carry > 0) { + digits.push(carry % 10) + carry = Math.floor(carry / 10) + } + } + + // (digits are reversed but we only want the sum so it doesn't matter) + + return digits.reduce((prev, current) => prev + current, 0) +} + +export { powerDigitSum } diff --git a/Project-Euler/Problem017.js b/Project-Euler/Problem017.js new file mode 100644 index 0000000000..65674188a4 --- /dev/null +++ b/Project-Euler/Problem017.js @@ -0,0 +1,117 @@ +/** + * Problem 17 - Number letter counts + * + * @see {@link https://projecteuler.net/problem=17} + * + * If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total. + * If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used? + * + * @author Chetan07j + */ + +// Array of number word length from 0 -> 19 +const ones = [4, 3, 3, 5, 4, 4, 3, 5, 5, 4, 3, 6, 6, 8, 8, 7, 7, 9, 8, 8] + +// Array for tens from 20, 30, 40, 50, 60, 70, 80, 90 in words length +const tens = [6, 6, 5, 5, 5, 7, 6, 6] + +// Word length for words thousand, hundred, and +const thousandLength = 8 +const hundredLength = 7 +const andLength = 3 + +/** + * Function to convert number to word + * + * This function is called recursively to handle thousand and its sub part + */ +const numberToWordLength = (n) => { + let count = 0 + + // If number is < 20 then return its corresponding value from ones + if (n < 20) { + return ones[n] + } + + /** + * To calculate tens value "n / 10 - 2" is performed, which might return decimal value + * to extract proper integer value Math.floor is added + * Here "-2" is performed as our tens array start with index 0 + * To get appropriate value from that for our number it is required + * e.g., + * -> 34 -> 34/10= 3.4 -> Math.floor(3.4) = 3 + * -> ones[3] = 5 // this is wrong + * -> 3 - 2 = 1 -> ones[1] = 6 + * + * To find ones part, unit is identified by n % 10 + * If > 0 then ones word is appended to tens word otherwise nothing + * e.g., + * 1. 34 -> 10 + * 2. 30 -> 6 + */ + if (n >= 20 && n < 100) { + const unit = n % 10 + return tens[Math.floor(n / 10 - 2)] + (unit !== 0 ? ones[unit] : 0) + } + + // Find thousand, hundred and sub part + const hundred = Math.floor(n / 100) % 10 + const thousand = Math.floor(n / 1000) + const sub = n % 100 + + // Find ones for thousand part number + // e.g., thousand = 2 => inWord = twothousand + if (n > 999) { + count += numberToWordLength(thousand) + thousandLength + } + + // Find ones for hundred part number + // e.g., hundred = 1 => inWord = onehundred + if (hundred !== 0) { + count += ones[hundred] + hundredLength + } + + // Find and part of number + // e.g., 922 => ninehundred"andtwentytwo" + if (sub !== 0) { + count += andLength + numberToWordLength(sub) + } + + // return number in word format + return count +} + +/** + * Function responsible for calculating total number word length + * for provided input number + * Validation is performed for input + * Loop is executed to find total word length for given number range + * starting from 1 + * + * + * @param {number} number + * @returns {number} + */ +const countNumberWordLength = (number) => { + let count = 0 + + // Not a number check + if (Number.isNaN(parseInt(number))) { + throw new Error('Invalid input, please provide valid number') + } + + // Number should be greater than 1 + if (number < 1) { + throw new Error('Please provide number greater that 1') + } + + // Loop to calculate word length by calling {@link numberToWord} + for (let i = 1; i <= number; i++) { + count += numberToWordLength(i) + } + + // return final count for number word length + return count +} + +export { countNumberWordLength } diff --git a/Project-Euler/Problem018.js b/Project-Euler/Problem018.js new file mode 100644 index 0000000000..6f57479b66 --- /dev/null +++ b/Project-Euler/Problem018.js @@ -0,0 +1,115 @@ +/** + * @file Provides solution for Project Euler Problem 18 - Maximum path sum I + * @author Eric Lavault {@link https://github.com/lvlte} + * @license MIT + */ + +/** + * Problem 18 - Maximum path sum I + * + * @see {@link https://projecteuler.net/problem=18} + * + * By starting at the top of the triangle below and moving to adjacent numbers + * on the row below, the maximum total from top to bottom is 23 : + * + * 3 + * 7 4 + * 2 4 6 + * 8 5 9 3 + * + * That is, 3 + 7 + 4 + 9 = 23. + * + * Find the maximum total from top to bottom of the triangle below : + * + * 75 + * 95 64 + * 17 47 82 + * 18 35 87 10 + * 20 04 82 47 65 + * 19 01 23 75 03 34 + * 88 02 77 73 07 63 67 + * 99 65 04 28 06 16 70 92 + * 41 41 26 56 83 40 80 70 33 + * 41 48 72 33 47 32 37 16 94 29 + * 53 71 44 65 25 43 91 52 97 51 14 + * 70 11 33 28 77 73 17 78 39 68 17 57 + * 91 71 52 38 17 14 91 43 58 50 27 29 48 + * 63 66 04 68 89 53 67 30 73 16 69 87 40 31 + * 04 62 98 27 23 09 70 98 73 93 38 53 60 04 23 + * + * NOTE: As there are only 16384 routes, it is possible to solve this problem + * by trying every route. However, Problem 67, is the same challenge with a + * triangle containing one-hundred rows; it cannot be solved by brute force, + * and requires a clever method! ;o) + */ + +const triangle = ` +75 +95 64 +17 47 82 +18 35 87 10 +20 04 82 47 65 +19 01 23 75 03 34 +88 02 77 73 07 63 67 +99 65 04 28 06 16 70 92 +41 41 26 56 83 40 80 70 33 +41 48 72 33 47 32 37 16 94 29 +53 71 44 65 25 43 91 52 97 51 14 +70 11 33 28 77 73 17 78 39 68 17 57 +91 71 52 38 17 14 91 43 58 50 27 29 48 +63 66 04 68 89 53 67 30 73 16 69 87 40 31 +04 62 98 27 23 09 70 98 73 93 38 53 60 04 23 +` + +export const maxPathSum = function (grid = triangle) { + /** + * If we reduce the problem to its simplest form, considering : + * + * 7 -> The max sum depends on the two adjacent numbers below 7, + * 2 4 not 7 itself. + * + * obviously 4 > 2 therefore the max sum is 7 + 4 = 11 + * + * 6 + * Likewise, with : 4 6 6 > 4 therefore the max sum is 6 + 6 = 12 + * + * Now, let's say we are given : + * + * 3 + * 7 6 + * 2 4 6 + * + * and we decompose it into sub-problems such that each one fits the simple + * case above, we got : + * + * . . 3 + * 7 . . 6 ? ? + * 2 4 . . 4 6 . . . + * + * Again, considering any number, the best path depends on the two adjacent + * numbers below it, not the number itself. That's why we have to compute + * the max sum from bottom to top, replacing each number with the sum of + * that number plus the greatest of the two adjacent numbers computed from + * the previous row. + * + * . . 3 15 + * 11 . . 12 -> 11 12 -> x x + * x x . . x x x x x x x x + * + * We are simplifying a complicated problem by breaking it down into simpler + * sub-problems in a recursive manner, this is called Dynamic Programming. + */ + + grid = grid + .split(/\r\n|\n/) + .filter((l) => l) + .map((r) => r.split(' ').map((n) => +n)) + + for (let i = grid.length - 2; i >= 0; i--) { + for (let j = 0; j < grid[i].length; j++) { + grid[i][j] += Math.max(grid[i + 1][j], grid[i + 1][j + 1]) + } + } + + return grid[0][0] +} diff --git a/Project-Euler/Problem019.js b/Project-Euler/Problem019.js new file mode 100644 index 0000000000..5b488c7f96 --- /dev/null +++ b/Project-Euler/Problem019.js @@ -0,0 +1,48 @@ +/** + * Problem 19 - Counting Sundays + * + * @see {@link https://projecteuler.net/problem=19} + * + * You are given the following information, but you may prefer to do some research for yourself. + * 1 Jan 1900 was a Monday. + * Thirty days has September, + * April, June and November. + * All the rest have thirty-one, + * Saving February alone, + * Which has twenty-eight, rain or shine. + * And on leap years, twenty-nine. + * A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400. + * How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)? + * + * @author ddaniel27 + */ +import { isLeapYear } from '../Maths/LeapYear' + +function problem19() { + let sundaysCount = 0 // Count of Sundays + let dayOfWeek = 2 // 1st Jan 1900 was a Monday, so 1st Jan 1901 was a Tuesday + + for (let year = 1901; year <= 2000; year++) { + for (let month = 1; month <= 12; month++) { + if (dayOfWeek === 0) { + // If it's a Sunday (0 is Sunday, 1 is Monday, ..., 6 is Saturday) + sundaysCount++ + } + + let daysInMonth = 31 + if (month === 4 || month === 6 || month === 9 || month === 11) { + // April, June, September, November + daysInMonth = 30 + } else if (month === 2) { + // February + daysInMonth = isLeapYear(year) ? 29 : 28 + } + + dayOfWeek = (dayOfWeek + daysInMonth) % 7 // Calculate the day of the week + } + } + + return sundaysCount +} + +export { problem19 } diff --git a/Project-Euler/Problem020.js b/Project-Euler/Problem020.js new file mode 100644 index 0000000000..256a4fb5de --- /dev/null +++ b/Project-Euler/Problem020.js @@ -0,0 +1,36 @@ +/** + * Problem 20 - Factorial digit sum + * + * @see {@link https://projecteuler.net/problem=20} + * + * n! means n ร— (n โˆ’ 1) ร— ... ร— 3 ร— 2 ร— 1 + * + * For example, 10! = 10 ร— 9 ร— ... ร— 3 ร— 2 ร— 1 = 3628800, + * and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27 + * + * Find the sum of the digits in the number 100! + */ + +const factorialDigitSum = (n = 100) => { + // Consider each digit*10^exp separately, right-to-left ([units, tens, ...]). + const digits = [1] + + for (let x = 2; x <= n; x++) { + let carry = 0 + for (let exp = 0; exp < digits.length; exp++) { + const prod = digits[exp] * x + carry + carry = Math.floor(prod / 10) + digits[exp] = prod % 10 + } + while (carry > 0) { + digits.push(carry % 10) + carry = Math.floor(carry / 10) + } + } + + // (digits are reversed but we only want the sum so it doesn't matter) + + return digits.reduce((prev, current) => prev + current, 0) +} + +export { factorialDigitSum } diff --git a/Project-Euler/Problem021.js b/Project-Euler/Problem021.js new file mode 100644 index 0000000000..dce2cd8a04 --- /dev/null +++ b/Project-Euler/Problem021.js @@ -0,0 +1,33 @@ +import { aliquotSum } from '../Maths/AliquotSum.js' + +/** + * Problem 21 - Amicable numbers + * + * @see {@link https://projecteuler.net/problem=21} + * + * Let d(n) be defined as the sum of proper divisors of n (numbers less than n which divide evenly into n). + * If d(a) = b and d(b) = a, where a != b, then a and b are an amicable pair and each of a and b are called amicable numbers. + * For example, the proper divisors of 220 are 1,2,4,5,10,11,20,22,44,55 and 110; therefore d(220) = 284. + * The proper divisors of 284 are 1,2,4,71 and 142; so d(284) = 220. + * Evaluate the sum of all amicable numbers under 10000 + * + * @author PraneethJain + */ + +function problem21(n) { + if (n < 2) { + throw new Error('Invalid Input') + } + + let result = 0 + for (let a = 2; a < n; ++a) { + const b = aliquotSum(a) // Sum of all proper divisors of a + // Check if b > a to ensure each pair isn't counted twice, and check if sum of proper divisors of b is equal to a + if (b > a && aliquotSum(b) === a) { + result += a + b + } + } + return result +} + +export { problem21 } diff --git a/Project-Euler/Problem023.js b/Project-Euler/Problem023.js new file mode 100644 index 0000000000..2adda7b2eb --- /dev/null +++ b/Project-Euler/Problem023.js @@ -0,0 +1,65 @@ +/** + * Problem 23 - Non-Abundant Sums + * + * @see {@link https://projecteuler.net/problem=23} + * + * A perfect number is a number for which the sum of its proper divisors is exactly equal to the number. For example, the sum of the proper divisors of 28 would be 1 + 2 + 4 + 7 + 14 = 28, which means that 28 is a perfect number. + * + * A number n is called deficient if the sum of its proper divisors is less than n and it is called abundant if this sum exceeds n. + * + * As 12 is the smallest abundant number, 1 + 2 + 3 + 4 + 6 = 16, the smallest number that can be written as the sum of two abundant numbers is 24. By mathematical analysis, it can be shown that all integers greater than 28123 can be written as the sum of two abundant numbers. However, this upper limit cannot be reduced any further by analysis even though it is known that the greatest number that cannot be expressed as the sum of two abundant numbers is less than this limit. + * + * Find the sum of all the positive integers which cannot be written as the sum of two abundant numbers. + * + */ + +/** + * collect the abundant numbers, generate and store their sums with each other, and check for numbers not in the list of sums, adds them and returns their sum. + * @param {number} [n = 28123] + * @returns {number} + */ + +function sumOfNonAbundantNumbers(n = 28123) { + const abundantNumbers = [] // array to store the abundant numbers + const sumOfAbundantNumbers = {} // instead of an array, checking an object takes way less time. sets may be used as well. + let sum = 0 + + for (let i = 1; i <= n; i++) { + if (isAbundant(i)) { + abundantNumbers.push(i) // collect the abundant numbers + abundantNumbers.forEach((num) => { + // collect their sums + const sum = num + i + sumOfAbundantNumbers[sum] = true + }) + } + } + + for (let i = 1; i <= n; i++) { + if (!sumOfAbundantNumbers[i]) { + // if the number is not found in the list of sums, then it is added + sum += i + } + } + + return sum +} + +/** + * generates the divisors of the number and checks if it is abundant + * @param {number} number + * @returns {bool} + */ + +function isAbundant(number) { + let sum = 0 + for (let i = 1; i <= number / 2; i++) { + if (number % i === 0) { + // generate divisors + sum += i // calculate their sums + } + } + return sum > number +} + +export { sumOfNonAbundantNumbers } diff --git a/Project-Euler/Problem025.js b/Project-Euler/Problem025.js new file mode 100644 index 0000000000..8d88edf8ee --- /dev/null +++ b/Project-Euler/Problem025.js @@ -0,0 +1,46 @@ +/** +* Problem 25 - 1000-digit Fibonacci number +* +* @see {@link https://projecteuler.net/problem=25} +* +* The Fibonacci sequence is defined by the recurrence relation: +* +* Fn = Fnโˆ’1 + Fnโˆ’2, where F1 = 1 and F2 = 1. +* +* Hence the first 12 terms will be: +* +* F1 = 1 +* F2 = 1 +* F3 = 2 +* F4 = 3 +* F5 = 5 +* F6 = 8 +* F7 = 13 +* F8 = 21 +* F9 = 34 +* F10 = 55 +* F11 = 89 +* F12 = 144 +* The 12th term, F12, is the first term to contain three digits. + +* What is the index of the first term in the Fibonacci sequence to contain 1000 digits? +*/ + +// brute force method + +function fibonacciIndex(t = 1000) { + const digits = 10n ** BigInt(t - 1) + let fib0 = BigInt(0) + let fib1 = BigInt(1) + let index = 1 + while (fib1 < digits) { + // using this to compare number of digits instead of .toString() significantly improved run time + const tempfib = fib1 + fib1 = fib1 + fib0 + fib0 = tempfib + index += 1 + } + return index +} + +export { fibonacciIndex } diff --git a/Project-Euler/Problem028.js b/Project-Euler/Problem028.js new file mode 100644 index 0000000000..48d00e1f91 --- /dev/null +++ b/Project-Euler/Problem028.js @@ -0,0 +1,53 @@ +/** + * Problem 28 - Number spiral diagonals + * + * @see {@link https://projecteuler.net/problem=28} + * + * Starting with the number 1 and moving to the right in a clockwise direction a 5 by 5 spiral is formed as follows: + * + * 21 22 23 24 25 + * 20 07 08 09 10 + * 19 06 01 02 11 + * 18 05 04 03 12 + * 17 16 15 14 13 + * + * It can be verified that the sum of the numbers on the diagonals is 101. + * What is the sum of the numbers on the diagonals in a 1001 by 1001 spiral formed in the same way? + * + * @author ddaniel27 + */ + +function problem28(dim) { + if (dim % 2 === 0) { + throw new Error('Dimension must be odd') + } + if (dim < 1) { + throw new Error('Dimension must be positive') + } + + let result = 1 + for (let i = 3; i <= dim; i += 2) { + /** + * Adding more dimensions to the matrix, we will find at the top-right corner the follow sequence: + * 01, 09, 25, 49, 81, 121, 169, ... + * So this can be expressed as: + * i^2, where i is all odd numbers + * + * Also, we can know which numbers are in each corner dimension + * Just develop the sequence counter clockwise from top-right corner like this: + * First corner: i^2 + * Second corner: i^2 - (i - 1) | The "i - 1" is the distance between corners in each dimension + * Third corner: i^2 - 2 * (i - 1) + * Fourth corner: i^2 - 3 * (i - 1) + * + * Doing the sum of each corner and simplifying, we found that the result for each dimension is: + * sumDim = 4 * i^2 + 6 * (1 - i) + * + * In this case I skip the 1x1 dim matrix because is trivial, that's why I start in a 3x3 matrix + */ + result += 4 * i * i + 6 * (1 - i) // Calculate sum of each dimension corner + } + return result +} + +export { problem28 } diff --git a/Project-Euler/Problem035.js b/Project-Euler/Problem035.js new file mode 100644 index 0000000000..0b11cd0357 --- /dev/null +++ b/Project-Euler/Problem035.js @@ -0,0 +1,39 @@ +/** + * 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/SieveOfEratosthenes' + +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/Problem044.js b/Project-Euler/Problem044.js new file mode 100644 index 0000000000..7ffc0dbb6e --- /dev/null +++ b/Project-Euler/Problem044.js @@ -0,0 +1,45 @@ +/** + * Problem 44 - Pentagon numbers + * + * @see {@link https://projecteuler.net/problem=44} + * + * Pentagonal numbers are generated by the formula, Pn=n(3nโˆ’1)/2. The first ten pentagonal numbers are: + * 1, 5, 12, 22, 35, 51, 70, 92, 117, 145, ... + * It can be seen that P4 + P7 = 22 + 70 = 92 = P8. However, their difference, 70 โˆ’ 22 = 48, is not pentagonal. + * Find the pair of pentagonal numbers, Pj and Pk, for which their sum and difference are pentagonal and D = |Pk โˆ’ Pj| is minimised; what is the value of D? + * + * @author ddaniel27 + */ + +function problem44(k) { + if (k < 1) { + throw new Error('Invalid Input') + } + + while (true) { + k++ + const n = (k * (3 * k - 1)) / 2 // calculate Pk + + for (let j = k - 1; j > 0; j--) { + const m = (j * (3 * j - 1)) / 2 // calculate all Pj < Pk + if (isPentagonal(n - m) && isPentagonal(n + m)) { + // Check sum and difference + return n - m // return D + } + } + } +} + +/** + * Function to check if a number is pentagonal or not + * This function solves n + * applying the solution for a quadratic function + * @see {@link https://en.wikipedia.org/wiki/Quadratic_function} + */ + +function isPentagonal(n) { + const pent = (Math.sqrt(24 * n + 1) + 1) / 6 + return pent === Math.floor(pent) +} + +export { problem44 } diff --git a/Project-Euler/test/Problem001.test.js b/Project-Euler/test/Problem001.test.js new file mode 100644 index 0000000000..22f7628d6a --- /dev/null +++ b/Project-Euler/test/Problem001.test.js @@ -0,0 +1,21 @@ +import { multiplesThreeAndFive } from '../Problem001.js' + +describe('Sum of multiples of 3 or 5', () => { + it('should throw error when number is negative number', () => { + expect(() => multiplesThreeAndFive(-24)).toThrowError( + 'No natural numbers exist below 1' + ) + }) + it('should throw error when number is 0', () => { + expect(() => multiplesThreeAndFive(0)).toThrowError( + 'No natural numbers exist below 1' + ) + }) + test('if the number is greater than 0', () => { + expect(multiplesThreeAndFive(10)).toBe(23) + }) + // Project Euler Condition Check + test('if the number is 1000', () => { + expect(multiplesThreeAndFive(1000)).toBe(233168) + }) +}) diff --git a/Project-Euler/test/Problem002.test.js b/Project-Euler/test/Problem002.test.js new file mode 100644 index 0000000000..375a867e9d --- /dev/null +++ b/Project-Euler/test/Problem002.test.js @@ -0,0 +1,16 @@ +import { EvenFibonacci } from '../Problem002' + +describe('Even Fibonacci numbers', () => { + it('should throw error when limit is less than 1', () => { + expect(() => EvenFibonacci(-1)).toThrowError( + "Fibonacci sequence limit can't be less than 1" + ) + }) + test('when limit is greater than 0', () => { + expect(EvenFibonacci(40)).toBe(44) + }) + // Project Euler Condition Check + test('when limit is 4 million', () => { + expect(EvenFibonacci(4e6)).toBe(4613732) + }) +}) diff --git a/Project-Euler/test/Problem003.test.js b/Project-Euler/test/Problem003.test.js new file mode 100644 index 0000000000..0f6cf4e6da --- /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/Problem004.test.js b/Project-Euler/test/Problem004.test.js new file mode 100644 index 0000000000..cd6a55ac98 --- /dev/null +++ b/Project-Euler/test/Problem004.test.js @@ -0,0 +1,11 @@ +import { largestPalindromic } from '../Problem004.js' + +describe('Largest Palindromic Number', () => { + test('if digit is 2', () => { + expect(largestPalindromic(2)).toBe(9009) + }) + // Project Euler Condition Check + test('if digit is 3', () => { + expect(largestPalindromic(3)).toBe(906609) + }) +}) diff --git a/Project-Euler/test/Problem005.test.js b/Project-Euler/test/Problem005.test.js new file mode 100644 index 0000000000..f3ac1b03d8 --- /dev/null +++ b/Project-Euler/test/Problem005.test.js @@ -0,0 +1,12 @@ +import { expect } from 'vitest' +import { findSmallestMultiple } from '../Problem005.js' + +describe.concurrent('Find smallest multiple', () => { + test.each([ + [10, 2520], + [15, 360360], + [20, 232792560] + ])('max divisor -> %i, smallest multiple -> %i', (a, expected) => { + expect(findSmallestMultiple(a)).toBe(expected) + }) +}) diff --git a/Project-Euler/test/Problem006.test.js b/Project-Euler/test/Problem006.test.js new file mode 100644 index 0000000000..1323a34ac2 --- /dev/null +++ b/Project-Euler/test/Problem006.test.js @@ -0,0 +1,11 @@ +import { squareDifference } from '../Problem006.js' + +describe('Square Difference', () => { + test('difference between the sum of the squares of the first ten natural numbers and the square of the sum', () => { + expect(squareDifference(10)).toBe(2640) + }) + // Project Euler Condition Check + test('difference between the sum of the squares of the first one hundred natural numbers and the square of the sum', () => { + expect(squareDifference()).toBe(25164150) + }) +}) diff --git a/Project-Euler/test/Problem007.test.js b/Project-Euler/test/Problem007.test.js new file mode 100644 index 0000000000..191d1e06af --- /dev/null +++ b/Project-Euler/test/Problem007.test.js @@ -0,0 +1,17 @@ +import { nthPrime } from '../Problem007.js' + +describe('checking nth prime number', () => { + it('should be invalid input if number is negative', () => { + expect(() => nthPrime(-3)).toThrowError('Invalid Input') + }) + it('should be invalid input if number is 0', () => { + expect(() => nthPrime(0)).toThrowError('Invalid Input') + }) + test('if the number is greater than 0', () => { + expect(nthPrime(10)).toBe(29) + }) + // Project Euler Condition Check + test('if the number is 10001', () => { + expect(nthPrime(10001)).toBe(104743) + }) +}) diff --git a/Project-Euler/test/Problem008.test.js b/Project-Euler/test/Problem008.test.js new file mode 100644 index 0000000000..b9d27324c0 --- /dev/null +++ b/Project-Euler/test/Problem008.test.js @@ -0,0 +1,104 @@ +import { largestAdjacentNumber } from '../Problem008' + +const grid1 = `73167176531330624919225119674426574742355349194934 +96983520312774506326239578318016984801869478851843 +85861560789112949495459501737958331952853208805511 +12540698747158523863050715693290963295227443043557 +66896648950445244523161731856403098711121722383113 +38991007832084782777189757278819047278899274961949 +30358907296290491560440772390713810515859307960866 +70172427121883998797908792274921901699720888093776 +65727333001053367881220235421809751254540594752243 +82834772897718748193457134085784719857638187485919 +53697817977846174064955149290862569321978468622482 +83972241375657056057490261407972968652414535100474 +82166370484403199890008895243450658541227588666881 +83247832478757285710958372981577583275832789327921 +17866458359124566529476545682848912883142607690042 +24219022671055626321111109370544217506941658960408 +84327878357761783787589375857378271083787811983779 +84580156166097919133875499200524063689912560717606 +05886116467109405077541002256983155200055935729725 +82347875831098357801578571807585817518287829189189` + +const grid2 = `73167176531330624919225119674426574742355349194934 +96983520312774506326239578318016984801869478851843 +85861560789112949495459501737958331952853208805511 +12540698747158523863050715693290963295227443043557 +66896648950445244523161731856403098711121722383113 +62229893423380308135336276614282806444486645238749 +30358907296290491560440772390713810515859307960866 +70172427121883998797908792274921901699720888093776 +65727333001053367881220235421809751254540594752243 +52584907711670556013604839586446706324415722155397 +53697817977846174064955149290862569321978468622482 +83972241375657056057490261407972968652414535100474 +82166370484403199890008895243450658541227588666881 +16427171479924442928230863465674813919123162824586 +17866458359124566529476545682848912883142607690042 +24219022671055626321111109370544217506941658960408 +07198403850962455444362981230987879927244284909188 +84580156166097919133875499200524063689912560717606 +05886116467109405077541002256983155200055935729725 +71636269561882670428252483600823257530420752963450` + +const grid3 = `89125732138957892357892768971807934878999818278898 +48327483578957875827583295789187588875238579887789 +74358275778171870973857835738758275210873583758279 +81347834738473878758758373857893758787772897580719 +81232847857895758758987587501087575387538183787098 +17047878395783578750837100983787587582797837508298 +42894789325732857893759187987487489748377578791989 +82147328972387832578327581919827382758932789798289 +83243289473847328974832947832748932472387895738978 +84738294738973289578753287582375238957573297892398 +29383748329748397483274832748327477575018978975289 +48327483758375846372864736476478364783647463278787 +73281473847832974328758975890189373857875875895898 +74328978748329789357389578329758329758937893758979 +81738957389579287598217589127589375893275987359889 +71890743894732897510875895783297591085738975837897 +10783974839479879857895789758975981735870175835789 +01494787857897583758975849758475107589754897589789 +09939858758919788017587897587387585775289757982898 +74718478978758758975897589789789798789178957789789` + +const grid4 = `99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999 +99999999999999999999999999999999999999999999999999` + +describe('checkLargestAdjacentNumberProduct', () => { + it('Random Example', () => { + const RESULT = largestAdjacentNumber(grid1, 13) + expect(RESULT).toBe(23514624000) + }) + it('Problem statement Example', () => { + const RESULT = largestAdjacentNumber(grid2, 13) + expect(RESULT).toBe(23514624000) + }) + it('Random Example 2', () => { + const RESULT = largestAdjacentNumber(grid3, 13) + expect(RESULT).toBe(580717154304) + }) + it('Example with all 9', () => { + const RESULT = largestAdjacentNumber(grid4, 13) + expect(RESULT).toBe(2541865828329) + }) +}) diff --git a/Project-Euler/test/Problem009.test.js b/Project-Euler/test/Problem009.test.js new file mode 100644 index 0000000000..3919fa2833 --- /dev/null +++ b/Project-Euler/test/Problem009.test.js @@ -0,0 +1,8 @@ +import { findSpecialPythagoreanTriplet } from '../Problem009.js' + +describe('Pythagorean Triplet', () => { + // Project Euler Condition Check + test('the multiplication of the pythagorean triplet where a + b + c = 1000', () => { + expect(findSpecialPythagoreanTriplet()).toBe(31875000) + }) +}) diff --git a/Project-Euler/test/Problem010.test.js b/Project-Euler/test/Problem010.test.js new file mode 100644 index 0000000000..2169cabc12 --- /dev/null +++ b/Project-Euler/test/Problem010.test.js @@ -0,0 +1,16 @@ +import { calculateSumOfPrimeNumbers } from '../Problem010' + +describe('checkAnagram', () => { + it('Return the sum of prime numbers up to but less than 14', () => { + const SUT = calculateSumOfPrimeNumbers(14) + expect(SUT).toBe(41) + }) + it('Return the sum of prime numbers up to but less than 10', () => { + const SUT = calculateSumOfPrimeNumbers(10) + expect(SUT).toBe(17) + }) + it('Return the sum of prime numbers up to but less than 100', () => { + const SUT = calculateSumOfPrimeNumbers(100) + expect(SUT).toBe(1060) + }) +}) diff --git a/Project-Euler/test/Problem011.test.js b/Project-Euler/test/Problem011.test.js new file mode 100644 index 0000000000..3f86dd1d48 --- /dev/null +++ b/Project-Euler/test/Problem011.test.js @@ -0,0 +1,63 @@ +import { largestProductInAGrid } from '../Problem011.js' + +const arr = [ + [8, 2, 22, 97, 38, 15, 0, 40, 0, 75, 4, 5, 7, 78, 52, 12, 50, 77, 91, 8], + [ + 49, 49, 99, 40, 17, 81, 18, 57, 60, 87, 17, 40, 98, 43, 69, 48, 4, 56, 62, 0 + ], + [ + 81, 49, 31, 73, 55, 79, 14, 29, 93, 71, 40, 67, 53, 88, 30, 3, 49, 13, 36, + 65 + ], + [52, 70, 95, 23, 4, 60, 11, 42, 69, 24, 68, 56, 1, 32, 56, 71, 37, 2, 36, 91], + [ + 22, 31, 16, 71, 51, 67, 63, 89, 41, 92, 36, 54, 22, 40, 40, 28, 66, 33, 13, + 80 + ], + [ + 24, 47, 32, 60, 99, 3, 45, 2, 44, 75, 33, 53, 78, 36, 84, 20, 35, 17, 12, 50 + ], + [ + 32, 98, 81, 28, 64, 23, 67, 10, 26, 38, 40, 67, 59, 54, 70, 66, 18, 38, 64, + 70 + ], + [ + 67, 26, 20, 68, 2, 62, 12, 20, 95, 63, 94, 39, 63, 8, 40, 91, 66, 49, 94, 21 + ], + [ + 24, 55, 58, 5, 66, 73, 99, 26, 97, 17, 78, 78, 96, 83, 14, 88, 34, 89, 63, + 72 + ], + [21, 36, 23, 9, 75, 0, 76, 44, 20, 45, 35, 14, 0, 61, 33, 97, 34, 31, 33, 95], + [78, 17, 53, 28, 22, 75, 31, 67, 15, 94, 3, 80, 4, 62, 16, 14, 9, 53, 56, 92], + [ + 16, 39, 5, 42, 96, 35, 31, 47, 55, 58, 88, 24, 0, 17, 54, 24, 36, 29, 85, 57 + ], + [86, 56, 0, 48, 35, 71, 89, 7, 5, 44, 44, 37, 44, 60, 21, 58, 51, 54, 17, 58], + [ + 19, 80, 81, 68, 5, 94, 47, 69, 28, 73, 92, 13, 86, 52, 17, 77, 4, 89, 55, 40 + ], + [4, 52, 8, 83, 97, 35, 99, 16, 7, 97, 57, 32, 16, 26, 26, 79, 33, 27, 98, 66], + [ + 88, 36, 68, 87, 57, 62, 20, 72, 3, 46, 33, 67, 46, 55, 12, 32, 63, 93, 53, + 69 + ], + [ + 4, 42, 16, 73, 38, 25, 39, 11, 24, 94, 72, 18, 8, 46, 29, 32, 40, 62, 76, 36 + ], + [ + 20, 69, 36, 41, 72, 30, 23, 88, 34, 62, 99, 69, 82, 67, 59, 85, 74, 4, 36, + 16 + ], + [ + 20, 73, 35, 29, 78, 31, 90, 1, 74, 31, 49, 71, 48, 86, 81, 16, 23, 57, 5, 54 + ], + [1, 70, 54, 71, 83, 51, 54, 69, 16, 92, 33, 48, 61, 43, 52, 1, 89, 19, 67, 48] +] + +describe('Checking Largest Product In A Grid', () => { + // Project Euler Condition Check + test('Test Euler Condition', () => { + expect(largestProductInAGrid(arr)).toBe(70600674) + }) +}) diff --git a/Project-Euler/test/Problem012.test.js b/Project-Euler/test/Problem012.test.js new file mode 100644 index 0000000000..2fd1f8d897 --- /dev/null +++ b/Project-Euler/test/Problem012.test.js @@ -0,0 +1,8 @@ +import { firstTriangularWith500Divisors } from '../Problem012' + +describe('checkFirstTriangularWith500Divisors()', () => { + it('Problem Statement Answer', () => { + const firstTriangular = firstTriangularWith500Divisors() + expect(firstTriangular).toBe(76576500) + }) +}) diff --git a/Project-Euler/test/Problem013.test.js b/Project-Euler/test/Problem013.test.js new file mode 100644 index 0000000000..7996ee1f29 --- /dev/null +++ b/Project-Euler/test/Problem013.test.js @@ -0,0 +1,11 @@ +import { largeSum } from '../Problem013.js' + +const bignum = + '37107287533902102798797998220837590246510135740250463769376774900097126481248969700780504170182605387432498619952474105947423330951305812372661730962991942213363574161572522430563301811072406154908250230675882075393461711719803104210475137780632466768926167069662363382013637841838368417873436172675728112879812849979408065481931592621691275889832738442742289174325203219235894228767964876702721893184745144573600130643909116721685684458871160315327670386486105843025439939619828917593665686757934951621764571418565606295021572231965867550793241933316490635246274190492910143244581382266334794475817892575867718337217661963751590579239728245598838407582035653253593990084026335689488301894586282278288018119938482628201427819413994056758715117009439035398664372827112653829987240784473053190104293586865155060062958648615320752733719591914205172558297169388870771546649911559348760353292171497005693854370070576826684624621495650076471787294438377604532826541087568284431911906346940378552177792951453612327252500029607107508256381565671088525835072145876576172410976447339110607218265236877223636045174237069058518606604482076212098132878607339694128114266041808683061932846081119106155694051268969251934325451728388641918047049293215058642563049483624672216484350762017279180399446930047329563406911573244438690812579451408905770622942919710792820955037687525678773091862540744969844508330393682126183363848253301546861961243487676812975343759465158038628759287849020152168555482871720121925776695478182833757993103614740356856449095527097864797581167263201004368978425535399209318374414978068609844840309812907779179908821879532736447567559084803087086987551392711854517078544161852424320693150332599594068957565367821070749269665376763262354472106979395067965269474259770973916669376304263398708541052684708299085211399427365734116182760315001271653786073615010808570091499395125570281987460043753582903531743471732693212357815498262974255273730794953759765105305946966067683156574377167401875275889028025717332296191766687138199318110487701902712526768027607800301367868099252546340106163286652636270218540497705585629946580636237993140746255962240744869082311749777923654662572469233228109171419143028819710328859780666976089293863828502533340334413065578016127815921815005561868836468420090470230530811728164304876237919698424872550366387845831148769693215490281042402013833512446218144177347063783299490636259666498587618221225225512486764533677201869716985443124195724099139590089523100588229554825530026352078153229679624948164195386821877476085327132285723110424803456124867697064507995236377742425354112916842768655389262050249103265729672370191327572567528565324825826546309220705859652229798860272258331913126375147341994889534765745501184957014548792889848568277260777137214037988797153829820378303147352772158034814451349137322665138134829543829199918180278916522431027392251122869539409579530664052326325380441000596549391598795936352974615218550237130764225512118369380358038858490341698116222072977186158236678424689157993532961922624679571944012690438771072750481023908955235974572318970677254791506150550495392297953090112996751986188088225875314529584099251203829009407770775672113067397083047244838165338735023408456470580773088295917476714036319800818712901187549131054712658197623331044818386269515456334926366572897563400500428462801835170705278318394258821455212272512503275512160354698120058176216521282765275169129689778932238195734329339946437501907836945765883352399886755061649651847751807381688378610915273579297013376217784275219262340194239963916804498399317331273132924185707147349566916674687634660915035914677504995186714302352196288948901024233251169136196266227326746080059154747183079839286853520694694454072476841822524674417161514036427982273348055556214818971426179103425986472045168939894221798260880768528778364618279934631376775430780936333301898264209010848802521674670883215120185883543223812876952786713296124747824645386369930090493103636197638780396218407357239979422340623539380833965132740801111666627891981488087797941876876144230030984490851411606618262936828367647447792391803351109890697907148578694408955299065364044742557608365997664579509666024396409905389607120198219976047599490197230297649139826800329731560371200413779037855660850892521673093931987275027546890690370753941304265231501194809377245048795150954100921645863754710598436791786391670211874924319957006419179697775990283006991536871371193661495281130587638027841075444973307840789923115535562561142322423255033685442488917353448899115014406480203690680639606723221932041495354150312888033953605329934036800697771065056663195481234880673210146739058568557934581403627822703280826165707739483275922328459417065250945123252306082291880205877731971983945018088807242966198081119777158542502016545090413245809786882778948721859617721078384350691861554356628840622574736922845095162084960398013400172393067166682355524525280460972253503534226472524250874054075591789781264330331690' + +describe('checking Large Sum', () => { + // Project Euler Condition Check + test('Test Euler Condition', () => { + expect(largeSum(bignum)).toBe('5537376230') + }) +}) diff --git a/Project-Euler/test/Problem014.test.js b/Project-Euler/test/Problem014.test.js new file mode 100644 index 0000000000..ff464dd42d --- /dev/null +++ b/Project-Euler/test/Problem014.test.js @@ -0,0 +1,15 @@ +import { expect } from 'vitest' +import { findLongestCollatzSequence } from '../Problem014.js' + +describe('Longest Collatz Sequence', () => { + test.each([ + [2, 1], + [13, 9], + [1000000, 837799] + ])( + 'if limit is %i, then the Longest Collatz Sequence will be %i', + (a, expected) => { + expect(findLongestCollatzSequence(a)).toBe(expected) + } + ) +}) diff --git a/Project-Euler/test/Problem016.test.js b/Project-Euler/test/Problem016.test.js new file mode 100644 index 0000000000..9bb229f1a0 --- /dev/null +++ b/Project-Euler/test/Problem016.test.js @@ -0,0 +1,16 @@ +import { powerDigitSum } from '../Problem016' + +describe('Check Problem 16 - Power digit sum', () => { + it('Power digit sum of 2^15', () => { + expect(powerDigitSum(2, 15)).toBe(26) + }) + + it('Power digit sum of 2^1000', () => { + expect(powerDigitSum()).toBe(1366) + expect(powerDigitSum(2, 1000)).toBe(1366) + }) + + it('Power digit sum of 3^5000', () => { + expect(powerDigitSum(3, 5000)).toBe(11097) + }) +}) diff --git a/Project-Euler/test/Problem017.test.js b/Project-Euler/test/Problem017.test.js new file mode 100644 index 0000000000..9310faa33a --- /dev/null +++ b/Project-Euler/test/Problem017.test.js @@ -0,0 +1,18 @@ +import { countNumberWordLength } from '../Problem017.js' + +describe('Number letter count', () => { + test.each([ + [5, 19], + [100, 864], + [1000, 21124] + ])('Number letter count from 1 to %i', (n, expected) => { + expect(countNumberWordLength(n)).toBe(expected) + }) + + test.each([ + ['test', 'Invalid input, please provide valid number'], + [0, 'Please provide number greater that 1'] + ])('Should throw an error for input %i', (n, expected) => { + expect(() => countNumberWordLength(n)).toThrowError(expected) + }) +}) diff --git a/Project-Euler/test/Problem018.test.js b/Project-Euler/test/Problem018.test.js new file mode 100644 index 0000000000..a9b3f21091 --- /dev/null +++ b/Project-Euler/test/Problem018.test.js @@ -0,0 +1,18 @@ +import { maxPathSum } from '../Problem018' + +const example = ` +3 +7 4 +2 4 6 +8 5 9 3 +` + +describe('Check Problem 18 - Maximum path sum I', () => { + it('Check example', () => { + expect(maxPathSum(example)).toBe(23) + }) + + it('Check solution', () => { + expect(maxPathSum()).toBe(1074) + }) +}) diff --git a/Project-Euler/test/Problem019.test.js b/Project-Euler/test/Problem019.test.js new file mode 100644 index 0000000000..8845e5a0ac --- /dev/null +++ b/Project-Euler/test/Problem019.test.js @@ -0,0 +1,8 @@ +import { problem19 } from '../Problem019.js' + +describe('checking sundays during the twentieth century', () => { + // Project Euler Challenge Check + test('result should be 171', () => { + expect(problem19()).toBe(171) + }) +}) diff --git a/Project-Euler/test/Problem020.test.js b/Project-Euler/test/Problem020.test.js new file mode 100644 index 0000000000..9a4c31e186 --- /dev/null +++ b/Project-Euler/test/Problem020.test.js @@ -0,0 +1,16 @@ +import { factorialDigitSum } from '../Problem020' + +describe('Check Problem 20 - Factorial digit sum', () => { + it('Factorial digit sum of 10!', () => { + expect(factorialDigitSum(10)).toBe(27) + }) + + it('Factorial digit sum of 100!', () => { + expect(factorialDigitSum()).toBe(648) + expect(factorialDigitSum(100)).toBe(648) + }) + + it('Factorial digit sum of 1000!', () => { + expect(factorialDigitSum(1000)).toBe(10539) + }) +}) diff --git a/Project-Euler/test/Problem021.test.js b/Project-Euler/test/Problem021.test.js new file mode 100644 index 0000000000..411f6da0d2 --- /dev/null +++ b/Project-Euler/test/Problem021.test.js @@ -0,0 +1,14 @@ +import { problem21 } from '../Problem021.js' + +describe('check sum of amicable numbers under n', () => { + test('should be invalid input if number is negative', () => { + expect(() => problem21(-1)).toThrowError('Invalid Input') + }) + test('should be invalid input if number is 0', () => { + expect(() => problem21(0)).toThrowError('Invalid Input') + }) + // Project Euler Condition Check + test('if the number is greater or equal to 1', () => { + expect(problem21(10000)).toBe(31626) + }) +}) diff --git a/Project-Euler/test/Problem023.test.js b/Project-Euler/test/Problem023.test.js new file mode 100644 index 0000000000..a62c4d8c42 --- /dev/null +++ b/Project-Euler/test/Problem023.test.js @@ -0,0 +1,23 @@ +import { sumOfNonAbundantNumbers } from '../Problem023' + +describe('Check Problem 23 - Non-Abundant Sums', () => { + it('Sum of all positive integers <= 10000 which cannot be written as the sum of two abundant numbers', () => { + expect(sumOfNonAbundantNumbers(10000)).toBe(3731004) + }) + + it('Sum of all positive integers <= n which cannot be written as the sum of two abundant numbers', () => { + expect(sumOfNonAbundantNumbers(15000)).toBe(4039939) + }) + + it('Sum of all positive integers <= n which cannot be written as the sum of two abundant numbers', () => { + expect(sumOfNonAbundantNumbers(20000)).toBe(4159710) + }) + + it('Sum of all positive integers <= n which cannot be written as the sum of two abundant numbers', () => { + expect(sumOfNonAbundantNumbers(28123)).toBe(4179871) + }) + + it('Sum of all positive integers <= n which cannot be written as the sum of two abundant numbers', () => { + expect(sumOfNonAbundantNumbers(30000)).toBe(4179871) + }) +}) diff --git a/Project-Euler/test/Problem025.test.js b/Project-Euler/test/Problem025.test.js new file mode 100644 index 0000000000..3e5090a027 --- /dev/null +++ b/Project-Euler/test/Problem025.test.js @@ -0,0 +1,27 @@ +import { fibonacciIndex } from '../Problem025' + +describe('Check Problem 25 - 1000 digit Fibonnaci number', () => { + it('First term of the Fibonnaci sequence containing 3 digits', () => { + expect(fibonacciIndex(3)).toBe(12) + }) + + it('First term of the Fibonnaci sequence containing 10 digits', () => { + expect(fibonacciIndex(10)).toBe(45) + }) + + it('First term of the Fibonnaci sequence containing 50 digits', () => { + expect(fibonacciIndex(50)).toBe(237) + }) + + it('First term of the Fibonnaci sequence containing 100 digits', () => { + expect(fibonacciIndex(100)).toBe(476) + }) + + it('First term of the Fibonnaci sequence containing 1000 digits', () => { + expect(fibonacciIndex(1000)).toBe(4782) + }) + + it('First term of the Fibonnaci sequence containing 10000 digits', () => { + expect(fibonacciIndex(10000)).toBe(47847) + }) +}) diff --git a/Project-Euler/test/Problem028.test.js b/Project-Euler/test/Problem028.test.js new file mode 100644 index 0000000000..ad99654598 --- /dev/null +++ b/Project-Euler/test/Problem028.test.js @@ -0,0 +1,17 @@ +import { problem28 } from '../Problem028.js' + +describe('checking number spiral diagonals', () => { + it('should be invalid input if number is negative', () => { + expect(() => problem28(-3)).toThrowError('Dimension must be positive') + }) + it('should be invalid input if number is not odd', () => { + expect(() => problem28(4)).toThrowError('Dimension must be odd') + }) + test('if the number is equal to 5 result should be 101', () => { + expect(problem28(5)).toBe(101) + }) + // Project Euler Condition Check + test('if the number is equal to 1001 result should be 669171001', () => { + expect(problem28(1001)).toBe(669171001) + }) +}) diff --git a/Project-Euler/test/Problem035.test.js b/Project-Euler/test/Problem035.test.js new file mode 100644 index 0000000000..ebaa4ac46f --- /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 new file mode 100644 index 0000000000..b3522a7884 --- /dev/null +++ b/Project-Euler/test/Problem044.test.js @@ -0,0 +1,18 @@ +import { problem44 } from '../Problem044.js' + +describe('checking nth prime number', () => { + test('should be invalid input if number is negative', () => { + expect(() => problem44(-3)).toThrowError('Invalid Input') + }) + test('should be invalid input if number is 0', () => { + expect(() => problem44(0)).toThrowError('Invalid Input') + }) + // Project Euler Condition Check + test('if the number is greater or equal to 1', () => { + expect(problem44(1)).toBe(5482660) + }) + // Project Euler Second Value for Condition Check + test('if the number is greater or equal to 2167', () => { + expect(problem44(2167)).toBe(8476206790) + }) +}) diff --git a/README.md b/README.md index d11a6504b3..bd9582fe94 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,68 @@ # The Algorithms - JavaScript + -[![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Javascript/blob/master/CONTRIBUTING.md)  -[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/TheAlgorithms/Javascript.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/TheAlgorithms/Javascript/context:javascript) -![Node CI](https://github.com/TheAlgorithms/Javascript/workflows/Node%20CI/badge.svg) -![update_directory_md](https://github.com/TheAlgorithms/Javascript/workflows/update_directory_md/badge.svg) -![](https://img.shields.io/github/repo-size/TheAlgorithms/Javascript.svg?label=Repo%20size&style=flat-square)  +JavaScript Repository of TheAlgorithms, which implements various algorithms and data structures in JavaScript. -### All algorithms implemented in JavaScript (for educational purposes only) +
-These are for demonstration purposes only. There are many implementations of sorts in the JavaScript standard library that are much better for performance reasons. +[![JavaScript Banner][banner]](DIRECTORY.md) -## Contribution Guidelines +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/TheAlgorithms/JavaScript) +[![Checks][checks]][actions] +[![codecov](https://codecov.io/gh/TheAlgorithms/JavaScript/graph/badge.svg?token=8VeZwL31KZ)](https://codecov.io/gh/TheAlgorithms/JavaScript) +[![Contributions Welcome][welcome]](CONTRIBUTING.md) +[![standard.js][standard-logo]][standard-js] +[![Discord chat][chat]][discord-server] -Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. +
-## List of Algorithms +--- -See our [directory](DIRECTORY.md). + -## Algorithm Explanation +

+ These implementations are for demonstrative purposes only. Dedicated implementations of these algorithms and data + structures are much better for performance and security reasons. We also do not provide any guarantee for api stability. +

-see our [wiki](https://github.com/TheAlgorithms/Javascript/wiki) +--- + + + +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 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 +many of the algorithms can be found in the [wiki][explanation]. + +--- + + + +[banner]: https://user-images.githubusercontent.com/68542775/167072911-dc31eac8-6885-4a05-9c25-279ecce22a79.png + + + +[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/actions/workflow/status/TheAlgorithms/JavaScript/Ci.yml?branch=master&label=checks + + + +[standard-js]: https://standardjs.com/ +[discord-server]: https://the-algorithms.com/discord/ +[actions]: https://github.com/TheAlgorithms/JavaScript/actions +[explanation]: https://github.com/TheAlgorithms/JavaScript/wiki +[repositories]: https://github.com/orgs/TheAlgorithms/repositories +[help-wanted]: https://github.com/TheAlgorithms/JavaScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22 + +## Thanks to all the contributors โค๏ธ + + + + diff --git a/Recursive/BinaryEquivalent.js b/Recursive/BinaryEquivalent.js new file mode 100644 index 0000000000..df92f7b3ff --- /dev/null +++ b/Recursive/BinaryEquivalent.js @@ -0,0 +1,21 @@ +/* + * Problem Statement: Given a positive number `num`, find it's binary equivalent using recursion + * + * What is Binary Equivalent? + * - In binary number system, a number is represented in terms of 0s and 1s, + * for example: + * - Binary Of 2 = 10 + * - Binary of 3 = 11 + * - Binary of 4 = 100 + * + * Reference on how to find Binary Equivalent + * - https://byjus.com/maths/decimal-to-binary/ + * + */ + +export const binaryEquivalent = (num) => { + if (num === 0 || num === 1) { + return String(num) + } + return binaryEquivalent(Math.floor(num / 2)) + String(num % 2) +} diff --git a/Recursive/BinarySearch.js b/Recursive/BinarySearch.js new file mode 100644 index 0000000000..ba622daaa1 --- /dev/null +++ b/Recursive/BinarySearch.js @@ -0,0 +1,33 @@ +/** + * @function BinarySearch + * @description Search the integer inside the sorted integers array using Binary Search Algorithm. + * @param {Integer[]} arr - sorted array of integers + * @param {Integer} low - The input integer + * @param {Integer} high - The input integer + * @param {Integer} searchValue - The input integer + * @return {Integer} - return index of searchValue if found else return -1. + * @see [BinarySearch](https://en.wikipedia.org/wiki/Binary_search_algorithm) + */ + +const binarySearch = (arr, searchValue, low = 0, high = arr.length - 1) => { + // base case + if (high < low || arr.length === 0) return -1 + + const mid = low + Math.floor((high - low) / 2) + + // If the element is present at the middle + if (arr[mid] === searchValue) { + return mid + } + + // If element is smaller than mid, then + // it can only be present in left subarray + if (arr[mid] > searchValue) { + return binarySearch(arr, searchValue, low, mid - 1) + } + + // Else the element can only be present in right subarray + return binarySearch(arr, searchValue, mid + 1, high) +} + +export { binarySearch } diff --git a/Recursive/EucledianGCD.js b/Recursive/EucledianGCD.js deleted file mode 100644 index 5f1c51a06d..0000000000 --- a/Recursive/EucledianGCD.js +++ /dev/null @@ -1,37 +0,0 @@ -function euclideanGCDRecursive (first, second) { - /* - Calculates GCD of two numbers using Euclidean Recursive Algorithm - :param first: First number - :param second: Second number - :return: GCD of the numbers - */ - if (second === 0) { - return first - } else { - return euclideanGCDRecursive(second, (first % second)) - } -} - -function euclideanGCDIterative (first, second) { - /* - Calculates GCD of two numbers using Euclidean Iterative Algorithm - :param first: First number - :param second: Second number - :return: GCD of the numbers - */ - while (second !== 0) { - const temp = second - second = first % second - first = temp - } - return first -} - -function main () { - const first = 20 - const second = 30 - console.log('Recursive GCD for %d and %d is %d', first, second, euclideanGCDRecursive(first, second)) - console.log('Iterative GCD for %d and %d is %d', first, second, euclideanGCDIterative(first, second)) -} - -main() diff --git a/Recursive/Factorial.js b/Recursive/Factorial.js new file mode 100644 index 0000000000..4e2b3c1bff --- /dev/null +++ b/Recursive/Factorial.js @@ -0,0 +1,23 @@ +/** + * @function Factorial + * @description function to find factorial using recursion. + * @param {Integer} n - The input integer + * @return {Integer} - Factorial of n. + * @see [Factorial](https://en.wikipedia.org/wiki/Factorial) + * @example 5! = 1*2*3*4*5 = 120 + * @example 2! = 1*2 = 2 + */ + +const factorial = (n) => { + if (!Number.isInteger(n) || n < 0) { + throw new RangeError('Input should be a non-negative whole number') + } + + if (n === 0) { + return 1 + } + + return n * factorial(n - 1) +} + +export { factorial } diff --git a/Recursive/FibonacciNumberRecursive.js b/Recursive/FibonacciNumberRecursive.js new file mode 100644 index 0000000000..911d078e91 --- /dev/null +++ b/Recursive/FibonacciNumberRecursive.js @@ -0,0 +1,16 @@ +/** + * @function Fibonacci + * @description Function to return the N-th Fibonacci number. + * @param {Integer} n - The input integer + * @return {Integer} - Return the N-th Fibonacci number + * @see [Fibonacci](https://en.wikipedia.org/wiki/Fibonacci_number) + */ + +const fibonacci = (n) => { + if (n < 2) { + return n + } + return fibonacci(n - 2) + fibonacci(n - 1) +} + +export { fibonacci } diff --git a/Recursive/FloodFill.js b/Recursive/FloodFill.js new file mode 100644 index 0000000000..5143b8f4ff --- /dev/null +++ b/Recursive/FloodFill.js @@ -0,0 +1,129 @@ +/** + * Flood fill. + * + * Flood fill, also called seed fill, is an algorithm that determines and alters the area connected to a given node in a + * multi-dimensional array with some matching attribute. It is used in the "bucket" fill tool of paint programs to fill + * connected, similarly-colored areas with a different color. + * + * (description adapted from https://en.wikipedia.org/wiki/Flood_fill) + * @see https://www.techiedelight.com/flood-fill-algorithm/ + */ + +const neighborOffsets = [ + [-1, -1], + [-1, 0], + [-1, 1], + [0, -1], + [0, 1], + [1, -1], + [1, 0], + [1, 1] +] + +function isInside(rgbData, location) { + const x = location[0] + const y = location[1] + return x >= 0 && x < rgbData.length && y >= 0 && y < rgbData[0].length +} + +function checkLocation(rgbData, location) { + if (!isInside(rgbData, location)) { + throw new Error('location should point to a pixel within the rgbData') + } +} + +function* neighbors(rgbData, location) { + for (const offset of neighborOffsets) { + const neighborLocation = [location[0] + offset[0], location[1] + offset[1]] + if (isInside(rgbData, neighborLocation)) { + yield neighborLocation + } + } +} + +/** + * Implements the flood fill algorithm through a breadth-first approach using a queue. + * + * @param rgbData The image to which the algorithm is applied. + * @param location The start location on the image. + * @param targetColor The old color to be replaced. + * @param replacementColor The new color to replace the old one. + */ +export function breadthFirstSearch( + rgbData, + location, + targetColor, + replacementColor +) { + checkLocation(rgbData, location) + + const queue = [] + queue.push(location) + + while (queue.length > 0) { + breadthFirstFill(rgbData, location, targetColor, replacementColor, queue) + } +} + +/** + * Implements the flood fill algorithm through a depth-first approach using recursion. + * + * @param rgbData The image to which the algorithm is applied. + * @param location The start location on the image. + * @param targetColor The old color to be replaced. + * @param replacementColor The new color to replace the old one. + */ +export function depthFirstSearch( + rgbData, + location, + targetColor, + replacementColor +) { + checkLocation(rgbData, location) + + depthFirstFill(rgbData, location, targetColor, replacementColor) +} + +/** + * Utility-function to implement the breadth-first loop. + * + * @param rgbData The image to which the algorithm is applied. + * @param location The start location on the image. + * @param targetColor The old color to be replaced. + * @param replacementColor The new color to replace the old one. + * @param queue The locations that still need to be visited. + */ +function breadthFirstFill( + rgbData, + location, + targetColor, + replacementColor, + queue +) { + const currentLocation = queue[0] + queue.shift() + + if (rgbData[currentLocation[0]][currentLocation[1]] === targetColor) { + rgbData[currentLocation[0]][currentLocation[1]] = replacementColor + for (const neighborLocation of neighbors(rgbData, currentLocation)) { + queue.push(neighborLocation) + } + } +} + +/** + * Utility-function to implement the depth-first loop. + * + * @param rgbData The image to which the algorithm is applied. + * @param location The start location on the image. + * @param targetColor The old color to be replaced. + * @param replacementColor The new color to replace the old one. + */ +function depthFirstFill(rgbData, location, targetColor, replacementColor) { + if (rgbData[location[0]][location[1]] === targetColor) { + rgbData[location[0]][location[1]] = replacementColor + for (const neighborLocation of neighbors(rgbData, location)) { + depthFirstFill(rgbData, neighborLocation, targetColor, replacementColor) + } + } +} diff --git a/Recursive/KochSnowflake.js b/Recursive/KochSnowflake.js new file mode 100644 index 0000000000..7b104da6e4 --- /dev/null +++ b/Recursive/KochSnowflake.js @@ -0,0 +1,118 @@ +/** + * The Koch snowflake is a fractal curve and one of the earliest fractals to have been described. + * + * The Koch snowflake can be built up iteratively, in a sequence of stages. The first stage is an equilateral triangle, + * and each successive stage is formed by adding outward bends to each side of the previous stage, making smaller + * equilateral triangles. This can be achieved through the following steps for each line: + * 1. divide the line segment into three segments of equal length. + * 2. draw an equilateral triangle that has the middle segment from step 1 as its base and points outward. + * 3. remove the line segment that is the base of the triangle from step 2. + * + * (description adapted from https://en.wikipedia.org/wiki/Koch_snowflake) + * (for a more detailed explanation and an implementation in the Processing language, see + * https://natureofcode.com/book/chapter-8-fractals/ #84-the-koch-curve-and-the-arraylist-technique). + */ + +/** Class to handle the vector calculations. */ +export class Vector2 { + constructor(x, y) { + this.x = x + this.y = y + } + + /** + * Vector addition + * + * @param vector The vector to be added. + * @returns The sum-vector. + */ + add(vector) { + const x = this.x + vector.x + const y = this.y + vector.y + return new Vector2(x, y) + } + + /** + * Vector subtraction + * + * @param vector The vector to be subtracted. + * @returns The difference-vector. + */ + subtract(vector) { + const x = this.x - vector.x + const y = this.y - vector.y + return new Vector2(x, y) + } + + /** + * Vector scalar multiplication + * + * @param scalar The factor by which to multiply the vector. + * @returns The scaled vector. + */ + multiply(scalar) { + const x = this.x * scalar + const y = this.y * scalar + return new Vector2(x, y) + } + + /** + * Vector rotation (see https://en.wikipedia.org/wiki/Rotation_matrix) + * + * @param angleInDegrees The angle by which to rotate the vector. + * @returns The rotated vector. + */ + rotate(angleInDegrees) { + const radians = (angleInDegrees * Math.PI) / 180 + const ca = Math.cos(radians) + const sa = Math.sin(radians) + const x = ca * this.x - sa * this.y + const y = sa * this.x + ca * this.y + return new Vector2(x, y) + } +} + +/** + * Go through the number of iterations determined by the argument "steps". + * + * Be careful with high values (above 5) since the time to calculate increases exponentially. + * + * @param initialVectors The vectors composing the shape to which the algorithm is applied. + * @param steps The number of iterations. + * @returns The transformed vectors after the iteration-steps. + */ +export function iterate(initialVectors, steps) { + let vectors = initialVectors + for (let i = 0; i < steps; i++) { + vectors = iterationStep(vectors) + } + + return vectors +} + +/** + * Loops through each pair of adjacent vectors. + * + * Each line between two adjacent vectors is divided into 4 segments by adding 3 additional vectors in-between the + * original two vectors. The vector in the middle is constructed through a 60 degree rotation so it is bent outwards. + * + * @param vectors The vectors composing the shape to which the algorithm is applied. + * @returns The transformed vectors after the iteration-step. + */ +function iterationStep(vectors) { + const newVectors = [] + for (let i = 0; i < vectors.length - 1; i++) { + const startVector = vectors[i] + const endVector = vectors[i + 1] + newVectors.push(startVector) + const differenceVector = endVector.subtract(startVector).multiply(1 / 3) + newVectors.push(startVector.add(differenceVector)) + newVectors.push( + startVector.add(differenceVector).add(differenceVector.rotate(60)) + ) + newVectors.push(startVector.add(differenceVector.multiply(2))) + } + + newVectors.push(vectors[vectors.length - 1]) + return newVectors +} diff --git a/Recursive/KochSnowflake.manual-test.js b/Recursive/KochSnowflake.manual-test.js new file mode 100644 index 0000000000..720e26b828 --- /dev/null +++ b/Recursive/KochSnowflake.manual-test.js @@ -0,0 +1,61 @@ +import { Vector2, iterate } from './KochSnowflake' + +/** + * Method to render the Koch snowflake to a canvas. + * + * @param canvasWidth The width of the canvas. + * @param steps The number of iterations. + * @returns The canvas of the rendered Koch snowflake. + */ +function getKochSnowflake(canvasWidth = 600, steps = 5) { + if (canvasWidth <= 0) { + throw new Error('canvasWidth should be greater than zero') + } + + const offsetX = canvasWidth / 10.0 + const offsetY = canvasWidth / 3.7 + const vector1 = new Vector2(offsetX, offsetY) + const vector2 = new Vector2( + canvasWidth / 2, + Math.sin(Math.PI / 3) * canvasWidth * 0.8 + offsetY + ) + const vector3 = new Vector2(canvasWidth - offsetX, offsetY) + const initialVectors = [] + initialVectors.push(vector1) + initialVectors.push(vector2) + initialVectors.push(vector3) + initialVectors.push(vector1) + const vectors = iterate(initialVectors, steps) + return drawToCanvas(vectors, canvasWidth, canvasWidth) +} + +/** + * Utility-method to render the Koch snowflake to a canvas. + * + * @param vectors The vectors defining the edges to be rendered. + * @param canvasWidth The width of the canvas. + * @param canvasHeight The height of the canvas. + * @returns The canvas of the rendered edges. + */ +function drawToCanvas(vectors, canvasWidth, canvasHeight) { + const canvas = document.createElement('canvas') + canvas.width = canvasWidth + canvas.height = canvasHeight + + // Draw the edges + const ctx = canvas.getContext('2d') + ctx.beginPath() + ctx.moveTo(vectors[0].x, vectors[0].y) + for (let i = 1; i < vectors.length; i++) { + ctx.lineTo(vectors[i].x, vectors[i].y) + } + ctx.stroke() + + return canvas +} + +// plot the results if the script is executed in a browser with a window-object +if (typeof window !== 'undefined') { + const canvas = getKochSnowflake() + document.body.append(canvas) +} diff --git a/Recursive/LetterCombination.js b/Recursive/LetterCombination.js new file mode 100644 index 0000000000..5131ebd857 --- /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/Palindrome.js b/Recursive/Palindrome.js new file mode 100644 index 0000000000..7d315aea31 --- /dev/null +++ b/Recursive/Palindrome.js @@ -0,0 +1,25 @@ +/** + * @function Palindrome + * @description Check whether the given string is Palindrome or not. + * @param {String} str - The input string + * @return {Boolean}. + * @see [Palindrome](https://en.wikipedia.org/wiki/Palindrome) + */ + +const palindrome = (str) => { + if (typeof str !== 'string') { + throw new TypeError('Invalid Input') + } + + if (str.length <= 1) { + return true + } + + if (str[0] !== str[str.length - 1]) { + return false + } else { + return palindrome(str.slice(1, str.length - 1)) + } +} + +export { palindrome } diff --git a/Recursive/PalindromePartitioning.js b/Recursive/PalindromePartitioning.js new file mode 100644 index 0000000000..9a5150f65d --- /dev/null +++ b/Recursive/PalindromePartitioning.js @@ -0,0 +1,30 @@ +import { palindrome } from './Palindrome' + +/* + * Given a string s, return all possible palindrome partitionings of s. + * A palindrome partitioning partitions a string into palindromic substrings. + * @see https://www.cs.columbia.edu/~sedwards/classes/2021/4995-fall/proposals/Palindrome.pdf + */ +const partitionPalindrome = (s) => { + const result = [] + backtrack(s, [], result) + return result +} + +const backtrack = (s, path, result) => { + if (s.length === 0) { + result.push([...path]) + return + } + + for (let i = 0; i < s.length; i++) { + const prefix = s.substring(0, i + 1) + if (palindrome(prefix)) { + path.push(prefix) + backtrack(s.substring(i + 1), path, result) + path.pop() + } + } +} + +export default partitionPalindrome diff --git a/Recursive/Partition.js b/Recursive/Partition.js new file mode 100644 index 0000000000..51465f8e93 --- /dev/null +++ b/Recursive/Partition.js @@ -0,0 +1,39 @@ +/** + * @function canPartition + * @description Check whether it is possible to partition the given array into two equal sum subsets using recursion. + * @param {number[]} nums - The input array of numbers. + * @param {number} index - The current index in the array being considered. + * @param {number} target - The target sum for each subset. + * @return {boolean}. + * @see [Partition Problem](https://en.wikipedia.org/wiki/Partition_problem) + */ + +const canPartition = (nums, index = 0, target = 0) => { + if (!Array.isArray(nums)) { + throw new TypeError('Invalid Input') + } + + const sum = nums.reduce((acc, num) => acc + num, 0) + + if (sum % 2 !== 0) { + return false + } + + if (target === sum / 2) { + return true + } + + if (index >= nums.length || target > sum / 2) { + return false + } + + // Include the current number in the first subset and check if a solution is possible. + const withCurrent = canPartition(nums, index + 1, target + nums[index]) + + // Exclude the current number from the first subset and check if a solution is possible. + const withoutCurrent = canPartition(nums, index + 1, target) + + return withCurrent || withoutCurrent +} + +export { canPartition } diff --git a/Recursive/SubsequenceRecursive.js b/Recursive/SubsequenceRecursive.js new file mode 100644 index 0000000000..c7bedcb7a6 --- /dev/null +++ b/Recursive/SubsequenceRecursive.js @@ -0,0 +1,30 @@ +/* + * Problem Statement: Find all distinct, non-empty subsequence of given string in lexicographical order using recursive approach. + * + * What is subsequence? + * A Subsequence is sequence obtained by deleting some or no elements without changing the order of elements + * Example: Given a string = "abcd" + * 1. "abc" is a subsequence + * 2. "abd" is a subsequence + * 3. But "ba" is not a subsequence (because order is changed) + * + * What is lexicographical order? + * In simple terms, lexicographical order is dictionary order. + * Example: Given a string = "abcd" + * 1. "abc" will come before "abcd". + * 2. "abd" will come before "ac". + * + * References for meaning of subsequence & lexicographical: + * https://en.wikipedia.org/wiki/Subsequence + * https://en.wikipedia.org/wiki/Lexicographic_order + */ + +export const subsequence = (str, seq, low, output = []) => { + if (low <= str.length && str.length !== 0) { + output.push(seq) + } + for (let i = low; i < str.length; i++) { + subsequence(str, seq + str[i], i + 1, output) + } + return output +} diff --git a/Recursive/TowerOfHanoi.js b/Recursive/TowerOfHanoi.js new file mode 100644 index 0000000000..57c4db716c --- /dev/null +++ b/Recursive/TowerOfHanoi.js @@ -0,0 +1,18 @@ +// wiki - https://en.wikipedia.org/wiki/Tower_of_Hanoi +// Recursive Javascript function to solve tower of hanoi + +export function TowerOfHanoi(n, from, to, aux, output = []) { + if (n === 1) { + output.push(`Move disk 1 from rod ${from} to rod ${to}`) + return output + } + TowerOfHanoi(n - 1, from, aux, to, output) + output.push(`Move disk ${n} from rod ${from} to rod ${to}`) + TowerOfHanoi(n - 1, aux, to, from, output) + return output +} + +// Driver code (A, C, B are the name of rods) + +// const n = 4 +// TowerOfHanoi(n, 'A', 'C', 'B') diff --git a/Recursive/test/BinaryEquivalent.test.js b/Recursive/test/BinaryEquivalent.test.js new file mode 100644 index 0000000000..ddabb7d477 --- /dev/null +++ b/Recursive/test/BinaryEquivalent.test.js @@ -0,0 +1,29 @@ +import { binaryEquivalent } from '../BinaryEquivalent' + +const tests = [ + { + test: 2, + expectedValue: '10' + }, + { + test: 0, + expectedValue: '0' + }, + { + test: 543, + expectedValue: '1000011111' + }, + { + test: 4697621023, + expectedValue: '100011000000000000000001000011111' + } +] + +describe('Binary Equivalent', () => { + test.each(tests)( + 'of $test should be $expectedValue', + ({ test, expectedValue }) => { + expect(binaryEquivalent(test)).toBe(expectedValue) + } + ) +}) diff --git a/Recursive/test/BinarySearch.test.js b/Recursive/test/BinarySearch.test.js new file mode 100644 index 0000000000..52574c3403 --- /dev/null +++ b/Recursive/test/BinarySearch.test.js @@ -0,0 +1,32 @@ +import { binarySearch } from '../BinarySearch' + +describe('BinarySearch', () => { + const arr = [2, 3, 4, 10, 25, 40, 45, 60, 100, 501, 700, 755, 800, 999] + const low = 0 + const high = arr.length - 1 + + it('should return index 3 for searchValue 10', () => { + const searchValue = 10 + expect(binarySearch(arr, searchValue, low, high)).toBe(3) + }) + + it('should return index 0 for searchValue 2', () => { + const searchValue = 2 + expect(binarySearch(arr, searchValue, low, high)).toBe(0) + }) + + it('should return index 13 for searchValue 999', () => { + const searchValue = 999 + expect(binarySearch(arr, searchValue, low, high)).toBe(13) + }) + + it('should return -1 for searchValue 1', () => { + const searchValue = 1 + expect(binarySearch(arr, searchValue, low, high)).toBe(-1) + }) + + it('should return -1 for searchValue 1000', () => { + const searchValue = 1000 + expect(binarySearch(arr, searchValue, low, high)).toBe(-1) + }) +}) diff --git a/Recursive/test/Factorial.test.js b/Recursive/test/Factorial.test.js new file mode 100644 index 0000000000..e89be9831e --- /dev/null +++ b/Recursive/test/Factorial.test.js @@ -0,0 +1,29 @@ +import { factorial } from '../Factorial' + +describe('Factorial', () => { + it('should return factorial 1 for value "0"', () => { + expect(factorial(0)).toBe(1) + }) + + it('should return factorial 120 for value "5"', () => { + expect(factorial(5)).toBe(120) + }) + + it('Throw Error for Invalid Input', () => { + expect(() => factorial('-')).toThrow( + 'Input should be a non-negative whole number' + ) + expect(() => factorial(null)).toThrow( + 'Input should be a non-negative whole number' + ) + expect(() => factorial(undefined)).toThrow( + 'Input should be a non-negative whole number' + ) + expect(() => factorial(3.142)).toThrow( + 'Input should be a non-negative whole number' + ) + expect(() => factorial(-1)).toThrow( + 'Input should be a non-negative whole number' + ) + }) +}) diff --git a/Recursive/test/FibonacciNumberRecursive.test.js b/Recursive/test/FibonacciNumberRecursive.test.js new file mode 100644 index 0000000000..fbb67ac84f --- /dev/null +++ b/Recursive/test/FibonacciNumberRecursive.test.js @@ -0,0 +1,19 @@ +import { fibonacci } from '../FibonacciNumberRecursive' + +describe('FibonacciNumberRecursive', () => { + it('should return 0', () => { + expect(fibonacci(0)).toBe(0) + }) + + it('should return 1', () => { + expect(fibonacci(1)).toBe(1) + }) + + it('should return 5', () => { + expect(fibonacci(5)).toBe(5) + }) + + it('should return 9', () => { + expect(fibonacci(9)).toBe(34) + }) +}) diff --git a/Recursive/test/FloodFill.test.js b/Recursive/test/FloodFill.test.js new file mode 100644 index 0000000000..eb44e75e09 --- /dev/null +++ b/Recursive/test/FloodFill.test.js @@ -0,0 +1,103 @@ +import { breadthFirstSearch, depthFirstSearch } from '../FloodFill' + +// some constants +const black = [0, 0, 0] +const green = [0, 255, 0] +const violet = [255, 0, 255] +const white = [255, 255, 255] +const orange = [255, 128, 0] + +describe('FloodFill', () => { + it('should calculate the correct colors using breadth-first approach', () => { + expect(testBreadthFirst([1, 1], green, orange, [1, 1])).toEqual(orange) + expect(testBreadthFirst([1, 1], green, orange, [0, 1])).toEqual(violet) + expect(testBreadthFirst([1, 1], green, orange, [6, 4])).toEqual(white) + }) + + it('should calculate the correct colors using depth-first approach', () => { + expect(testDepthFirst([1, 1], green, orange, [1, 1])).toEqual(orange) + expect(testDepthFirst([1, 1], green, orange, [0, 1])).toEqual(violet) + expect(testDepthFirst([1, 1], green, orange, [6, 4])).toEqual(white) + }) +}) + +describe.each([breadthFirstSearch, depthFirstSearch])('%o', (floodFillFun) => { + it.each([ + [1, -1], + [-1, 1], + [0, 7], + [7, 0] + ])('throws for start position [%i, %i]', (location) => { + expect(() => + floodFillFun(generateTestRgbData(), location, green, orange) + ).toThrowError() + }) +}) + +/** + * Utility-function to test the function "breadthFirstSearch". + * + * @param fillLocation The start location on the image where the flood fill is applied. + * @param targetColor The old color to be replaced. + * @param replacementColor The new color to replace the old one. + * @param testLocation The location of the color to be checked. + * @return The color at testLocation. + */ +function testBreadthFirst( + fillLocation, + targetColor, + replacementColor, + testLocation +) { + const rgbData = generateTestRgbData() + breadthFirstSearch(rgbData, fillLocation, targetColor, replacementColor) + return rgbData[testLocation[0]][testLocation[1]] +} + +/** + * Utility-function to test the function "depthFirstSearch". + * + * @param fillLocation The start location on the image where the flood fill is applied. + * @param targetColor The old color to be replaced. + * @param replacementColor The new color to replace the old one. + * @param testLocation The location of the color to be checked. + * @return The color at testLocation. + */ +function testDepthFirst( + fillLocation, + targetColor, + replacementColor, + testLocation +) { + const rgbData = generateTestRgbData() + depthFirstSearch(rgbData, fillLocation, targetColor, replacementColor) + return rgbData[testLocation[0]][testLocation[1]] +} + +/** + * Generates the rgbData-matrix for the tests. + * + * @return example rgbData-matrix. + */ +function generateTestRgbData() { + const layout = [ + [violet, violet, green, green, black, green, green], + [violet, green, green, black, green, green, green], + [green, green, green, black, green, green, green], + [black, black, green, black, white, white, green], + [violet, violet, black, violet, violet, white, white], + [green, green, green, violet, violet, violet, violet], + [violet, violet, violet, violet, violet, violet, violet] + ] + + // transpose layout-matrix so the x-index comes before the y-index + const transposed = [] + for (let x = 0; x < layout[0].length; x++) { + transposed[x] = [] + for (let y = 0; y < layout.length; y++) { + transposed[x][y] = layout[y][x] + } + } + + return transposed +} diff --git a/Recursive/test/KochSnowflake.test.js b/Recursive/test/KochSnowflake.test.js new file mode 100644 index 0000000000..2362f820f3 --- /dev/null +++ b/Recursive/test/KochSnowflake.test.js @@ -0,0 +1,30 @@ +import { iterate, Vector2 } from '../KochSnowflake' + +describe('KochSnowflake', () => { + it('should produce the correctly-transformed vectors', () => { + expect(iterate([new Vector2(0, 0), new Vector2(1, 0)], 1)[0]).toEqual({ + x: 0, + y: 0 + }) + + expect(iterate([new Vector2(0, 0), new Vector2(1, 0)], 1)[1]).toEqual({ + x: 1 / 3, + y: 0 + }) + + expect(iterate([new Vector2(0, 0), new Vector2(1, 0)], 1)[2]).toEqual({ + x: 1 / 2, + y: Math.sin(Math.PI / 3) / 3 + }) + + expect(iterate([new Vector2(0, 0), new Vector2(1, 0)], 1)[3]).toEqual({ + x: 2 / 3, + y: 0 + }) + + expect(iterate([new Vector2(0, 0), new Vector2(1, 0)], 1)[4]).toEqual({ + x: 1, + y: 0 + }) + }) +}) diff --git a/Recursive/test/LetterCombination.test.js b/Recursive/test/LetterCombination.test.js new file mode 100644 index 0000000000..29631b8f47 --- /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/Recursive/test/Palindrome.test.js b/Recursive/test/Palindrome.test.js new file mode 100644 index 0000000000..dee9b0af2e --- /dev/null +++ b/Recursive/test/Palindrome.test.js @@ -0,0 +1,24 @@ +import { palindrome } from '../Palindrome' + +describe('Palindrome', () => { + it('expects to return true for palindrome string', () => { + const isPalindrome = palindrome('madam') + expect(isPalindrome).toBe(true) + }) + + it('expects to return true for Empty String', () => { + const isPalindrome = palindrome('') + expect(isPalindrome).toBe(true) + }) + + it('expects to return false for non-palindrome string', () => { + const isPalindrome = palindrome('foobar') + expect(isPalindrome).toBe(false) + }) + + it('Throw Error for Invalid Input', () => { + expect(() => palindrome(123)).toThrow('Invalid Input') + expect(() => palindrome(null)).toThrow('Invalid Input') + expect(() => palindrome(undefined)).toThrow('Invalid Input') + }) +}) diff --git a/Recursive/test/PalindromePartitioning.test.js b/Recursive/test/PalindromePartitioning.test.js new file mode 100644 index 0000000000..1bb218cdd4 --- /dev/null +++ b/Recursive/test/PalindromePartitioning.test.js @@ -0,0 +1,12 @@ +import partitionPalindrome from '../PalindromePartitioning' + +describe('Palindrome Partitioning', () => { + it('should return all possible palindrome partitioning of s', () => { + expect(partitionPalindrome('aab')).toEqual([ + ['a', 'a', 'b'], + ['aa', 'b'] + ]) + expect(partitionPalindrome('a')).toEqual([['a']]) + expect(partitionPalindrome('ab')).toEqual([['a', 'b']]) + }) +}) diff --git a/Recursive/test/Partition.test.js b/Recursive/test/Partition.test.js new file mode 100644 index 0000000000..929519999a --- /dev/null +++ b/Recursive/test/Partition.test.js @@ -0,0 +1,24 @@ +import { canPartition } from '../Partition' + +describe('Partition (Recursive)', () => { + it('expects to return true for an array that can be partitioned', () => { + const result = canPartition([1, 5, 11, 5]) + expect(result).toBe(true) + }) + + it('expects to return false for an array that cannot be partitioned', () => { + const result = canPartition([1, 2, 3, 5]) + expect(result).toBe(false) + }) + + it('expects to return true for an empty array (0 elements)', () => { + const result = canPartition([]) + expect(result).toBe(true) + }) + + it('Throw Error for Invalid Input', () => { + expect(() => canPartition(123)).toThrow('Invalid Input') + expect(() => canPartition(null)).toThrow('Invalid Input') + expect(() => canPartition(undefined)).toThrow('Invalid Input') + }) +}) diff --git a/Search/BinarySearch.js b/Search/BinarySearch.js index 3a150d6384..c5477cb7b9 100644 --- a/Search/BinarySearch.js +++ b/Search/BinarySearch.js @@ -7,7 +7,7 @@ * value is found or the interval is empty. */ -function binarySearch (arr, x, low = 0, high = arr.length - 1) { +function binarySearchRecursive(arr, x, low = 0, high = arr.length - 1) { const mid = Math.floor(low + (high - low) / 2) if (high >= low) { @@ -18,53 +18,35 @@ function binarySearch (arr, x, low = 0, high = arr.length - 1) { if (x < arr[mid]) { // arr[mid] is an upper bound for x, so if x is in arr => low <= x < mid - return binarySearch(arr, x, low, mid - 1) + return binarySearchRecursive(arr, x, low, mid - 1) } else { // arr[mid] is a lower bound for x, so if x is in arr => mid < x <= high - return binarySearch(arr, x, mid + 1, high) + return binarySearchRecursive(arr, x, mid + 1, high) } } else { // if low > high => we have searched the whole array without finding the item return -1 } } +function binarySearchIterative(arr, x, low = 0, high = arr.length - 1) { + while (high >= low) { + const mid = Math.floor(low + (high - low) / 2) -/* ---------------------------------- Test ---------------------------------- */ - -const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -const stringArr = [ - 'Alpha', - 'Bravo', - 'Charlie', - 'Delta', - 'Echo', - 'Foxtrot', - 'Golf', - 'Hotel', - 'India', - 'Juliet', - 'Kilo', - 'Lima', - 'Mike', - 'November', - 'Oscar', - 'Papa', - 'Quebec', - 'Romeo', - 'Sierra', - 'Tango', - 'Uniform', - 'Victor', - 'Whiskey', - 'X-Ray', - 'Yankee', - 'Zulu' -] + if (arr[mid] === x) { + // item found => return its index + return mid + } -console.log(binarySearch(arr, 3)) -console.log(binarySearch(arr, 7)) -console.log(binarySearch(arr, 13)) + if (x < arr[mid]) { + // arr[mid] is an upper bound for x, so if x is in arr => low <= x < mid + high = mid - 1 + } else { + // arr[mid] is a lower bound for x, so if x is in arr => mid < x <= high + low = mid + 1 + } + } + // if low > high => we have searched the whole array without finding the item + return -1 +} -console.log(binarySearch(stringArr, 'Charlie')) -console.log(binarySearch(stringArr, 'Zulu')) -console.log(binarySearch(stringArr, 'Sierra')) +export { binarySearchIterative, binarySearchRecursive } diff --git a/Search/ExponentialSearch.js b/Search/ExponentialSearch.js index e46a5ee89e..d00a981645 100644 --- a/Search/ExponentialSearch.js +++ b/Search/ExponentialSearch.js @@ -9,12 +9,12 @@ * */ -function binarySearch (arr, x, floor, ceiling) { +function binarySearch(arr, value, floor, ceiling) { // Middle index const mid = Math.floor((floor + ceiling) / 2) // If value is at the mid position return this position - if (arr[mid] === x) { + if (arr[mid] === value) { return mid } @@ -31,7 +31,7 @@ function binarySearch (arr, x, floor, ceiling) { } } -function exponentialSearch (arr, length, value) { +function exponentialSearch(arr, length, value) { // If value is the first element of the array return this position if (arr[0] === value) { return 0 @@ -47,12 +47,8 @@ function exponentialSearch (arr, length, value) { return binarySearch(arr, value, i / 2, Math.min(i, length)) } -const arr = [2, 3, 4, 10, 40, 65, 78, 100] -const value = 78 -const result = exponentialSearch(arr, arr.length, value) +export { binarySearch, exponentialSearch } -if (result < 0) { - console.log('Element not found') -} else { - console.log('Element found at position :' + result) -} +// const arr = [2, 3, 4, 10, 40, 65, 78, 100] +// const value = 78 +// const result = exponentialSearch(arr, arr.length, value) diff --git a/Search/FibonacciSearch.js b/Search/FibonacciSearch.js new file mode 100644 index 0000000000..36459ec8de --- /dev/null +++ b/Search/FibonacciSearch.js @@ -0,0 +1,77 @@ +/**************************************************************************** + * Fibonacci Search JavaScript Implementation + * Author Alhassan Atama Isiaka + * Version v1.0.0 + * Copyright 2020 + * https://github.com/komputarist + * + * This implementation is based on Generalizing the Fibonacci search we + * define the Fibonacci search of degree K. Like the Fibonacci search, + * which it reduces to for K = 2, the Fibonacci search of degree K + * involves only addition and subtraction. + * Capocelli R.M. (1991) A Generalization of the Fibonacci Search. In: + * Bergum G.E., Philippou A.N., Horadam A.F. (eds) Applications of Fibonacci + * Numbers. Springer, Dordrecht. https://doi.org/10.1007/978-94-011-3586-3_9 + * + * This snippet is free. Feel free to improve on it + * + * We define a function fibonacciSearch() that takes an array of numbers, + * the item (number) to be searched for and the length of the items in the array + ****************************************************************************/ + +export const fibonacciSearch = (arr, x, n) => { + let fib2 = 0 // (K-2)'th Fibonacci Number + let fib1 = 1 // (K-1)'th Fibonacci Number. + let fibK = fib2 + fib1 // Kth Fibonacci + + /* We want to store the smallest fibonacci number smaller such that + number is greater than or equal to n, we use fibK for this */ + while (fibK < n) { + fib2 = fib1 + fib1 = fibK + fibK = fib2 + fib1 + } + // This marks the eliminated range from front + let offset = -1 + + /* while there are elements to be checked. We compare arr[fib2] with x. + When fibM becomes 1, fib2 becomes 0 */ + + while (fibK > 1) { + // Check if fibK is a valid location + const i = Math.min(offset + fib2, n - 1) + + /* If x is greater than the value at + index fib2, Partition the subarray array + from offset to i */ + if (arr[i] < x) { + fibK = fib1 + fib1 = fib2 + fib2 = fibK - fib1 + offset = i + /* If x is greater than the value at + index fib2, cut the subarray array + from offset to i */ + } else if (arr[i] > x) { + fibK = fib2 + fib1 = fib1 - fib2 + fib2 = fibK - fib1 + } else { + // return index for found element + return i + } + } + + // comparing the last element with x */ + if (fib1 && arr[offset + 1] === x) { + return offset + 1 + } + // element not found. return -1 + return -1 +} + +// Example +// const myArray = [10, 22, 35, 40, 45, 50, 80, 82, 85, 90, 100] +// const n = myArray.length +// const x = 90 +// const fibFinder = fibonacciSearch(myArray, x, n) diff --git a/Search/InterpolationSearch.js b/Search/InterpolationSearch.js index c256fd8c7e..93f3b78b0e 100644 --- a/Search/InterpolationSearch.js +++ b/Search/InterpolationSearch.js @@ -9,7 +9,7 @@ * */ -function interpolationSearch (arr, key) { +export function interpolationSearch(arr, key) { const length = arr.length - 1 let low = 0 let high = length @@ -37,10 +37,3 @@ function interpolationSearch (arr, key) { return -1 } - -const arr = [2, 6, 8, 10, 12, 14, 16, 18, 20, 22, 26, 34, 39] - -console.log('Found at position :' + interpolationSearch(arr, 2)) -console.log('Found at position :' + interpolationSearch(arr, 12)) -console.log('Found at position :' + interpolationSearch(arr, 1000)) -console.log('Found at position :' + interpolationSearch(arr, 39)) diff --git a/Search/JumpSearch.js b/Search/JumpSearch.js index 02751a0fd9..a7b7b443e7 100644 --- a/Search/JumpSearch.js +++ b/Search/JumpSearch.js @@ -1,8 +1,8 @@ /* The Jump Search algorithm allows to combine a linear search with a speed optimization. - * This means that instead of going 1 by 1, we will increase the step of โˆšn and increase that - * step of โˆšn which make the step getting bigger and bigger. - * The asymptotic analysis of Jump Search is o(โˆšn). Like the binary search, it needs to be sorted. - * The advantage against binary search is that Jump Search traversed back only once. + * This means that instead of going 1 by 1, we will increase the step of โˆšn and increase that + * step of โˆšn which make the step getting bigger and bigger. + * The asymptotic analysis of Jump Search is o(โˆšn). Like the binary search, it needs to be sorted. + * The advantage against binary search is that Jump Search traversed back only once. */ const jumpSearch = (arr, value) => { @@ -29,7 +29,5 @@ const jumpSearch = (arr, value) => { } return -1 } -const arr = [0, 0, 4, 7, 10, 23, 34, 40, 55, 68, 77, 90] -jumpSearch(arr, 4) -jumpSearch(arr, 34) -jumpSearch(arr, 77) + +export { jumpSearch } diff --git a/Search/LinearSearch.js b/Search/LinearSearch.js index 06d9e94a6d..96a9d1fa34 100644 --- a/Search/LinearSearch.js +++ b/Search/LinearSearch.js @@ -3,25 +3,31 @@ * value within a list. It sequentially checks each element of the list * for the target value until a match is found or until all the elements * have been searched. + * + * @see https://en.wikipedia.org/wiki/Linear_search */ -function SearchArray (searchNum, ar) { - var position = Search(ar, searchNum) +function SearchArray(searchNum, ar, output = (v) => console.log(v)) { + const position = Search(ar, searchNum) if (position !== -1) { - console.log('The element was found at ' + (position + 1)) + output('The element was found at ' + (position + 1)) } else { - console.log('The element not found') + output('The element not found') } } // Search โ€œtheArrayโ€ for the specified โ€œkeyโ€ value -function Search (theArray, key) { - for (var n = 0; n < theArray.length; n++) { - if (theArray[n] === key) { return n } +function Search(theArray, key) { + for (let n = 0; n < theArray.length; n++) { + if (theArray[n] === key) { + return n + } } return -1 } -var ar = [1, 2, 3, 4, 5, 6, 7, 8, 9] -SearchArray(3, ar) -SearchArray(4, ar) -SearchArray(11, ar) +export { SearchArray, Search } + +// const ar = [1, 2, 3, 4, 5, 6, 7, 8, 9] +// SearchArray(3, ar) +// SearchArray(4, ar) +// SearchArray(11, ar) diff --git a/Search/Minesweeper.js b/Search/Minesweeper.js new file mode 100644 index 0000000000..3de9fc9170 --- /dev/null +++ b/Search/Minesweeper.js @@ -0,0 +1,38 @@ +/* + * Author: IcarusTheFly (https://github.com/IcarusTheFly) + * Minesweeper explanation can be found in: https://en.wikipedia.org/wiki/Minesweeper_(video_game) + * This function will take a rectangular matrix filled with boolean values - the value for a cell + * with a mine will be true, otherwise it will be false. + * As a result it will return a rectangular matrix where each cell will have an integer that + * counts all the mines in the adjacent cells + * Two cells should share at least one corner to be considered adjacent + */ + +/** + * @function minesweeper + * @description It counts the amount of mines surrounding every cell and returns a formatted matrix + * @param {boolean[][]} matrix + * @returns {number[][]} Matrix of numbers with the amount of mines surrounding each cell + */ + +export const minesweeper = (matrix) => { + const arrResult = [] + for (let x = 0; x < matrix.length; x++) { + const arrLine = [] + for (let y = 0; y < matrix[x].length; y++) { + let minesInCell = 0 + for (let xi = x - 1; xi <= x + 1; xi++) { + if (matrix[xi] !== undefined) { + for (let yi = y - 1; yi <= y + 1; yi++) { + if ((xi !== x || yi !== y) && matrix[xi][yi] === true) { + minesInCell++ + } + } + } + } + arrLine.push(minesInCell) + } + arrResult.push(arrLine) + } + return arrResult +} diff --git a/Search/QuickSelectSearch.js b/Search/QuickSelectSearch.js new file mode 100644 index 0000000000..c332af6721 --- /dev/null +++ b/Search/QuickSelectSearch.js @@ -0,0 +1,55 @@ +/* + * Places the `k` smallest elements in `array` in the first `k` indices: `[0..k-1]` + * Modifies the passed in array *in place* + * Returns a slice of the wanted elements for convenience + * Efficient mainly because it never performs a full sort. + * + * The only guarantees are that: + * + * - The `k`th element is in its final sort index (if the array were to be sorted) + * - All elements before index `k` are smaller than the `k`th element + * + * [Reference](http://en.wikipedia.org/wiki/Quickselect) + */ +export function quickSelectSearch(array, k) { + if (!array || array.length <= k) { + throw new Error('Invalid arguments') + } + + let from = 0 + let to = array.length - 1 + while (from < to) { + let left = from + let right = to + const pivot = array[Math.ceil((left + right) * 0.5)] + + while (left < right) { + if (array[left] >= pivot) { + const tmp = array[left] + array[left] = array[right] + array[right] = tmp + --right + } else { + ++left + } + } + + if (array[left] > pivot) { + --left + } + + if (k <= left) { + to = left + } else { + from = left + 1 + } + } + return array +} + +/* ---------------------------------- Test ---------------------------------- */ + +// const arr = [1121111, 21, 333, 41, 5, 66, 7777, 28, 19, 11110] +// quickSelectSearch(arr, 5) // [ 19, 21, 28, 41, 5, 66, 333, 11110, 1121111, 7777 ] +// quickSelectSearch(arr, 2) // [ 19, 5, 21, 41, 28, 333, 11110, 1121111, 7777, 66 ] +// quickSelectSearch(arr, 7) // [ 19, 5, 21, 41, 28, 66, 333, 7777, 11110, 1121111 ] diff --git a/Search/RabinKarp.js b/Search/RabinKarp.js new file mode 100644 index 0000000000..e6d6394ede --- /dev/null +++ b/Search/RabinKarp.js @@ -0,0 +1,64 @@ +/* + * Implements the Rabin-Karp algorithm for pattern searching. + * + * The Rabin-Karp algorithm is a string searching algorithm that uses hashing to find patterns in strings. + * It is faster than naive string matching algorithms because it avoids comparing every character in the text. + * + * This implementation uses a rolling hash function to efficiently compute the hash values of substrings. + * It also uses a modulo operator to reduce the size of the hash values, which helps to prevent hash collisions. + * + * The algorithm returns an array of indices where the pattern is found in the text. If the pattern is not + * found, the algorithm returns an empty array. + * + * [Reference](https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm) + */ + +const BASE = 256 // The number of characters in the alphabet +const MOD = 997 // A prime number used for the hash function + +function rabinKarpSearch(text, pattern) { + const patternLength = pattern.length + const textLength = text.length + const hashPattern = hash(pattern, patternLength) + const hashText = [] + const indices = [] + + // Calculate the hash of the first substring in the text + hashText[0] = hash(text, patternLength) + + // Precompute BASE^(patternLength-1) % MOD + const basePow = Math.pow(BASE, patternLength - 1) % MOD + + for (let i = 1; i <= textLength - patternLength + 1; i++) { + // Update the rolling hash by removing the first character + // and adding the next character in the text + hashText[i] = + (BASE * (hashText[i - 1] - text.charCodeAt(i - 1) * basePow) + + text.charCodeAt(i + patternLength - 1)) % + MOD + + // In case of hash collision, check character by character + if (hashText[i] < 0) { + hashText[i] += MOD + } + + // Check if the hashes match and perform a character-wise comparison + if (hashText[i] === hashPattern) { + if (text.substring(i, i + patternLength) === pattern) { + indices.push(i) // Store the index where the pattern is found + } + } + } + + return indices +} + +function hash(str, length) { + let hashValue = 0 + for (let i = 0; i < length; i++) { + hashValue = (hashValue * BASE + str.charCodeAt(i)) % MOD + } + return hashValue +} + +export { rabinKarpSearch } diff --git a/Search/SlidingWindow.js b/Search/SlidingWindow.js new file mode 100644 index 0000000000..3a758413b9 --- /dev/null +++ b/Search/SlidingWindow.js @@ -0,0 +1,47 @@ +/** + * Sliding Window: + * This pattern involve creating a window which can either be + * an array or numbers from one position to another. + * + * Depending on a certain condition, the window either increases + * or closes (and a new window is created). + * + * Very useful for keeping track of a subset of data in an + * array/string etc. + * + * Time Complexity: Best - O(n); + * + * Examples: + * maxSubarraySum([1,2,5,2,8,1,5],2) // returns 10 + * maxSubarraySum([1,2,5,2,8,1,5],15) // returns null + * maxSubarraySum([5,2,6,9],3) // returns 17 + * @param {[Int]} arr - An array of integers on which we will perform the test. + * @param {Int} num - An integer that displays the size of the window you want to check. + * @returns {Int / Null} - Returns a total of N consecutive numbers or null + */ + +function slidingWindow(arr, num) { + // Edge Case: + // If the length of the array shorter than the window size (num) return null. + if (arr.length < num) return null + // The highest amount of consecutive numbers + let maxSum = 0 + // Temp amount of consecutive numbers - For comparative purposes + let tempSum = 0 + // loop over the array {num} times and save their total amount in {maxSum} + for (let i = 0; i < num; i++) { + maxSum += arr[i] + } + // initialize {tempSum} to {maxSum}. + tempSum = maxSum + // loop over the array n times + for (let i = num; i < arr.length; i++) { + // Add the next num in the array and remove the first one + tempSum = tempSum - arr[i - num] + arr[i] + // save the largest number between {maxNum} and {tempNum} in maxSum. + maxSum = Math.max(maxSum, tempSum) + } + return maxSum +} + +export { slidingWindow } diff --git a/Search/StringSearch.js b/Search/StringSearch.js new file mode 100644 index 0000000000..cc3ad737a7 --- /dev/null +++ b/Search/StringSearch.js @@ -0,0 +1,83 @@ +/* + * String Search + */ + +function makeTable(str) { + // create a table of size equal to the length of `str` + // table[i] will store the prefix of the longest prefix of the substring str[0..i] + const table = new Array(str.length) + let maxPrefix = 0 + // the longest prefix of the substring str[0] has length + table[0] = 0 + + // for the substrings the following substrings, we have two cases + for (let i = 1; i < str.length; i++) { + // case 1. the current character doesn't match the last character of the longest prefix + while (maxPrefix > 0 && str.charAt(i) !== str.charAt(maxPrefix)) { + // if that is the case, we have to backtrack, and try find a character that will be equal to the current character + // if we reach 0, then we couldn't find a character + maxPrefix = table[maxPrefix - 1] + } + // case 2. The last character of the longest prefix matches the current character in `str` + if (str.charAt(maxPrefix) === str.charAt(i)) { + // if that is the case, we know that the longest prefix at position i has one more character. + // for example consider `.` be any character not contained in the set [a.c] + // str = abc....abc + // consider `i` to be the last character `c` in `str` + // maxPrefix = will be 2 (the first `c` in `str`) + // maxPrefix now will be 3 + maxPrefix++ + // so the max prefix for table[9] is 3 + } + table[i] = maxPrefix + } + return table +} + +// Find all the words that matches in a given string `str` +export function stringSearch(str, word) { + // find the prefix table in O(n) + const prefixes = makeTable(word) + const matches = [] + + // `j` is the index in `P` + let j = 0 + // `i` is the index in `S` + let i = 0 + while (i < str.length) { + // Case 1. S[i] == P[j] so we move to the next index in `S` and `P` + if (str.charAt(i) === word.charAt(j)) { + i++ + j++ + } + // Case 2. `j` is equal to the length of `P` + // that means that we reached the end of `P` and thus we found a match + // Next we have to update `j` because we want to save some time + // instead of updating to j = 0 , we can jump to the last character of the longest prefix well known so far. + // j-1 means the last character of `P` because j is actually `P.length` + // e.g. + // S = a b a b d e + // P = `a b`a b + // we will jump to `a b` and we will compare d and a in the next iteration + // a b a b `d` e + // a b `a` b + if (j === word.length) { + matches.push(i - j) + j = prefixes[j - 1] + // Case 3. + // S[i] != P[j] There's a mismatch! + } else if (str.charAt(i) !== word.charAt(j)) { + // if we found at least a character in common, do the same thing as in case 2 + if (j !== 0) { + j = prefixes[j - 1] + } else { + // else j = 0, and we can move to the next character S[i+1] + i++ + } + } + } + + return matches +} + +// stringSearch('Hello search the position of me', 'pos') diff --git a/Search/TernarySearch.js b/Search/TernarySearch.js new file mode 100644 index 0000000000..ea0d049341 --- /dev/null +++ b/Search/TernarySearch.js @@ -0,0 +1,86 @@ +/* Ternary search is similar to binary search but it divides the sorted array + * into three parts and determines which part the key lies in. The array will + * be divided into three intervals by using two middle points, mid1 and mid2. + * The value of the key will first be compared with the two mid points, the value + * will be returned if there is a match. Then, if the value of the key is less + * than mid1, narrow the interval to the first part. Else, if the value of the + * key is greater than mid2, narrow the interval to the third part. Otherwise, + * narrow the interval to the middle part. Repeat the steps until the value is + * found or the interval is empty (value not found after checking all elements). + * + * Reference: https://www.geeksforgeeks.org/ternary-search/ + */ + +function ternarySearchRecursive(arr, key, low = 0, high = arr.length - 1) { + if (high >= low) { + // find the mid1 and mid2 + const mid1 = Math.floor(low + (high - low) / 3) + const mid2 = Math.floor(high - (high - low) / 3) + + // check if key is found at any mid + if (arr[mid1] === key) { + // return index of key if found + return mid1 + } + if (arr[mid2] === key) { + // return index of key if found + return mid2 + } + + // since the key is not found at mid, + // check in which region it is present + // and repeat the Search operation + // in that region + if (key < arr[mid1]) { + // the key lies in between low and mid1 + return ternarySearchRecursive(arr, key, low, mid1 - 1) + } else if (key > arr[mid2]) { + // the key lies in between mid2 and high + return ternarySearchRecursive(arr, key, mid2 + 1, high) + } else { + // the key lies in between mid1 and mid2 + return ternarySearchRecursive(arr, key, mid1 + 1, mid2 - 1) + } + } else { + // if low > high => we have searched the whole array without finding the item + return -1 + } +} + +function ternarySearchIterative(arr, key, low = 0, high = arr.length - 1) { + while (high >= low) { + // find the mid1 and mid2 + const mid1 = Math.floor(low + (high - low) / 3) + const mid2 = Math.floor(high - (high - low) / 3) + + // check if key is found at any mid + if (arr[mid1] === key) { + // return index of key if found + return mid1 + } + if (arr[mid2] === key) { + // return index of key if found + return mid2 + } + + // since the key is not found at mid, + // check in which region it is present + // and repeat the Search operation + // in that region + if (key < arr[mid1]) { + // the key lies in between low and mid1 + high = mid1 - 1 + } else if (key > arr[mid2]) { + // the key lies in between mid2 and high + low = mid2 + 1 + } else { + // the key lies in between mid1 and mid2 + low = mid1 + 1 + high = mid2 - 1 + } + } + // the key was not found + return -1 +} + +export { ternarySearchRecursive, ternarySearchIterative } diff --git a/Search/UnionFind.js b/Search/UnionFind.js new file mode 100644 index 0000000000..5b234da9a1 --- /dev/null +++ b/Search/UnionFind.js @@ -0,0 +1,89 @@ +/** + * union find data structure for javascript + * + * In computer science, a disjoint-set data structure, also called a unionโ€“find data structure or mergeโ€“find set, + * is a data structure that stores a collection of disjoint (non-overlapping) sets. Equivalently, it stores a partition + * of a set into disjoint subsets. It provides operations for adding new sets, merging sets (replacing them by their union), + * and finding a representative member of a set. + * The last operation allows to find out efficiently if any two elements are in the same or different sets. + * + * Disjoint-set data structures play a key role in Kruskal's algorithm for finding the minimum spanning tree of a graph. + * The importance of minimum spanning trees means that disjoint-set data structures underlie a wide variety of algorithms. + * In addition, disjoint-set data structures also have applications to symbolic computation, as well in compilers, + * especially for register allocation problems. + * + * you can learn more on disjoint-set / unionโ€“find data structure at https://en.wikipedia.org/wiki/Disjoint-set_data_structure + */ +function UnionFind(n, key) { + if (!(this instanceof UnionFind)) return new UnionFind(n) + if (key && typeof key !== 'function') { + throw new Error('key has to be a function or else left undefined') + } + let cnt, length + // init Union Find with number of distinct groups. Each group will be referred to as index of the array of size 'size' starting at 0. + // Provide an optional key function that maps these indices. I.e. for the groups starting with 1 provide function(a){return a-1;}. The default value is function(a){return a;}. + key = + key || + function (a) { + return a + } + cnt = length = n + const id = new Array(n) + const sz = new Array(n) + for (let i = 0; i < n; i++) { + id[i] = i + sz[i] = 1 + } + // Returns the number of elements of uf object. + this.size = function () { + return length + } + // Returns the number of distinct groups left inside the object. + this.count = function () { + return cnt + } + // Return the root (value) of the group in which p is. + this.find = function (p) { + p = key(p) + while (p !== id[p]) { + id[p] = id[id[p]] + p = id[p] + } + return p + } + // Returns true if p and p are both in same group, false otherwise. + this.connected = function (p, q) { + p = key(p) + q = key(q) + ensureIndexWithinBounds(p, q) + return this.find(p) === this.find(q) + } + // Combine elements in groups p and q into a single group. In other words connect the two groups. + this.union = function (p, q) { + p = key(p) + q = key(q) + ensureIndexWithinBounds(p, q) + const i = this.find(p) + const j = this.find(q) + if (i === j) return + if (sz[i] < sz[j]) { + id[i] = j + sz[j] += sz[i] + } else { + id[j] = i + sz[i] += sz[j] + } + cnt-- + } + function ensureIndexWithinBounds(args) { + for (let i = arguments.length - 1; i >= 0; i--) { + const p = arguments[i] + if (p >= length) + throw new Error( + 'Index out of bounds. The maximum index can be length-1' + ) + } + } +} + +export { UnionFind } diff --git a/Search/test/BinarySearch.test.js b/Search/test/BinarySearch.test.js new file mode 100644 index 0000000000..a903b4a24e --- /dev/null +++ b/Search/test/BinarySearch.test.js @@ -0,0 +1,49 @@ +import { binarySearchIterative, binarySearchRecursive } from '../BinarySearch' + +const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +const stringArr = [ + 'Alpha', + 'Bravo', + 'Charlie', + 'Delta', + 'Echo', + 'Foxtrot', + 'Golf', + 'Hotel', + 'India', + 'Juliet', + 'Kilo', + 'Lima', + 'Mike', + 'November', + 'Oscar', + 'Papa', + 'Quebec', + 'Romeo', + 'Sierra', + 'Tango', + 'Uniform', + 'Victor', + 'Whiskey', + 'X-Ray', + 'Yankee', + 'Zulu' +] + +describe('Binary Search', () => { + const funcs = [binarySearchIterative, binarySearchRecursive] + for (const func of funcs) { + test('expect to return the index of the item in the array', () => { + expect(func(arr, 3)).toBe(2) + }) + test('expect to return -1 if not in array', () => { + expect(func(arr, 11)).toBe(-1) + }) + test('expect to return the index of the item in the array', () => { + expect(func(stringArr, 'Charlie')).toBe(2) + }) + test('expect to return -1 if not in array', () => { + expect(func(stringArr, 'Zoft')).toBe(-1) + }) + } +}) diff --git a/Search/test/ExponentialSearch.test.js b/Search/test/ExponentialSearch.test.js new file mode 100644 index 0000000000..d8706230d0 --- /dev/null +++ b/Search/test/ExponentialSearch.test.js @@ -0,0 +1,15 @@ +import { exponentialSearch } from '../ExponentialSearch' + +test('The Exponential Search of the Array [2, 3, 4, 10, 40, 65, 78, 100] is 6 where the value = 78', () => { + const arr = [2, 3, 4, 10, 40, 65, 78, 100] + const value = 78 + const result = exponentialSearch(arr, arr.length, value) + expect(result).toEqual(6) +}) + +test('The Exponential Search of the Array [2, 3, 4, 10, 40, 65, 78, 100] is -1 where the value = 178', () => { + const arr = [2, 3, 4, 10, 40, 65, 78, 100] + const value = 178 + const result = exponentialSearch(arr, arr.length, value) + expect(result).toEqual(-1) +}) diff --git a/Search/test/FibonacciSearch.test.js b/Search/test/FibonacciSearch.test.js new file mode 100644 index 0000000000..477dc8fe73 --- /dev/null +++ b/Search/test/FibonacciSearch.test.js @@ -0,0 +1,22 @@ +import { fibonacciSearch } from '../FibonacciSearch' + +test('fibonacciSearch([10, 22, 35, 40, 45, 50, 80, 82, 85, 90, 100], 90, arr.length) => 9', () => { + const arr = [10, 22, 35, 40, 45, 50, 80, 82, 85, 90, 100] + const target = 90 + const res = fibonacciSearch(arr, target, arr.length) + expect(res).toEqual(9) +}) + +test('fibonacciSearch([1, 11, 55, 56, 78, 82, 104], 104, arr.length) => 6', () => { + const arr = [1, 11, 55, 56, 78, 82, 104] + const target = 104 + const res = fibonacciSearch(arr, target, arr.length) + expect(res).toEqual(6) +}) + +test('fibonacciSearch([40, 45, 50, 80, 82, 85, 90, 100]. 190, arr.length) => -1', () => { + const arr = [40, 45, 50, 80, 82, 85, 90, 100] + const target = 190 + const res = fibonacciSearch(arr, target, arr.length) + expect(res).toEqual(-1) +}) diff --git a/Search/test/InterpolationSearch.test.js b/Search/test/InterpolationSearch.test.js new file mode 100644 index 0000000000..674c059db2 --- /dev/null +++ b/Search/test/InterpolationSearch.test.js @@ -0,0 +1,15 @@ +import { interpolationSearch } from '../InterpolationSearch' + +test('interpolationSearch([2, 6, 8, 14, 122, 169], 144) => -1', () => { + const array = [2, 6, 8, 14, 122, 169] + const key = 144 + const res = interpolationSearch(array, key) + expect(res).toEqual(-1) +}) + +test('interpolationSearch([2, 6, 8, 14, 122, 169], 122) => 4', () => { + const array = [2, 6, 8, 14, 122, 169] + const key = 122 + const res = interpolationSearch(array, key) + expect(res).toEqual(4) +}) diff --git a/Search/test/LinearSearch.test.js b/Search/test/LinearSearch.test.js new file mode 100644 index 0000000000..d593d9aa1c --- /dev/null +++ b/Search/test/LinearSearch.test.js @@ -0,0 +1,35 @@ +import { Search as linearSearch } from '../LinearSearch' + +const tests = [ + { + test: { + arr: [1, 2, 300, 401, 450, 504, 800, 821, 855, 900, 1002], + target: 900 + }, + expectedValue: 9 + }, + { + test: { + arr: [1, 104, 110, 4, 44, 55, 56, 78], + target: 104 + }, + expectedValue: 1 + }, + { + test: { + arr: [-4, 5, 50, 77, 821, 85, 99, 100], + target: 192 + }, + expectedValue: -1 + } +] + +describe('Linear Search', () => { + it.each(tests)( + 'linearSearch($test.arr, $test.target) => $expectedValue', + ({ test, expectedValue }) => { + const { arr, target } = test + expect(linearSearch(arr, target)).toBe(expectedValue) + } + ) +}) diff --git a/Search/test/Minesweeper.test.js b/Search/test/Minesweeper.test.js new file mode 100644 index 0000000000..d5624c7d1b --- /dev/null +++ b/Search/test/Minesweeper.test.js @@ -0,0 +1,75 @@ +import { minesweeper } from '../Minesweeper' + +describe('Testing minesweeper function', () => { + it('should return the expected 3x3 array', () => { + const input = [ + [true, false, false], + [false, true, false], + [false, false, false] + ] + const expectedOutput = [ + [1, 2, 1], + [2, 1, 1], + [1, 1, 1] + ] + expect(minesweeper(input)).toStrictEqual(expectedOutput) + }) + + it('should return the expected 3x4 array', () => { + const input = [ + [true, false, false, true], + [false, false, true, false], + [true, true, false, true] + ] + const expectedOutput = [ + [0, 2, 2, 1], + [3, 4, 3, 3], + [1, 2, 3, 1] + ] + expect(minesweeper(input)).toStrictEqual(expectedOutput) + }) + + it('should return the expected 5x2 array', () => { + const input = [ + [true, false], + [true, false], + [false, true], + [false, false], + [false, false] + ] + const expectedOutput = [ + [1, 2], + [2, 3], + [2, 1], + [1, 1], + [0, 0] + ] + expect(minesweeper(input)).toStrictEqual(expectedOutput) + }) + + it('should return the correct result when there are no mines', () => { + const input = [ + [false, false, false], + [false, false, false] + ] + const expectedOutput = [ + [0, 0, 0], + [0, 0, 0] + ] + expect(minesweeper(input)).toStrictEqual(expectedOutput) + }) + + it('should return the correct result when there are mines in every cell', () => { + const input = [ + [true, true, true], + [true, true, true], + [true, true, true] + ] + const expectedOutput = [ + [3, 5, 3], + [5, 8, 5], + [3, 5, 3] + ] + expect(minesweeper(input)).toStrictEqual(expectedOutput) + }) +}) diff --git a/Search/test/RabinKarp.test.js b/Search/test/RabinKarp.test.js new file mode 100644 index 0000000000..3d10097509 --- /dev/null +++ b/Search/test/RabinKarp.test.js @@ -0,0 +1,30 @@ +import { rabinKarpSearch } from '../RabinKarp' + +describe('Rabin-Karp Search', function () { + it('should find the pattern in the text', function () { + const text = 'ABABDABACDABABCABAB' + const pattern = 'DAB' + const expected = [4, 9] + + const result = rabinKarpSearch(text, pattern) + expect(result).to.deep.equal(expected) + }) + + it('should handle multiple occurrences of the pattern', function () { + const text = 'ABABABABABAB' + const pattern = 'ABAB' + const expected = [2, 4, 6, 8] + + const result = rabinKarpSearch(text, pattern) + expect(result).to.deep.equal(expected) + }) + + it('should handle pattern not found', function () { + const text = 'ABCD' + const pattern = 'XYZ' + const expected = [] + + const result = rabinKarpSearch(text, pattern) + expect(result).to.deep.equal(expected) + }) +}) diff --git a/Search/test/SlidingWindow.test.js b/Search/test/SlidingWindow.test.js new file mode 100644 index 0000000000..463b44b69a --- /dev/null +++ b/Search/test/SlidingWindow.test.js @@ -0,0 +1,16 @@ +import { slidingWindow } from '../SlidingWindow' + +test('expect to return the largest sum of sequence in the array', () => { + const sum = slidingWindow([1, 2, 5, 2, 8, 1, 5], 2) + expect(sum).toBe(10) +}) + +test('expect to return the largest sum of sequence in the array', () => { + const sum = slidingWindow([5, 2, 6, 9], 3) + expect(sum).toBe(17) +}) + +test('expect to return null when the sequence size is larger then the array length', () => { + const sum = slidingWindow([1, 2, 5, 2, 8, 1, 5], 15) + expect(sum).toBe(null) +}) diff --git a/Search/test/TernarySearch.test.js b/Search/test/TernarySearch.test.js new file mode 100644 index 0000000000..375c5c5fbd --- /dev/null +++ b/Search/test/TernarySearch.test.js @@ -0,0 +1,51 @@ +import { + ternarySearchRecursive, + ternarySearchIterative +} from '../TernarySearch' + +test('should return the index of a number in an array of numbers:', () => { + const indexNumber = ternarySearchRecursive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 3) + expect(indexNumber).toBe(2) +}) + +test('should return the index of a number in an array of numbers:', () => { + const indexNumber = ternarySearchIterative([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 8) + expect(indexNumber).toBe(7) +}) + +test('should return the index of a number in an array of numbers:', () => { + const indexNumber = ternarySearchRecursive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0) + expect(indexNumber).toBe(-1) +}) + +test('should return the index of a number in an array of numbers:', () => { + const indexNumber = ternarySearchIterative( + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + 12 + ) + expect(indexNumber).toBe(-1) +}) + +test('should return the index of a string in an array of strings:', () => { + const indexNumber = ternarySearchRecursive( + ['Ali', 'Cathrynli', 'Josuke', 'Thomas'], + 'Cathrynli' + ) + expect(indexNumber).toBe(1) +}) + +test('should return the index of a string in an array of strings:', () => { + const indexNumber = ternarySearchRecursive( + ['Ali', 'Cathrynli', 'Josuke', 'Thomas'], + 'Josuke' + ) + expect(indexNumber).toBe(2) +}) + +test('should return the index of a string in an array of strings:', () => { + const indexNumber = ternarySearchRecursive( + ['Ali', 'Cathrynli', 'Josuke', 'Thomas'], + 'Angela' + ) + expect(indexNumber).toBe(-1) +}) diff --git a/Search/test/UnionFind.test.js b/Search/test/UnionFind.test.js new file mode 100644 index 0000000000..6b58bb2a4b --- /dev/null +++ b/Search/test/UnionFind.test.js @@ -0,0 +1,54 @@ +import { UnionFind } from '../UnionFind' + +const uf = new UnionFind(5) + +test('should expose .size():', () => { + const size = uf.size() + expect(size).toBe(5) +}) + +test('should do .union(num1, num2):', () => { + uf.union(1, 2) + uf.union(3, 4) + uf.union(0, 4) + expect(uf.connected(1, 2)).toBe(true) + expect(uf.connected(1, 2)).toBe(true) + + expect(uf.connected(3, 4)).toBe(true) + expect(uf.connected(3, 0)).toBe(true) + expect(uf.connected(4, 0)).toBe(true) + + expect(uf.connected(1, 3)).toBe(false) + expect(uf.connected(1, 4)).toBe(false) + expect(uf.connected(1, 0)).toBe(false) + expect(uf.connected(2, 3)).toBe(false) + expect(uf.connected(2, 4)).toBe(false) + expect(uf.connected(2, 0)).toBe(false) +}) + +test('.count(), should return the number of disparate groups:', () => { + expect(uf.count()).toBe(2) +}) + +test('should check if two components are connected, .connected(num1, num2):', () => { + expect(uf.connected(1, 2)).toBe(true) + expect(uf.connected(1, 3)).toBe(false) +}) + +test('should find the root of the tree in which the given element lives, .find(num):', () => { + expect(uf.find(1)).toBe(1) + expect(uf.find(2)).toBe(1) + expect(uf.find(3)).toBe(3) + expect(uf.find(4)).toBe(3) + expect(uf.find(0)).toBe(3) +}) + +test('should always change the id of the smaller tree and preserve the id of the larger one', () => { + uf.union(2, 3) + expect(uf.count()).toBe(1) + expect(uf.find(0)).toBe(3) + expect(uf.find(1)).toBe(3) + expect(uf.find(2)).toBe(3) + expect(uf.find(3)).toBe(3) + expect(uf.find(4)).toBe(3) +}) diff --git a/Search/test/jumpSearch.test.js b/Search/test/jumpSearch.test.js new file mode 100644 index 0000000000..189aa2fd51 --- /dev/null +++ b/Search/test/jumpSearch.test.js @@ -0,0 +1,19 @@ +import { jumpSearch } from '../JumpSearch' + +test('jumpSearch([0, 0, 4, 7, 10, 23, 34, 40, 55, 68, 77, 90], 77) => 10', () => { + const arr = [0, 0, 4, 7, 10, 23, 34, 40, 55, 68, 77, 90] + const res = jumpSearch(arr, 77) + expect(res).toEqual(10) +}) + +test('jumpSearch([11, 12, 15, 65, 78, 90], 4) => -1', () => { + const arr = [11, 12, 15, 65, 78, 90] + const res = jumpSearch(arr, 4) + expect(res).toEqual(-1) +}) + +test('jumpSearch([11, 12, 15, 65, 78, 90], 11) => 0', () => { + const arr = [11, 12, 15, 65, 78, 90] + const res = jumpSearch(arr, 11) + expect(res).toEqual(0) +}) diff --git a/Sorts/AlphaNumericalSort.js b/Sorts/AlphaNumericalSort.js new file mode 100644 index 0000000000..fb6be8f424 --- /dev/null +++ b/Sorts/AlphaNumericalSort.js @@ -0,0 +1,38 @@ +/* + https://en.wikipedia.org/wiki/Natural_sort_order + + In computing, natural sort order (or natural sorting) is the ordering of strings in alphabetical order, + except that multi-digit numbers are treated atomically, i.e., as if they were a single character. Natural sort order + has been promoted as being more human-friendly ("natural") than machine-oriented, pure alphabetical sort order.[1] + + For example, in alphabetical sorting, "z11" would be sorted before "z2" because the "1" in the first string is sorted as smaller + than "2", while in natural sorting "z2" is sorted before "z11" because "2" is treated as smaller than "11". + + Alphabetical sorting: + 1.z11 + 2.z2 + + Natural sorting: + 1. z2 + 2. z11 + + P.S. use this function, as there are a lot of implementations on the stackoverflow and other forums, but many of them don't work correctly (can't pass all my tests) + +*/ + +const alphaNumericalSort = (a, b) => { + /* + https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare + + The localeCompare() method returns a number indicating whether a reference string comes before, or after, or is the same as the given string in sort order. + + The new locales and options arguments let applications specify the language whose sort order should be used and customize the behavior of the function. + In older implementations, which ignore the locales and options arguments, the locale and sort order used are entirely implementation-dependent. + Syntax: + localeCompare(compareString, locales, options) + + */ + return a.localeCompare(b, undefined, { numeric: true }) +} + +export { alphaNumericalSort } diff --git a/Sorts/BeadSort.js b/Sorts/BeadSort.js new file mode 100644 index 0000000000..6a6f69398d --- /dev/null +++ b/Sorts/BeadSort.js @@ -0,0 +1,56 @@ +/** + * Bead Sort, also known as Gravity sort. + * + * This algorithm was inspired from natural phenomena and was designed keeping in mind objects (or beads) falling under + * the influence of gravity. + * + * NOTE: It only works for arrays of positive integers. + * + * Wikipedia: https://en.wikipedia.org/wiki/Bead_sort + */ +export function beadSort(sequence) { + /* Let's ensure our sequence has only Positive Integers */ + if (sequence.some((integer) => integer < 0)) { + throw RangeError('Sequence must be a list of Positive integers Only!') + } + + const sequenceLength = sequence.length + const max = Math.max(...sequence) + + // Set initial Grid + const grid = sequence.map((number) => { + const maxArr = new Array(max) + + for (let i = 0; i < number; i++) { + maxArr[i] = '*' + } + + return maxArr + }) + + // Drop the Beads! + for (let col = 0; col < max; col++) { + let beadsCount = 0 + + for (let row = 0; row < sequenceLength; row++) { + if (grid[row][col] === '*') { + beadsCount++ + } + } + + for (let row = sequenceLength - 1; row > -1; row--) { + if (beadsCount) { + grid[row][col] = '*' + beadsCount-- + } else { + grid[row][col] = undefined + } + } + } + + /* Finally, let's turn our Bead rows into their Respective Numbers */ + return grid.map((beadArray) => { + const beadsArray = beadArray.filter((bead) => bead === '*') + return beadsArray.length + }) +} diff --git a/Sorts/BinaryInsertionSort.js b/Sorts/BinaryInsertionSort.js new file mode 100644 index 0000000000..68912762dc --- /dev/null +++ b/Sorts/BinaryInsertionSort.js @@ -0,0 +1,60 @@ +/** + * Pure Implementation of Binary Search Algorithm + * + * Binary insertion sort is a sorting algorithm similar to insertion sort, + * but instead of using linear search to find the position + * where the element should be inserted, we use binary search. + * Thus, we reduce the number of comparisons for inserting one element from O(N) + * (Time complexity in Insertion Sort) to O(log N). + * + */ + +/** + * Search the key element in the array from start position to end position. + * + * @param {Array} array Array of numbers. + * @param {Number} key Value to be searched + * @param {Number} start start index position of array + * @param {Number} end end index position of array + * @return {Number} Position of the key element + */ +function binarySearch(array, key, start, end) { + if (start === end) { + if (array[start] > key) { + return start + } else { + return start + 1 + } + } + + if (start > end) { + return start + } + + const mid = Math.floor((start + end) / 2) + + if (array[mid] < key) { + return binarySearch(array, key, mid + 1, end) + } else if (array[mid] > key) { + return binarySearch(array, key, start, mid - 1) + } else { + return mid + } +} + +/** + * Binary Insertion Sort + * + * @param {Array} list List to be sorted. + * @return {Array} The sorted list. + */ +export function binaryInsertionSort(array) { + const totalLength = array.length + for (let i = 1; i < totalLength; i += 1) { + const key = array[i] + const indexPosition = binarySearch(array, key, 0, i - 1) + array.splice(i, 1) + array.splice(indexPosition, 0, key) + } + return array +} diff --git a/Sorts/BogoSort.js b/Sorts/BogoSort.js index 68d68edcc2..eeb4f7feeb 100644 --- a/Sorts/BogoSort.js +++ b/Sorts/BogoSort.js @@ -1,56 +1,38 @@ -/* - * A simple helper function that checks, if the array is - * sorted in ascending order. +/** + * Checks whether the given array is sorted in ascending order. */ - -// > [].isSorted() -// true -// > [1].isSorted() -// true -// > [1,2,3].isSorted() -// true -// > [3,2,1].isSorted() -// false -/* eslint no-extend-native: ["off", { "exceptions": ["Object"] }] */ -Array.prototype.isSorted = function () { - const length = this.length +export function isSorted(array) { + const length = array.length for (let i = 0; i < length - 1; i++) { - if (this[i] > this[i + 1]) { + if (array[i] > array[i + 1]) { return false } } return true } -/* - * A simple helper function to shuffle the array randomly in place. +/** + * Shuffles the given array randomly in place. */ -Array.prototype.shuffle = function () { - for (let i = this.length - 1; i; i--) { +function shuffle(array) { + for (let i = array.length - 1; i; i--) { const m = Math.floor(Math.random() * i) - const n = this[i - 1] - this[i - 1] = this[m] - this[m] = n + const n = array[i - 1] + array[i - 1] = array[m] + array[m] = n } } -/* - * Implementation of the bogosort algorithm. This sorting algorithm randomly - * rearranges the array until it is sorted. +/** + * Implementation of the bogosort algorithm. + * + * This sorting algorithm randomly rearranges the array until it is sorted. + * * For more information see: https://en.wikipedia.org/wiki/Bogosort */ -function bogoSort (items) { - while (!items.isSorted()) { - items.shuffle() +export function bogoSort(items) { + while (!isSorted(items)) { + shuffle(items) } return items } - -// Implementation of bogoSort - -var ar = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(ar) -bogoSort(ar) -// Array after sort -console.log(ar) diff --git a/Sorts/BubbleSort.js b/Sorts/BubbleSort.js index 3232991d1d..5571fac047 100644 --- a/Sorts/BubbleSort.js +++ b/Sorts/BubbleSort.js @@ -1,61 +1,60 @@ -/* Bubble Sort is a algorithm to sort an array. It -* compares adjacent element and swaps thier position -* The big O on bubble sort in worst and best case is O(N^2). - * Not efficient. -*/ +/* Bubble Sort is an algorithm to sort an array. It + * compares adjacent element and swaps their position + * The big O on bubble sort in worst and best case is O(N^2). + * Not efficient. + * Somehow if the array is sorted or nearly sorted then we can optimize bubble sort by adding a flag. + * + * In bubble sort, we keep iterating while something was swapped in + * the previous inner-loop iteration. By swapped I mean, in the + * inner loop iteration, we check each number if the number proceeding + * it is greater than itself, if so we swap them. + * + * Wikipedia: https://en.wikipedia.org/wiki/Bubble_sort + * Animated Visual: https://www.toptal.com/developers/sorting-algorithms/bubble-sort + */ -function bubbleSort (items) { +/** + * Using 2 for loops. + */ +export function bubbleSort(items) { const length = items.length - for (let i = (length - 1); i > 0; i--) { + let noSwaps + + for (let i = length; i > 0; i--) { + // flag for optimization + noSwaps = true // Number of passes - for (let j = (length - i); j > 0; j--) { + for (let j = 0; j < i - 1; j++) { // Compare the adjacent positions - if (items[j] < items[j - 1]) { + if (items[j] > items[j + 1]) { // Swap the numbers - [items[j], items[j - 1]] = [items[j - 1], items[j]] + ;[items[j], items[j + 1]] = [items[j + 1], items[j]] + noSwaps = false } } + if (noSwaps) { + break + } } -} - -// Implementation of bubbleSort -var ar = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log('-----before sorting-----') -console.log(ar) -bubbleSort(ar) -// Array after sort -console.log('-----after sorting-----') -console.log(ar) + return items +} -/* alternative implementation of bubble sort algorithm. - Using a while loop instead. For educational purposses only +/** + * Using a while loop and a for loop. */ -/* -*In bubble sort, we keep iterating while something was swapped in -*the previous inner-loop iteration. By swapped I mean, in the -*inner loop iteration, we check each number if the number proceeding -*it is greater than itself, if so we swap them. -*/ - -function alternativeBubbleSort (arr) { +export function alternativeBubbleSort(arr) { let swapped = true + while (swapped) { swapped = false for (let i = 0; i < arr.length - 1; i++) { if (arr[i] > arr[i + 1]) { - [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]] + ;[arr[i], arr[i + 1]] = [arr[i + 1], arr[i]] swapped = true } } } + return arr } - -// test -console.log('-----before sorting-----') -var array = [10, 5, 3, 8, 2, 6, 4, 7, 9, 1] -console.log(array) -console.log('-----after sorting-----') -console.log(alternativeBubbleSort(array)) diff --git a/Sorts/BucketSort.js b/Sorts/BucketSort.js index 9423c4c104..a6cc2200fa 100644 --- a/Sorts/BucketSort.js +++ b/Sorts/BucketSort.js @@ -1,17 +1,23 @@ -/* -Wikipedia says: Bucket sort, or bin sort, is a sorting algorithm that works by distributing the -elements of an array into a number of buckets. Each bucket is then sorted individually, either using -a different sorting algorithm, or by recursively applying the bucket sorting algorithm. It is a -distribution sort, and is a cousin of radix sort in the most to least significant digit flavour. -Bucket sort is a generalization of pigeonhole sort. Bucket sort can be implemented with comparisons -and therefore can also be considered a comparison sort algorithm. The computational complexity estimates -involve the number of buckets. - -Time Complexity of Solution: -Best Case O(n); Average Case O(n); Worst Case O(n) - -*/ -function bucketSort (list, size) { +/** + * BucketSort implementation. + * + * Wikipedia says: Bucket sort, or bin sort, is a sorting algorithm that works by distributing the elements of an array + * into a number of buckets. Each bucket is then sorted individually, either using a different sorting algorithm, or by + * recursively applying the bucket sorting algorithm. It is a distribution sort, and is a cousin of radix sort in the + * most to least significant digit flavour. Bucket sort is a generalization of pigeonhole sort. Bucket sort can be + * implemented with comparisons and therefore can also be considered a comparison sort algorithm. The computational + * complexity estimates involve the number of buckets. + * + * @see https://en.wikipedia.org/wiki/Bucket_sort#:~:text=Bucket%20sort%2C%20or%20bin%20sort,applying%20the%20bucket%20sorting%20algorithm.&text=Sort%20each%20non%2Dempty%20bucket. + * + * Time Complexity of Solution: + * Best Case O(n); Average Case O(n); Worst Case O(n) + * + * @param {number[]} list The array of numbers to be sorted. + * @param {number} size The size of the buckets used. If not provided, size will be 5. + * @return {number[]} An array of numbers sorted in increasing order. + */ +export function bucketSort(list, size) { if (undefined === size) { size = 5 } @@ -45,20 +51,10 @@ function bucketSort (list, size) { const sorted = [] // now sort every bucket and merge it to the sorted list for (let iBucket = 0; iBucket < buckets.length; iBucket++) { - const arr = buckets[iBucket].sort() + const arr = buckets[iBucket].sort((a, b) => a - b) for (let iSorted = 0; iSorted < arr.length; iSorted++) { sorted.push(arr[iSorted]) } } return sorted } - -// Testing -const arrOrignal = [5, 6, 7, 8, 1, 2, 12, 14] -// > bucketSort(arrOrignal) -// [1, 2, 5, 6, 7, 8, 12, 14] -// Array before Sort -console.log(arrOrignal) -const arrSorted = bucketSort(arrOrignal) -// Array after sort -console.log(arrSorted) diff --git a/Sorts/CocktailShakerSort.js b/Sorts/CocktailShakerSort.js index 337fa5f9f7..033c7c461a 100644 --- a/Sorts/CocktailShakerSort.js +++ b/Sorts/CocktailShakerSort.js @@ -1,40 +1,31 @@ -/* - * Cocktail shaker sort is a sort algorithm that is a bidirectional bubble sort - * more information: https://en.wikipedia.org/wiki/Cocktail_shaker_sort - * more information: https://en.wikipedia.org/wiki/Bubble_sort +/** + * Cocktail Shaker Sort is an algorithm that is a Bidirectional Bubble Sort. * + * The algorithm extends bubble sort by operating in two directions. + * While it improves on bubble sort by more quickly moving items to the beginning of the list, it provides only marginal + * performance improvements. + * + * Wikipedia (Cocktail Shaker Sort): https://en.wikipedia.org/wiki/Cocktail_shaker_sort + * Wikipedia (Bubble Sort): https://en.wikipedia.org/wiki/Bubble_sort */ -function cocktailShakerSort (items) { +export function cocktailShakerSort(items) { for (let i = items.length - 1; i > 0; i--) { - let swapped = false let j - // backwards + // Backwards for (j = items.length - 1; j > i; j--) { if (items[j] < items[j - 1]) { - [items[j], items[j - 1]] = [items[j - 1], items[j]] - swapped = true + ;[items[j], items[j - 1]] = [items[j - 1], items[j]] } } - // forwards + // Forwards for (j = 0; j < i; j++) { if (items[j] > items[j + 1]) { - [items[j], items[j + 1]] = [items[j + 1], items[j]] - swapped = true + ;[items[j], items[j + 1]] = [items[j + 1], items[j]] } } - if (!swapped) { - return - } } -} -// Implementation of cocktailShakerSort - -var ar = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(ar) -cocktailShakerSort(ar) -// Array after sort -console.log(ar) + return items +} diff --git a/Sorts/CombSort.js b/Sorts/CombSort.js index ce34ce1577..56614a2120 100644 --- a/Sorts/CombSort.js +++ b/Sorts/CombSort.js @@ -1,21 +1,28 @@ -/* -Wikipedia says: Comb sort improves on bubble sort. +/** + * Comb sort improves on bubble sort. + * + * The basic idea is to eliminate turtles, or small values + * near the end of the list, since in a bubble sort these slow the sorting + * down tremendously. Rabbits, large values around the beginning of the list, + * do not pose a problem in bubble sort. + * + * In bubble sort, when any two elements are compared, they always have a + * gap (distance from each other) of 1. The basic idea of comb sort is + * that the gap can be much more than 1. The inner loop of bubble sort, + * which does the actual swap, is modified such that gap between swapped + * elements goes down (for each iteration of outer loop) in steps of + * a "shrink factor" k: [ n/k, n/k2, n/k3, ..., 1 ]. + * + * Wikipedia: https://en.wikipedia.org/wiki/Comb_sort + */ -The basic idea is to eliminate turtles, or small values -near the end of the list, since in a bubble sort these slow the sorting -down tremendously. Rabbits, large values around the beginning of the list, -do not pose a problem in bubble sort. - -In bubble sort, when any two elements are compared, they always have a -gap (distance from each other) of 1. The basic idea of comb sort is -that the gap can be much more than 1. The inner loop of bubble sort, -which does the actual swap, is modified such that gap between swapped -elements goes down (for each iteration of outer loop) in steps of -a "shrink factor" k: [ n/k, n/k2, n/k3, ..., 1 ]. - -*/ - -function combSort (list) { +/** + * combSort returns an array of numbers sorted in increasing order. + * + * @param {number[]} list The array of numbers to sort. + * @return {number[]} The array of numbers sorted in increasing order. + */ +function combSort(list) { if (list.length === 0) { return list } @@ -33,7 +40,7 @@ function combSort (list) { while (gap + i < list.length) { if (list[i] > list[i + gap]) { - [list[i], list[i + gap]] = [list[i + gap], list[i]] + ;[list[i], list[i + gap]] = [list[i + gap], list[i]] isSwapped = true } i += 1 @@ -41,9 +48,5 @@ function combSort (list) { } return list } -const arrOrignal = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(arrOrignal) -const arrSorted = combSort(arrOrignal) -// Array after sort -console.log(arrSorted) + +export { combSort } diff --git a/Sorts/CountingSort.js b/Sorts/CountingSort.js index 56e2b24788..c7d495d92f 100644 --- a/Sorts/CountingSort.js +++ b/Sorts/CountingSort.js @@ -1,37 +1,37 @@ -/* - * Counting sort is an algorithm for sorting a collection of objects according to keys that are small integers; - * that is, it is an integer sorting algorithm. - * more information: https://en.wikipedia.org/wiki/Counting_sort - * counting sort visualization: https://www.cs.usfca.edu/~galles/visualization/CountingSort.html +/** + * Counting sort is an algorithm for sorting a collection + * of objects according to keys that are small integers. + * + * It is an integer sorting algorithm. + * + * Wikipedia: https://en.wikipedia.org/wiki/Counting_sort + * Animated Visual: https://www.cs.usfca.edu/~galles/visualization/CountingSort.html */ -function countingSort (arr, min, max) { - let i - let z = 0 - const count = [] - - for (i = min; i <= max; i++) { - count[i] = 0 +export const countingSort = (arr, min, max) => { + // Create an auxiliary resultant array + const res = [] + // Create and initialize the frequency[count] array + const count = new Array(max - min + 1).fill(0) + // Populate the freq array + for (let i = 0; i < arr.length; i++) { + count[arr[i] - min]++ } - - for (i = 0; i < arr.length; i++) { - count[arr[i]]++ + // Create a prefix sum array out of the frequency[count] array + count[0] -= 1 + for (let i = 1; i < count.length; i++) { + count[i] += count[i - 1] } - - for (i = min; i <= max; i++) { - while (count[i]-- > 0) { - arr[z++] = i - } + // Populate the result array using the prefix sum array + for (let i = arr.length - 1; i >= 0; i--) { + res[count[arr[i] - min]] = arr[i] + count[arr[i] - min]-- } - - return arr + return res } -const arr = [3, 0, 2, 5, 4, 1] - -// Array before Sort -console.log('-----before sorting-----') -console.log(arr) -// Array after sort -console.log('-----after sorting-----') -console.log(countingSort(arr, 0, 5)) +/** + * Implementation of Counting Sort + */ +// const array = [3, 0, 2, 5, 4, 1] +// countingSort(array, 0, 5) diff --git a/Sorts/CycleSort.js b/Sorts/CycleSort.js index 5853712974..11ec013482 100644 --- a/Sorts/CycleSort.js +++ b/Sorts/CycleSort.js @@ -1,13 +1,20 @@ -/* -Wikipedia says: Cycle sort is an in-place, unstable sorting algorithm, -a comparison sort that is theoretically optimal in terms of the total -number of writes to the original array, unlike any other in-place sorting -algorithm. It is based on the idea that the permutation to be sorted can -be factored into cycles, which can individually be rotated to give a sorted result. -*/ +/** + * Cycle sort is an in-place, unstable sorting algorithm, + * a comparison sort that is theoretically optimal in terms of the total + * number of writes to the original array, unlike any other in-place sorting + * algorithm. It is based on the idea that the permutation to be sorted can + * be factored into cycles, which can individually be rotated to give a sorted result. + * + * Wikipedia: https://en.wikipedia.org/wiki/Cycle_sort + */ -function cycleSort (list) { - let writes = 0 +/** + * cycleSort takes an input array of numbers and returns the array sorted in increasing order. + * + * @param {number[]} list An array of numbers to be sorted. + * @return {number[]} An array of numbers sorted in increasing order. + */ +function cycleSort(list) { for (let cycleStart = 0; cycleStart < list.length; cycleStart++) { let value = list[cycleStart] let position = cycleStart @@ -18,11 +25,10 @@ function cycleSort (list) { position++ } } - // if its the same continue + // if it is the same, continue if (position === cycleStart) { continue } - while (value === list[position]) { position++ } @@ -30,7 +36,6 @@ function cycleSort (list) { const oldValue = list[position] list[position] = value value = oldValue - writes++ // rotate the rest while (position !== cycleStart) { @@ -46,14 +51,9 @@ function cycleSort (list) { const oldValueCycle = list[position] list[position] = value value = oldValueCycle - writes++ } } - return writes + return list } -const arrOrignal = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(arrOrignal) -cycleSort(arrOrignal) -// Array after sort -console.log(arrOrignal) + +export { cycleSort } diff --git a/Sorts/DutchNationalFlagSort.js b/Sorts/DutchNationalFlagSort.js new file mode 100644 index 0000000000..c90cabf371 --- /dev/null +++ b/Sorts/DutchNationalFlagSort.js @@ -0,0 +1,33 @@ +/** + * @function dutchNationalFlagSort + * @description Dutch National Flag Sort is an algorithm to sort an array containing 0s, 1s, and 2s in linear time. + Time complexity of Dutch National Flag Sort Algorithm is O(n). + Auxiliary Space required for Dutch National Flag Sort Algorithm is O(1). + * @param {Integer[]} nums - Array of integers containing 0s, 1s, and 2s. + * @return {Integer[]} - Array of integers sorted in non-decreasing order. + * @see [Dutch National Flag Sort](https://en.wikipedia.org/wiki/Dutch_national_flag_problem) + */ +export function dutchNationalFlagSort(nums) { + let low = 0 + let mid = 0 + let high = nums.length - 1 + + while (mid <= high) { + switch (nums[mid]) { + case 0: + ;[nums[low], nums[mid]] = [nums[mid], nums[low]] + low++ + mid++ + break + case 1: + mid++ + break + case 2: + ;[nums[mid], nums[high]] = [nums[high], nums[mid]] + high-- + break + } + } + + return nums +} diff --git a/Sorts/FindSecondLargestElement.js b/Sorts/FindSecondLargestElement.js new file mode 100644 index 0000000000..504b7e1192 --- /dev/null +++ b/Sorts/FindSecondLargestElement.js @@ -0,0 +1,25 @@ +/* + * Find Second Largest is a real technical interview question. + * Chances are you will be asked to find the second largest value + * inside of an array of numbers. You must also be able to filter + * out duplicate values. It's important to know how to do this with + * clean code that is also easy to explain. + * + * Resources: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set + */ + +const secondLargestElement = (array) => { + const largestElement = Math.max(...array) + let element = -Number.MAX_VALUE + + for (let i = 0; i < array.length; i++) { + if (element < array[i] && array[i] !== largestElement) { + element = array[i] + } + } + + return element +} + +export { secondLargestElement } diff --git a/Sorts/FisherYatesShuffle.js b/Sorts/FisherYatesShuffle.js new file mode 100644 index 0000000000..214cb5baa7 --- /dev/null +++ b/Sorts/FisherYatesShuffle.js @@ -0,0 +1,18 @@ +export const shuffle = (array) => { + let maxLength = array.length + let temp + let idx + + // While there remain elements to shuffle... + while (maxLength) { + // Pick a remaining element... + idx = Math.floor(Math.random() * maxLength--) + + // And swap it with the current element + temp = array[maxLength] + array[maxLength] = array[idx] + array[idx] = temp + } + + return array +} diff --git a/Sorts/FlashSort.js b/Sorts/FlashSort.js index 21dc000bed..ca7bb587fb 100644 --- a/Sorts/FlashSort.js +++ b/Sorts/FlashSort.js @@ -1,11 +1,14 @@ -/* - * Flashsort is a distribution sorting algorithm showing linear computational complexity O(n) for uniformly distributed +/** + * Flashsort is a distribution sorting algorithm showing linear + * computational complexity O(n) for uniformly distributed * data sets and relatively little additional memory requirement. - * more information: https://en.wikipedia.org/wiki/Flashsort + * + * Wikipedia: https://en.wikipedia.org/wiki/Flashsort */ -function flashSort (arr) { - let max = 0; let min = arr[0] +export function flashSort(arr) { + let max = 0 + let min = arr[0] const n = arr.length const m = ~~(0.45 * n) const l = new Array(m) @@ -43,12 +46,14 @@ function flashSort (arr) { arr[0] = hold // permutation - let move = 0; let t; let flash + let move = 0 + let t + let flash let j = 0 let k = m - 1 - while (move < (n - 1)) { - while (j > (l[k] - 1)) { + while (move < n - 1) { + while (j > l[k] - 1) { ++j k = ~~(c1 * (arr[j] - min)) } @@ -56,7 +61,7 @@ function flashSort (arr) { flash = arr[j] while (j !== l[k]) { k = ~~(c1 * (flash - min)) - hold = arr[t = --l[k]] + hold = arr[(t = --l[k])] arr[t] = flash flash = hold ++move @@ -75,11 +80,8 @@ function flashSort (arr) { return arr } -const array = [3, 0, 2, 5, -1, 4, 1, -2] - -// Array before Sort -console.log('-----before sorting-----') -console.log(array) -// Array after sort -console.log('-----after sorting-----') -console.log(flashSort(array)) +/** + * Implementation of Flash Sort + */ +// const array = [3, 0, 2, 5, -1, 4, 1, -2] +// flashSort(array) diff --git a/Sorts/GnomeSort.js b/Sorts/GnomeSort.js index d8ccd5ce02..fab66787bc 100644 --- a/Sorts/GnomeSort.js +++ b/Sorts/GnomeSort.js @@ -3,7 +3,7 @@ * more information: https://en.wikipedia.org/wiki/Gnome_sort * */ -function gnomeSort (items) { +export function gnomeSort(items) { if (items.length <= 1) { return } @@ -14,18 +14,15 @@ function gnomeSort (items) { if (items[i - 1] <= items[i]) { i++ } else { - [items[i], items[i - 1]] = [items[i - 1], items[i]] + ;[items[i], items[i - 1]] = [items[i - 1], items[i]] i = Math.max(1, i - 1) } } + return items } // Implementation of gnomeSort -var ar = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(ar) -gnomeSort(ar) -// Array after sort -console.log(ar) +// const ar = [5, 6, 7, 8, 1, 2, 12, 14] +// gnomeSort(ar) diff --git a/Sorts/HeapSort.js b/Sorts/HeapSort.js index d7540919e5..12c16154bc 100644 --- a/Sorts/HeapSort.js +++ b/Sorts/HeapSort.js @@ -33,7 +33,7 @@ Array.prototype.heapify = function (index, heapSize) { * utilizing the heap property. * For more information see: https://en.wikipedia.org/wiki/Heapsort */ -function heapSort (items) { +export function heapSort(items) { const length = items.length for (let i = Math.floor(length / 2) - 1; i > -1; i--) { @@ -50,9 +50,5 @@ function heapSort (items) { // Implementation of heapSort -var ar = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(ar) -heapSort(ar) -// Array after sort -console.log(ar) +// const ar = [5, 6, 7, 8, 1, 2, 12, 14] +// heapSort(ar) diff --git a/Sorts/HeapSortV2.js b/Sorts/HeapSortV2.js index 6f010eea47..c3b9cb0873 100644 --- a/Sorts/HeapSortV2.js +++ b/Sorts/HeapSortV2.js @@ -2,7 +2,7 @@ let arrayLength = 0 /* to create MAX array */ -function heapRoot (input, i) { +function heapRoot(input, i) { const left = 2 * i + 1 const right = 2 * i + 2 let max = i @@ -21,11 +21,11 @@ function heapRoot (input, i) { } } -function swap (input, indexA, indexB) { - [input[indexA], input[indexB]] = [input[indexB], input[indexA]] +function swap(input, indexA, indexB) { + ;[input[indexA], input[indexB]] = [input[indexB], input[indexA]] } -function heapSort (input) { +export function heapSort(input) { arrayLength = input.length for (let i = Math.floor(arrayLength / 2); i >= 0; i -= 1) { @@ -38,8 +38,5 @@ function heapSort (input) { heapRoot(input, 0) } + return input } - -const arr = [3, 0, 2, 5, -1, 4, 1] -heapSort(arr) -console.log(arr) diff --git a/Sorts/InsertionSort.js b/Sorts/InsertionSort.js index 15ec02a600..9a40bd2bf7 100644 --- a/Sorts/InsertionSort.js +++ b/Sorts/InsertionSort.js @@ -1,15 +1,17 @@ /* In insertion sort, we divide the initial unsorted array into two parts; -* sorted part and unsorted part. Initially the sorted part just has one -* element (Array of only 1 element is a sorted array). We then pick up -* element one by one from unsorted part; insert into the sorted part at -* the correct position and expand sorted part one element at a time. -*/ -function insertionSort (unsortedList) { - var len = unsortedList.length - for (var i = 1; i < len; i++) { - var tmp = unsortedList[i] // Copy of the current element. + * sorted part and unsorted part. Initially the sorted part just has one + * element (Array of only 1 element is a sorted array). We then pick up + * element one by one from unsorted part; insert into the sorted part at + * the correct position and expand sorted part one element at a time. + */ + +export function insertionSort(unsortedList) { + const len = unsortedList.length + for (let i = 1; i < len; i++) { + let j + const tmp = unsortedList[i] // Copy of the current element. /* Check through the sorted part and compare with the number in tmp. If large, shift the number */ - for (var j = i - 1; j >= 0 && (unsortedList[j] > tmp); j--) { + for (j = i - 1; j >= 0 && unsortedList[j] > tmp; j--) { // Shift the number unsortedList[j + 1] = unsortedList[j] } @@ -19,6 +21,42 @@ function insertionSort (unsortedList) { } } -var arr = [5, 3, 1, 2, 4, 8, 3, 8] -insertionSort(arr) -console.log(arr) +/** + * @function insertionSortAlternativeImplementation + * @description InsertionSort is a stable sorting algorithm + * @param {Integer[]} array - Array of integers + * @return {Integer[]} - Sorted array + * @see [InsertionSort](https://en.wikipedia.org/wiki/Quicksort) + */ + +/* + * Big-O Analysis + * Time Complexity + - O(N^2) on average and worst case scenario + - O(N) on best case scenario (when input array is already almost sorted) + * Space Complexity + - O(1) +*/ + +export function insertionSortAlternativeImplementation(array) { + const length = array.length + if (length < 2) return array + + for (let i = 1; i < length; i++) { + // Take current element in array + const currentItem = array[i] + // Take index of previous element in array + let j = i - 1 + + // While j >= 0 and previous element is greater than current element + while (j >= 0 && array[j] > currentItem) { + // Move previous, greater element towards the unsorted part + array[j + 1] = array[j] + j-- + } + // Insert currentItem number at the correct position in sorted part. + array[j + 1] = currentItem + } + // Return array sorted in ascending order + return array +} diff --git a/Sorts/IntroSort.js b/Sorts/IntroSort.js index e7ba322c5e..4874b44082 100644 --- a/Sorts/IntroSort.js +++ b/Sorts/IntroSort.js @@ -1,7 +1,7 @@ /** - * @function Intosort (As implemented in STD C++ Lib) + * @function Introsort (As implemented in STD C++ Lib) * The function performs introsort which is used in - * C++ Standard LIbrary, the implemntation is inspired from] + * C++ Standard LIbrary, the implementation is inspired from] * library routine itself. * ALGORITHM: * 1) It performs quicksort on array until the recursion depth @@ -15,7 +15,7 @@ * @see [Introsort](https://en.wikipedia.org/wiki/Introsort) * @author [Lakhan Nad](https://github.com/Lakhan-Nad) */ -function introsort (array, compare) { +function introsort(array, compare) { /** * @function Default Comparison Function * This function is same as implemented by @@ -28,12 +28,12 @@ function introsort (array, compare) { * 0 if a is equal to b * 1 if a greater than b */ - var defaultComparator = function (x, y) { + const defaultComparator = function (x, y) { if (x === undefined && y === undefined) return 0 if (x === undefined) return 1 if (y === undefined) return -1 - var xString = toString(x) - var yString = toString(y) + const xString = toString(x) + const yString = toString(y) if (xString < yString) return -1 if (xString > yString) return 1 return 0 @@ -45,7 +45,7 @@ function introsort (array, compare) { * @param {Object} obj * @returns {String} String representation of given object */ - var toString = function (obj) { + const toString = function (obj) { if (obj === null) return 'null' if (typeof obj === 'boolean' || typeof obj === 'number') { return obj.toString() @@ -75,8 +75,8 @@ function introsort (array, compare) { * [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) */ return (function (array, comparator) { - var swap = function (index1, index2) { - var temp = array[index1] + const swap = function (index1, index2) { + const temp = array[index1] array[index1] = array[index2] array[index2] = temp } @@ -85,14 +85,14 @@ function introsort (array, compare) { * If the length of array is less than * this then we simply perform insertion sort */ - var THRESHOLD = 16 + const THRESHOLD = 16 /** * @constant TUNEMAXDEPTH - * Constant usec to increase or decrease value + * Constant used to increase or decrease value * of maxDepth */ - var TUNEMAXDEPTH = 1 - var len = array.length + const TUNEMAXDEPTH = 1 + const len = array.length /** * Return if array is only of length 1 * Array of size 1 is always sorted @@ -104,14 +104,14 @@ function introsort (array, compare) { * Calculate maxDepth = log2(len) * Taken from implementation in stdc++ */ - var maxDepth = Math.floor(Math.log2(len)) * TUNEMAXDEPTH + const maxDepth = Math.floor(Math.log2(len)) * TUNEMAXDEPTH /** * The very first call to quicksort * this initiates sort routine */ quickSort(0, len, maxDepth) /** - * A final checlk call to insertion sort + * A final check call to insertion sort * on sorted array */ insertionSort(0, len) @@ -125,7 +125,7 @@ function introsort (array, compare) { * @param {Number} last one more than the last index of array segment * @param {Number} depth this measures how many recursive calls are done */ - function quickSort (start, last, depth) { + function quickSort(start, last, depth) { if (last - start <= THRESHOLD) { insertionSort(start, last) return @@ -133,23 +133,23 @@ function introsort (array, compare) { heapSort(start, last) return } - var pivot = (last + start) >> 1 + let pivot = (last + start) >> 1 pivot = partition(start, last, pivot) quickSort(start, pivot, depth - 1) quickSort(pivot + 1, last, depth - 1) } /** * @function Helper function to quicksort - * @param {Number} start the start of array segment to partitiion + * @param {Number} start the start of array segment to partition * @param {Number} last one more than last index of the array segment * @param {Number} pivot the index of pivot to be used * @returns {Number} the index of pivot after partition */ - function partition (start, last, pivot) { + function partition(start, last, pivot) { swap(start, pivot) pivot = start - var lo = start - var hi = last + let lo = start + let hi = last while (true) { lo++ while (comparator(array[lo], array[pivot]) <= 0 && lo !== last) { @@ -174,8 +174,8 @@ function introsort (array, compare) { * @param {Number} start the first index of array segment to be sorted * @param {Number} last one more than last index of array to be sorted */ - function insertionSort (start, last) { - var i, j + function insertionSort(start, last) { + let i, j for (i = start + 1; i < last; i++) { j = i - 1 while (j >= 0 && comparator(array[j], array[j + 1]) > 0) { @@ -191,8 +191,8 @@ function introsort (array, compare) { * @param {Number} start the first index of array segment to be sorted * @param {Number} last one more than last index of array to be sorted */ - function heapSort (start, last) { - var x = (last + start) >> 1 + function heapSort(start, last) { + let x = (last + start) >> 1 while (x - start >= 0) { heapify(x, start, last) x-- @@ -210,9 +210,9 @@ function introsort (array, compare) { * @param {Number} start the start index of array segment that cur belongs to * @param {Number} last one more than last index of segment that cur belongs to */ - function heapify (cur, start, last) { - var size = last - start - var max, lt, rt + function heapify(cur, start, last) { + const size = last - start + let max, lt, rt cur = cur - start while (true) { max = cur @@ -244,15 +244,15 @@ function introsort (array, compare) { /** * @example Demo run of the sort routine * The data is randomly generated - * Prints RIGHT:) if the sort routine worked as expected - * If not prints WRONG!! + * Returns 'RIGHT:)' if the sort routine worked as expected, + * 'WRONG!!' otherwise */ -(function demo () { +function demo1() { const data = [] const size = 1000000 - var i = 0 - var temp - var c = function (a, b) { + let i = 0 + let temp + const c = function (a, b) { return a - b } for (i = 0; i < size; i++) { @@ -260,7 +260,7 @@ function introsort (array, compare) { data.push(temp) } introsort(data, c) - var faulty = false + let faulty = false for (i = 1; i < size; i++) { if (data[i] < data[i - 1]) { faulty = true @@ -268,23 +268,23 @@ function introsort (array, compare) { } } if (faulty) { - console.log('WRONG!!') + return 'WRONG!!' } else { - console.log('RIGHT:)') + return 'RIGHT:)' } -})(); +} /** * @example Demo run of the sort routine * using the default compare function and * comparing the results with Array.sort */ -(function demo () { +function demo2() { const data = [] const data2 = [] const size = 1000000 - var i = 0 - var temp + let i = 0 + let temp for (i = 0; i < size; i++) { temp = Math.random() * Number.MAX_SAFE_INTEGER data.push(temp) @@ -292,7 +292,7 @@ function introsort (array, compare) { } introsort(data) data2.sort() - var faulty = false + let faulty = false for (i = 1; i < size; i++) { if (data[i] !== data2[i]) { faulty = true @@ -300,8 +300,10 @@ function introsort (array, compare) { } } if (faulty) { - console.log('WRONG Implented Comparator!!') + return 'WRONG Implemented Comparator!!' } else { - console.log('Comparator Works Fine:)') + return 'Comparator Works Fine:)' } -})() +} + +export { introsort, demo1, demo2 } diff --git a/Sorts/MergeSort.js b/Sorts/MergeSort.js index ea1947d635..7cf693ce3a 100644 --- a/Sorts/MergeSort.js +++ b/Sorts/MergeSort.js @@ -1,48 +1,48 @@ -/** - * Merge Sort is an algorithm where the main list is divided down into two half - * sized lists, which then have merge sort called on these two smaller lists - * recursively until there is only a sorted list of one. +/* + * MergeSort implementation. + * + * Merge Sort is an algorithm where the main list is divided down into two half sized lists, which then have merge sort + * called on these two smaller lists recursively until there is only a sorted list of one. * * On the way up the recursive calls, the lists will be merged together inserting * the smaller value first, creating a larger sorted list. */ /** - * Sort and merge two given arrays - * @param {Array} list1 - sublist to break down - * @param {Array} list2 - sublist to break down - * @return {Array} merged list + * Sort and merge two given arrays. + * + * @param {Array} list1 Sublist to break down. + * @param {Array} list2 Sublist to break down. + * @return {Array} The merged list. */ -function merge (list1, list2) { - var results = [] +export function merge(list1, list2) { + const results = [] + let i = 0 + let j = 0 - while (list1.length && list2.length) { - if (list1[0] <= list2[0]) { - results.push(list1.shift()) + while (i < list1.length && j < list2.length) { + if (list1[i] < list2[j]) { + results.push(list1[i++]) } else { - results.push(list2.shift()) + results.push(list2[j++]) } } - return results.concat(list1, list2) + + return results.concat(list1.slice(i), list2.slice(j)) } /** - * Break down the lists into smaller pieces to be merged - * @param {Array} list - list to be sorted - * @return {Array} sorted list + * Break down the lists into smaller pieces to be merged. + * + * @param {Array} list List to be sorted. + * @return {Array} The sorted list. */ -function mergeSort (list) { +export function mergeSort(list) { if (list.length < 2) return list - var listHalf = Math.floor(list.length / 2) - var subList1 = list.slice(0, listHalf) - var subList2 = list.slice(listHalf, list.length) + const listHalf = Math.floor(list.length / 2) + const subList1 = list.slice(0, listHalf) + const subList2 = list.slice(listHalf, list.length) return merge(mergeSort(subList1), mergeSort(subList2)) } - -// Merge Sort Example -var unsortedArray = [10, 5, 3, 8, 2, 6, 4, 7, 9, 1] -var sortedArray = mergeSort(unsortedArray) - -console.log('Before:', unsortedArray, 'After:', sortedArray) diff --git a/Sorts/OddEvenSort.js b/Sorts/OddEvenSort.js new file mode 100644 index 0000000000..f8eba7c474 --- /dev/null +++ b/Sorts/OddEvenSort.js @@ -0,0 +1,34 @@ +/* + oddโ€“even sort or oddโ€“even transposition sort + is a relatively simple sorting algorithm, developed originally for use on parallel processors with local interconnections. + It is a comparison sort related to bubble sort, with which it shares many characteristics. + + for more information : https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort +*/ + +// Helper function to swap array items +function swap(arr, i, j) { + const tmp = arr[i] + arr[i] = arr[j] + arr[j] = tmp +} + +export function oddEvenSort(arr) { + let sorted = false + while (!sorted) { + sorted = true + for (let i = 1; i < arr.length - 1; i += 2) { + if (arr[i] > arr[i + 1]) { + swap(arr, i, i + 1) + sorted = false + } + } + for (let i = 0; i < arr.length - 1; i += 2) { + if (arr[i] > arr[i + 1]) { + swap(arr, i, i + 1) + sorted = false + } + } + } + return arr +} diff --git a/Sorts/PancakeSort.js b/Sorts/PancakeSort.js new file mode 100644 index 0000000000..3c2be53714 --- /dev/null +++ b/Sorts/PancakeSort.js @@ -0,0 +1,81 @@ +/* + * Unlike a traditional sorting algorithm, which attempts to sort with the fewest + * comparisons possible, the goal of pancake sort is to sort the sequence in as few reversals as + * possible. The idea is to do something similar to Selection Sort. We one by one place + * maximum element at the end and reduce the size of current array by one. + * + * Source: https://www.geeksforgeeks.org/pancake-sorting/ + * + * This sorting algorithm is inspired by the pancake problem (hence the name), + * where a spatula can be placed anywhere between two pancakes and flip all pancakes + * above. + * + * The interesting about this algorithm (besides its name) is that instead of comparisons, + * the algorithm relies on flipping an array. + * + * Source: https://en.wikipedia.org/wiki/Pancake_sorting#The_original_pancake_problem + * + */ + +/** + * Unlike Array.prototype.reverse, flipArray reverses only a subarray of the given + * array, determined by the parameters startIndex and endIndex + * + * @param {number[]} array The array to flip + * @param {number} startIndex The start of the subarray + * @param {number} endIndex The end of the subarray + * @returns The flipped array + */ +export function flipArray(array, startIndex, endIndex) { + while (startIndex < endIndex) { + // swap front and back of the subarray + const temp = array[startIndex] + array[startIndex] = array[endIndex] + array[endIndex] = temp + + // essentially reducing the problem to a smaller subarray + startIndex++ + endIndex-- + } + + return array +} + +/** + * Returns the index of the maximum number of a subarray in a given array + * + * @param {number[]} array The array to found the maximum number's index + * @param {*} startIndex The start of the subarray + * @param {*} endIndex The end of the subarray + * @returns The index of the maximum number + */ +export function findMax(array, startIndex, endIndex) { + let maxIndex = 0 + for (let i = startIndex; i <= endIndex; i++) { + if (array[i] > array[maxIndex]) maxIndex = i + } + + return maxIndex +} + +/** + * The Pancake Sort algorithm. + * + * Note that even though it's a completely different concept of sorting an + * array, it's rather simple! + * + * @param {number[]} array The array to sort + * @returns The sorted array + */ +export function pancakeSort(array) { + for (let subarraySize = array.length; subarraySize > 1; subarraySize--) { + const maximumIndex = findMax(array, 0, subarraySize - 1) + + if (maximumIndex !== subarraySize - 1) { + flipArray(array, 0, maximumIndex) + flipArray(array, 0, subarraySize - 1) + } + } + + return array +} diff --git a/Sorts/PigeonHoleSort.js b/Sorts/PigeonHoleSort.js new file mode 100644 index 0000000000..ca81c816a6 --- /dev/null +++ b/Sorts/PigeonHoleSort.js @@ -0,0 +1,37 @@ +/* +https://en.wikipedia.org/wiki/Pigeonhole_sort + +*Pigeonhole sorting is a sorting algorithm that is suitable +* for sorting lists of elements where the number of elements +* (n) and the length of the range of possible key values (N) +* are approximately the same. + */ +export function pigeonHoleSort(arr) { + let min = arr[0] + let max = arr[0] + + for (let i = 0; i < arr.length; i++) { + if (arr[i] > max) { + max = arr[i] + } + if (arr[i] < min) { + min = arr[i] + } + } + + const range = max - min + 1 + const pigeonhole = Array(range).fill(0) + + for (let i = 0; i < arr.length; i++) { + pigeonhole[arr[i] - min]++ + } + + let index = 0 + + for (let j = 0; j < range; j++) { + while (pigeonhole[j]-- > 0) { + arr[index++] = j + min + } + } + return arr +} diff --git a/Sorts/QuickSort.js b/Sorts/QuickSort.js index d134b89c6b..3885054e36 100644 --- a/Sorts/QuickSort.js +++ b/Sorts/QuickSort.js @@ -1,18 +1,21 @@ -/* -* Quick sort is a comparison sorting algorithm that uses a divide and conquer strategy. -* For more information see here: https://en.wikipedia.org/wiki/Quicksort -*/ -function quickSort (items) { - var length = items.length +/** + * @function QuickSort + * @description Quick sort is a comparison sorting algorithm that uses a divide and conquer strategy. + * @param {Integer[]} items - Array of integers + * @return {Integer[]} - Sorted array. + * @see [QuickSort](https://en.wikipedia.org/wiki/Quicksort) + */ +function quickSort(items) { + const length = items.length if (length <= 1) { return items } - var PIVOT = items[0] - var GREATER = [] - var LESSER = [] + const PIVOT = items[0] + const GREATER = [] + const LESSER = [] - for (var i = 1; i < length; i++) { + for (let i = 1; i < length; i++) { if (items[i] > PIVOT) { GREATER.push(items[i]) } else { @@ -20,18 +23,8 @@ function quickSort (items) { } } - var sorted = quickSort(LESSER) - sorted.push(PIVOT) - sorted = sorted.concat(quickSort(GREATER)) - + const sorted = [...quickSort(LESSER), PIVOT, ...quickSort(GREATER)] return sorted } -// Implementation of quick sort - -var ar = [0, 5, 3, 2, 2] -// Array before Sort -console.log(ar) -ar = quickSort(ar) -// Array after sort -console.log(ar) +export { quickSort } diff --git a/Sorts/QuickSortRecursive.js b/Sorts/QuickSortRecursive.js new file mode 100644 index 0000000000..e45dc34dac --- /dev/null +++ b/Sorts/QuickSortRecursive.js @@ -0,0 +1,65 @@ +/* + Quicksort is the most popular sorting algorithm and there have + lots of different implementations but the "recursive" or "Partition in place" + is one of the most efficient implementations below we have discussed how to + implement it. + + Partition in place => "in place" Partition in place indicates that we + do not need any other space to store the auxiliary array and the term + "partition" denotes that we split the list into two parts one is less + than the pivot and the other is greater than the pivot and repeats this + process recursively and breaks the problem into sub-problems and makes + it singular so that the behavior or "divide and conquer" get involved + too. + + Problem & Source of Explanation => https://www.cs.auckland.ac.nz/software/AlgAnim/qsort1a.html +*/ + +/** + * Partition in place QuickSort. + * @param {number[]} inputList list of values. + * @param {number} low lower index for partition. + * @param {number} high higher index for partition. + */ +const quickSort = (inputList, low, high) => { + if (!Array.isArray(inputList)) { + throw new TypeError('Please input a valid list or array.') + } + if (low < high) { + // get the partition index. + const pIndex = partition(inputList, low, high) + // recursively call the quickSort method again. + quickSort(inputList, low, pIndex - 1) + quickSort(inputList, pIndex + 1, high) + } + return inputList +} + +/** + * Partition In Place method. + * @param {number[]} partitionList list for partitioning. + * @param {number} low lower index for partition. + * @param {number} high higher index for partition. + * @returns {number} `pIndex` pivot index value. + */ +const partition = (partitionList, low, high) => { + const pivot = partitionList[high] + let pIndex = low + for (let index = low; index <= high - 1; index++) { + if (partitionList[index] < pivot) { + // swap variables using array destructuring + ;[partitionList[index], partitionList[pIndex]] = [ + partitionList[pIndex], + partitionList[index] + ] + pIndex += 1 + } + } + ;[partitionList[pIndex], partitionList[high]] = [ + partitionList[high], + partitionList[pIndex] + ] + return pIndex +} + +export { quickSort } diff --git a/Sorts/RadixSort.js b/Sorts/RadixSort.js index b6eb208e99..36f50a12ee 100644 --- a/Sorts/RadixSort.js +++ b/Sorts/RadixSort.js @@ -1,38 +1,38 @@ /* -* Radix sorts an integer array without comparing the integers. -* It groups the integers by their digits which share the same -* significant position. -* For more information see: https://en.wikipedia.org/wiki/Radix_sort -*/ -function radixSort (items, RADIX) { + * Radix sorts an integer array without comparing the integers. + * It groups the integers by their digits which share the same + * significant position. + * For more information see: https://en.wikipedia.org/wiki/Radix_sort + */ +export function radixSort(items, RADIX) { // default radix is then because we usually count to base 10 if (RADIX === undefined || RADIX < 1) { RADIX = 10 } - var maxLength = false - var placement = 1 + let maxLength = false + let placement = 1 while (!maxLength) { maxLength = true - var buckets = [] + const buckets = [] - for (var i = 0; i < RADIX; i++) { + for (let i = 0; i < RADIX; i++) { buckets.push([]) } - for (var j = 0; j < items.length; j++) { - var tmp = items[j] / placement + for (let j = 0; j < items.length; j++) { + const tmp = items[j] / placement buckets[Math.floor(tmp % RADIX)].push(items[j]) if (maxLength && tmp > 0) { maxLength = false } } - var a = 0 - for (var b = 0; b < RADIX; b++) { - var buck = buckets[b] - for (var k = 0; k < buck.length; k++) { + let a = 0 + for (let b = 0; b < RADIX; b++) { + const buck = buckets[b] + for (let k = 0; k < buck.length; k++) { items[a] = buck[k] a++ } @@ -41,12 +41,3 @@ function radixSort (items, RADIX) { } return items } - -// Implementation of radixSort - -var ar = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(ar) -radixSort(ar) -// Array after sort -console.log(ar) diff --git a/Sorts/SelectionSort.js b/Sorts/SelectionSort.js index 38a4bfff24..4e183be59a 100644 --- a/Sorts/SelectionSort.js +++ b/Sorts/SelectionSort.js @@ -8,29 +8,30 @@ *from the unsorted subarray is picked and moved to the sorted subarray. */ -function selectionSort (items) { - var length = items.length - for (var i = 0; i < length - 1; i++) { +export const selectionSort = (list) => { + if (!Array.isArray(list)) { + throw new TypeError('Given input is not an array') + } + const items = [...list] // We don't want to modify the original array + const length = items.length + for (let i = 0; i < length - 1; i++) { + if (typeof items[i] !== 'number') { + throw new TypeError('One of the items in your array is not a number') + } // Number of passes - var min = i // min holds the current minimum number position for each pass; i holds the Initial min number - for (var j = i + 1; j < length; j++) { // Note that j = i + 1 as we only need to go through unsorted array - if (items[j] < items[min]) { // Compare the numbers + let min = i // min holds the current minimum number position for each pass; i holds the Initial min number + for (let j = i + 1; j < length; j++) { + // Note that j = i + 1 as we only need to go through unsorted array + if (items[j] < items[min]) { + // Compare the numbers min = j // Change the current min number position if a smaller num is found } } if (min !== i) { // After each pass, if the current min num != initial min num, exchange the position. // Swap the numbers - [items[i], items[min]] = [items[min], [items[i]]] + ;[items[i], items[min]] = [items[min], items[i]] } } + return items } - -// Implementation of Selection Sort - -var ar = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(ar) -selectionSort(ar) -// Array after sort -console.log(ar) diff --git a/Sorts/ShellSort.js b/Sorts/ShellSort.js index 43f29b537a..34d45ed106 100644 --- a/Sorts/ShellSort.js +++ b/Sorts/ShellSort.js @@ -3,17 +3,17 @@ * more information: https://en.wikipedia.org/wiki/Shellsort * */ -function shellSort (items) { - var interval = 1 +export function shellSort(items) { + let interval = 1 while (interval < items.length / 3) { interval = interval * 3 + 1 } while (interval > 0) { - for (var outer = interval; outer < items.length; outer++) { - var value = items[outer] - var inner = outer + for (let outer = interval; outer < items.length; outer++) { + const value = items[outer] + let inner = outer while (inner > interval - 1 && items[inner - interval] >= value) { items[inner] = items[inner - interval] @@ -25,12 +25,3 @@ function shellSort (items) { } return items } - -// Implementation of shellSort - -var ar = [5, 6, 7, 8, 1, 2, 12, 14] -// Array before Sort -console.log(ar) -shellSort(ar) -// Array after sort -console.log(ar) diff --git a/Sorts/SimplifiedWiggleSort.js b/Sorts/SimplifiedWiggleSort.js new file mode 100644 index 0000000000..ae0ea54e20 --- /dev/null +++ b/Sorts/SimplifiedWiggleSort.js @@ -0,0 +1,36 @@ +/* + * Wiggle sort sorts the array into a wave like array. + * An array โ€˜arr[0..n-1]โ€™ is sorted in wave form if arr[0] <= arr[1] >= arr[2] <= arr[3] >= arr[4] <= โ€ฆ.. + * KEEP IN MIND: there are also more strict definitions of wiggle sort which use + * the rule arr[0] < arr[1] > arr[2] < arr[3] > arr[4] < โ€ฆ but this function + * allows for equality of values next to each other. + */ +import { quickSelectSearch } from '../Search/QuickSelectSearch.js' + +export const simplifiedWiggleSort = function (arr) { + // find Median using QuickSelect + let median = quickSelectSearch(arr, Math.floor(arr.length / 2.0)) + median = median[Math.floor(arr.length / 2.0)] + + const sorted = new Array(arr.length) + + let smallerThanMedianIndx = 0 + let greaterThanMedianIndx = arr.length - 1 - (arr.length % 2) + + for (let i = 0; i < arr.length; i++) { + if (arr[i] > median) { + sorted[greaterThanMedianIndx] = arr[i] + greaterThanMedianIndx -= 2 + } else { + if (smallerThanMedianIndx < arr.length) { + sorted[smallerThanMedianIndx] = arr[i] + smallerThanMedianIndx += 2 + } else { + sorted[greaterThanMedianIndx] = arr[i] + greaterThanMedianIndx -= 2 + } + } + } + + return sorted +} diff --git a/Sorts/StoogeSort.js b/Sorts/StoogeSort.js new file mode 100644 index 0000000000..ba13168bf0 --- /dev/null +++ b/Sorts/StoogeSort.js @@ -0,0 +1,21 @@ +/* + * Stooge Sort sorts an array based on divide and conquer principle + * note the exceptionally bad time complexity + * more information: https://en.wikipedia.org/wiki/Stooge_sort + * + */ +export function stoogeSort(items, leftEnd, rightEnd) { + if (items[rightEnd - 1] < items[leftEnd]) { + const temp = items[leftEnd] + items[leftEnd] = items[rightEnd - 1] + items[rightEnd - 1] = temp + } + const length = rightEnd - leftEnd + if (length > 2) { + const third = Math.floor(length / 3) + stoogeSort(items, leftEnd, rightEnd - third) + stoogeSort(items, leftEnd + third, rightEnd) + stoogeSort(items, leftEnd, rightEnd - third) + } + return items +} diff --git a/Sorts/SwapSort.js b/Sorts/SwapSort.js new file mode 100644 index 0000000000..1a2364e074 --- /dev/null +++ b/Sorts/SwapSort.js @@ -0,0 +1,31 @@ +/** + * @function SwapSort + * @description Swap Sort is an algorithm to find the number of swaps required to sort an array. + Time complexity of Swap Sort Algorithm is O(nlogn). + Auxiliary Space required for Swap Sort Algorithm is O(n). + * @param {Integer[]} items - Array of integers + * @return {Integer} - Number of swaps required to sort the array. + * @see [SwapSort](https://www.geeksforgeeks.org/minimum-number-swaps-required-sort-array/) + */ + +export function minSwapsToSort(items) { + const sortedArray = items.slice() + sortedArray.sort() + const indexMap = {} + for (let i = 0; i < items.length; i++) { + indexMap[items[i]] = i + } + let swaps = 0 + for (let i = 0; i < items.length; i++) { + if (items[i] !== sortedArray[i]) { + const temp = items[i] + items[i] = items[indexMap[sortedArray[i]]] + items[indexMap[sortedArray[i]]] = temp + + indexMap[temp] = indexMap[sortedArray[i]] + indexMap[sortedArray[i]] = i + swaps++ + } + } + return swaps +} diff --git a/Sorts/TimSort.js b/Sorts/TimSort.js new file mode 100644 index 0000000000..1a3cf10b8f --- /dev/null +++ b/Sorts/TimSort.js @@ -0,0 +1,115 @@ +/** + * @function Timsort is a hybrid stable sorting algorithm, derived from merge sort and insertion sort, + * designed to perform well on many kinds of real-world data. + * It was implemented by Tim Peters in 2002 for use in the Python programming language. + * It is also used to sort arrays of non-primitive type in Java SE 7, + * on the Android platform, in GNU Octave, on V8, Swift and Rust. + * 1) It sorts small partitions using Insertion Sort. + * 2) Merges the partition using Merge Sort. + * @see [Timsort](https://en.wikipedia.org/wiki/Timsort) + * @param {Array} array + */ + +const Timsort = (array) => { + // Default size of a partition + const RUN = 32 + const n = array.length + // Sorting the partitions using Insertion Sort + for (let i = 0; i < n; i += RUN) { + InsertionSort(array, i, Math.min(i + RUN - 1, n - 1)) + } + for (let size = RUN; size < n; size *= 2) { + for (let left = 0; left < n; left += 2 * size) { + const mid = left + size - 1 + const right = Math.min(left + 2 * size - 1, n - 1) + Merge(array, left, mid, right) + } + } + return array +} + +/** + * @function performs insertion sort on the partition + * @param {Array} array array to be sorted + * @param {Number} left left index of partition + * @param {Number} right right index of partition + */ + +const InsertionSort = (array, left, right) => { + for (let i = left + 1; i <= right; i++) { + const key = array[i] + let j = i - 1 + while (j >= left && array[j] > key) { + array[j + 1] = array[j] + j-- + } + array[j + 1] = key + } +} + +/** + * @function merges two sorted partitions + * @param {Array} array array to be sorted + * @param {Number} left left index of partition + * @param {Number} mid mid index of partition + * @param {Number} right right index of partition + */ + +const Merge = (array, left, mid, right) => { + if (mid >= right) return + const len1 = mid - left + 1 + const len2 = right - mid + const larr = Array(len1) + const rarr = Array(len2) + for (let i = 0; i < len1; i++) { + larr[i] = array[left + i] + } + for (let i = 0; i < len2; i++) { + rarr[i] = array[mid + 1 + i] + } + let i = 0 + let j = 0 + let k = left + while (i < larr.length && j < rarr.length) { + if (larr[i] < rarr[j]) { + array[k++] = larr[i++] + } else { + array[k++] = rarr[j++] + } + } + while (i < larr.length) { + array[k++] = larr[i++] + } + while (j < rarr.length) { + array[k++] = rarr[j++] + } +} + +/** + * @example Test of Timsort functions. + * Data is randomly generated. + * Return "RIGHT" if it works as expected, + * otherwise "FAULTY" + */ +const demo = () => { + const size = 1000000 + const data = Array(size) + for (let i = 0; i < size; i++) { + data[i] = Math.random() * Number.MAX_SAFE_INTEGER + } + const isSorted = function (array) { + const n = array.length + for (let i = 0; i < n - 1; i++) { + if (array[i] > array[i + 1]) return false + } + return true + } + Timsort(data) + if (isSorted(data)) { + return 'RIGHT' + } else { + return 'FAULTY' + } +} + +export { Timsort, demo } diff --git a/Sorts/TopologicalSort.js b/Sorts/TopologicalSort.js index e784ad0e6b..fe2bdb49fa 100644 --- a/Sorts/TopologicalSort.js +++ b/Sorts/TopologicalSort.js @@ -1,10 +1,9 @@ - -function TopologicalSorter () { - var graph = {} - var isVisitedNode - var finishTimeCount - var finishingTimeList - var nextNode +export function TopologicalSorter() { + const graph = {} + let isVisitedNode + let finishTimeCount + let finishingTimeList + let nextNode this.addOrder = function (nodeA, nodeB) { nodeA = String(nodeA) @@ -18,8 +17,11 @@ function TopologicalSorter () { finishTimeCount = 0 finishingTimeList = [] - for (var node in graph) { - if (Object.prototype.hasOwnProperty.call(graph, node) && !isVisitedNode[node]) { + for (const node in graph) { + if ( + Object.prototype.hasOwnProperty.call(graph, node) && + !isVisitedNode[node] + ) { dfsTraverse(node) } } @@ -28,13 +30,15 @@ function TopologicalSorter () { return item1.finishTime > item2.finishTime ? -1 : 1 }) - return finishingTimeList.map(function (value) { return value.node }) + return finishingTimeList.map(function (value) { + return value.node + }) } - function dfsTraverse (node) { + function dfsTraverse(node) { isVisitedNode[node] = true if (graph[node]) { - for (var i = 0; i < graph[node].length; i++) { + for (let i = 0; i < graph[node].length; i++) { nextNode = graph[node][i] if (isVisitedNode[nextNode]) continue dfsTraverse(nextNode) @@ -42,18 +46,18 @@ function TopologicalSorter () { } finishingTimeList.push({ - node: node, + node, finishTime: ++finishTimeCount }) } } /* TEST */ -var topoSorter = new TopologicalSorter() -topoSorter.addOrder(5, 2) -topoSorter.addOrder(5, 0) -topoSorter.addOrder(4, 0) -topoSorter.addOrder(4, 1) -topoSorter.addOrder(2, 3) -topoSorter.addOrder(3, 1) -console.log(topoSorter.sortAndGetOrderedItems()) +// const topoSorter = new TopologicalSorter() +// topoSorter.addOrder(5, 2) +// topoSorter.addOrder(5, 0) +// topoSorter.addOrder(4, 0) +// topoSorter.addOrder(4, 1) +// topoSorter.addOrder(2, 3) +// topoSorter.addOrder(3, 1) +// topoSorter.sortAndGetOrderedItems() diff --git a/Sorts/WiggleSort.js b/Sorts/WiggleSort.js deleted file mode 100644 index fdb2a70db1..0000000000 --- a/Sorts/WiggleSort.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Wiggle sort sorts the array into a wave like array. - * An array โ€˜arr[0..n-1]โ€™ is sorted in wave form if arr[0] >= arr[1] <= arr[2] >= arr[3] <= arr[4] >= โ€ฆ.. - * - */ - -/* eslint no-extend-native: ["off", { "exceptions": ["Object"] }] */ -Array.prototype.wiggleSort = function () { - for (let i = 0; i < this.length; ++i) { - const shouldNotBeLessThan = i % 2 - const isLessThan = this[i] < this[i + 1] - if (shouldNotBeLessThan && isLessThan) { - [this[i], this[i + 1]] = [this[i + 1], this[i]] - } - } - return this -} - -// Implementation of wiggle sort - -var arr = [3, 5, 2, 1, 6, 4] -// Array before Wiggle Sort -console.log(arr) // [3, 5, 2, 1, 6, 4] - -arr.wiggleSort() -// Array after wiggle sort -console.log(arr) // [ 3, 5, 2, 6, 1, 4 ] diff --git a/Sorts/test/AlphaNumericalSort.test.js b/Sorts/test/AlphaNumericalSort.test.js new file mode 100644 index 0000000000..3213dafb72 --- /dev/null +++ b/Sorts/test/AlphaNumericalSort.test.js @@ -0,0 +1,47 @@ +import { alphaNumericalSort } from '../AlphaNumericalSort' + +describe('alphaNumericalComparer', () => { + test('given array of eng symbols return correct sorted array', () => { + const src = ['b', 'a', 'c'] + src.sort(alphaNumericalSort) + expect(src).toEqual(['a', 'b', 'c']) + }) + + test('given array of numbers return correct sorted array', () => { + const src = ['15', '0', '5'] + src.sort(alphaNumericalSort) + expect(src).toEqual(['0', '5', '15']) + }) + + test('correct sort with numbers and strings', () => { + const src = ['3', 'a1b15c', 'z', 'a1b14c'] + src.sort(alphaNumericalSort) + expect(src).toEqual(['3', 'a1b14c', 'a1b15c', 'z']) + }) + + test('correct sort with long numbers', () => { + const src = [ + 'abc999999999999999999999999999999999cba', + 'abc999999999999999999999999999999990cba', + 'ab' + ] + src.sort(alphaNumericalSort) + expect(src).toEqual([ + 'ab', + 'abc999999999999999999999999999999990cba', + 'abc999999999999999999999999999999999cba' + ]) + }) + + test('correct sort with z prefix', () => { + const src = ['z', 'abc003def', 'abc1def', 'a'] + src.sort(alphaNumericalSort) + expect(src).toEqual(['a', 'abc1def', 'abc003def', 'z']) + }) + + test('correct sort with other language', () => { + const src = ['ะฐ10ะฑ', 'ะฐ2ะฑ', 'ะฒ10ะณ', 'ะฒ05ะณ'] + src.sort(alphaNumericalSort) + expect(src).toEqual(['ะฐ2ะฑ', 'ะฐ10ะฑ', 'ะฒ05ะณ', 'ะฒ10ะณ']) + }) +}) diff --git a/Sorts/test/BeadSort.test.js b/Sorts/test/BeadSort.test.js new file mode 100644 index 0000000000..6e3d6bf07c --- /dev/null +++ b/Sorts/test/BeadSort.test.js @@ -0,0 +1,12 @@ +import { beadSort } from '../BeadSort' + +describe('BeadSort', () => { + it('should sort arrays correctly', () => { + expect(beadSort([5, 4, 3, 2, 1])).toEqual([1, 2, 3, 4, 5]) + expect(beadSort([7, 9, 4, 3, 5])).toEqual([3, 4, 5, 7, 9]) + }) + + it('should throw a RangeError when the array contains negative integers', () => { + expect(() => beadSort([-1, 5, 8, 4, 3, 19])).toThrow(RangeError) + }) +}) diff --git a/Sorts/test/BinaryInsertionSort.test.js b/Sorts/test/BinaryInsertionSort.test.js new file mode 100644 index 0000000000..84abe6a7ce --- /dev/null +++ b/Sorts/test/BinaryInsertionSort.test.js @@ -0,0 +1,8 @@ +import { binaryInsertionSort } from '../BinaryInsertionSort' + +describe('BinaryInsertionSort', () => { + it('should sort arrays correctly', () => { + expect(binaryInsertionSort([5, 4, 3, 2, 1])).toEqual([1, 2, 3, 4, 5]) + expect(binaryInsertionSort([7, 9, 4, 3, 5])).toEqual([3, 4, 5, 7, 9]) + }) +}) diff --git a/Sorts/test/BogoSort.test.js b/Sorts/test/BogoSort.test.js new file mode 100644 index 0000000000..22a692336e --- /dev/null +++ b/Sorts/test/BogoSort.test.js @@ -0,0 +1,27 @@ +import { bogoSort, isSorted } from '../BogoSort' + +describe('isSorted', () => { + it('should return true for empty arrays', () => { + expect(isSorted([])).toBe(true) + }) + + it('should return true for single-element arrays', () => { + expect(isSorted([1])).toBe(true) + }) + + it('should return true for arrays that are properly sorted', () => { + expect(isSorted([1, 2, 3])).toBe(true) + }) + + it('should return false for arrays that are not properly sorted', () => { + expect(isSorted([3, 2, 1])).toBe(false) + }) +}) + +describe('bogoSort', () => { + it('should (eventually) sort the array', () => { + expect(bogoSort([5, 6, 7, 8, 1, 2, 12, 14])).toEqual([ + 1, 2, 5, 6, 7, 8, 12, 14 + ]) + }) +}) diff --git a/Sorts/test/BubbleSort.test.js b/Sorts/test/BubbleSort.test.js new file mode 100644 index 0000000000..8b70738b74 --- /dev/null +++ b/Sorts/test/BubbleSort.test.js @@ -0,0 +1,31 @@ +import { alternativeBubbleSort, bubbleSort } from '../BubbleSort' + +describe('bubbleSort', () => { + it('should sort arrays correctly', () => { + expect(bubbleSort([5, 4, 1, 2, 3])).toEqual([1, 2, 3, 4, 5]) + expect(bubbleSort([])).toEqual([]) + expect(bubbleSort([1, 2, 3])).toEqual([1, 2, 3]) + expect(bubbleSort([5, 6, 7, 8, 1, 2, 12, 14])).toEqual([ + 1, 2, 5, 6, 7, 8, 12, 14 + ]) + expect(bubbleSort([5, 6, 7, 8, 9, 4])).toEqual([4, 5, 6, 7, 8, 9]) + expect(bubbleSort([20, 30, 40])).toEqual([20, 30, 40]) + expect(bubbleSort([2, 1, 3])).toEqual([1, 2, 3]) + expect(bubbleSort([10, 15, 16, 100])).toEqual([10, 15, 16, 100]) + expect(bubbleSort([10, 9, 11])).toEqual([9, 10, 11]) + expect(bubbleSort([10, 9, 12])).toEqual([9, 10, 12]) + expect(bubbleSort([3, 2, 1])).toEqual([1, 2, 3]) + expect(bubbleSort([10, 9, 8])).toEqual([8, 9, 10]) + }) +}) + +describe('alternativeBubbleSort', () => { + it('should sort arrays correctly', () => { + expect(alternativeBubbleSort([5, 4, 1, 2, 3])).toEqual([1, 2, 3, 4, 5]) + expect(alternativeBubbleSort([])).toEqual([]) + expect(alternativeBubbleSort([1, 2, 3])).toEqual([1, 2, 3]) + expect(alternativeBubbleSort([5, 6, 7, 8, 1, 2, 12, 14])).toEqual([ + 1, 2, 5, 6, 7, 8, 12, 14 + ]) + }) +}) diff --git a/Sorts/test/BucketSort.test.js b/Sorts/test/BucketSort.test.js new file mode 100644 index 0000000000..08c0233c54 --- /dev/null +++ b/Sorts/test/BucketSort.test.js @@ -0,0 +1,81 @@ +import { bucketSort } from '../BucketSort' + +describe('Tests for bucketSort function', () => { + it('should correctly sort an input list that is sorted backwards', () => { + const array = [5, 4, 3, 2, 1] + expect(bucketSort(array)).toEqual([1, 2, 3, 4, 5]) + }) + + it('should correctly sort an input list that is unsorted', () => { + const array = [15, 24, 3, 2224, 1] + expect(bucketSort(array)).toEqual([1, 3, 15, 24, 2224]) + }) + + describe('Variations of input array lengths', () => { + it('should return an empty list with the input list is an empty list', () => { + expect(bucketSort([])).toEqual([]) + }) + + it('should correctly sort an input list of length 1', () => { + expect(bucketSort([100])).toEqual([100]) + }) + + it('should correctly sort an input list of an odd length', () => { + expect(bucketSort([101, -10, 321])).toEqual([-10, 101, 321]) + }) + + it('should correctly sort an input list of an even length', () => { + expect(bucketSort([40, 42, 56, 45, 12, 3])).toEqual([ + 3, 12, 40, 42, 45, 56 + ]) + }) + }) + + describe('Variations of input array elements', () => { + it('should correctly sort an input list that contains only positive numbers', () => { + expect(bucketSort([50, 33, 11, 2])).toEqual([2, 11, 33, 50]) + }) + + it('should correctly sort an input list that contains only negative numbers', () => { + expect(bucketSort([-1, -21, -2, -35])).toEqual([-35, -21, -2, -1]) + }) + + it('should correctly sort an input list that contains only a mix of positive and negative numbers', () => { + expect(bucketSort([-40, 42, 56, -45, 12, -3])).toEqual([ + -45, -40, -3, 12, 42, 56 + ]) + }) + + it('should correctly sort an input list that contains only whole numbers', () => { + expect(bucketSort([11, 3, 12, 4, -15])).toEqual([-15, 3, 4, 11, 12]) + }) + + it('should correctly sort an input list that contains only decimal numbers', () => { + expect(bucketSort([1.0, 1.42, 2.56, 33.45, 13.12, 2.3])).toEqual([ + 1.0, 1.42, 2.3, 2.56, 13.12, 33.45 + ]) + }) + + it('should correctly sort an input list that contains only a mix of whole and decimal', () => { + expect(bucketSort([32.4, 12.42, 56, 45, 12, 3])).toEqual([ + 3, 12, 12.42, 32.4, 45, 56 + ]) + }) + + it('should correctly sort an input list that contains only fractional numbers', () => { + expect(bucketSort([0.98, 0.4259, 0.56, -0.456, -0.12, 0.322])).toEqual([ + -0.456, -0.12, 0.322, 0.4259, 0.56, 0.98 + ]) + }) + + it('should correctly sort an input list that contains only a mix of whole, decimal, and fractional', () => { + expect(bucketSort([-40, -0.222, 5.6, -4.5, 12, 0.333])).toEqual([ + -40, -4.5, -0.222, 0.333, 5.6, 12 + ]) + }) + + it('should correctly sort an input list that contains duplicates', () => { + expect(bucketSort([4, 3, 4, 2, 1, 2])).toEqual([1, 2, 2, 3, 4, 4]) + }) + }) +}) diff --git a/Sorts/test/CocktailShakerSort.test.js b/Sorts/test/CocktailShakerSort.test.js new file mode 100644 index 0000000000..2d512ae8ab --- /dev/null +++ b/Sorts/test/CocktailShakerSort.test.js @@ -0,0 +1,15 @@ +import { cocktailShakerSort } from '../CocktailShakerSort' + +describe('CocktailShakerSort', () => { + it('should sort arrays correctly', () => { + expect(cocktailShakerSort([5, 4, 1, 2, 3])).toEqual([1, 2, 3, 4, 5]) + expect(cocktailShakerSort([1, 2, 3])).toEqual([1, 2, 3]) + expect(cocktailShakerSort([5, 6, 7, 8, 1, 2, 12, 14])).toEqual([ + 1, 2, 5, 6, 7, 8, 12, 14 + ]) + }) + + it('should work for empty arrays, too', () => { + expect(cocktailShakerSort([])).toEqual([]) + }) +}) diff --git a/Sorts/test/CombSort.test.js b/Sorts/test/CombSort.test.js new file mode 100644 index 0000000000..f9271a597e --- /dev/null +++ b/Sorts/test/CombSort.test.js @@ -0,0 +1,79 @@ +import { combSort } from '../CombSort' + +describe('combSort function', () => { + it('should correctly sort an input list that is sorted backwards', () => { + const array = [5, 4, 3, 2, 1] + expect(combSort(array)).toEqual([1, 2, 3, 4, 5]) + }) + + it('should correctly sort an input list that is unsorted', () => { + const array = [15, 24, 3, 2224, 1] + expect(combSort(array)).toEqual([1, 3, 15, 24, 2224]) + }) + + describe('Variations of input array lengths', () => { + it('should return an empty list with the input list is an empty list', () => { + expect(combSort([])).toEqual([]) + }) + + it('should correctly sort an input list of length 1', () => { + expect(combSort([100])).toEqual([100]) + }) + + it('should correctly sort an input list of an odd length', () => { + expect(combSort([101, -10, 321])).toEqual([-10, 101, 321]) + }) + + it('should correctly sort an input list of an even length', () => { + expect(combSort([40, 42, 56, 45, 12, 3])).toEqual([3, 12, 40, 42, 45, 56]) + }) + }) + + describe('Variations of input array elements', () => { + it('should correctly sort an input list that contains only positive numbers', () => { + expect(combSort([50, 33, 11, 2])).toEqual([2, 11, 33, 50]) + }) + + it('should correctly sort an input list that contains only negative numbers', () => { + expect(combSort([-1, -21, -2, -35])).toEqual([-35, -21, -2, -1]) + }) + + it('should correctly sort an input list that contains only a mix of positive and negative numbers', () => { + expect(combSort([-40, 42, 56, -45, 12, -3])).toEqual([ + -45, -40, -3, 12, 42, 56 + ]) + }) + + it('should correctly sort an input list that contains only whole numbers', () => { + expect(combSort([11, 3, 12, 4, -15])).toEqual([-15, 3, 4, 11, 12]) + }) + + it('should correctly sort an input list that contains only decimal numbers', () => { + expect(combSort([1.0, 1.42, 2.56, 33.45, 13.12, 2.3])).toEqual([ + 1.0, 1.42, 2.3, 2.56, 13.12, 33.45 + ]) + }) + + it('should correctly sort an input list that contains only a mix of whole and decimal', () => { + expect(combSort([32.4, 12.42, 56, 45, 12, 3])).toEqual([ + 3, 12, 12.42, 32.4, 45, 56 + ]) + }) + + it('should correctly sort an input list that contains only fractional numbers', () => { + expect(combSort([0.98, 0.4259, 0.56, -0.456, -0.12, 0.322])).toEqual([ + -0.456, -0.12, 0.322, 0.4259, 0.56, 0.98 + ]) + }) + + it('should correctly sort an input list that contains only a mix of whole, decimal, and fractional', () => { + expect(combSort([-40, -0.222, 5.6, -4.5, 12, 0.333])).toEqual([ + -40, -4.5, -0.222, 0.333, 5.6, 12 + ]) + }) + + it('should correctly sort an input list that contains duplicates', () => { + expect(combSort([4, 3, 4, 2, 1, 2])).toEqual([1, 2, 2, 3, 4, 4]) + }) + }) +}) diff --git a/Sorts/test/CountingSort.test.js b/Sorts/test/CountingSort.test.js new file mode 100644 index 0000000000..7d5d185dff --- /dev/null +++ b/Sorts/test/CountingSort.test.js @@ -0,0 +1,25 @@ +import { countingSort } from '../CountingSort' + +test('The countingSort of the array [3, 0, 2, 5, 4, 1] is [0, 1, 2, 3, 4, 5]', () => { + const array = [3, 0, 2, 5, 4, 1] + const res = countingSort(array, 0, 5) + expect(res).toEqual([0, 1, 2, 3, 4, 5]) +}) + +test('The countingSort of the array [6, 4, 2, 1, 3, 5] is [1, 2, 3, 4, 5, 6]', () => { + const array = [6, 4, 2, 1, 3, 5] + const res = countingSort(array, 1, 6) + expect(res).toEqual([1, 2, 3, 4, 5, 6]) +}) + +test('The countingSort of the array [11, 14, 12, 15, 16, 13] is [11, 12, 13, 14, 15, 16]', () => { + const array = [11, 14, 12, 15, 16, 13] + const res = countingSort(array, 11, 16) + expect(res).toEqual([11, 12, 13, 14, 15, 16]) +}) + +test('The countingSort of the array [13, 18, 2, 15, 43, 11] is [2, 11, 13, 15, 18, 43]', () => { + const array = [13, 18, 2, 15, 43, 11] + const res = countingSort(array, 2, 43) + expect(res).toEqual([2, 11, 13, 15, 18, 43]) +}) diff --git a/Sorts/test/CycleSort.test.js b/Sorts/test/CycleSort.test.js new file mode 100644 index 0000000000..158e0b4797 --- /dev/null +++ b/Sorts/test/CycleSort.test.js @@ -0,0 +1,81 @@ +import { cycleSort } from '../CycleSort' + +describe('cycleSort function', () => { + it('should correctly sort an input list that is sorted backwards', () => { + const array = [5, 4, 3, 2, 1] + expect(cycleSort(array)).toEqual([1, 2, 3, 4, 5]) + }) + + it('should correctly sort an input list that is unsorted', () => { + const array = [15, 24, 3, 2224, 1] + expect(cycleSort(array)).toEqual([1, 3, 15, 24, 2224]) + }) + + describe('Variations of input array lengths', () => { + it('should return an empty list with the input list is an empty list', () => { + expect(cycleSort([])).toEqual([]) + }) + + it('should correctly sort an input list of length 1', () => { + expect(cycleSort([100])).toEqual([100]) + }) + + it('should correctly sort an input list of an odd length', () => { + expect(cycleSort([101, -10, 321])).toEqual([-10, 101, 321]) + }) + + it('should correctly sort an input list of an even length', () => { + expect(cycleSort([40, 42, 56, 45, 12, 3])).toEqual([ + 3, 12, 40, 42, 45, 56 + ]) + }) + }) + + describe('Variations of input array elements', () => { + it('should correctly sort an input list that contains only positive numbers', () => { + expect(cycleSort([50, 33, 11, 2])).toEqual([2, 11, 33, 50]) + }) + + it('should correctly sort an input list that contains only negative numbers', () => { + expect(cycleSort([-1, -21, -2, -35])).toEqual([-35, -21, -2, -1]) + }) + + it('should correctly sort an input list that contains only a mix of positive and negative numbers', () => { + expect(cycleSort([-40, 42, 56, -45, 12, -3])).toEqual([ + -45, -40, -3, 12, 42, 56 + ]) + }) + + it('should correctly sort an input list that contains only whole numbers', () => { + expect(cycleSort([11, 3, 12, 4, -15])).toEqual([-15, 3, 4, 11, 12]) + }) + + it('should correctly sort an input list that contains only decimal numbers', () => { + expect(cycleSort([1.0, 1.42, 2.56, 33.45, 13.12, 2.3])).toEqual([ + 1.0, 1.42, 2.3, 2.56, 13.12, 33.45 + ]) + }) + + it('should correctly sort an input list that contains only a mix of whole and decimal', () => { + expect(cycleSort([32.4, 12.42, 56, 45, 12, 3])).toEqual([ + 3, 12, 12.42, 32.4, 45, 56 + ]) + }) + + it('should correctly sort an input list that contains only fractional numbers', () => { + expect(cycleSort([0.98, 0.4259, 0.56, -0.456, -0.12, 0.322])).toEqual([ + -0.456, -0.12, 0.322, 0.4259, 0.56, 0.98 + ]) + }) + + it('should correctly sort an input list that contains only a mix of whole, decimal, and fractional', () => { + expect(cycleSort([-40, -0.222, 5.6, -4.5, 12, 0.333])).toEqual([ + -40, -4.5, -0.222, 0.333, 5.6, 12 + ]) + }) + + it('should correctly sort an input list that contains duplicates', () => { + expect(cycleSort([4, 3, 4, 2, 1, 2])).toEqual([1, 2, 2, 3, 4, 4]) + }) + }) +}) diff --git a/Sorts/test/DutchNationalFlagSort.test.js b/Sorts/test/DutchNationalFlagSort.test.js new file mode 100644 index 0000000000..33cde385f8 --- /dev/null +++ b/Sorts/test/DutchNationalFlagSort.test.js @@ -0,0 +1,11 @@ +import { dutchNationalFlagSort } from '../DutchNationalFlagSort' + +describe('DutchNationalFlagSort', () => { + it('should sort arrays correctly', () => { + expect(dutchNationalFlagSort([2, 0, 2, 1, 1, 0])).toEqual([ + 0, 0, 1, 1, 2, 2 + ]) + expect(dutchNationalFlagSort([2, 1, 0])).toEqual([0, 1, 2]) + expect(dutchNationalFlagSort([1, 0, 0, 0, 1])).toEqual([0, 0, 0, 1, 1]) + }) +}) diff --git a/Sorts/test/FindSecondLargestElement.test.js b/Sorts/test/FindSecondLargestElement.test.js new file mode 100644 index 0000000000..98c8c301b9 --- /dev/null +++ b/Sorts/test/FindSecondLargestElement.test.js @@ -0,0 +1,13 @@ +import { secondLargestElement } from '../FindSecondLargestElement' + +test('The second largest element of the array [1, 2, 3, 4, 5] is 4', () => { + const array = [1, 2, 3, 4, 5] + const res = secondLargestElement(array) + expect(res).toEqual(4) +}) + +test('The second largest element of the array [-1, -2, -3, -4, -5] is -2', () => { + const array = [-1, -2, -3, -4, -5] + const res = secondLargestElement(array) + expect(res).toEqual(-2) +}) diff --git a/Sorts/test/FisherYatesShuffle.test.js b/Sorts/test/FisherYatesShuffle.test.js new file mode 100644 index 0000000000..0971501a91 --- /dev/null +++ b/Sorts/test/FisherYatesShuffle.test.js @@ -0,0 +1,27 @@ +import { shuffle } from '../FisherYatesShuffle' + +describe('shuffle', () => { + it('expects to have a new array with same size', () => { + const fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] + const mixedArray = shuffle(fibonacci) + + expect(mixedArray).toHaveLength(fibonacci.length) + }) + + it('expects to have a new array with same values', () => { + const fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] + const mixedArray = shuffle(fibonacci) + + expect(mixedArray).toContain(0) + expect(mixedArray).toContain(1) + expect(mixedArray).toContain(2) + expect(mixedArray).toContain(3) + expect(mixedArray).toContain(5) + expect(mixedArray).toContain(8) + expect(mixedArray).toContain(13) + expect(mixedArray).toContain(21) + expect(mixedArray).toContain(34) + expect(mixedArray).toContain(55) + expect(mixedArray).toContain(89) + }) +}) diff --git a/Sorts/test/FlashSort.test.js b/Sorts/test/FlashSort.test.js new file mode 100644 index 0000000000..65074de1a6 --- /dev/null +++ b/Sorts/test/FlashSort.test.js @@ -0,0 +1,25 @@ +import { flashSort } from '../FlashSort' + +test('The flash sort of the array [3, 0, 2, 5, -1, 4, 1, -2] is [-2, -1, 0, 1, 2, 3, 4, 5]', () => { + const array = [3, 0, 2, 5, -1, 4, 1, -2] + const res = flashSort(array) + expect(res).toEqual([-2, -1, 0, 1, 2, 3, 4, 5]) +}) + +test('The flash sort of the array [-3, 0, 2, -5, -1, 4, 1, -2] is [-5, -3, -2, -1, 0, 1, 2, 4]', () => { + const array = [-3, 0, 2, -5, -1, 4, 1, -2] + const res = flashSort(array) + expect(res).toEqual([-5, -3, -2, -1, 0, 1, 2, 4]) +}) + +test('The flash sort of the array [13, 0, 12, 5, -1, 14, 1, -2] is [-2, -1, 0, 1, 5, 12, 13, 14]', () => { + const array = [13, 0, 12, 5, -1, 14, 1, -2] + const res = flashSort(array) + expect(res).toEqual([-2, -1, 0, 1, 5, 12, 13, 14]) +}) + +test('The flash sort of the array [-3, 0, -2, -5, -1, -4, -1, -2] is [-5, -4, -3, -2, -2, -1, -1, 0]', () => { + const array = [-3, 0, -2, -5, -1, -4, -1, -2] + const res = flashSort(array) + expect(res).toEqual([-5, -4, -3, -2, -2, -1, -1, 0]) +}) diff --git a/Sorts/test/GnomeSort.test.js b/Sorts/test/GnomeSort.test.js new file mode 100644 index 0000000000..7799ba6023 --- /dev/null +++ b/Sorts/test/GnomeSort.test.js @@ -0,0 +1,19 @@ +import { gnomeSort } from '../GnomeSort' + +test('The gnomeSort of the array [5, 4, 3, 2, 1] is [1, 2, 3, 4, 5]', () => { + const arr = [5, 4, 3, 2, 1] + const res = gnomeSort(arr) + expect(res).toEqual([1, 2, 3, 4, 5]) +}) + +test('The gnomeSort of the array [-5, 4, -3, 2, -1] is [-5, -3, -1, 2, 4]', () => { + const arr = [-5, 4, -3, 2, -1] + const res = gnomeSort(arr) + expect(res).toEqual([-5, -3, -1, 2, 4]) +}) + +test('The gnomeSort of the array [15, 4, -13, 2, -11] is [-13, -11, 2, 4, 15]', () => { + const arr = [15, 4, -13, 2, -11] + const res = gnomeSort(arr) + expect(res).toEqual([-13, -11, 2, 4, 15]) +}) diff --git a/Sorts/test/HeapSort.test.js b/Sorts/test/HeapSort.test.js new file mode 100644 index 0000000000..5ee2381e89 --- /dev/null +++ b/Sorts/test/HeapSort.test.js @@ -0,0 +1,25 @@ +import { heapSort } from '../HeapSort' + +test('The HeapSort of the array [5, 4, 3, 2, 1] is [1, 2, 3, 4, 5]', () => { + const array = [5, 4, 3, 2, 1] + const res = heapSort(array) + expect(res).toEqual([1, 2, 3, 4, 5]) +}) + +test('The HeapSort of the array [-5, -4, -3, -2, -1] is [-5, -4, -3, -2, -1]', () => { + const array = [-5, -4, -3, -2, -1] + const res = heapSort(array) + expect(res).toEqual([-5, -4, -3, -2, -1]) +}) + +test('The HeapSort of the array [50, 43, 31, 52, 91] is [31, 43, 50, 52, 91]', () => { + const array = [50, 43, 31, 52, 91] + const res = heapSort(array) + expect(res).toEqual([31, 43, 50, 52, 91]) +}) + +test('The HeapSort of the array [] is []', () => { + const array = [] + const res = heapSort(array) + expect(res).toEqual([]) +}) diff --git a/Sorts/test/HeapSortV2.test.js b/Sorts/test/HeapSortV2.test.js new file mode 100644 index 0000000000..a74ee4f463 --- /dev/null +++ b/Sorts/test/HeapSortV2.test.js @@ -0,0 +1,19 @@ +import { heapSort } from '../HeapSortV2' + +test('The heapSort of the array [4, 3, 2, 1] is [1, 2, 3, 4]', () => { + const arr = [4, 3, 2, 1] + const res = heapSort(arr) + expect(res).toEqual([1, 2, 3, 4]) +}) + +test('The heapSort of the array [] is []', () => { + const arr = [] + const res = heapSort(arr) + expect(res).toEqual([]) +}) + +test('The heapSort of the array [41, 31, 32, 31] is [31, 31, 32, 41]', () => { + const arr = [41, 31, 32, 31] + const res = heapSort(arr) + expect(res).toEqual([31, 31, 32, 41]) +}) diff --git a/Sorts/test/InsertionSort.test.js b/Sorts/test/InsertionSort.test.js new file mode 100644 index 0000000000..481f152a1d --- /dev/null +++ b/Sorts/test/InsertionSort.test.js @@ -0,0 +1,25 @@ +import { insertionSortAlternativeImplementation } from '../InsertionSort' + +describe('insertionSortAlternativeImplementation', () => { + it('expects to work with empty array', () => { + expect(insertionSortAlternativeImplementation([])).toEqual([]) + }) + + it('expects to return input array when array.length is less than 2', () => { + const input = [3] + expect(insertionSortAlternativeImplementation(input)).toEqual(input) + }) + + it('expects to return array sorted in ascending order', () => { + expect(insertionSortAlternativeImplementation([14, 11])).toEqual([11, 14]) + expect(insertionSortAlternativeImplementation([21, 22, 23])).toEqual([ + 21, 22, 23 + ]) + expect(insertionSortAlternativeImplementation([1, 3, 2, 3, 7, 2])).toEqual([ + 1, 2, 2, 3, 3, 7 + ]) + expect(insertionSortAlternativeImplementation([1, 6, 4, 5, 9, 2])).toEqual([ + 1, 2, 4, 5, 6, 9 + ]) + }) +}) diff --git a/Sorts/test/MergeSort.test.js b/Sorts/test/MergeSort.test.js new file mode 100644 index 0000000000..c4e4f398d6 --- /dev/null +++ b/Sorts/test/MergeSort.test.js @@ -0,0 +1,25 @@ +import { merge, mergeSort } from '../MergeSort' + +describe('merge', () => { + it('should merge arrays correctly', () => { + expect(merge([5, 4], [1, 2, 3])).toEqual([1, 2, 3, 5, 4]) + expect(merge([], [1, 2])).toEqual([1, 2]) + expect(merge([1, 2, 3], [1])).toEqual([1, 1, 2, 3]) + expect(merge([], [])).toEqual([]) + }) +}) + +describe('MergeSort', () => { + it('should work for empty arrays', () => { + expect(mergeSort([])).toEqual([]) + }) + + it('should sort arrays correctly', () => { + expect(mergeSort([5, 4])).toEqual([4, 5]) + expect(mergeSort([8, 4, 10, 15, 9])).toEqual([4, 8, 9, 10, 15]) + expect(mergeSort([1, 2, 3])).toEqual([1, 2, 3]) + expect(mergeSort([10, 5, 3, 8, 2, 6, 4, 7, 9, 1])).toEqual([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + ]) + }) +}) diff --git a/Sorts/test/OddEvenSort.test.js b/Sorts/test/OddEvenSort.test.js new file mode 100644 index 0000000000..6d75851e5e --- /dev/null +++ b/Sorts/test/OddEvenSort.test.js @@ -0,0 +1,25 @@ +import { oddEvenSort } from '../OddEvenSort' + +test('The OddEvenSort of the array [5, 4, 3, 2, 1] is [1, 2, 3, 4, 5]', () => { + const arr = [5, 4, 3, 2, 1] + const res = oddEvenSort(arr) + expect(res).toEqual([1, 2, 3, 4, 5]) +}) + +test('The OddEvenSort of the array [] is []', () => { + const arr = [] + const res = oddEvenSort(arr) + expect(res).toEqual([]) +}) + +test('The OddEvenSort of the array [10, 14, 12, 20] is [10, 12, 14, 20]', () => { + const arr = [10, 14, 12, 20] + const res = oddEvenSort(arr) + expect(res).toEqual([10, 12, 14, 20]) +}) + +test('The OddEvenSort of the array [166, 169, 144] is [144, 166, 169]', () => { + const arr = [166, 169, 144] + const res = oddEvenSort(arr) + expect(res).toEqual([144, 166, 169]) +}) diff --git a/Sorts/test/PancakeSort.test.js b/Sorts/test/PancakeSort.test.js new file mode 100644 index 0000000000..b9b3368bee --- /dev/null +++ b/Sorts/test/PancakeSort.test.js @@ -0,0 +1,24 @@ +import { flipArray, findMax, pancakeSort } from '../PancakeSort' + +describe('flipArray', () => { + it('should flip any subarray of any array', () => { + expect(flipArray([1, 2, 3, 4], 0, 3)).toEqual([4, 3, 2, 1]) + expect(flipArray([1, 2, 3, 4, 5], 2, 4)).toEqual([1, 2, 5, 4, 3]) + expect(flipArray([], 0, 0)).toEqual([]) + }) +}) + +describe('findMax', () => { + it('should find the index of the maximum value in any subarray of any array', () => { + expect(findMax([1, 3, 2, 5], 0, 3)).toEqual(3) + expect(findMax([1, 3, 2, 5], 0, 2)).toEqual(1) + }) +}) + +describe('pancakeSort', () => { + it('should sort any array', () => { + expect(pancakeSort([4, 3, 2, 1])).toEqual([1, 2, 3, 4]) + expect(pancakeSort([3, 1, 4, 2])).toEqual([1, 2, 3, 4]) + expect(pancakeSort([100, 1000, 10, 1])).toEqual([1, 10, 100, 1000]) + }) +}) diff --git a/Sorts/test/PigeonHoleSort.test.js b/Sorts/test/PigeonHoleSort.test.js new file mode 100644 index 0000000000..0c1313a2e4 --- /dev/null +++ b/Sorts/test/PigeonHoleSort.test.js @@ -0,0 +1,19 @@ +import { pigeonHoleSort } from '../PigeonHoleSort' + +test('The pigeonHoleSort of the array [1, 4, 3, 2] is [1, 2, 3, 4]', () => { + const arr = [1, 4, 3, 2] + const res = pigeonHoleSort(arr) + expect(res).toEqual([1, 2, 3, 4]) +}) + +test('The pigeonHoleSort of the array [5, 4, 1, 2] is [1, 2, 4, 5]', () => { + const arr = [5, 4, 1, 2] + const res = pigeonHoleSort(arr) + expect(res).toEqual([1, 2, 4, 5]) +}) + +test('The pigeonHoleSort of the array [18, 31, 29, 35, 11] is [11, 18, 29, 31, 35]', () => { + const arr = [18, 31, 29, 35, 11] + const res = pigeonHoleSort(arr) + expect(res).toEqual([11, 18, 29, 31, 35]) +}) diff --git a/Sorts/test/QuickSort.test.js b/Sorts/test/QuickSort.test.js new file mode 100644 index 0000000000..b6c3815da1 --- /dev/null +++ b/Sorts/test/QuickSort.test.js @@ -0,0 +1,14 @@ +import { quickSort } from '../QuickSort' + +describe('QuickSort', () => { + it('should work for empty arrays', () => { + expect(quickSort([])).toEqual([]) + }) + + it('should sort arrays correctly', () => { + expect(quickSort([5, 4, 3, 10, 2, 1])).toEqual([1, 2, 3, 4, 5, 10]) + expect(quickSort([5, 4])).toEqual([4, 5]) + expect(quickSort([1, 2, 3])).toEqual([1, 2, 3]) + expect(quickSort([0, 5, 3, 2, 2])).toEqual([0, 2, 2, 3, 5]) + }) +}) diff --git a/Sorts/test/QuickSortRecursive.test.js b/Sorts/test/QuickSortRecursive.test.js new file mode 100644 index 0000000000..7516877a11 --- /dev/null +++ b/Sorts/test/QuickSortRecursive.test.js @@ -0,0 +1,31 @@ +import { quickSort } from '../QuickSortRecursive' + +describe('QuickSortRecursive | Partition In Place Method', () => { + it('Expectedly, throw some error if we pass a non-array input', () => { + expect(() => quickSort('xyz', 0, 2)).toThrow( + 'Please input a valid list or array.' + ) + expect(() => quickSort(null, 0, 4)).toThrow( + 'Please input a valid list or array.' + ) + expect(() => quickSort(55, 0, 2)).toThrow( + 'Please input a valid list or array.' + ) + }) + + it('Expectedly, the quickSort method will sort the unsorted list in ascending order', () => { + const unSortArray = [5, 9, 3, 4, 6, 2, 0, 1, 7, 8] + const sortedExpectedArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + expect(quickSort(unSortArray, 0, unSortArray.length - 1)).toEqual( + sortedExpectedArray + ) + }) + + it('Expectedly, the quickSort method will arrange the list of character values in dictionary order.', () => { + const unSortList = ['d', 'e', 'c', 'a', 'f', 'b'] + const sortedExpectedList = ['a', 'b', 'c', 'd', 'e', 'f'] + expect(quickSort(unSortList, 0, unSortList.length - 1)).toEqual( + sortedExpectedList + ) + }) +}) diff --git a/Sorts/test/RadixSort.test.js b/Sorts/test/RadixSort.test.js new file mode 100644 index 0000000000..ccff5c95f6 --- /dev/null +++ b/Sorts/test/RadixSort.test.js @@ -0,0 +1,19 @@ +import { radixSort } from '../RadixSort' + +test('The RadixSort of the array [4, 3, 2, 1] is [1, 2, 3, 4]', () => { + const arr = [4, 3, 2, 1] + const res = radixSort(arr, 10) + expect(res).toEqual([1, 2, 3, 4]) +}) + +test('The RadixSort of the array [] is []', () => { + const arr = [] + const res = radixSort(arr, 10) + expect(res).toEqual([]) +}) + +test('The RadixSort of the array [14, 16, 10, 12] is [10, 12, 14, 16]', () => { + const arr = [14, 16, 10, 12] + const res = radixSort(arr, 10) + expect(res).toEqual([10, 12, 14, 16]) +}) diff --git a/Sorts/test/SecondLargestElement.test.js b/Sorts/test/SecondLargestElement.test.js new file mode 100644 index 0000000000..f736981ff5 --- /dev/null +++ b/Sorts/test/SecondLargestElement.test.js @@ -0,0 +1,25 @@ +import { secondLargestElement } from '../FindSecondLargestElement' + +test('The second largest element of the array [100, 200, 300, 400] is 300', () => { + const array = [100, 200, 300, 400] + const res = secondLargestElement(array) + expect(res).toBe(300) +}) + +test('The second largest element of the array [1100, 2400, 1300, 4002] is 2400', () => { + const array = [1100, 2400, 1300, 4002] + const res = secondLargestElement(array) + expect(res).toBe(2400) +}) + +test('The second largest element of the array [10, 20, 39, 34] is 34', () => { + const array = [10, 20, 39, 34] + const res = secondLargestElement(array) + expect(res).toBe(34) +}) + +test('The second largest element of the array [1, 20, 3, 40] is 20', () => { + const array = [1, 20, 3, 40] + const res = secondLargestElement(array) + expect(res).toBe(20) +}) diff --git a/Sorts/test/SelectionSort.test.js b/Sorts/test/SelectionSort.test.js new file mode 100644 index 0000000000..6dd076f663 --- /dev/null +++ b/Sorts/test/SelectionSort.test.js @@ -0,0 +1,24 @@ +import { selectionSort } from '../SelectionSort' + +describe('selectionSort', () => { + it('expects to return the array sorted in ascending order', () => { + const toSort = [5, 6, 7, 8, 1, 2, 12, 14] + const expected = [1, 2, 5, 6, 7, 8, 12, 14] + + expect(selectionSort(toSort)).toEqual(expected) + }) + + it('expects to throw if it is not a valid array', () => { + expect(() => selectionSort('abc')).toThrow('Given input is not an array') + expect(() => selectionSort(123)).toThrow('Given input is not an array') + expect(() => selectionSort({})).toThrow('Given input is not an array') + expect(() => selectionSort(null)).toThrow('Given input is not an array') + expect(() => selectionSort()).toThrow('Given input is not an array') + }) + + it('expects to throw if one of the elements in the array is not a number', () => { + expect(() => selectionSort([1, 'x', 2])).toThrow( + 'One of the items in your array is not a number' + ) + }) +}) diff --git a/Sorts/test/ShellSort.test.js b/Sorts/test/ShellSort.test.js new file mode 100644 index 0000000000..fd73f74e2e --- /dev/null +++ b/Sorts/test/ShellSort.test.js @@ -0,0 +1,25 @@ +import { shellSort } from '../ShellSort' + +test('The ShellSort of the array [5, 4, 3, 2, 1] is [1, 2, 3, 4, 5]', () => { + const arr = [5, 4, 3, 2, 1] + const res = shellSort(arr) + expect(res).toEqual([1, 2, 3, 4, 5]) +}) + +test('The ShellSort of the array [] is []', () => { + const arr = [] + const res = shellSort(arr) + expect(res).toEqual([]) +}) + +test('The ShellSort of the array [15, 24, 31, 42, 11] is [11, 15, 24, 31, 42]', () => { + const arr = [15, 24, 31, 42, 11] + const res = shellSort(arr) + expect(res).toEqual([11, 15, 24, 31, 42]) +}) + +test('The ShellSort of the array [121, 190, 169] is [121, 169, 190]', () => { + const arr = [121, 190, 169] + const res = shellSort(arr) + expect(res).toEqual([121, 169, 190]) +}) diff --git a/Sorts/test/SimplifiedWiggleSort.test.js b/Sorts/test/SimplifiedWiggleSort.test.js new file mode 100644 index 0000000000..ceba27732d --- /dev/null +++ b/Sorts/test/SimplifiedWiggleSort.test.js @@ -0,0 +1,27 @@ +import { simplifiedWiggleSort } from '../SimplifiedWiggleSort.js' + +describe('simplified wiggle sort', () => { + test('simplified wiggle sort for chars', () => { + const src = ['a', 'b', 'c'] + expect(simplifiedWiggleSort(src)).toEqual(['a', 'c', 'b']) + }) + + test('wiggle sort with duplicates, even array', () => { + const src = [2, 2, 1, 3] + expect(simplifiedWiggleSort(src)).toEqual([1, 3, 2, 2]) + }) + + test('wiggle sort with duplicates, odd array', () => { + const src = [1, 1, 1, 2, 4] + expect(simplifiedWiggleSort(src)).toEqual([1, 4, 1, 2, 1]) + }) + + test( + 'simplified wiggle sort which leads to equal values next to ' + + 'each other', + () => { + const src = [3, 3, 5, 1] + expect(simplifiedWiggleSort(src)).toEqual([1, 5, 3, 3]) + } + ) +}) diff --git a/Sorts/test/StoogeSort.test.js b/Sorts/test/StoogeSort.test.js new file mode 100644 index 0000000000..bd3250c78b --- /dev/null +++ b/Sorts/test/StoogeSort.test.js @@ -0,0 +1,31 @@ +import { stoogeSort } from '../StoogeSort' + +test('The StoogeSort of the array [1, 6, 4, 7, 2] is [1, 2, 4, 6, 7]', () => { + const arr = [1, 6, 4, 7, 2] + const res = stoogeSort(arr, 0, arr.length) + expect(res).toEqual([1, 2, 4, 6, 7]) +}) + +test('The StoogeSort of the array [] is []', () => { + const arr = [] + const res = stoogeSort(arr, 0, arr.length) + expect(res).toEqual([]) +}) + +test('The StoogeSort of the array [46, 15, 49, 65, 23] is [15, 23, 46, 49, 65]', () => { + const arr = [46, 15, 49, 65, 23] + const res = stoogeSort(arr, 0, arr.length) + expect(res).toEqual([15, 23, 46, 49, 65]) +}) + +test('The StoogeSort of the array [136, 459, 132, 566, 465] is [132, 136, 459, 465, 566]', () => { + const arr = [136, 459, 132, 566, 465] + const res = stoogeSort(arr, 0, arr.length) + expect(res).toEqual([132, 136, 459, 465, 566]) +}) + +test('The StoogeSort of the array [45, 3, 156, 1, 56] is [1, 3, 45, 56, 156]', () => { + const arr = [45, 3, 156, 1, 56] + const res = stoogeSort(arr, 0, arr.length) + expect(res).toEqual([1, 3, 45, 56, 156]) +}) diff --git a/Sorts/test/SwapSort.test.js b/Sorts/test/SwapSort.test.js new file mode 100644 index 0000000000..3a5d4f1d5a --- /dev/null +++ b/Sorts/test/SwapSort.test.js @@ -0,0 +1,18 @@ +import { minSwapsToSort } from '../SwapSort' + +describe('SwapSort', () => { + it('should work for empty arrays', () => { + expect(minSwapsToSort([])).toEqual(0) + }) + + it('should work for sorted arrays', () => { + expect(minSwapsToSort([1, 2, 3, 4, 5, 6])).toEqual(0) + }) + + it('should return correct results', () => { + expect(minSwapsToSort([7, 6, 2, 5, 11, 0])).toEqual(2) + expect(minSwapsToSort([3, 3, 2, 1, 0])).toEqual(2) + expect(minSwapsToSort([3, 0, 2, 1, 9, 8, 7, 6])).toEqual(4) + expect(minSwapsToSort([1, 0, 14, 0, 8, 6, 8])).toEqual(3) + }) +}) diff --git a/Sorts/test/TimSort.test.js b/Sorts/test/TimSort.test.js new file mode 100644 index 0000000000..4bb5c023e4 --- /dev/null +++ b/Sorts/test/TimSort.test.js @@ -0,0 +1,25 @@ +import { Timsort } from '../TimSort' + +test('The Timsort of the array [5, 4, 3, 2, 1] is [1, 2, 3, 4, 5]', () => { + const arr = [5, 4, 3, 2, 1] + const res = Timsort(arr) + expect(res).toEqual([1, 2, 3, 4, 5]) +}) + +test('The Timsort of the array [] is []', () => { + const arr = [] + const res = Timsort(arr) + expect(res).toEqual([]) +}) + +test('The Timsort of the array [-5, -4, -3, -2, -1] is [-5, -4, -3, -2, -1]', () => { + const arr = [-5, -4, -3, -2, -1] + const res = Timsort(arr) + expect(res).toEqual([-5, -4, -3, -2, -1]) +}) + +test('The Timsort of the array [9, 0, -5, -11, 3] is [-11, -5, 0, 3, 9]', () => { + const arr = [9, 0, -5, -11, 3] + const res = Timsort(arr) + expect(res).toEqual([-11, -5, 0, 3, 9]) +}) diff --git a/String/AlphaNumericPalindrome.js b/String/AlphaNumericPalindrome.js new file mode 100644 index 0000000000..98976f1d7d --- /dev/null +++ b/String/AlphaNumericPalindrome.js @@ -0,0 +1,36 @@ +/** + * @function alphaNumericPalindrome + * @description alphaNumericPalindrome should return true if the string has alphanumeric characters that are palindrome irrespective of special characters and the letter case. + * @param {string} str the string to check + * @returns {boolean} + * @see [Palindrome](https://en.wikipedia.org/wiki/Palindrome) + * @example + * The function alphaNumericPalindrome() receives a string with varying formats + * like "racecar", "RaceCar", and "race CAR" + * The string can also have special characters + * like "2A3*3a2", "2A3 3a2", and "2_A3*3#A2" + * + * But the catch is, we have to check only if the alphanumeric characters + * are palindrome i.e remove spaces, symbols, punctuations etc + * and the case of the characters doesn't matter + */ +const alphaNumericPalindrome = (str) => { + if (typeof str !== 'string') { + throw new TypeError('Argument should be string') + } + + // removing all the special characters and turning everything to lowercase + const newStr = str.replace(/[^a-z0-9]+/gi, '').toLowerCase() + const midIndex = newStr.length >> 1 // x >> y = floor(x / 2^y) + + for (let i = 0; i < midIndex; i++) { + if (newStr.at(i) !== newStr.at(~i)) { + // ~n = -(n + 1) + return false + } + } + + return true +} + +export default alphaNumericPalindrome diff --git a/String/AlternativeStringArrange.js b/String/AlternativeStringArrange.js new file mode 100644 index 0000000000..72b365b32a --- /dev/null +++ b/String/AlternativeStringArrange.js @@ -0,0 +1,47 @@ +// Alternative arrange the two given strings in one string in O(n) time complexity. + +// Problem Source & Explanation: https://www.geeksforgeeks.org/alternatively-merge-two-strings-in-java/ + +/** + * Alternative arrange the two given strings in one string in O(n) time complexity. + * @param {String} str1 first input string + * @param {String} str2 second input string + * @returns `String` return one alternative arrange string. + */ +const AlternativeStringArrange = (str1, str2) => { + // firstly, check that both inputs are strings. + if (typeof str1 !== 'string' || typeof str2 !== 'string') { + return 'Not string(s)' + } + + // output string value. + let outStr = '' + + // get first string length. + const firstStringLength = str1.length + // get second string length. + const secondStringLength = str2.length + // absolute length for operation. + const absLength = + firstStringLength > secondStringLength + ? firstStringLength + : secondStringLength + + // Iterate the character count until the absolute count is reached. + for (let charCount = 0; charCount < absLength; charCount++) { + // If firstStringLength is lesser than the charCount it means they are able to re-arrange. + if (charCount < firstStringLength) { + outStr += str1[charCount] + } + + // If secondStringLength is lesser than the charCount it means they are able to re-arrange. + if (charCount < secondStringLength) { + outStr += str2[charCount] + } + } + + // return the output string. + return outStr +} + +export { AlternativeStringArrange } diff --git a/String/BoyerMoore.js b/String/BoyerMoore.js new file mode 100644 index 0000000000..6ec8108b71 --- /dev/null +++ b/String/BoyerMoore.js @@ -0,0 +1,49 @@ +/* + * + * + *Implementation of the Boyer-Moore String Search Algorithm. + *The Boyerโ€“Moore string search algorithm allows linear time in + *search by skipping indices when searching inside a string for a pattern. + * + * + * + * + **/ +const buildBadMatchTable = (str) => { + const tableObj = {} + const strLength = str.length + for (let i = 0; i < strLength - 1; i++) { + tableObj[str[i]] = strLength - 1 - i + } + if (tableObj[str[strLength - 1]] === undefined) { + tableObj[str[strLength - 1]] = strLength + } + return tableObj +} + +const boyerMoore = (str, pattern) => { + const badMatchTable = buildBadMatchTable(pattern) + let offset = 0 + const patternLastIndex = pattern.length - 1 + const maxOffset = str.length - pattern.length + // if the offset is bigger than maxOffset, cannot be found + while (offset <= maxOffset) { + let scanIndex = 0 + while (pattern[scanIndex] === str[scanIndex + offset]) { + if (scanIndex === patternLastIndex) { + // found at this index + return offset + } + scanIndex++ + } + const badMatchString = str[offset + patternLastIndex] + if (badMatchTable[badMatchString]) { + // increase the offset if it exists + offset += badMatchTable[badMatchString] + } else { + offset++ + } + } + return -1 +} +export { boyerMoore } diff --git a/String/CheckAnagram.js b/String/CheckAnagram.js index 1621296923..4a7c521b65 100644 --- a/String/CheckAnagram.js +++ b/String/CheckAnagram.js @@ -1,50 +1,75 @@ -// Anagram check is case sensitive; i.e. Aba and aba is not a anagram. -// inputs are strings i.e. str1 and str2 -const checkAnagram = (str1, str2) => { +// An [Anagram](https://en.wikipedia.org/wiki/Anagram) is a string that is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once. Anagram check is not case-sensitive; +/** + * @function checkAnagramRegex + * @param {string} str1 + * @param {string} str2 + * @returns {boolean} + * @description - check anagram with the help of Regex + * @example - checkAnagramRegex('node', 'deno') => true + * @example - checkAnagramRegex('Eleven plus two', 'Twelve plus one') => true + */ +const checkAnagramRegex = (str1, str2) => { // check that inputs are strings. if (typeof str1 !== 'string' || typeof str2 !== 'string') { - return 'Not string(s)' + throw new TypeError('Both arguments should be strings.') } // If both strings have not same lengths then they can not be anagram. if (str1.length !== str2.length) { - return 'Not Anagram' + return false } - // Use hashmap to keep count of characters in str1 + /** + * str1 converted to an array and traverse each letter of str1 by reduce method + * reduce method return string which is empty or not. + */ + return ![...str1].reduce( + (str2Acc, cur) => str2Acc.replace(new RegExp(cur, 'i'), ''), // remove the similar letter from str2Acc in case-insensitive + str2 + ) +} - const str1CharCount = new Map() +/** + * @function checkAnagramMap + * @description - check anagram via using HashMap + * @param {string} str1 + * @param {string} str2 + * @returns {boolean} + * @example - checkAnagramMap('node', 'deno') => true + * @example - checkAnagramMap('Eleven plus two', 'Twelve plus one') => true + */ +const checkAnagramMap = (str1, str2) => { + // check that inputs are strings. + if (typeof str1 !== 'string' || typeof str2 !== 'string') { + throw new TypeError('Both arguments should be strings.') + } - for (let i = 0; i < str1.length; i++) { - let previousCount = 0 - if (str1CharCount.has(str1[i])) { - previousCount = str1CharCount.get(str1[i]) - } - str1CharCount.set(str1[i], previousCount + 1) + // If both strings have not same lengths then they can not be anagram. + if (str1.length !== str2.length) { + return false } - // Now check if second string has same characters? + const str1List = Array.from(str1.toUpperCase()) // str1 to array - for (let i = 0; i < str2.length; i++) { - let previousCount = 0 - // if str1CharCount has no key for str2[i] then not anagram. - if (!str1CharCount.has(str2[i])) { - return 'Not anagrams' + // get the occurrences of str1 characters by using HashMap + const str1Occurs = str1List.reduce( + (map, char) => map.set(char, map.get(char) + 1 || 1), + new Map() + ) + + for (const char of str2.toUpperCase()) { + // if char has not exist to the map it's return false + if (!str1Occurs.has(char)) { + return false } - previousCount = str1CharCount.get(str2[i]) - str1CharCount.set(str2[i], previousCount - 1) - } - // Now check if all entries in hashmap has zeros. + let getCharCount = str1Occurs.get(char) + str1Occurs.set(char, --getCharCount) - for (const key in str1CharCount) { - if (str1CharCount[key] !== 0) { return 'Not anagrams' } + getCharCount === 0 && str1Occurs.delete(char) } - return 'Anagrams' + return true } -console.log(checkAnagram('abcd', 'bcad')) // should print anagram -console.log(checkAnagram('abcd', 'abef')) // should print not anagram -console.log(checkAnagram(10, 'abcd'))// should print Not String(s). -console.log(checkAnagram('abs', 'abds'))// should print not anagram +export { checkAnagramRegex, checkAnagramMap } diff --git a/String/CheckCamelCase.js b/String/CheckCamelCase.js new file mode 100644 index 0000000000..470fa6241f --- /dev/null +++ b/String/CheckCamelCase.js @@ -0,0 +1,20 @@ +// CheckCamelCase method checks the given string is in camelCase or not. + +// Problem Source & Explanation: https://en.wikipedia.org/wiki/Camel_case + +/** + * checkCamelCase method returns true if the string in camelCase, else return the false. + * @param {String} varName the name of the variable to check. + * @returns `Boolean` return true if the string is in camelCase, else return false. + */ +const checkCamelCase = (varName) => { + // firstly, check that input is a string or not. + if (typeof varName !== 'string') { + throw new TypeError('Argument is not a string.') + } + + const pat = /^[a-z][A-Za-z]*$/ + return pat.test(varName) +} + +export { checkCamelCase } diff --git a/String/CheckExceeding.js b/String/CheckExceeding.js new file mode 100644 index 0000000000..bc0d127832 --- /dev/null +++ b/String/CheckExceeding.js @@ -0,0 +1,40 @@ +/** + * @function checkExceeding + * @description - Exceeding words are words where the gap between two adjacent characters is increasing. The gap is the distance in ascii + * @param {string} str + * @returns {boolean} + * @example - checkExceeding('delete') => true, ascii difference - [1, 7, 7, 15, 15] which is incremental + * @example - checkExceeding('update') => false, ascii difference - [5, 12, 3, 19, 15] which is not incremental + */ +const checkExceeding = (str) => { + if (typeof str !== 'string') { + throw new TypeError('Argument is not a string') + } + + const upperChars = str.toUpperCase().replace(/[^A-Z]/g, '') // remove all from str except A to Z alphabets + + const adjacentDiffList = [] + + for (let i = 0; i < upperChars.length - 1; i++) { + // destructuring current char & adjacent char by index, cause in javascript String is an object. + const { [i]: char, [i + 1]: adjacentChar } = upperChars + + if (char !== adjacentChar) { + adjacentDiffList.push( + Math.abs(char.charCodeAt() - adjacentChar.charCodeAt()) + ) + } + } + + for (let i = 0; i < adjacentDiffList.length - 1; i++) { + const { [i]: charDiff, [i + 1]: secondCharDiff } = adjacentDiffList + + if (charDiff > secondCharDiff) { + return false + } + } + + return true +} + +export { checkExceeding } diff --git a/String/CheckFlatCase.js b/String/CheckFlatCase.js new file mode 100644 index 0000000000..5f8a332999 --- /dev/null +++ b/String/CheckFlatCase.js @@ -0,0 +1,22 @@ +// checkFlatCase method checks if the given string is in flatcase or not. Flatcase is a convention +// where all letters are in lowercase, and there are no spaces between words. +// thisvariable is an example of flatcase. In camelCase it would be thisVariable, snake_case this_variable and so on. + +// Problem Source & Explanation: https://en.wikipedia.org/wiki/Naming_convention_(programming) + +/** + * checkFlatCase method returns true if the string in flatcase, else return the false. + * @param {string} varname the name of the variable to check. + * @returns {boolean} return true if the string is in flatcase, else return false. + */ +const checkFlatCase = (varname) => { + // firstly, check that input is a string or not. + if (typeof varname !== 'string') { + throw new TypeError('Argument is not a string.') + } + + const pat = /^[a-z]*$/ + return pat.test(varname) +} + +export { checkFlatCase } diff --git a/String/CheckKebabCase.js b/String/CheckKebabCase.js new file mode 100644 index 0000000000..52d322f921 --- /dev/null +++ b/String/CheckKebabCase.js @@ -0,0 +1,20 @@ +// CheckKebabCase method checks the given string is in kebab-case or not. + +// Problem Source & Explanation: https://en.wikipedia.org/wiki/Naming_convention_(programming) + +/** + * CheckKebabCase method returns true if the string in kebab-case, else return the false. + * @param {String} varName the name of the variable to check. + * @returns `Boolean` return true if the string is in kebab-case, else return false. + */ +const CheckKebabCase = (varName) => { + // firstly, check that input is a string or not. + if (typeof varName !== 'string') { + throw new TypeError('Argument is not a string.') + } + + const pat = /(\w+)-(\w)([\w-]*)/ + return pat.test(varName) && !varName.includes('_') +} + +export { CheckKebabCase } diff --git a/String/CheckPalindrome.js b/String/CheckPalindrome.js index b368eae89a..a717ccd5f4 100644 --- a/String/CheckPalindrome.js +++ b/String/CheckPalindrome.js @@ -5,21 +5,13 @@ const checkPalindrome = (str) => { if (typeof str !== 'string') { return 'Not a string' } - // Store the length of the input string in a variable - const length = str.length - if (length === 0) { + if (str.length === 0) { return 'Empty string' } - // Iterate through the length of the string - // Compare the first character to the last, the second character to the second last, and so on - for (let i = 0; i < length / 2; i++) { - // at the first instance of a mismatch - if (str[i] !== str[length - 1 - i]) { - return 'Not a Palindrome' - } - } - return 'Palindrome' + // Reverse only works with array, thus convert the string to array, reverse it and convert back to string + // return as palindrome if the reversed string is equal to the input string + const reversed = [...str].reverse().join('') + return str === reversed ? 'Palindrome' : 'Not a Palindrome' } -console.log(checkPalindrome('madam')) -console.log(checkPalindrome('abcd')) +export { checkPalindrome } diff --git a/String/CheckPangram.js b/String/CheckPangram.js new file mode 100644 index 0000000000..8e9fbe1e96 --- /dev/null +++ b/String/CheckPangram.js @@ -0,0 +1,54 @@ +/** + * What is Pangram? + * Pangram is a sentence that contains all the letters in the alphabet https://en.wikipedia.org/wiki/Pangram + */ + +/** + * @function checkPangramRegex + * @description - This function check pangram with the help of regex pattern + * @param {string} string + * @returns {boolean} + * @example - checkPangramRegex("'The quick brown fox jumps over the lazy dog' is a pangram") => true + * @example - checkPangramRegex('"Waltz, bad nymph, for quick jigs vex." is a pangram') => true + */ +const checkPangramRegex = (string) => { + if (typeof string !== 'string') { + throw new TypeError('The given value is not a string') + } + + /** + * Match all 26 alphabets using regex, with the help of: + * Capturing group - () -> Groups multiple tokens together and creates a capture group for extracting a substring or using a backreference. + * Character set - [a-z] -> Matches a char in the range a to z in case-insensitive for the 'i' flag + * Negative lookahead - (?!) -> Specifies a group that can not match after the main expression (if it matches, the result is discarded). + * Dot - . -> Matches any character except linebreaks. Equivalent to + * Star - * -> Matches 0 or more of the preceding token. + * Numeric reference - \{$n} -> Matches the results of a capture group. E.g. - \1 matches the results of the first capture group & \3 matches the third. + */ + return string.match(/([a-z])(?!.*\1)/gi).length === 26 +} + +/** + * @function checkPangramSet + * @description - This function detect the pangram sentence by HashSet + * @param {string} string + * @returns {boolean} + */ +const checkPangramSet = (string) => { + if (typeof string !== 'string') { + throw new TypeError('The given value is not a string') + } + + const lettersSet = new Set() + + for (const letter of string.toUpperCase()) { + if (/[A-Z]/.test(letter)) { + // if the letter is a valid uppercase alphabet then the add method insert the letter to the HashSet + lettersSet.add(letter) + } + } + + return lettersSet.size === 26 +} + +export { checkPangramRegex, checkPangramSet } diff --git a/String/CheckPascalCase.js b/String/CheckPascalCase.js new file mode 100644 index 0000000000..71aaa26770 --- /dev/null +++ b/String/CheckPascalCase.js @@ -0,0 +1,20 @@ +// CheckPascalCase method checks the given string is in PascalCase or not. + +// Problem Source & Explanation: https://www.theserverside.com/definition/Pascal-case + +/** + * CheckPascalCase method returns true if the string in PascalCase, else return the false. + * @param {String} VarName the name of the variable to check. + * @returns `Boolean` return true if the string is in PascalCase, else return false. + */ +const CheckPascalCase = (VarName) => { + // firstly, check that input is a string or not. + if (typeof VarName !== 'string') { + throw new TypeError('Argument is not a string.') + } + + const pat = /^[A-Z][A-Za-z]*$/ + return pat.test(VarName) +} + +export { CheckPascalCase } diff --git a/String/CheckRearrangePalindrome.js b/String/CheckRearrangePalindrome.js new file mode 100644 index 0000000000..c3feb59f16 --- /dev/null +++ b/String/CheckRearrangePalindrome.js @@ -0,0 +1,37 @@ +/** + * What is a palindrome? https://en.wikipedia.org/wiki/Palindrome + * Receives a string and returns whether it can be rearranged to become a palindrome or not + * The string can only be a palindrome if the count of ALL characters is even or if the ONLY ONE character count is odd + * Input is a string + * + **/ + +export const palindromeRearranging = (str) => { + // check that input is a string + if (typeof str !== 'string') { + return 'Not a string' + } + // Check if is a empty string + if (!str) { + return 'Empty string' + } + + // First obtain the character count for each character in the string and store it in an object. + // Filter the object's values to only the odd character counts. + const charCounts = [...str].reduce((counts, char) => { + counts[char] = counts[char] ? counts[char] + 1 : 1 + return counts + }, {}) + // If the length of the resulting array is 0 or 1, the string can be a palindrome. + return ( + Object.values(charCounts).filter((count) => count % 2 !== 0).length <= 1 + ) +} + +// testing + +// > palindromeRearranging('aaeccrr') +// true + +// > palindromeRearranging('leve') +// false diff --git a/String/CheckSnakeCase.js b/String/CheckSnakeCase.js new file mode 100644 index 0000000000..07eda707ae --- /dev/null +++ b/String/CheckSnakeCase.js @@ -0,0 +1,20 @@ +// CheckSnakeCase method checks the given string is in snake_case or not. + +// Problem Source & Explanation: https://en.wikipedia.org/wiki/Naming_convention_(programming) + +/** + * checkSnakeCase method returns true if the string in snake_case, else return the false. + * @param {String} varName the name of the variable to check. + * @returns `Boolean` return true if the string is in snake_case, else return false. + */ +const checkSnakeCase = (varName) => { + // firstly, check that input is a string or not. + if (typeof varName !== 'string') { + throw new TypeError('Argument is not a string.') + } + + const pat = /(.*?)_([a-zA-Z])*/ + return pat.test(varName) +} + +export { checkSnakeCase } diff --git a/String/CheckWordOccurrence.js b/String/CheckWordOccurrence.js new file mode 100644 index 0000000000..8412674e68 --- /dev/null +++ b/String/CheckWordOccurrence.js @@ -0,0 +1,27 @@ +/** + * @function checkWordOccurrence + * @description - this function count all the words in a sentence and return an word occurrence object + * @param {string} str + * @param {boolean} isCaseSensitive + * @returns {Object} + */ +const checkWordOccurrence = (str, isCaseSensitive = false) => { + if (typeof str !== 'string') { + throw new TypeError('The first param should be a string') + } + + if (typeof isCaseSensitive !== 'boolean') { + throw new TypeError('The second param should be a boolean') + } + + const modifiedStr = isCaseSensitive ? str.toLowerCase() : str + + return modifiedStr + .split(/\s+/) // remove all spaces and distribute all word in List + .reduce((occurrence, word) => { + occurrence[word] = occurrence[word] + 1 || 1 + return occurrence + }, {}) +} + +export { checkWordOccurrence } diff --git a/String/CountLetters.js b/String/CountLetters.js new file mode 100644 index 0000000000..6b64001505 --- /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/CountSubstrings.js b/String/CountSubstrings.js new file mode 100644 index 0000000000..f936f8eab0 --- /dev/null +++ b/String/CountSubstrings.js @@ -0,0 +1,29 @@ +/** + * @function countSubstrings + * @description Given a string of words or phrases, count the occurrences of a substring + * @param {String} str - The input string + * @param {String} substring - The substring + * @return {Number} - The number of substring occurrences + * @example countSubstrings("This is a string", "is") => 2 + * @example countSubstrings("Hello", "e") => 1 + */ + +const countSubstrings = (str, substring) => { + if (typeof str !== 'string' || typeof substring !== 'string') { + throw new TypeError('Argument should be string') + } + + if (substring.length === 0) return str.length + 1 + + let count = 0 + let position = str.indexOf(substring) + + while (position > -1) { + count++ + position = str.indexOf(substring, position + 1) + } + + return count +} + +export { countSubstrings } diff --git a/String/CountVowels.js b/String/CountVowels.js new file mode 100644 index 0000000000..d9dc42368a --- /dev/null +++ b/String/CountVowels.js @@ -0,0 +1,21 @@ +/** + * @function countVowels + * @description Given a string of words or phrases, count the number of vowels. + * @param {String} str - The input string + * @return {Number} - The number of vowels + * @example countVowels("ABCDE") => 2 + * @example countVowels("Hello") => 2 + */ + +const countVowels = (str) => { + if (typeof str !== 'string') { + throw new TypeError('Input should be a string') + } + + const vowelRegex = /[aeiou]/gi + const vowelsArray = str.match(vowelRegex) || [] + + return vowelsArray.length +} + +export { countVowels } diff --git a/String/CreatePermutations.js b/String/CreatePermutations.js new file mode 100644 index 0000000000..4d363d1e29 --- /dev/null +++ b/String/CreatePermutations.js @@ -0,0 +1,38 @@ +/* +a permutation of a set is, loosely speaking, an arrangement of its members into a sequence or linear order, or if the set is already ordered, a rearrangement of its elements. +The word "permutation" also refers to the act or process of changing the linear order of an ordered set +More at : https://en.wikipedia.org/wiki/Permutation +*/ + +const createPermutations = (str) => { + // convert string to array + const arr = str.split('') + + // get array length + const strLen = arr.length + // this will hold all the permutations + const perms = [] + let rest + let picked + let restPerms + let next + + // if strLen is zero, return the same string + if (strLen === 0) { + return [str] + } + // loop to the length to get all permutations + for (let i = 0; i < strLen; i++) { + rest = Object.create(arr) + picked = rest.splice(i, 1) + + restPerms = createPermutations(rest.join('')) + + for (let j = 0, jLen = restPerms.length; j < jLen; j++) { + next = picked.concat(restPerms[j]) + perms.push(next.join('')) + } + } + return perms +} +export { createPermutations } diff --git a/String/DiceCoefficient.js b/String/DiceCoefficient.js new file mode 100644 index 0000000000..f6916d5d45 --- /dev/null +++ b/String/DiceCoefficient.js @@ -0,0 +1,50 @@ +/* The Sรธrensenโ€“Dice coefficient is a statistic used to gauge the similarity of two samples. + * Applied to strings, it can give you a value between 0 and 1 (included) which tells you how similar they are. + * Dice coefficient is calculated by comparing the bigrams of both strings, + * a bigram is a substring of the string of length 2. + * read more: https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient + */ + +// Time complexity: O(m + n), m and n being the sizes of string A and string B + +// Find the bistrings of a string and return a hashmap (key => bistring, value => count) +function mapBigrams(string) { + const bigrams = new Map() + for (let i = 0; i < string.length - 1; i++) { + const bigram = string.substring(i, i + 2) + const count = bigrams.get(bigram) + bigrams.set(bigram, (count || 0) + 1) + } + return bigrams +} + +// Calculate the number of common bigrams between a map of bigrams and a string + +function countCommonBigrams(bigrams, string) { + let count = 0 + for (let i = 0; i < string.length - 1; i++) { + const bigram = string.substring(i, i + 2) + if (bigrams.has(bigram)) count++ + } + return count +} + +// Calculate Dice coeff of 2 strings +function diceCoefficient(stringA, stringB) { + if (stringA === stringB) return 1 + else if (stringA.length < 2 || stringB.length < 2) return 0 + + const bigramsA = mapBigrams(stringA) + + const lengthA = stringA.length - 1 + const lengthB = stringB.length - 1 + + let dice = (2 * countCommonBigrams(bigramsA, stringB)) / (lengthA + lengthB) + + // cut 0.xxxxxx to 0.xx for simplicity + dice = Math.floor(dice * 100) / 100 + + return dice +} + +export { diceCoefficient } diff --git a/String/FirstUniqueCharacter.js b/String/FirstUniqueCharacter.js new file mode 100644 index 0000000000..1bacd83070 --- /dev/null +++ b/String/FirstUniqueCharacter.js @@ -0,0 +1,30 @@ +/** + * @function firstUniqChar + * @description Given a string str, find the first non-repeating character in it and return its index. If it does not exist, return -1. + * @param {String} str - The input string + * @return {Number} - The index of first unique character. + * @example firstUniqChar("javascript") => 0 + * @example firstUniqChar("sesquipedalian") => 3 + * @example firstUniqChar("aabb") => -1 + */ + +const firstUniqChar = (str) => { + if (typeof str !== 'string') { + throw new TypeError('Argument should be string') + } + const count = new Map() + + for (const char of str) { + if (!count[char]) { + count[char] = 1 + } else { + count[char]++ + } + } + for (let i = 0; i < str.length; i++) { + if (count[str[i]] === 1) return i + } + return -1 +} + +export { firstUniqChar } diff --git a/String/FormatPhoneNumber.js b/String/FormatPhoneNumber.js new file mode 100644 index 0000000000..5b50672143 --- /dev/null +++ b/String/FormatPhoneNumber.js @@ -0,0 +1,16 @@ +/** + * @description - function that takes 10 digits and returns a string of the formatted phone number e.g.: 1234567890 -> (123) 456-7890 + * @param {string} phoneNumber + * @returns {string} - Format to (XXX) XXX-XXXX pattern + */ +const formatPhoneNumber = (phoneNumber) => { + if (phoneNumber.length !== 10 || isNaN(phoneNumber)) { + // return "Invalid phone number." + throw new TypeError('Invalid phone number!') + } + + let index = 0 + return '(XXX) XXX-XXXX'.replace(/X/g, () => phoneNumber[index++]) +} + +export default formatPhoneNumber diff --git a/String/GenerateGUID.js b/String/GenerateGUID.js new file mode 100644 index 0000000000..1583920261 --- /dev/null +++ b/String/GenerateGUID.js @@ -0,0 +1,20 @@ +/* +Generates a UUID/GUID in Node.Js. +The script uses `Math.random` in combination with the timestamp for better randomness. +The function generate an RFC4122 (https://www.ietf.org/rfc/rfc4122.txt) version 4 UUID/GUID +*/ + +export const Guid = () => { + const pattern = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + let currentDateMilliseconds = new Date().getTime() + return pattern.replace(/[xy]/g, (currentChar) => { + const randomChar = (currentDateMilliseconds + Math.random() * 16) % 16 | 0 + currentDateMilliseconds = Math.floor(currentDateMilliseconds / 16) + return ( + currentChar === 'x' ? randomChar : (randomChar & 0x7) | 0x8 + ).toString(16) + }) +} + +// > Guid() +// 'edc848db-3478-1760-8b55-7986003d895f' diff --git a/String/HammingDistance.js b/String/HammingDistance.js new file mode 100644 index 0000000000..d3316f1988 --- /dev/null +++ b/String/HammingDistance.js @@ -0,0 +1,32 @@ +/** + * Hamming Distance: https://en.wikipedia.org/wiki/Hamming_distance + * + * + * Hamming distance is a metric for comparing two binary data strings. + * + * While comparing two binary strings of equal length, Hamming distance + * is the number of bit positions in which the two bits are different. + * The Hamming distance between two strings, a and b is denoted as d(a,b) + */ + +/** + * @param {string} a + * @param {string} b + * @return {number} + */ + +export const hammingDistance = (a, b) => { + if (a.length !== b.length) { + throw new Error('Strings must be of the same length') + } + + let distance = 0 + + for (let i = 0; i < a.length; i += 1) { + if (a[i] !== b[i]) { + distance += 1 + } + } + + return distance +} diff --git a/String/IsPalindrome.js b/String/IsPalindrome.js new file mode 100644 index 0000000000..1e64d22f7f --- /dev/null +++ b/String/IsPalindrome.js @@ -0,0 +1,37 @@ +/** + * @function isPalindromeIterative + * @description isPalindromeIterative function checks whether the provided input is palindrome or not + * @param {String | Number} x - The input to check + * @return {boolean} - Input is palindrome or not + * @see [Palindrome](https://en.wikipedia.org/wiki/Palindrome) + */ + +/* + * Big-O Analysis + * Time Complexity + - O(N) on average and worst case scenario as input is traversed in linear fashion + - O(1) on best case scenario if the input already is a string (otherwise toString() method takes O(N)) + and the first & last characters don't match, triggering an early return + * Space Complexity + - O(1) +*/ + +export function isPalindromeIterative(x) { + if (typeof x !== 'string' && typeof x !== 'number') { + throw new TypeError('Input must be a string or a number') + } + + // Convert x to string whether it's number or string + const string = x.toString() + const length = string.length + + if (length === 1) return true + + // Apply two pointers technique to compare first and last elements on each iteration + for (let start = 0, end = length - 1; start < end; start++, end--) { + // Early return if compared items are different, input is not a palindrome + if (string[start] !== string[end]) return false + } + // If early return in condition inside for loop is not reached, then input is palindrome + return true +} diff --git a/String/KMPPatternSearching.js b/String/KMPPatternSearching.js new file mode 100644 index 0000000000..af9c07e43e --- /dev/null +++ b/String/KMPPatternSearching.js @@ -0,0 +1,55 @@ +// Implementing KMP Search Algorithm to search all the instances of pattern in +// given text +// Reference Book: Introduction to Algorithms, CLRS + +// Explanation: https://www.topcoder.com/community/competitive-programming/tutorials/introduction-to-string-searching-algorithms/ + +const computeLPS = (pattern) => { + const lps = Array(pattern.length) + lps[0] = 0 + for (let i = 1; i < pattern.length; i++) { + let matched = lps[i - 1] + while (matched > 0 && pattern[i] !== pattern[matched]) { + matched = lps[matched - 1] + } + if (pattern[i] === pattern[matched]) { + matched++ + } + lps[i] = matched + } + return lps +} + +/** + * Returns all indices where pattern starts in text + * @param {*} text a big text in which pattern string is to find + * @param {*} pattern the string to find + */ +const KMPSearch = (text, pattern) => { + if (!pattern || !text) { + return [] // no results + } + + // lps[i] = length of proper prefix of pattern[0]...pattern[i-1] + // which is also proper suffix of it + const lps = computeLPS(pattern) + const result = [] + + let matched = 0 + for (let i = 0; i < text.length; i++) { + while (matched > 0 && text[i] !== pattern[matched]) { + matched = lps[matched - 1] + } + if (text[i] === pattern[matched]) { + matched++ + } + if (matched === pattern.length) { + result.push(i - pattern.length + 1) + matched = lps[matched - 1] + } + } + + return result +} + +export { KMPSearch } diff --git a/String/LengthofLongestSubstringWithoutRepetition.js b/String/LengthofLongestSubstringWithoutRepetition.js new file mode 100644 index 0000000000..42b5894b7c --- /dev/null +++ b/String/LengthofLongestSubstringWithoutRepetition.js @@ -0,0 +1,27 @@ +/* + * @description : Given a string, the function finds the length of the longest substring without any repeating characters + * @param {String} str - The input string + * @returns {Number} The Length of the longest substring in a given string without repeating characters + * @example lengthOfLongestSubstring("abcabcbb") => 3 + * @example lengthOfLongestSubstring("bbbbb") => 1 + * @see https://leetcode.com/problems/longest-substring-without-repeating-characters/ + */ + +const lengthOfLongestSubstring = (s) => { + if (typeof s !== 'string') { + throw new TypeError('Invalid Input Type') + } + let maxLength = 0 + let start = 0 + const charMap = new Map() + for (let end = 0; end < s.length; end++) { + if (charMap.has(s[end])) { + start = Math.max(start, charMap.get(s[end]) + 1) + } + charMap.set(s[end], end) + maxLength = Math.max(maxLength, end - start + 1) + } + return maxLength +} + +export { lengthOfLongestSubstring } diff --git a/String/LevenshteinDistance.js b/String/LevenshteinDistance.js new file mode 100644 index 0000000000..1b6ece70c7 --- /dev/null +++ b/String/LevenshteinDistance.js @@ -0,0 +1,43 @@ +/* The Levenshtein distance (a.k.a edit distance) is a +measure of similarity between two strings. It is +defined as the minimum number of changes required to +convert string a into string b (this is done by +inserting, deleting or replacing a character in +string a). +The smaller the Levenshtein distance, +the more similar the strings are. This is a very +common problem in the application of Dynamic Programming. +*/ + +const levenshteinDistance = (a, b) => { + // Declaring array 'D' with rows = len(a) + 1 and columns = len(b) + 1: + const distanceMatrix = Array(b.length + 1) + .fill(null) + .map(() => Array(a.length + 1).fill(null)) + + // Initializing first column: + for (let i = 0; i <= a.length; i += 1) { + distanceMatrix[0][i] = i + } + + // Initializing first row: + for (let j = 0; j <= b.length; j += 1) { + distanceMatrix[j][0] = j + } + + for (let j = 1; j <= b.length; j += 1) { + for (let i = 1; i <= a.length; i += 1) { + const indicator = a[i - 1] === b[j - 1] ? 0 : 1 + // choosing the minimum of all three, vis-a-vis: + distanceMatrix[j][i] = Math.min( + distanceMatrix[j][i - 1] + 1, // deletion + distanceMatrix[j - 1][i] + 1, // insertion + distanceMatrix[j - 1][i - 1] + indicator // substitution + ) + } + } + + return distanceMatrix[b.length][a.length] +} + +export { levenshteinDistance } diff --git a/String/Lower.js b/String/Lower.js new file mode 100644 index 0000000000..bfdf88c601 --- /dev/null +++ b/String/Lower.js @@ -0,0 +1,20 @@ +/** + * @function lower + * @description Will convert the entire string to lowercase letters. + * @param {String} str - The input string + * @returns {String} Lowercase string + * @example lower("HELLO") => hello + * @example lower("He_llo") => he_llo + */ + +const lower = (str) => { + if (typeof str !== 'string') { + throw new TypeError('Invalid Input Type') + } + + return str.replace(/[A-Z]/g, (char) => + String.fromCharCode(char.charCodeAt() + 32) + ) +} + +export default lower diff --git a/String/MaxCharacter.js b/String/MaxCharacter.js new file mode 100644 index 0000000000..73f0aff7dd --- /dev/null +++ b/String/MaxCharacter.js @@ -0,0 +1,37 @@ +/** + * @function maxCharacter + * @example - Given a string of characters, return the character that appears the most often. Example: input = "Hello World!" return "l" + * @param {string} str + * @param {RegExp} ignorePattern - ignore the char in str that is not required + * @returns {string} - char + */ +const maxCharacter = (str, ignorePattern) => { + // initially it's count only alphabets + if (typeof str !== 'string') { + throw new TypeError('Argument should be a string') + } else if (!str) { + throw new Error('The param should be a nonempty string') + } + + // store all char in occurrence map + const occurrenceMap = new Map() + + for (const char of str) { + if (!ignorePattern?.test(char)) { + occurrenceMap.set(char, occurrenceMap.get(char) + 1 || 1) + } + } + + // find the max char from the occurrence map + let max = { char: '', occur: -Infinity } + + for (const [char, occur] of occurrenceMap) { + if (occur > max.occur) { + max = { char, occur } + } + } + + return max.char +} + +export default maxCharacter diff --git a/String/MaxWord.js b/String/MaxWord.js new file mode 100644 index 0000000000..633d751481 --- /dev/null +++ b/String/MaxWord.js @@ -0,0 +1,44 @@ +// Given a sentence, return the most occurring word + +/** + * @param {string} sentence - the sentence you want to find the most occurring word + * @returns {string} - the most occurring word + * + * @example + * - maxWord('lala lili lala'); // lala + */ +const maxWord = (sentence = '') => { + if (typeof sentence !== 'string') { + throw new TypeError('the param should be string') + } + + if (!sentence) { + return null + } + + const words = sentence.split(' ') + if (words.length < 2) { + return words[0] + } + + const occurrences = {} + words.forEach((word) => { + occurrences[word.toLocaleLowerCase()] = + occurrences[word.toLocaleLowerCase()] + 1 || 1 + }) + + const max = Object.keys(occurrences).reduce( + (n, word) => { + if (occurrences[word] > n.count) { + return { word, count: occurrences[word] } + } else { + return n + } + }, + { word: '', count: 0 } + ) + + return max.word +} + +export { maxWord } diff --git a/String/PatternMatching.js b/String/PatternMatching.js index b6b64bb5b3..b90260ce7c 100644 --- a/String/PatternMatching.js +++ b/String/PatternMatching.js @@ -8,6 +8,9 @@ return the starting index if the given pattern is available in the text */ const checkIfPatternExists = (text, pattern) => { + if (typeof text !== 'string' || typeof pattern !== 'string') { + throw new TypeError('Given input is not a string') + } const textLength = text.length // Store the length of the text in a variable const patternLength = pattern.length // Store the length of the pattern in a variable @@ -21,16 +24,10 @@ const checkIfPatternExists = (text, pattern) => { // For each iteration of j check if the value of // j + 1 is equal to the length of the pattern if (j + 1 === patternLength) { - console.log(`Given pattern is found at index ${i}`) + return `Given pattern is found at index ${i}` } } } } -const main = () => { - const text = 'AABAACAADAABAAAABAA' - const pattern = 'AABA' - checkIfPatternExists(text.toLowerCase(), pattern.toLowerCase()) -} - -main() +export { checkIfPatternExists } diff --git a/String/PercentageOfLetters.js b/String/PercentageOfLetters.js new file mode 100644 index 0000000000..fbd6a03be3 --- /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/PermutateString.js b/String/PermutateString.js new file mode 100644 index 0000000000..d68c0261b9 --- /dev/null +++ b/String/PermutateString.js @@ -0,0 +1,39 @@ +'use strict' + +const permutate = (aString) => { + if (typeof aString !== 'string' || !aString) { + throw new Error('The arg must be a valid, non empty string') + } + const characters = aString.split('') + let permutations = [[characters.shift()]] + while (characters.length) { + const currentCharacter = characters.shift() + permutations = calculateCurrentCharacterPermutation( + permutations, + currentCharacter + ) + } + return permutations + .map((character) => character.join('')) + .filter((item, index, self) => self.indexOf(item) === index) + .sort() +} + +const calculateCurrentCharacterPermutation = ( + allPermutations, + currentCharacter +) => { + const currentPermutations = [] + allPermutations.forEach((permutation) => { + let index = 0 + while (index <= permutation.length) { + const tmp = [...permutation] + tmp.splice(index, 0, currentCharacter) + currentPermutations.push(tmp) + index++ + } + }) + return currentPermutations +} + +export { permutate } diff --git a/String/ReverseString.js b/String/ReverseString.js index 8709e17df9..ae5b617895 100644 --- a/String/ReverseString.js +++ b/String/ReverseString.js @@ -1,14 +1,10 @@ /** - * A short example showing how to reverse a string - * @flow + * A short example showing how to reverse a string. */ - -/** - * Create a new string and append - * @complexity O(n) - */ - -function ReverseStringIterative (string) { +function ReverseStringIterative(string) { + if (typeof string !== 'string') { + throw new TypeError('The given value is not a string') + } let reversedString = '' let index @@ -20,26 +16,17 @@ function ReverseStringIterative (string) { } /** - * JS disallows string mutation so we're actually a bit slower. * - * @complexity: O(n) + * @author dev-madhurendra + * Reverses a number by converting it to a string. * - * 'some' -> 'eoms' -> 'emos' + * @param {string} str - The number to reverse. + * @returns {string} The reversed number. + * + * @example + * const reversed = reverseString("hello"); // Returns olleh */ -function ReverseStringIterativeInplace (string) { - const _string = string.split('') - - for (let i = 0; i < Math.floor(_string.length / 2); i++) { - const first = _string[i] - const second = _string[_string.length - 1 - i] - _string[i] = second - _string[_string.length - 1 - i] = first - } - - return _string.join('') -} +const ReverseStringIterativeInplace = (str) => [...str].reverse().join('') -// testing -console.log(ReverseStringIterative('Javascript')) -console.log(ReverseStringIterativeInplace('Javascript')) +export { ReverseStringIterative, ReverseStringIterativeInplace } diff --git a/String/ReverseWords.js b/String/ReverseWords.js index 0f0b92a288..9796c6208b 100644 --- a/String/ReverseWords.js +++ b/String/ReverseWords.js @@ -1,15 +1,17 @@ +/** + * @function reverseWords + * @param {string} str + * @returns {string} - reverse string + */ const reverseWords = (str) => { - // Split string into words - // Ex. "I Love JS" => ["I", "Love", "JS"] - const words = str.split(' ') - // reverse words - // ["I", "Love", "JS"] => ["JS", "Love", "I"] - const reversedWords = words.reverse() - // join reversed words with space and return - // ["JS", "Love", "I"] => "JS Love I" - return reversedWords.join(' ') + if (typeof str !== 'string') { + throw new TypeError('The given value is not a string') + } + + return str + .split(/\s+/) // create an array with each word in string + .reduceRight((reverseStr, word) => `${reverseStr} ${word}`, '') // traverse the array from last & create an string + .trim() // remove the first useless space } -// testing -console.log(reverseWords('I Love JS')) -console.log(reverseWords('My Name Is JavaScript')) +export default reverseWords diff --git a/String/ScrambleStrings.js b/String/ScrambleStrings.js new file mode 100644 index 0000000000..d9e02b0665 --- /dev/null +++ b/String/ScrambleStrings.js @@ -0,0 +1,50 @@ +// Problem Statement and Explanation: https://leetcode.com/problems/scramble-string/ + +/** + * Given two strings s1 and s2 of the same length, return true if s2 is a scrambled string of s1, otherwise, return false. + * @param {string} s1 + * @param {string} s2 + * @return {boolean} + */ + +const isScramble = (s1, s2) => { + return helper({}, s1, s2) +} + +const helper = function (dp, s1, s2) { + const map = {} + + if (dp[s1 + s2] !== undefined) return dp[s1 + s2] + if (s1 === s2) return true + + for (let j = 0; j < s1.length; j++) { + if (map[s1[j]] === undefined) map[s1[j]] = 0 + if (map[s2[j]] === undefined) map[s2[j]] = 0 + map[s1[j]]++ + map[s2[j]]-- + } + + for (const key in map) { + if (map[key] !== 0) { + dp[s1 + s2] = false + return false + } + } + + for (let i = 1; i < s1.length; i++) { + if ( + (helper(dp, s1.substr(0, i), s2.substr(0, i)) && + helper(dp, s1.substr(i), s2.substr(i))) || + (helper(dp, s1.substr(0, i), s2.substr(s2.length - i)) && + helper(dp, s1.substr(i), s2.substr(0, s2.length - i))) + ) { + dp[s1 + s2] = true + return true + } + } + + dp[s1 + s2] = false + return false +} + +export { isScramble } diff --git a/String/Upper.js b/String/Upper.js new file mode 100644 index 0000000000..3008f3ea3a --- /dev/null +++ b/String/Upper.js @@ -0,0 +1,19 @@ +/** + * @function upper + * @description Will convert the entire string to uppercase letters. + * @param {String} str - The input string + * @return {String} Uppercase string + * @example upper("hello") => HELLO + * @example upper("He_llo") => HE_LLO + */ +const upper = (str) => { + if (typeof str !== 'string') { + throw new TypeError('Argument should be string') + } + + return str.replace(/[a-z]/g, (char) => + String.fromCharCode(char.charCodeAt() - 32) + ) +} + +export default upper diff --git a/String/ValidateCreditCard.js b/String/ValidateCreditCard.js new file mode 100644 index 0000000000..6fa3799cf5 --- /dev/null +++ b/String/ValidateCreditCard.js @@ -0,0 +1,63 @@ +/** + * Validate a given credit card number + * + * The core of the validation of credit card numbers is the Luhn algorithm. + * + * The validation sum should be completely divisible by 10 which is calculated as follows, + * every first digit is added directly to the validation sum. + * For every second digit in the credit card number, the digit is multiplied by 2. + * If the product is greater than 10 the digits of the product are added. + * This resultant digit is considered for the validation sum rather than the digit itself. + * + * Ref: https://www.geeksforgeeks.org/luhn-algorithm/ + */ + +const luhnValidation = (creditCardNumber) => { + let validationSum = 0 + creditCardNumber.split('').forEach((digit, index) => { + let currentDigit = parseInt(digit) + if (index % 2 === 0) { + // Multiply every 2nd digit from the left by 2 + currentDigit *= 2 + // if product is greater than 10 add the individual digits of the product to get a single digit + if (currentDigit > 9) { + currentDigit %= 10 + currentDigit += 1 + } + } + validationSum += currentDigit + }) + + return validationSum % 10 === 0 +} + +const validateCreditCard = (creditCardString) => { + const validStartSubString = ['4', '5', '6', '37', '34', '35'] // Valid credit card numbers start with these numbers + + if (typeof creditCardString !== 'string') { + throw new TypeError('The given value is not a string') + } + + const errorMessage = `${creditCardString} is an invalid credit card number because ` + if (isNaN(creditCardString)) { + throw new TypeError(errorMessage + 'it has nonnumerical characters.') + } + const creditCardStringLength = creditCardString.length + if (!(creditCardStringLength >= 13 && creditCardStringLength <= 16)) { + throw new Error(errorMessage + 'of its length.') + } + if ( + !validStartSubString.some((subString) => + creditCardString.startsWith(subString) + ) + ) { + throw new Error(errorMessage + 'of its first two digits.') + } + if (!luhnValidation(creditCardString)) { + throw new Error(errorMessage + 'it fails the Luhn check.') + } + + return true +} + +export { validateCreditCard } diff --git a/String/ValidateEmail.js b/String/ValidateEmail.js new file mode 100644 index 0000000000..0ea8849987 --- /dev/null +++ b/String/ValidateEmail.js @@ -0,0 +1,12 @@ +/** + * Returns whether the given string is a valid email address or not. + */ +const validateEmail = (str) => { + if (str === '' || str === null) { + throw new TypeError('Email Address String Null or Empty.') + } + + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str) +} + +export { validateEmail } diff --git a/String/ZFunction.js b/String/ZFunction.js new file mode 100644 index 0000000000..9d592a6ddc --- /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/AlphaNumericPalindrome.test.js b/String/test/AlphaNumericPalindrome.test.js new file mode 100644 index 0000000000..ab7373b53b --- /dev/null +++ b/String/test/AlphaNumericPalindrome.test.js @@ -0,0 +1,21 @@ +import alphaNumericPalindrome from '../AlphaNumericPalindrome' + +describe('Testing the alpha numeric palindrome', () => { + // should return true if the given string has alphanumeric characters that are palindrome irrespective of case and symbols + it('Testing with valid alphabetic palindrome', () => { + expect(alphaNumericPalindrome('eye')).toBe(true) + expect(alphaNumericPalindrome('Madam')).toBe(true) + expect(alphaNumericPalindrome('race CAR')).toBe(true) + expect(alphaNumericPalindrome('A man, a plan, a canal. Panama')).toBe(true) + }) + + it('Testing with number and symbol', () => { + expect(alphaNumericPalindrome('0_0 (: /-:) 0-0')).toBe(true) + expect(alphaNumericPalindrome('03_|53411435|_30')).toBe(true) + }) + + it('Testing with alphabets and symbols', () => { + expect(alphaNumericPalindrome('five|_/|evif')).toBe(true) + expect(alphaNumericPalindrome('five|_/|four')).toBe(false) + }) +}) diff --git a/String/test/AlternativeStringArrange.test.js b/String/test/AlternativeStringArrange.test.js new file mode 100644 index 0000000000..9ae657c6e2 --- /dev/null +++ b/String/test/AlternativeStringArrange.test.js @@ -0,0 +1,22 @@ +import { AlternativeStringArrange } from '../AlternativeStringArrange' + +test('AlternativeStringArrange(Agrtm, loih) -> Algorithm', () => { + const str1 = 'Agrtm' + const str2 = 'loih' + const res = AlternativeStringArrange(str1, str2) + expect(res).toEqual('Algorithm') +}) + +test('AlternativeStringArrange(JvSrp, aacit) -> JavaScript', () => { + const str1 = 'JvSrp' + const str2 = 'aacit' + const res = AlternativeStringArrange(str1, str2) + expect(res).toEqual('JavaScript') +}) + +test('AlternativeStringArrange(abc, def) -> adbecf', () => { + const str1 = 'abc' + const str2 = 'def' + const res = AlternativeStringArrange(str1, str2) + expect(res).toEqual('adbecf') +}) diff --git a/String/test/BoyerMoore.test.js b/String/test/BoyerMoore.test.js new file mode 100644 index 0000000000..0ae73dbf12 --- /dev/null +++ b/String/test/BoyerMoore.test.js @@ -0,0 +1,15 @@ +import { boyerMoore } from '../BoyerMoore' + +describe('Testing the boyer moore algorithm', () => { + it('Testing with alphabetical strings', () => { + expect(boyerMoore('THIS IS A TEST TEXT', 'TEST')).toBe(10) + expect(boyerMoore('AAIOOOAADDZXYCAADAABAABA', 'AADA')).toBe(14) + expect(boyerMoore('Hello World! This is a test case.', 'Boyer')).toBe(-1) + }) + + it('Testing with alphabets and symbols', () => { + expect(boyerMoore('AA&&@_OPOODDA##!', '@_')).toBe(4) + expect(boyerMoore('LK_||{{}}[[$($', '||')).toBe(3) + expect(boyerMoore('__||{{__+}}[[$($', '-}}')).toBe(-1) + }) +}) diff --git a/String/test/CheckAnagram.test.js b/String/test/CheckAnagram.test.js new file mode 100644 index 0000000000..f90bc922f6 --- /dev/null +++ b/String/test/CheckAnagram.test.js @@ -0,0 +1,177 @@ +import { checkAnagramMap, checkAnagramRegex } from '../CheckAnagram' + +describe('Testing checkAnagramRegex', () => { + it.each` + inputOne | inputTwo + ${123456} | ${'abcd'} + ${[1, 2, 3, 4, 5, 6]} | ${'abcd'} + ${{ test: 'test' }} | ${'abcd'} + ${'abcd'} | ${123456} + ${'abcd'} | ${[1, 2, 3, 4, 5, 6]} + ${'abcd'} | ${{ test: 'test' }} + `( + 'expects to throw the type Error given values $inputOne and $inputTwo', + ({ inputOne, inputTwo }) => { + expect(() => checkAnagramRegex(inputOne, inputTwo)).toThrowError() + } + ) + + it('expects to return false if the arguments have different lengths', () => { + const SUT = checkAnagramRegex('abs', 'abds') + expect(SUT).toBe(false) + }) + + it('expects to return false if the arguments are not anagrams', () => { + const SUT = checkAnagramRegex('abcs', 'abds') + expect(SUT).toBe(false) + }) + + it('expects to return true if the arguments are anagrams', () => { + const SUT = checkAnagramRegex('abcd', 'bcad') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments of length 1 and are the same letter', () => { + const SUT = checkAnagramRegex('a', 'a') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments of are both empty strings', () => { + const SUT = checkAnagramRegex('', '') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments are anagrams with an odd length', () => { + const SUT = checkAnagramRegex('abcde', 'edcab') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments are anagrams with an even length', () => { + const SUT = checkAnagramRegex('abcdef', 'fedcab') + expect(SUT).toBe(true) + }) + + it('expects to return false if either argument is an empty string while the other is not', () => { + const SUT = checkAnagramRegex('', 'edcab') + expect(SUT).toBe(false) + const SUT2 = checkAnagramRegex('edcab', '') + expect(SUT2).toBe(false) + }) + + it('expects to return true if the arguments contain the same letters but have unequal case', () => { + const SUT = checkAnagramRegex('ABDCE', 'abcde') + expect(SUT).toBe(true) + const SUT2 = checkAnagramRegex('AbCdE', 'aBCdE') + expect(SUT2).toBe(true) + const SUT3 = checkAnagramRegex('Eleven plus two', 'Twelve plus one') + expect(SUT3).toBe(true) + }) + + it('expects to return true if the arguments are anagrams and contain number characters', () => { + const SUT = checkAnagramRegex('a1b2', '12ba') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments are anagrams and contain space characters', () => { + const SUT = checkAnagramRegex('a1 b2', '1 2ba') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments are anagrams and contain punctuation characters', () => { + const SUT = checkAnagramRegex('a!1b@2', '1@2ba!') + expect(SUT).toBe(true) + }) + + it('expects to return false if the arguments contain the same letters but contain a different amount of space characters', () => { + const SUT = checkAnagramRegex('ea cb', 'e cba') + expect(SUT).toBe(false) + }) +}) + +describe('Testing checkAnagramMap', () => { + it.each` + inputOne | inputTwo + ${123456} | ${'abcd'} + ${[1, 2, 3, 4, 5, 6]} | ${'abcd'} + ${{ test: 'test' }} | ${'abcd'} + ${'abcd'} | ${123456} + ${'abcd'} | ${[1, 2, 3, 4, 5, 6]} + ${'abcd'} | ${{ test: 'test' }} + `( + 'expects to throw the type Error given values $inputOne and $inputTwo', + ({ inputOne, inputTwo }) => { + expect(() => checkAnagramMap(inputOne, inputTwo)).toThrowError() + } + ) + + it('expects to return false if the arguments have different lengths', () => { + const SUT = checkAnagramMap('abs', 'abds') + expect(SUT).toBe(false) + }) + + it('expects to return false if the arguments are not anagrams', () => { + const SUT = checkAnagramMap('abcs', 'abds') + expect(SUT).toBe(false) + }) + + it('expects to return true if the arguments are anagrams', () => { + const SUT = checkAnagramMap('abcd', 'bcad') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments of length 1 and are the same letter', () => { + const SUT = checkAnagramMap('a', 'a') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments of are both empty strings', () => { + const SUT = checkAnagramMap('', '') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments are anagrams with an odd length', () => { + const SUT = checkAnagramMap('abcde', 'edcab') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments are anagrams with an even length', () => { + const SUT = checkAnagramMap('abcdef', 'fedcab') + expect(SUT).toBe(true) + }) + + it('expects to return false if either argument is an empty string while the other is not', () => { + const SUT = checkAnagramMap('', 'edcab') + expect(SUT).toBe(false) + const SUT2 = checkAnagramMap('edcab', '') + expect(SUT2).toBe(false) + }) + + it('expects to return true if the arguments contain the same letters but have unequal case', () => { + const SUT = checkAnagramMap('ABDCE', 'abcde') + expect(SUT).toBe(true) + const SUT2 = checkAnagramMap('AbCdE', 'aBCdE') + expect(SUT2).toBe(true) + const SUT3 = checkAnagramMap('Eleven plus two', 'Twelve plus one') + expect(SUT3).toBe(true) + }) + + it('expects to return true if the arguments are anagrams and contain number characters', () => { + const SUT = checkAnagramMap('a1b2', '12ba') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments are anagrams and contain space characters', () => { + const SUT = checkAnagramMap('a1 b2', '1 2ba') + expect(SUT).toBe(true) + }) + + it('expects to return true if the arguments are anagrams and contain punctuation characters', () => { + const SUT = checkAnagramMap('a!1b@2', '1@2ba!') + expect(SUT).toBe(true) + }) + + it('expects to return false if the arguments contain the same letters but contain a different amount of space characters', () => { + const SUT = checkAnagramMap('ea cb', 'e cba') + expect(SUT).toBe(false) + }) +}) diff --git a/String/test/CheckCamelCase.test.js b/String/test/CheckCamelCase.test.js new file mode 100644 index 0000000000..5875e0cd10 --- /dev/null +++ b/String/test/CheckCamelCase.test.js @@ -0,0 +1,22 @@ +import { checkCamelCase } from '../CheckCamelCase' +describe('checkCamelCase', () => { + it('expect to throw an error if input is not a string', () => { + expect(() => checkCamelCase(null)).toThrow() + }) + + it('expects to return true if the input is in camel case format', () => { + const value = 'dockerBuild' + const result = checkCamelCase(value) + expect(result).toBe(true) + }) + + it('expects to return false if the input is not in camel case format', () => { + const value = 'docker_build' + const result = checkCamelCase(value) + expect(result).toBe(false) + }) + + it('should throw when input is not a string', () => { + expect(() => checkCamelCase(100)).toThrowError() + }) +}) diff --git a/String/test/CheckExceeding.test.js b/String/test/CheckExceeding.test.js new file mode 100644 index 0000000000..5318b7357b --- /dev/null +++ b/String/test/CheckExceeding.test.js @@ -0,0 +1,48 @@ +import { checkExceeding } from '../CheckExceeding' + +describe('Testing CheckExceeding function', () => { + it('Testing the invalid types', () => { + expect(() => checkExceeding(Math.random())).toThrow( + 'Argument is not a string' + ) + expect(() => checkExceeding(null)).toThrow('Argument is not a string') + expect(() => checkExceeding(false)).toThrow('Argument is not a string') + expect(() => checkExceeding(false)).toThrow('Argument is not a string') + }) + + it('Testing with empty string', () => { + expect(checkExceeding('')).toBe(true) + }) + + it('Testing with linear alphabets', () => { + expect(checkExceeding('a b c d e ')).toBe(true) + expect(checkExceeding('f g h i j ')).toBe(true) + expect(checkExceeding('k l m n o ')).toBe(true) + expect(checkExceeding('p q r s t ')).toBe(true) + expect(checkExceeding('u v w x y z')).toBe(true) + }) + + it('Testing not exceeding words', () => { + expect(checkExceeding('Hello')).toBe(false) + expect(checkExceeding('world')).toBe(false) + expect(checkExceeding('update')).toBe(false) + expect(checkExceeding('university')).toBe(false) + expect(checkExceeding('dog')).toBe(false) + expect(checkExceeding('exceeding')).toBe(false) + expect(checkExceeding('resolved')).toBe(false) + expect(checkExceeding('future')).toBe(false) + expect(checkExceeding('fixed')).toBe(false) + expect(checkExceeding('codes')).toBe(false) + expect(checkExceeding('facebook')).toBe(false) + expect(checkExceeding('vscode')).toBe(false) + }) + + it('Testing exceeding words', () => { + expect(checkExceeding('bee')).toBe(true) // [ 3 ] + expect(checkExceeding('can')).toBe(true) // [ 2, 13 ] + expect(checkExceeding('good')).toBe(true) // [ 8, 11 ] + expect(checkExceeding('bad')).toBe(true) // [ 1, 3 ] + expect(checkExceeding('play')).toBe(true) // [ 4, 11, 24 ] + expect(checkExceeding('delete')).toBe(true) // [1, 7, 7, 15, 15] + }) +}) diff --git a/String/test/CheckFlatCase.test.js b/String/test/CheckFlatCase.test.js new file mode 100644 index 0000000000..ccac811bf6 --- /dev/null +++ b/String/test/CheckFlatCase.test.js @@ -0,0 +1,22 @@ +import { checkFlatCase } from '../CheckFlatCase' + +describe('checkFlatCase function', () => { + it('should return false when the input string is not in flatcase', () => { + const actual = checkFlatCase('this is not in flatcase') + expect(actual).toBe(false) + }) + + it('should return true when the input string is a single letter character', () => { + const actual = checkFlatCase('a') + expect(actual).toBe(true) + }) + + it('should return true when the input string is a string of lowercase letter characters with no spaces', () => { + const actual = checkFlatCase('abcdefghijklmnopqrstuvwxyz') + expect(actual).toBe(true) + }) + + it('should throw when input is not a string', () => { + expect(() => checkFlatCase(100)).toThrowError() + }) +}) diff --git a/String/test/CheckKebabCase.test.js b/String/test/CheckKebabCase.test.js new file mode 100644 index 0000000000..239d91674e --- /dev/null +++ b/String/test/CheckKebabCase.test.js @@ -0,0 +1,17 @@ +import { CheckKebabCase } from '../CheckKebabCase' + +test('CheckKebabCase(The-Algorithms) -> true', () => { + const word = 'The-Algorithms' + const res = CheckKebabCase(word) + expect(res).toBeTruthy() +}) + +test('CheckKebabCase(The Algorithms) -> false', () => { + const word = 'The Algorithms' + const res = CheckKebabCase(word) + expect(res).toBeFalsy() +}) + +test('CheckKebabCase throws when input is not a string', () => { + expect(() => CheckKebabCase(100)).toThrowError() +}) diff --git a/String/test/CheckPalindrome.test.js b/String/test/CheckPalindrome.test.js new file mode 100644 index 0000000000..cfe88f7e53 --- /dev/null +++ b/String/test/CheckPalindrome.test.js @@ -0,0 +1,16 @@ +import { checkPalindrome } from '../CheckPalindrome' + +describe('checkPalindrome', () => { + it('expects to return "Palindrome" if the given string is a palindrome', () => { + const SUT = checkPalindrome('madam') + expect(SUT).toBe('Palindrome') + }) + it('expects to return "Empty string" if the given string is empty', () => { + const SUT = checkPalindrome('') + expect(SUT).toBe('Empty string') + }) + it('expects to return "Not a string" if the given string is not a string', () => { + const SUT = checkPalindrome(123) + expect(SUT).toBe('Not a string') + }) +}) diff --git a/String/test/CheckPangram.test.js b/String/test/CheckPangram.test.js new file mode 100644 index 0000000000..fa6e6ec69d --- /dev/null +++ b/String/test/CheckPangram.test.js @@ -0,0 +1,67 @@ +import { checkPangramRegex, checkPangramSet } from '../CheckPangram' + +describe('Testing checkPangramRegex function', () => { + it('"The quick brown fox jumps over the lazy dog" is a pangram', () => { + expect( + checkPangramRegex('The quick brown fox jumps over the lazy dog') + ).toBe(true) + }) + + it('"Waltz, bad nymph, for quick jigs vex." is a pangram', () => { + expect(checkPangramRegex('Waltz, bad nymph, for quick jigs vex.')).toBe( + true + ) + }) + + it('"Jived fox nymph grabs quick waltz." is a pangram', () => { + expect(checkPangramRegex('Jived fox nymph grabs quick waltz.')).toBe(true) + }) + + it('"My name is Unknown" is NOT a pangram', () => { + expect(checkPangramRegex('My name is Unknown')).toBe(false) + }) + + it('"The quick brown fox jumps over the la_y dog" is NOT a pangram', () => { + expect( + checkPangramRegex('The quick brown fox jumps over the la_y dog') + ).toBe(false) + }) + + it('Throws an error if given param is not a string', () => { + expect(() => { + checkPangramRegex(undefined) + }).toThrow('The given value is not a string') + }) +}) + +describe('Testing checkPangramSet function', () => { + it('"The quick brown fox jumps over the lazy dog" is a pangram', () => { + expect(checkPangramSet('The quick brown fox jumps over the lazy dog')).toBe( + true + ) + }) + + it('"Waltz, bad nymph, for quick jigs vex." is a pangram', () => { + expect(checkPangramSet('Waltz, bad nymph, for quick jigs vex.')).toBe(true) + }) + + it('"Jived fox nymph grabs quick waltz." is a pangram', () => { + expect(checkPangramSet('Jived fox nymph grabs quick waltz.')).toBe(true) + }) + + it('"My name is Unknown" is NOT a pangram', () => { + expect(checkPangramSet('My name is Unknown')).toBe(false) + }) + + it('"The quick brown fox jumps over the la_y dog" is NOT a pangram', () => { + expect(checkPangramSet('The quick brown fox jumps over the la_y dog')).toBe( + false + ) + }) + + it('Throws an error if given param is not a string', () => { + expect(() => { + checkPangramSet(undefined) + }).toThrow('The given value is not a string') + }) +}) diff --git a/String/test/CheckPascalCase.test.js b/String/test/CheckPascalCase.test.js new file mode 100644 index 0000000000..139b66844b --- /dev/null +++ b/String/test/CheckPascalCase.test.js @@ -0,0 +1,23 @@ +import { CheckPascalCase } from '../CheckPascalCase' + +test('CheckPascalCase(TheAlgorithms) -> true', () => { + const word = 'TheAlgorithms' + const res = CheckPascalCase(word) + expect(res).toBeTruthy() +}) + +test('CheckPascalCase(theAlgorithms) -> false', () => { + const word = 'theAlgorithms' + const res = CheckPascalCase(word) + expect(res).toBeFalsy() +}) + +test('CheckPascalCase(The Algorithms) -> false', () => { + const word = 'The Algorithms' + const res = CheckPascalCase(word) + expect(res).toBeFalsy() +}) + +test('CheckPascalCase throws when input is not a string', () => { + expect(() => CheckPascalCase(100)).toThrowError() +}) diff --git a/String/test/CheckRearrangePalindrome.test.js b/String/test/CheckRearrangePalindrome.test.js new file mode 100644 index 0000000000..09429b7257 --- /dev/null +++ b/String/test/CheckRearrangePalindrome.test.js @@ -0,0 +1,25 @@ +import { palindromeRearranging } from '../CheckRearrangePalindrome' + +test('palindromeRearranging(apple) -> false', () => { + const word = 'apple' + const res = palindromeRearranging(word) + expect(res).toBeFalsy() +}) + +test('palindromeRearranging(aapplle) -> true', () => { + const word = 'aapplle' + const res = palindromeRearranging(word) + expect(res).toBeTruthy() +}) + +test('palindromeRearranging(value) -> false', () => { + const word = 'value' + const res = palindromeRearranging(word) + expect(res).toBeFalsy() +}) + +test('palindromeRearranging(aaeccrr) -> true', () => { + const word = 'aaeccrr' + const res = palindromeRearranging(word) + expect(res).toBeTruthy() +}) diff --git a/String/test/CheckSnakeCase.test.js b/String/test/CheckSnakeCase.test.js new file mode 100644 index 0000000000..dabe73a990 --- /dev/null +++ b/String/test/CheckSnakeCase.test.js @@ -0,0 +1,18 @@ +import { checkSnakeCase } from '../CheckSnakeCase' +describe('checkSnakeCase', () => { + it('expect to throw an error if input is not a string', () => { + expect(() => checkSnakeCase(0)).toThrow() + }) + + it('expects to return true if the input is in snake case format', () => { + const value = 'docker_build' + const result = checkSnakeCase(value) + expect(result).toBe(true) + }) + + it('expects to return false if the input is not in snake case format', () => { + const value = 'dockerBuild' + const result = checkSnakeCase(value) + expect(result).toBe(false) + }) +}) diff --git a/String/test/CheckWordOcurrence.test.js b/String/test/CheckWordOcurrence.test.js new file mode 100644 index 0000000000..910dd39c3a --- /dev/null +++ b/String/test/CheckWordOcurrence.test.js @@ -0,0 +1,48 @@ +import { checkWordOccurrence } from '../CheckWordOccurrence' + +describe('Testing checkWordOccurrence', () => { + it('expects throw on insert wrong string', () => { + const value = 123 + + expect(() => checkWordOccurrence(value)).toThrow() + }) + + it('expect throw on insert wrong param for case sensitive', () => { + const value = 'hello' + + expect(() => checkWordOccurrence(value, value)).toThrow() + }) + + it('check occurrence with case sensitive', () => { + const stringToTest = 'The quick brown fox jumps over the lazy dog' + const expectResult = { + The: 1, + quick: 1, + brown: 1, + fox: 1, + jumps: 1, + over: 1, + the: 1, + lazy: 1, + dog: 1 + } + + expect(checkWordOccurrence(stringToTest)).toEqual(expectResult) + }) + + it('check occurrence with case insensitive', () => { + const stringToTest = 'The quick brown fox jumps over the lazy dog' + const expectResult = { + the: 2, + quick: 1, + brown: 1, + fox: 1, + jumps: 1, + over: 1, + lazy: 1, + dog: 1 + } + + expect(checkWordOccurrence(stringToTest, true)).toEqual(expectResult) + }) +}) diff --git a/String/test/CountLetters.test.js b/String/test/CountLetters.test.js new file mode 100644 index 0000000000..424dcfa31d --- /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/CountSubstrings.test.js b/String/test/CountSubstrings.test.js new file mode 100644 index 0000000000..66628b1a14 --- /dev/null +++ b/String/test/CountSubstrings.test.js @@ -0,0 +1,52 @@ +import { countSubstrings } from '../CountSubstrings' + +describe('CountSubstrings', () => { + it('count multiple occurrences of substring in a string', () => { + const str = 'This is a string' + const substring = 'is' + const count = countSubstrings(str, substring) + expect(count).toBe(2) + }) + + it('should return 0 when input substring has no occurrences', () => { + const str = 'Jurassic Park' + const substring = 'World' + const count = countSubstrings(str, substring) + expect(count).toBe(0) + }) + + it('should return 1 when input substring is of length 1 that is equal to string', () => { + const str = 's' + const substring = 's' + const count = countSubstrings(str, substring) + expect(count).toBe(1) + }) + + it('should return the correct result when input string contains spaces', () => { + const str = 'ab cd ef ghi' + const substring = ' ' + const count = countSubstrings(str, substring) + expect(count).toBe(4) + }) + + it('should return the correct result when input substring contains number or special characters', () => { + const str = 'abc1@2def1@2' + const substring = '1@2' + const count = countSubstrings(str, substring) + expect(count).toBe(2) + }) + + it('should return string.length + 1 when the input substring is an empty string', () => { + const str = 'empty' + const substring = '' + const count = countSubstrings(str, substring) + expect(count).toBe(6) + }) + + it('should return correct result when input is overlapping substring', () => { + const str = 'aaa' + const substring = 'aa' + const count = countSubstrings(str, substring) + expect(count).toBe(2) + }) +}) diff --git a/String/test/CountVowels.test.js b/String/test/CountVowels.test.js new file mode 100644 index 0000000000..b05d60e145 --- /dev/null +++ b/String/test/CountVowels.test.js @@ -0,0 +1,67 @@ +import { countVowels } from '../CountVowels' + +describe('CountVowels', () => { + it('expect throws on use wrong param', () => { + expect(() => countVowels(0)).toThrow() + }) + + it('count the vowels in a string', () => { + const value = 'Mad World' + const count = countVowels(value) + expect(count).toBe(2) + }) + + it('should return 0 when input is a string with no vowels', () => { + const value = 'bcdfgh' + const count = countVowels(value) + expect(count).toBe(0) + }) + + it('should return 1 when input is a string of length 1 that is a vowel', () => { + const value = 'a' + const count = countVowels(value) + expect(count).toBe(1) + }) + + it('should return the correct result when input is in all uppercase letters', () => { + const value = 'ABCDE' + const count = countVowels(value) + expect(count).toBe(2) + }) + + it('should return the correct result when input is in all lowercase letters', () => { + const value = 'abcdefghi' + const count = countVowels(value) + expect(count).toBe(3) + }) + + it('should return the correct result when input string contains spaces', () => { + const value = 'abc def ghi' + const count = countVowels(value) + expect(count).toBe(3) + }) + + it('should return the correct result when input contains number characters', () => { + const value = 'a1b2c3' + const count = countVowels(value) + expect(count).toBe(1) + }) + + it('should return the correct result when input contains punctuation characters', () => { + const value = 'a!b.ce)' + const count = countVowels(value) + expect(count).toBe(2) + }) + + it('should return 0 when the input is an empty string', () => { + const value = '' + const count = countVowels(value) + expect(count).toBe(0) + }) + + it('should count multiple occurrences of the same vowel in the input', () => { + const value = 'aaaaa' + const count = countVowels(value) + expect(count).toBe(5) + }) +}) diff --git a/String/test/CreatePermutations.test.js b/String/test/CreatePermutations.test.js new file mode 100644 index 0000000000..5bb82848c0 --- /dev/null +++ b/String/test/CreatePermutations.test.js @@ -0,0 +1,14 @@ +import { createPermutations } from '../CreatePermutations' + +describe('createPermutations', () => { + it('expects to generate 6 different combinations', () => { + const text = 'abc' + const SUT = createPermutations(text) + expect(SUT).toStrictEqual(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']) + }) + it('expects to generate 2 different combinations', () => { + const text = '12' + const SUT = createPermutations(text) + expect(SUT).toStrictEqual(['12', '21']) + }) +}) diff --git a/String/test/DiceCoefficient.test.js b/String/test/DiceCoefficient.test.js new file mode 100644 index 0000000000..cdad3d4c7a --- /dev/null +++ b/String/test/DiceCoefficient.test.js @@ -0,0 +1,21 @@ +import { diceCoefficient } from '../DiceCoefficient' + +describe('diceCoefficient', () => { + it('should calculate edit distance between two strings', () => { + // equal strings return 1 (max possible value) + expect(diceCoefficient('abc', 'abc')).toBe(1) + expect(diceCoefficient('', '')).toBe(1) + + // string length needs to be at least 2 (unless equal) + expect(diceCoefficient('a', '')).toBe(0) + expect(diceCoefficient('', 'a')).toBe(0) + + expect(diceCoefficient('skate', 'ate')).toBe(0.66) + + expect(diceCoefficient('money', 'honey')).toBe(0.75) + + expect(diceCoefficient('love', 'hate')).toBe(0) + + expect(diceCoefficient('skilled', 'killed')).toBe(0.9) + }) +}) diff --git a/String/test/FirstUniqueCharacter.test.js b/String/test/FirstUniqueCharacter.test.js new file mode 100644 index 0000000000..b2bc465018 --- /dev/null +++ b/String/test/FirstUniqueCharacter.test.js @@ -0,0 +1,9 @@ +import { firstUniqChar } from '../FirstUniqueCharacter' + +describe('firstUniqChar', () => { + it('locates the index of first unique character in the string', () => { + expect(firstUniqChar('javascript')).toEqual(0) + expect(firstUniqChar('sesquipedalian')).toEqual(3) + expect(firstUniqChar('aabb')).toEqual(-1) + }) +}) diff --git a/String/test/FormatPhoneNumber.test.js b/String/test/FormatPhoneNumber.test.js new file mode 100644 index 0000000000..920e63a543 --- /dev/null +++ b/String/test/FormatPhoneNumber.test.js @@ -0,0 +1,17 @@ +import formatPhoneNumber from '../FormatPhoneNumber' + +describe('Testing the formatPhoneNumber functions', () => { + it('expects to throw a type error', () => { + expect(() => formatPhoneNumber('1234567')).toThrow('Invalid phone number!') + expect(() => formatPhoneNumber('123456text')).toThrow( + 'Invalid phone number!' + ) + expect(() => formatPhoneNumber(12345)).toThrow('Invalid phone number!') + }) + + it('expects to return the formatted phone number', () => { + expect(formatPhoneNumber('1234567890')).toEqual('(123) 456-7890') + expect(formatPhoneNumber('2124323322')).toEqual('(212) 432-3322') + expect(formatPhoneNumber('1721543455')).toEqual('(172) 154-3455') + }) +}) diff --git a/String/test/HammingDistance.test.js b/String/test/HammingDistance.test.js new file mode 100644 index 0000000000..bc52cafc64 --- /dev/null +++ b/String/test/HammingDistance.test.js @@ -0,0 +1,21 @@ +import { hammingDistance } from '../HammingDistance' + +test('should throw an error when trying to compare the strings of different length', () => { + const compareStringsOfDifferentLength = () => { + hammingDistance('abc', 'abcd') + } + + expect(compareStringsOfDifferentLength).toThrowError() +}) + +test('should calculate difference between two strings', () => { + expect(hammingDistance('a', 'a')).toBe(0) +}) + +test('should calculate difference between two strings', () => { + expect(hammingDistance('abc', 'add')).toBe(2) +}) + +test('should calculate difference between two strings', () => { + expect(hammingDistance('1011101', '1001001')).toBe(2) +}) diff --git a/String/test/IsPalindrome.test.js b/String/test/IsPalindrome.test.js new file mode 100644 index 0000000000..bec42aca26 --- /dev/null +++ b/String/test/IsPalindrome.test.js @@ -0,0 +1,33 @@ +import { isPalindromeIterative } from '../IsPalindrome' + +describe('isPalindrome', () => { + it('expects to return true with empty string', () => { + expect(isPalindromeIterative('')).toEqual(true) + }) + + it('expects to return true when length of input is 1', () => { + const numberInput = 6 + const stringInput = 'a' + expect(isPalindromeIterative(numberInput)).toEqual(true) + expect(isPalindromeIterative(stringInput)).toEqual(true) + }) + + it('expects to return true when input is palindrome', () => { + expect(isPalindromeIterative(121)).toEqual(true) + expect(isPalindromeIterative('yooy')).toEqual(true) + expect(isPalindromeIterative('19noon91')).toEqual(true) + expect(isPalindromeIterative('!*tyyt*!')).toEqual(true) + }) + + it('expects to return false when input is not palindrome', () => { + expect(isPalindromeIterative('hello')).toEqual(false) + expect(isPalindromeIterative(189)).toEqual(false) + expect(isPalindromeIterative('!*98[!')).toEqual(false) + }) + + it('expects to throw error when input is not a string or a number', () => { + expect(() => isPalindromeIterative(undefined)).toThrowError() + expect(() => isPalindromeIterative({ key: 'val' })).toThrowError() + expect(() => isPalindromeIterative([])).toThrowError() + }) +}) diff --git a/String/test/KMPPatternSearching.test.js b/String/test/KMPPatternSearching.test.js new file mode 100644 index 0000000000..fde89083d7 --- /dev/null +++ b/String/test/KMPPatternSearching.test.js @@ -0,0 +1,27 @@ +import { KMPSearch } from '../KMPPatternSearching' + +describe('KMP Matcher', () => { + it('TC1: expects to return matching indices for pattern in text', () => { + const text = 'ABC ABCDAB ABCDABCDABDE' + const pattern = 'ABCDABD' + expect(KMPSearch(text, pattern)).toStrictEqual([15]) + }) + + it('TC2: expects to return matching indices for pattern in text', () => { + const text = 'ABC ABCDABD ABCDABCDABDE' + const pattern = 'ABCDABD' + expect(KMPSearch(text, pattern)).toStrictEqual([4, 16]) + }) + + it('TC3: expects to return matching indices for pattern in text', () => { + const text = 'AAAAA' + const pattern = 'AAA' + expect(KMPSearch(text, pattern)).toStrictEqual([0, 1, 2]) + }) + + it('TC4: expects to return matching indices for pattern in text', () => { + const text = 'ABCD' + const pattern = 'BA' + expect(KMPSearch(text, pattern)).toStrictEqual([]) + }) +}) diff --git a/String/test/LengthofLongestSubstringWithoutRepetition.test.js b/String/test/LengthofLongestSubstringWithoutRepetition.test.js new file mode 100644 index 0000000000..e2cb565619 --- /dev/null +++ b/String/test/LengthofLongestSubstringWithoutRepetition.test.js @@ -0,0 +1,26 @@ +import { lengthOfLongestSubstring } from '../LengthofLongestSubstringWithoutRepetition' + +describe('LengthOfLongestSubstring', () => { + it('should throw error if parameter is not string', () => { + expect(() => lengthOfLongestSubstring(345)).toThrowError(TypeError) + expect(() => lengthOfLongestSubstring(true)).toThrowError(TypeError) + expect(() => lengthOfLongestSubstring(null)).toThrowError(TypeError) + }) + + it('should check substrings containing unique characters', () => { + expect(lengthOfLongestSubstring('abcabcbb')).toBe(3) + expect(lengthOfLongestSubstring('bbbbb')).toBe(1) + expect(lengthOfLongestSubstring('pwwkew')).toBe(3) + expect(lengthOfLongestSubstring(' ')).toBe(1) + expect(lengthOfLongestSubstring('abcdefghijklmnaaaaa')).toBe(14) + }) + + it('should give zero for empty strings', () => { + expect(lengthOfLongestSubstring('')).toBe(0) + }) + + it('should be case-sensitive', () => { + expect(lengthOfLongestSubstring('AaBbCc')).toBe(6) + expect(lengthOfLongestSubstring('AbCdEf')).toBe(6) + }) +}) diff --git a/String/test/LevenshteinDistance.test.js b/String/test/LevenshteinDistance.test.js new file mode 100644 index 0000000000..a901fd7cae --- /dev/null +++ b/String/test/LevenshteinDistance.test.js @@ -0,0 +1,26 @@ +import { levenshteinDistance } from '../LevenshteinDistance' + +describe('levenshteinDistance', () => { + it('should calculate edit distance between two strings', () => { + expect(levenshteinDistance('', '')).toBe(0) + expect(levenshteinDistance('a', '')).toBe(1) + expect(levenshteinDistance('', 'a')).toBe(1) + expect(levenshteinDistance('abc', '')).toBe(3) + expect(levenshteinDistance('', 'abc')).toBe(3) + + // Should just add I to the beginning. + expect(levenshteinDistance('igloo', 'gloo')).toBe(1) + + // Should just substitute i with o, m with g and insert e at end + expect(levenshteinDistance('firm', 'forge')).toBe(3) + + // Should just substitute f with s, g with t and delete h + expect(levenshteinDistance('fighting', 'sitting')).toBe(3) + + // Should add 4 letters b, a, s and e at the beginning. + expect(levenshteinDistance('ball', 'baseball')).toBe(4) + + // Should delete 4 letters b, a, s and e at the beginning and replace the last 4 with f, o, o, t + expect(levenshteinDistance('baseball', 'foot')).toBe(8) + }) +}) diff --git a/String/test/Lower.test.js b/String/test/Lower.test.js new file mode 100644 index 0000000000..01fbd7c2ba --- /dev/null +++ b/String/test/Lower.test.js @@ -0,0 +1,19 @@ +import lower from '../Lower' + +describe('Testing the Lower function', () => { + it('Test 1: Check by invalid type', () => { + expect(() => lower(345)).toThrowError() + expect(() => lower(true)).toThrowError() + expect(() => lower(null)).toThrowError() + }) + + it('Test 2: Check by uppercase string', () => { + expect(lower('WORLD')).toBe('world') + expect(lower('Hello_WORLD')).toBe('hello_world') + }) + + it('Test 3: Check by lowercase string', () => { + expect(lower('hello')).toBe('hello') + expect(lower('hello_world')).toBe('hello_world') + }) +}) diff --git a/String/test/MaxCharacter.test.js b/String/test/MaxCharacter.test.js new file mode 100644 index 0000000000..d73af578ac --- /dev/null +++ b/String/test/MaxCharacter.test.js @@ -0,0 +1,21 @@ +import maxCharacter from '../MaxCharacter' + +describe('Testing the maxCharacter function', () => { + it('Expect throw with wrong arg', () => { + expect(() => maxCharacter(123)).toThrow() + expect(() => maxCharacter('')).toThrow() + }) + + it('Check the max character in string', () => { + const theString = "I can't do that" + const maxCharInAllCount = maxCharacter(theString) + const maxChar = maxCharacter(theString, /\s/) + + expect(maxCharInAllCount).toBe(' ') + expect(maxChar).toBe('t') + + expect(maxCharacter('!!!Hello, World!!!', /[a-z]/)).toBe('!') + + expect(maxCharacter('!!!Hello, World!!!', /[^a-z]/i)).toBe('l') + }) +}) diff --git a/String/test/MaxWord.test.js b/String/test/MaxWord.test.js new file mode 100644 index 0000000000..b2d902c54a --- /dev/null +++ b/String/test/MaxWord.test.js @@ -0,0 +1,12 @@ +import { maxWord } from '../MaxWord' + +describe('Testing the maxWord function', () => { + it('Expect throw with non string argument', () => { + expect(() => maxWord(10)).toThrow() + }) + it('get the max word', () => { + const string = 'be be be be a a banana' + const mostOccurringWord = maxWord(string) + expect(mostOccurringWord).toBe('be') + }) +}) diff --git a/String/test/PatternMatching.test.js b/String/test/PatternMatching.test.js new file mode 100644 index 0000000000..aff4e70d83 --- /dev/null +++ b/String/test/PatternMatching.test.js @@ -0,0 +1,28 @@ +import { checkIfPatternExists } from '../PatternMatching' +describe('checkIfPatternExists', () => { + it('expects to find a pattern with correct input', () => { + const text = 'AABAACAADAABAAAABAA' + const pattern = 'AABA' + const SUT = checkIfPatternExists(text.toLowerCase(), pattern.toLowerCase()) + expect(SUT).toBe('Given pattern is found at index 0') + }) + it('expects to return a message when there is no pattern', () => { + const text = 'ABCDEFG' + const pattern = 'AEG' + const SUT = checkIfPatternExists(text.toLowerCase(), pattern.toLowerCase()) + expect(SUT).toBe(undefined) + }) + it('expects to find a pattern independent of casing', () => { + const text = 'AbCAAAAAAB' + const pattern = 'abc' + const SUT = checkIfPatternExists(text, pattern) + expect(SUT).toBe(undefined) + }) + it('expects to throw an error message when given input is not a string', () => { + const text = 123444456 + const pattern = 123 + expect(() => checkIfPatternExists(text, pattern)).toThrow( + 'Given input is not a string' + ) + }) +}) diff --git a/String/test/PercentageOfLetters.test.js b/String/test/PercentageOfLetters.test.js new file mode 100644 index 0000000000..7a58dd0ecc --- /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/PermutateString.test.js b/String/test/PermutateString.test.js new file mode 100644 index 0000000000..4b09fece8a --- /dev/null +++ b/String/test/PermutateString.test.js @@ -0,0 +1,31 @@ +import { permutate } from '../PermutateString' + +describe('Permutate a string', () => { + it('expects to throw an Error with an empty string', () => { + expect(() => { + permutate() + }).toThrow('The arg must be a valid, non empty string') + }) + it('expects to permute "no" into [no, on]', () => { + expect(['no', 'on']).toEqual(permutate('no')) + }) + it('expects to permute "yes" into [esy, eys, sey, sye, yes, yse]', () => { + expect(['esy', 'eys', 'sey', 'sye', 'yes', 'yse']).toEqual(permutate('yes')) + }) + it('expects to permute "good" into [dgoo dogo doog gdoo godo good odgo odog ogdo ogod oodg oogd ]', () => { + expect([ + 'dgoo', + 'dogo', + 'doog', + 'gdoo', + 'godo', + 'good', + 'odgo', + 'odog', + 'ogdo', + 'ogod', + 'oodg', + 'oogd' + ]).toEqual(permutate('good')) + }) +}) diff --git a/String/test/ReverseString.test.js b/String/test/ReverseString.test.js new file mode 100644 index 0000000000..45adeda4c1 --- /dev/null +++ b/String/test/ReverseString.test.js @@ -0,0 +1,51 @@ +import { + ReverseStringIterative, + ReverseStringIterativeInplace +} from '../ReverseString' + +describe('ReverseStringIterative', () => { + it('expects to reverse a simple string', () => { + expect(ReverseStringIterative('reverse')).toEqual('esrever') + expect(ReverseStringIterative('some')).toEqual('emos') + expect(ReverseStringIterative('string')).toEqual('gnirts') + expect(ReverseStringIterative('The Algorithms Javascript')).toEqual( + 'tpircsavaJ smhtiroglA ehT' + ) + }) + + it('expects to reverse a string with spaces in between', () => { + expect(ReverseStringIterative('reverse me')).toEqual('em esrever') + }) + + it('expects to reverse a simple string without capitalizing the first letter', () => { + expect(ReverseStringIterative('Javascript')).toEqual('tpircsavaJ') + }) + + it.each` + input + ${123456} + ${[1, 2, 3, 4, 5, 6]} + ${{ test: 'test' }} + ${null} + `( + 'expects to throw a type error given a value that is $input', + ({ input }) => { + expect(() => ReverseStringIterative(input)).toThrow( + 'The given value is not a string' + ) + } + ) + + it('expects to return a empty string with an empty string is given', () => { + expect(ReverseStringIterative('')).toEqual('') + }) +}) + +describe('ReverseStringIterativeInplace', () => { + it.each([ + ['hello', 'olleh'], + ['word', 'drow'] + ])('reverse of %s is %s', (value, expected) => { + expect(ReverseStringIterativeInplace(value)).toBe(expected) + }) +}) diff --git a/String/test/ReverseWords.test.js b/String/test/ReverseWords.test.js new file mode 100644 index 0000000000..78abb8a879 --- /dev/null +++ b/String/test/ReverseWords.test.js @@ -0,0 +1,26 @@ +import reverseWords from '../ReverseWords' + +describe('Testing the reverseWords function', () => { + it.each` + input + ${123456} + ${[1, 2, 3, 4, 5, 6]} + ${{ test: 'test' }} + ${null} + `( + 'expects to throw a type error given a value that is $input', + ({ input }) => { + expect(() => { + reverseWords(input) + }).toThrow('The given value is not a string') + } + ) + + it('expects to reverse words to return a joined word', () => { + expect(reverseWords('I Love JS')).toBe('JS Love I') + expect(reverseWords('Hello World')).toBe('World Hello') + expect(reverseWords('The Algorithms Javascript')).toBe( + 'Javascript Algorithms The' + ) + }) +}) diff --git a/String/test/ScrambleStrings.test.js b/String/test/ScrambleStrings.test.js new file mode 100644 index 0000000000..dcc7569540 --- /dev/null +++ b/String/test/ScrambleStrings.test.js @@ -0,0 +1,15 @@ +import { isScramble } from '../ScrambleStrings' + +describe('ScrambleStrings', () => { + it('expects to return true for same string', () => { + expect(isScramble('a', 'a')).toBe(true) + }) + + it('expects to return false for non-scrambled strings', () => { + expect(isScramble('abcde', 'caebd')).toBe(false) + }) + + it('expects to return true for scrambled strings', () => { + expect(isScramble('great', 'rgeat')).toBe(true) + }) +}) diff --git a/String/test/Upper.test.js b/String/test/Upper.test.js new file mode 100644 index 0000000000..1cc227689c --- /dev/null +++ b/String/test/Upper.test.js @@ -0,0 +1,9 @@ +import upper from '../Upper' + +describe('Testing the Upper function', () => { + it('return uppercase strings', () => { + expect(upper('hello')).toBe('HELLO') + expect(upper('WORLD')).toBe('WORLD') + expect(upper('hello_WORLD')).toBe('HELLO_WORLD') + }) +}) diff --git a/String/test/ValidateCreditCard.test.js b/String/test/ValidateCreditCard.test.js new file mode 100644 index 0000000000..bd80df5f9b --- /dev/null +++ b/String/test/ValidateCreditCard.test.js @@ -0,0 +1,61 @@ +import { validateCreditCard } from '../ValidateCreditCard' + +describe('Validate credit card number', () => { + it('should throw error if card number is boolean', () => { + const invalidCC = true + expect(() => validateCreditCard(invalidCC)).toThrow( + 'The given value is not a string' + ) + }) + it('returns true if the credit card number is valid', () => { + const validCreditCard = '4111111111111111' + const validationResult = validateCreditCard(validCreditCard) + expect(validationResult).toBe(true) + }) + it('should throw an error on non-numeric character in given credit card number', () => { + const nonNumericCCNumbers = ['123ABCDEF', 'ABCDKDKD', 'ADS232'] + nonNumericCCNumbers.forEach((nonNumericCC) => + expect(() => validateCreditCard(nonNumericCC)).toThrow( + `${nonNumericCC} is an invalid credit card number because ` + + 'it has nonnumerical characters.' + ) + ) + }) + it('should throw an error on credit card with invalid length', () => { + const ccWithInvalidLength = ['41111', '4111111111111111111111'] + ccWithInvalidLength.forEach((invalidCC) => + expect(() => validateCreditCard(invalidCC)).toThrow( + `${invalidCC} is an invalid credit card number because ` + + 'of its length.' + ) + ) + }) + it('should throw an error on credit card with invalid start substring', () => { + const ccWithInvalidStartSubstring = [ + '12345678912345', + '23456789123456', + '789123456789123', + '891234567891234', + '912345678912345', + '31345678912345', + '32345678912345', + '33345678912345', + '38345678912345' + ] + ccWithInvalidStartSubstring.forEach((invalidCC) => + expect(() => validateCreditCard(invalidCC)).toThrow( + `${invalidCC} is an invalid credit card number because ` + + 'of its first two digits.' + ) + ) + }) + it('should throw an error on credit card with luhn check fail', () => { + const invalidCCs = ['411111111111111', '371211111111111', '49999999999999'] + invalidCCs.forEach((invalidCC) => + expect(() => validateCreditCard(invalidCC)).toThrow( + `${invalidCC} is an invalid credit card number because ` + + 'it fails the Luhn check.' + ) + ) + }) +}) diff --git a/String/test/ValidateEmail.test.js b/String/test/ValidateEmail.test.js new file mode 100644 index 0000000000..1f3aa70ddc --- /dev/null +++ b/String/test/ValidateEmail.test.js @@ -0,0 +1,28 @@ +import { validateEmail } from '../ValidateEmail' + +describe('Validation of an Email Address', () => { + it('expects to return false', () => { + expect(validateEmail('mahfoudh.arous.com')).toEqual(false) + }) + + it('expects to return false', () => { + expect(validateEmail('mahfoudh.arous@com')).toEqual(false) + }) + + it('expects to return true', () => { + expect(validateEmail('mahfoudh.arous@gmail.com')).toEqual(true) + }) + + it('expects to return true', () => { + expect(validateEmail('icristianbaciu@.helsinki.edu')).toEqual(true) + }) + + it('expects to throw a type error', () => { + expect(() => { + validateEmail('') + }).toThrow('Email Address String Null or Empty.') + expect(() => { + validateEmail(null) + }).toThrow('Email Address String Null or Empty.') + }) +}) diff --git a/String/test/ZFunction.test.js b/String/test/ZFunction.test.js new file mode 100644 index 0000000000..a945f2804f --- /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/Timing-Functions/GetMonthDays.js b/Timing-Functions/GetMonthDays.js new file mode 100644 index 0000000000..43c2e7e6e6 --- /dev/null +++ b/Timing-Functions/GetMonthDays.js @@ -0,0 +1,38 @@ +/** + function that takes month number and its year and returns the number of days within it + * @param monthNumber. + * @param year. + e.g.: mahfoudh.arous@gmail.com -> true + e.g.: mahfoudh.arous.com ->false +*/ + +import { isLeapYear } from '../Maths/LeapYear' + +const getMonthDays = (monthNumber, year) => { + const the31DaysMonths = [1, 3, 5, 7, 8, 10, 12] + const the30DaysMonths = [4, 6, 9, 11] + + if ( + !the31DaysMonths.includes(monthNumber) && + !the30DaysMonths.includes(monthNumber) && + monthNumber !== 2 + ) { + throw new TypeError('Invalid Month Number.') + } + + if (the31DaysMonths.includes(monthNumber)) { + return 31 + } + + if (the30DaysMonths.includes(monthNumber)) { + return 30 + } + + if (isLeapYear(year)) { + return 29 + } + + return 28 +} + +export { getMonthDays } diff --git a/TimingFunctions/IntervalTimer.js b/Timing-Functions/IntervalTimer.js similarity index 85% rename from TimingFunctions/IntervalTimer.js rename to Timing-Functions/IntervalTimer.js index 2864a32c27..4eb34041a0 100644 --- a/TimingFunctions/IntervalTimer.js +++ b/Timing-Functions/IntervalTimer.js @@ -12,8 +12,7 @@ class IntervalTimer { * @param callBack The callback function to be executed. * @return {IntervalTimer} If exists, the existing object. */ - constructor (interval = 10, - callBack = () => {}) { + constructor(interval = 10, callBack = () => {}) { this.prevInterval = 0 if (this.instance == null) { this.interval = interval @@ -27,7 +26,7 @@ class IntervalTimer { /** * @description Starts the timer. */ - startTimer () { + startTimer() { this.timer = setInterval(this.callBack, this.interval) } @@ -35,7 +34,7 @@ class IntervalTimer { * @description Resets the timer. * @return {number} Elapsed time in milliseconds. */ - resetTimer () { + resetTimer() { clearInterval(this.timer) this.callBack = () => {} return this.getElapsedTime() @@ -44,7 +43,7 @@ class IntervalTimer { /** * @return {number} Elapsed time in milliseconds since reset. */ - getElapsedTime (offset = 0) { + getElapsedTime(offset = 0) { this.timeElapsed = this.timer - this.prevInterval this.prevInterval = this.timer return this.timeElapsed - offset @@ -53,7 +52,7 @@ class IntervalTimer { /** * @return {number} Elapsed time since start. */ - getRunTime () { + getRunTime() { return this.timer } } @@ -63,7 +62,7 @@ class IntervalTimer { * Saturday, 01 August 2020 8:33 AM * @description Example usage */ -const ExampleIntervalTimer = function () { +const ExampleIntervalTimer = function (output = (v) => console.log(v)) { /** * Create am object with default settings. * @type {IntervalTimer} Used to get timing information. @@ -82,12 +81,12 @@ const ExampleIntervalTimer = function () { // ... A test ... // The time taken to run the test. - console.log(timer.getElapsedTime(initOffset)) + output(timer.getElapsedTime(initOffset)) /** * Returns the elapsed time and resets the timer to 0. */ - console.log(timer.resetTimer()) + output(timer.resetTimer()) } -ExampleIntervalTimer() +export { IntervalTimer, ExampleIntervalTimer } diff --git a/Timing-Functions/ParseDate.js b/Timing-Functions/ParseDate.js new file mode 100644 index 0000000000..67f4e4cd0e --- /dev/null +++ b/Timing-Functions/ParseDate.js @@ -0,0 +1,27 @@ +import { getMonthDays } from './GetMonthDays' + +function checkDate(date) { + if (date.day < 1 || date.day > getMonthDays(date.month, date.year)) { + throw new Error('Invalid day value.') + } +} + +function parseDate(dateString) { + const regex = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/ + + const match = dateString.match(regex) + + if (!match) { + throw new Error("Invalid date format. Please use 'dd/mm/yyyy'.") + } + + const res = { + day: parseInt(match[1], 10), + month: parseInt(match[2], 10), + year: parseInt(match[3], 10) + } + checkDate(res) + return res +} + +export { parseDate } diff --git a/Timing-Functions/test/GetMonthDays.test.js b/Timing-Functions/test/GetMonthDays.test.js new file mode 100644 index 0000000000..1222cc4ae0 --- /dev/null +++ b/Timing-Functions/test/GetMonthDays.test.js @@ -0,0 +1,38 @@ +import { getMonthDays } from '../GetMonthDays' + +describe('Get the Days of a Month', () => { + it.each([ + [1, 2024, 31], + [2, 2024, 29], + [3, 2024, 31], + [4, 2024, 30], + [5, 2024, 31], + [6, 2024, 30], + [7, 2024, 31], + [8, 2024, 31], + [9, 2024, 30], + [10, 2024, 31], + [11, 2024, 30], + [12, 2024, 31], + [1, 2023, 31], + [2, 2023, 28], + [3, 2023, 31], + [4, 2023, 30], + [5, 2023, 31], + [6, 2023, 30], + [7, 2023, 31], + [8, 2023, 31], + [9, 2023, 30], + [10, 2023, 31], + [11, 2023, 30], + [12, 2023, 31] + ])('Month %i in year %i has %i days', (month, year, expected) => { + expect(getMonthDays(month, year)).toBe(expected) + }) + + it('expects to throw a type error', () => { + expect(() => { + getMonthDays(13, 2020) + }).toThrow('Invalid Month Number.') + }) +}) diff --git a/Timing-Functions/test/ParseDate.test.js b/Timing-Functions/test/ParseDate.test.js new file mode 100644 index 0000000000..3b807bb8a5 --- /dev/null +++ b/Timing-Functions/test/ParseDate.test.js @@ -0,0 +1,40 @@ +import { parseDate } from '../ParseDate' + +describe('parseDate', () => { + it.each([ + ['18/03/2024', { year: 2024, month: 3, day: 18 }], + ['29/02/2024', { year: 2024, month: 2, day: 29 }], + ['28/02/2023', { year: 2023, month: 2, day: 28 }], + ['01/12/2024', { year: 2024, month: 12, day: 1 }], + ['1/12/2024', { year: 2024, month: 12, day: 1 }], + ['10/1/2024', { year: 2024, month: 1, day: 10 }] + ])('Returns correct output for %s', (dateString, expected) => { + expect(parseDate(dateString)).toStrictEqual(expected) + }) + + it.each([ + '18-03-2024', + '18.03.2024', + '03/2024', + '01/02/03/2024', + '123/03/2024' + ])('Throws for %s', (wrongDateString) => { + expect(() => { + parseDate(wrongDateString) + }).toThrow() + }) + + it.each([ + '40/03/2024', + '30/02/2024', + '29/02/2023', + '31/04/2023', + '00/01/2024', + '01/00/2024', + '01/13/2024' + ])('Throws for %s', (wrongDateString) => { + expect(() => { + parseDate(wrongDateString) + }).toThrow() + }) +}) diff --git a/Trees/BreadthFirstTreeTraversal.js b/Trees/BreadthFirstTreeTraversal.js new file mode 100644 index 0000000000..a2524c18fd --- /dev/null +++ b/Trees/BreadthFirstTreeTraversal.js @@ -0,0 +1,69 @@ +/* + Breadth First Tree Traversal or level order traversal implementation in javascript + Author: @GerardUbuntu +*/ + +class Node { + constructor(data) { + this.data = data + this.left = null + this.right = null + } +} + +class BinaryTree { + constructor() { + this.root = null + } + + breadthFirstIterative() { + const traversal = [] + if (this.root) { + traversal.push(this.root) + } + for (let i = 0; i < traversal.length; i++) { + const currentNode = traversal[i] + if (currentNode.left) { + traversal.push(currentNode.left) + } + if (currentNode.right) { + traversal.push(currentNode.right) + } + traversal[i] = currentNode.data + } + return traversal + } + + breadthFirstRecursive() { + const traversal = [] + const h = this.getHeight(this.root) + for (let i = 0; i !== h; i++) { + this.traverseLevel(this.root, i, traversal) + } + return traversal + } + + // Computing the height of the tree + getHeight(node) { + if (node === null) { + return 0 + } + const lheight = this.getHeight(node.left) + const rheight = this.getHeight(node.right) + return lheight > rheight ? lheight + 1 : rheight + 1 + } + + traverseLevel(node, levelRemaining, traversal) { + if (node === null) { + return + } + if (levelRemaining === 0) { + traversal.push(node.data) + } else { + this.traverseLevel(node.left, levelRemaining - 1, traversal) + this.traverseLevel(node.right, levelRemaining - 1, traversal) + } + } +} + +export { BinaryTree, Node } diff --git a/Trees/DepthFirstSearch.js b/Trees/DepthFirstSearch.js index ec8ca7e669..f4ce2cfed5 100644 --- a/Trees/DepthFirstSearch.js +++ b/Trees/DepthFirstSearch.js @@ -2,37 +2,34 @@ * Author: Surendra Kumar * DFS Algorithm implementation in JavaScript * DFS Algorithm for traversing or searching graph data structures. -*/ + */ -function traverseDFS (root) { - const stack = [root] +// traverses a give tree from specified root's value +function traverseDFS(tree, rootValue) { + const stack = [] const res = [] - + stack.push(searchDFS(tree, rootValue)) + // if root is not present in the tree, returning empty array + if (!stack[0]) return res while (stack.length) { const curr = stack.pop() - res.push(curr.key) - - if (curr.right) { - stack.push(curr.right) - } - + res.push(curr.value) if (curr.left) { - stack.push(curr.left) + stack.push(tree[curr.left]) + } + if (curr.right) { + stack.push(tree[curr.right]) } } - return res.reverse() } -function searchDFS (tree, value) { - var stack = [] - +function searchDFS(tree, value) { + const stack = [] stack.push(tree[0]) - while (stack.length !== 0) { for (let i = 0; i < stack.length; i++) { - var node = stack.pop() - + const node = stack.pop() if (node.value === value) { return node } @@ -47,30 +44,4 @@ function searchDFS (tree, value) { return null } -var tree = [ - { value: 6, left: 1, right: 2 }, - { value: 5, left: 3, right: 4 }, - { value: 7, left: null, right: 5 }, - { value: 3, left: 6, right: null }, - { value: 4, left: null, right: null }, - { value: 9, left: 7, right: 8 }, - { value: 2, left: 9, right: null }, - { value: 8, left: null, right: null }, - { value: 10, left: null, right: null }, - { value: 1, left: null, right: null } -] - -searchDFS(tree, 9) -searchDFS(tree, 10) - -traverseDFS(6) - -// 6 -// / \ -// 5 7 -// / \ \ -// 3 4 9 -// / / \ -// 2 8 10 -// / -// 1 +export { searchDFS, traverseDFS } diff --git a/Trees/FenwickTree.js b/Trees/FenwickTree.js new file mode 100644 index 0000000000..d84b4f0f66 --- /dev/null +++ b/Trees/FenwickTree.js @@ -0,0 +1,36 @@ +/* + * Author: Mohit Kumar + * Fenwick Tree Implementation in JavaScript + * Fenwick Tree Implementation for finding prefix sum. + */ + +class FenwickTree { + constructor(feneickArray, array, n) { + for (let i = 1; i <= n; i++) { + feneickArray[i] = 0 + } + for (let i = 0; i < n; i++) { + this.update(feneickArray, n, i, array[i]) + } + } + + update(feneickArray, n, index, value) { + index = index + 1 + while (index <= n) { + feneickArray[index] += value + index += index & -index + } + } + + getPrefixSum(feneickArray, index) { + let currSum = 0 + index = index + 1 + while (index > 0) { + currSum += feneickArray[index] + index -= index & -index + } + + return currSum + } +} +export { FenwickTree } diff --git a/Trees/test/BreadthFirstTreeTraversal.test.js b/Trees/test/BreadthFirstTreeTraversal.test.js new file mode 100644 index 0000000000..aa1a2795d4 --- /dev/null +++ b/Trees/test/BreadthFirstTreeTraversal.test.js @@ -0,0 +1,38 @@ +import { BinaryTree, Node } from '../BreadthFirstTreeTraversal' + +describe('Breadth First Tree Traversal', () => { + const binaryTree = new BinaryTree() + + const root = new Node(7) + root.left = new Node(5) + root.right = new Node(8) + root.left.left = new Node(3) + root.left.right = new Node(6) + root.left.right.right = new Node(9) + binaryTree.root = root + + // Visualization : + // + // 7 + // / \ + // 5 8 + // / \ + // 3 6 + // \ + // 9 + + it('Binary tree - Empty case', () => { + const emptyTree = new BinaryTree() + expect(emptyTree.breadthFirstIterative()).toStrictEqual([]) + }) + + it('Binary tree - Level order recursive traversal', () => { + const traversal = binaryTree.breadthFirstRecursive() + expect(traversal).toStrictEqual([7, 5, 8, 3, 6, 9]) + }) + + it('Binary tree - Level order iterative traversal', () => { + const traversal = binaryTree.breadthFirstIterative() + expect(traversal).toStrictEqual([7, 5, 8, 3, 6, 9]) + }) +}) diff --git a/Trees/test/DepthFirstSearch.test.js b/Trees/test/DepthFirstSearch.test.js new file mode 100644 index 0000000000..6c76fbb3d6 --- /dev/null +++ b/Trees/test/DepthFirstSearch.test.js @@ -0,0 +1,46 @@ +import { searchDFS, traverseDFS } from '../DepthFirstSearch' + +describe('Depth First Tree Traversal', () => { + const tree = [ + { value: 6, left: 1, right: 2 }, + { value: 5, left: 3, right: 4 }, + { value: 7, left: null, right: 5 }, + { value: 3, left: 6, right: null }, + { value: 4, left: null, right: null }, + { value: 9, left: 7, right: 8 }, + { value: 2, left: 9, right: null }, + { value: 8, left: null, right: null }, + { value: 10, left: null, right: null }, + { value: 1, left: null, right: null } + ] + + // 6 + // / \ + // 5 7 + // / \ \ + // 3 4 9 + // / / \ + // 2 8 10 + // / + // 1 + + it('should be null if given value is not present in the tree - DF Search', () => { + const res = searchDFS(tree, 200) + expect(res).toStrictEqual(null) + }) + + it('should return the node if given value is present in the tree - DF Search', () => { + const res = searchDFS(tree, 9) + expect(res).toStrictEqual({ value: 9, left: 7, right: 8 }) + }) + + it('should return empty array if given root is not present in the tree - DF Traversal', () => { + const traversal = traverseDFS(tree, 200) + expect(traversal).toStrictEqual([]) + }) + + it('should return DFT array of given tree from specified root if given root is present in the tree - DF Traversal', () => { + const traversal = traverseDFS(tree, 6) + expect(traversal).toStrictEqual([1, 2, 3, 4, 5, 8, 10, 9, 7, 6]) + }) +}) diff --git a/Trees/test/FenwickTree.test.js b/Trees/test/FenwickTree.test.js new file mode 100644 index 0000000000..0626da2c2f --- /dev/null +++ b/Trees/test/FenwickTree.test.js @@ -0,0 +1,22 @@ +import { FenwickTree } from '../FenwickTree' + +describe('Fenwick Tree Implementation', () => { + const fenwickArray = new Array(1000) + const array = [3, 2, 0, 6, 5, -1, 2] + const length = array.length + + const fenwickTree = new FenwickTree(fenwickArray, array, length) + + it('Fenwick Tree - Prefix sum of array', () => { + const prefixSum = fenwickTree.getPrefixSum(fenwickArray, 6) + expect(prefixSum).toBe(23) + }) + + array[2] += 6 + fenwickTree.update(fenwickArray, length, 2, 6) + + it('Fenwick Tree - Prefix sum of Updated array', () => { + const prefixSum = fenwickTree.getPrefixSum(fenwickArray, 6) + expect(prefixSum).toBe(23) + }) +}) diff --git a/Web-Programming/OpenWeatherMaps.js b/Web-Programming/OpenWeatherMaps.js deleted file mode 100644 index 7f1d4e31b1..0000000000 --- a/Web-Programming/OpenWeatherMaps.js +++ /dev/null @@ -1,31 +0,0 @@ -const fetch = require('node-fetch') - -const APPID = '' // <-- Put your OpenWeatherMap appid here! -const URL_BASE = 'http://api.openweathermap.org/data/2.5/' - -async function currentWeather (location) { - const response = await fetch(`${URL_BASE}weather?q=${location}&appid=${APPID}`) - const data = await response.json() - return data -} - -async function weatherForecast (location) { - const response = await fetch(`${URL_BASE}forecast?q=${location}&appid=${APPID}`) - const data = await response.json() - return data -} - -async function oneCallApi (latitude, longitude) { - const response = await fetch(`${URL_BASE}onecall?lat=${latitude}&lon=${longitude}&appid=${APPID}`) - const data = await response.json() - return data -} - -currentWeather('Kolkata') - .then(data => console.log(data)) - -weatherForecast('Kolkata') - .then(data => console.log(data)) - -oneCallApi(55.68, 12.57) - .then(data => console.log(data)) diff --git a/Web-Programming/StockPrice.js b/Web-Programming/StockPrice.js deleted file mode 100644 index f2e6563057..0000000000 --- a/Web-Programming/StockPrice.js +++ /dev/null @@ -1,30 +0,0 @@ -const fetch = require('node-fetch') -const jsdom = require('jsdom') - -// function to get the stock price from the given symbol -async function getStockPrice (stockSymbol) { - // parsing the html page body - const url = `https://in.finance.yahoo.com/lookup?s=$${stockSymbol}` - const response = await fetch(url) - const pageBody = await response.text() - const dom = new jsdom.JSDOM(pageBody, 'text/html') - // returning the price as a number - return parseFloat(dom.window.document.querySelectorAll('td')[2].textContent.replace(/,/g, '')) -} - -async function main () { - // Using async await to ensure synchronous behaviour - await getStockPrice('GOOGL') - .then(response => console.log(`GOOGL stock price: $ ${response}`)) - - await getStockPrice('AAPL') - .then(response => console.log(`AAPL stock price: $ ${response}`)) - - await getStockPrice('MSFT') - .then(response => console.log(`MSFT stock price: $ ${response}`)) - - await getStockPrice('AMZN') - .then(response => console.log(`AMZN stock price: $ ${response}`)) -} - -main() diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..5c38ba06a8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2237 @@ +{ + "name": "javascript", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "javascript", + "version": "1.0.0", + "license": "GPL-3.0", + "devDependencies": { + "@vitest/coverage-v8": "^1.2.1", + "globby": "^13.2.2", + "husky": "^8.0.3", + "prettier": "^3.0.3", + "vitest": "^1.2.1" + }, + "engines": { + "node": ">=20.6.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.11.6", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.4", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.6", + "magic-string": "^0.30.5", + "magicast": "^0.3.3", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "^1.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.2.1", + "@vitest/utils": "1.2.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.2.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@jest/schemas": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/snapshot/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/spy": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/@jest/schemas": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/diff-sequences": { + "version": "29.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "13.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/husky": { + "version": "8.0.3", + "dev": true, + "license": "MIT", + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/magicast": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "source-map-js": "^1.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mlly": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react-is": { + "version": "18.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.5.1", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ufo": { + "version": "1.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.2.1", + "@vitest/runner": "1.2.1", + "@vitest/snapshot": "1.2.1", + "@vitest/spy": "1.2.1", + "@vitest/utils": "1.2.1", + "acorn-walk": "^8.3.2", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^1.3.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.1", + "vite": "^5.0.0", + "vite-node": "1.2.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "^1.0.0", + "@vitest/ui": "^1.0.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/npm-run-path": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json index afbded11b8..e667e1eea2 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,25 @@ { "name": "javascript", "version": "1.0.0", + "type": "module", "description": "A repository for All algorithms implemented in Javascript (for educational purposes only)", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest run", + "test-watch": "vitest", + "style": "npx prettier . --write", + "check-style": "npx prettier . --check", + "prepare": "husky install" }, "author": "TheAlgorithms", "license": "GPL-3.0", - "dependencies": { - "jsdom": "^16.3.0", - "node-fetch": "2.6.1" - }, "devDependencies": { - "standard": "^14.3.4", - "doctest": "^0.17.1" + "globby": "^13.2.2", + "husky": "^8.0.3", + "prettier": "^3.0.3", + "vitest": "^1.2.1", + "@vitest/coverage-v8": "^1.2.1" + }, + "engines": { + "node": ">=20.6.0" } } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000000..05f33b84f5 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + restoreMocks: true, + coverage: { + reporter: ['text', 'json', 'html'] + } + } +})