diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0a19890aed44..f41af80a3459 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @yanglbme @vil02 @BamaCharanChhandogi @alxkm @siriak +* @DenizAltunkapan @yanglbme @alxkm diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3969075d668..39bde758d68e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,13 +1,16 @@ name: Build on: [push, pull_request] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 21 distribution: 'temurin' diff --git a/.github/workflows/clang-format-lint.yml b/.github/workflows/clang-format-lint.yml index 588c05e42e8f..ca014e115282 100644 --- a/.github/workflows/clang-format-lint.yml +++ b/.github/workflows/clang-format-lint.yml @@ -3,13 +3,16 @@ on: push: {} pull_request: {} +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: DoozyX/clang-format-lint-action@v0.18 + - uses: actions/checkout@v5 + - uses: DoozyX/clang-format-lint-action@v0.20 with: source: './src' extensions: 'java' diff --git a/.github/workflows/close-failed-prs.yml b/.github/workflows/close-failed-prs.yml new file mode 100644 index 000000000000..5d54eea07821 --- /dev/null +++ b/.github/workflows/close-failed-prs.yml @@ -0,0 +1,154 @@ +name: Close stale PRs with failed workflows + +on: + schedule: + - cron: '0 3 * * *' # runs daily at 03:00 UTC + workflow_dispatch: + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + close-stale: + runs-on: ubuntu-latest + steps: + - name: Close stale PRs + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const mainBranches = ['main', 'master']; + const cutoffDays = 14; + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - cutoffDays); + + console.log(`Checking PRs older than: ${cutoff.toISOString()}`); + + try { + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + sort: 'updated', + direction: 'asc', + per_page: 100 + }); + + console.log(`Found ${prs.length} open PRs to check`); + + for (const pr of prs) { + try { + const updated = new Date(pr.updated_at); + + if (updated > cutoff) { + console.log(`โฉ Skipping PR #${pr.number} - updated recently`); + continue; + } + + console.log(`๐Ÿ” Checking PR #${pr.number}: "${pr.title}"`); + + // Get commits + const commits = await github.paginate(github.rest.pulls.listCommits, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + per_page: 100 + }); + + const meaningfulCommits = commits.filter(c => { + const msg = c.commit.message.toLowerCase(); + const isMergeFromMain = mainBranches.some(branch => + msg.startsWith(`merge branch '${branch}'`) || + msg.includes(`merge remote-tracking branch '${branch}'`) + ); + return !isMergeFromMain; + }); + + // Get checks with error handling + let hasFailedChecks = false; + let allChecksCompleted = false; + let hasChecks = false; + + try { + const { data: checks } = await github.rest.checks.listForRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: pr.head.sha + }); + + hasChecks = checks.check_runs.length > 0; + hasFailedChecks = checks.check_runs.some(c => c.conclusion === 'failure'); + allChecksCompleted = checks.check_runs.every(c => + c.status === 'completed' || c.status === 'skipped' + ); + } catch (error) { + console.log(`โš ๏ธ Could not fetch checks for PR #${pr.number}: ${error.message}`); + } + + // Get workflow runs with error handling + let hasFailedWorkflows = false; + let allWorkflowsCompleted = false; + let hasWorkflows = false; + + try { + const { data: runs } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + head_sha: pr.head.sha, + per_page: 50 + }); + + hasWorkflows = runs.workflow_runs.length > 0; + hasFailedWorkflows = runs.workflow_runs.some(r => r.conclusion === 'failure'); + allWorkflowsCompleted = runs.workflow_runs.every(r => + ['completed', 'skipped', 'cancelled'].includes(r.status) + ); + + console.log(`PR #${pr.number}: ${runs.workflow_runs.length} workflow runs found`); + + } catch (error) { + console.log(`โš ๏ธ Could not fetch workflow runs for PR #${pr.number}: ${error.message}`); + } + + console.log(`PR #${pr.number}: ${meaningfulCommits.length} meaningful commits`); + console.log(`Checks - has: ${hasChecks}, failed: ${hasFailedChecks}, completed: ${allChecksCompleted}`); + console.log(`Workflows - has: ${hasWorkflows}, failed: ${hasFailedWorkflows}, completed: ${allWorkflowsCompleted}`); + + // Combine conditions - only consider if we actually have checks/workflows + const hasAnyFailure = (hasChecks && hasFailedChecks) || (hasWorkflows && hasFailedWorkflows); + const allCompleted = (!hasChecks || allChecksCompleted) && (!hasWorkflows || allWorkflowsCompleted); + + if (meaningfulCommits.length === 0 && hasAnyFailure && allCompleted) { + console.log(`โœ… Closing PR #${pr.number} (${pr.title})`); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: `This pull request has been automatically closed because its workflows or checks failed and it has been inactive for more than ${cutoffDays} days. Please fix the workflows and reopen if you'd like to continue. Merging from main/master alone does not count as activity.` + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + state: 'closed' + }); + + console.log(`โœ… Successfully closed PR #${pr.number}`); + } else { + console.log(`โฉ Not closing PR #${pr.number} - conditions not met`); + } + + } catch (prError) { + console.error(`โŒ Error processing PR #${pr.number}: ${prError.message}`); + continue; + } + } + + } catch (error) { + console.error(`โŒ Fatal error: ${error.message}`); + throw error; + } \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a0908a2b57d9..3a4cfd829b6b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -10,12 +10,9 @@ on: schedule: - cron: '53 3 * * 0' -env: - LANGUAGE: 'java-kotlin' - jobs: - analyze: - name: Analyze + analyzeJava: + name: AnalyzeJava runs-on: 'ubuntu-latest' permissions: actions: read @@ -24,24 +21,46 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 21 distribution: 'temurin' - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: - languages: ${{ env.LANGUAGE }} + languages: 'java-kotlin' - name: Build run: mvn --batch-mode --update-snapshots verify - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 + with: + category: "/language:java-kotlin" + + analyzeActions: + name: AnalyzeActions + runs-on: 'ubuntu-latest' + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: 'actions' + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 with: - category: "/language:${{env.LANGUAGE}}" + category: "/language:actions" ... diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index a07c6f458083..3df7c4b1fc9e 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -8,14 +8,17 @@ name: Infer - master pull_request: +permissions: + contents: read + jobs: run_infer: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 21 distribution: 'temurin' @@ -41,6 +44,7 @@ jobs: cd .. git clone https://github.com/facebook/infer.git cd infer + git checkout 01aaa268f9d38723ba69c139e10f9e2a04b40b1c ./build-infer.sh java cp -r infer ../Java diff --git a/.github/workflows/project_structure.yml b/.github/workflows/project_structure.yml new file mode 100644 index 000000000000..f9fb82a2781c --- /dev/null +++ b/.github/workflows/project_structure.yml @@ -0,0 +1,25 @@ +--- +name: ProjectStructure + +'on': + workflow_dispatch: + push: + branches: + - master + pull_request: + +permissions: + contents: read + +jobs: + check_structure: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + + - name: Check project structure + run: python3 .github/workflows/scripts/check_structure.py +... diff --git a/.github/workflows/scripts/check_structure.py b/.github/workflows/scripts/check_structure.py new file mode 100644 index 000000000000..914f64369207 --- /dev/null +++ b/.github/workflows/scripts/check_structure.py @@ -0,0 +1,27 @@ +import pathlib +import sys + + +def _is_java_file_properly_located(java_file: pathlib.Path) -> bool: + main_parents = java_file.parent.parents + return ( + pathlib.Path("src/main/java/com/thealgorithms/") in main_parents + or pathlib.Path("src/test/java/com/thealgorithms/") in main_parents + ) + + +def _find_misplaced_java_files() -> list[pathlib.Path]: + return [ + java_file + for java_file in pathlib.Path(".").rglob("*.java") + if not _is_java_file_properly_located(java_file) + ] + + +if __name__ == "__main__": + misplaced_files = _find_misplaced_java_files() + if misplaced_files: + print("The following java files are not located in the correct directory:") + for _ in misplaced_files: + print(_) + sys.exit(1) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 6fb47c5d2dc9..bb613daf8f1d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,11 +2,16 @@ name: 'Close stale issues and PRs' on: schedule: - cron: '0 0 * * *' +permissions: + contents: read jobs: stale: + permissions: + issues: write + pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution!' close-issue-message: 'Please reopen this issue once you have made the required changes. If you need help, feel free to ask in our [Discord](https://the-algorithms.com/discord) server or ping one of the maintainers here. Thank you for your contribution!' diff --git a/.github/workflows/update-directorymd.yml b/.github/workflows/update-directorymd.yml new file mode 100644 index 000000000000..6692a884a867 --- /dev/null +++ b/.github/workflows/update-directorymd.yml @@ -0,0 +1,42 @@ +name: Generate Directory Markdown + +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + generate-directory: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v5 + + - name: Run Directory Tree Generator + uses: DenizAltunkapan/directory-tree-generator@v2 + with: + path: src + extensions: .java + show-extensions: false + + - name: Commit changes + run: | + git config --global user.name "$GITHUB_ACTOR" + git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" + git add DIRECTORY.md + git diff --cached --quiet || git commit -m "Update DIRECTORY.md" + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.REPO_SCOPED_TOKEN }} + branch: update-directory + base: master + title: "Update DIRECTORY.md" + body: "Automatically generated update of the directory tree." + commit-message: "Update DIRECTORY.md" + draft: false diff --git a/.github/workflows/update_directory.yml b/.github/workflows/update_directory.yml deleted file mode 100644 index c811d244e54b..000000000000 --- a/.github/workflows/update_directory.yml +++ /dev/null @@ -1,92 +0,0 @@ -# This GitHub Action updates the DIRECTORY.md file (if needed) when doing a git push or pull_request -name: Update Directory -permissions: - contents: write -on: - push: - paths: - - 'src/**' - pull_request: - paths: - - 'src/**' - workflow_dispatch: - inputs: - logLevel: - description: 'Log level' - required: true - default: 'info' - type: choice - options: - - info - - warning - - debug -jobs: - update_directory_md: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - name: Update Directory - shell: python - run: | - import os - from typing import Iterator - - URL_BASE = "https://github.com/TheAlgorithms/Java/blob/master" - g_output = [] - - - def good_filepaths(top_dir: str = ".") -> Iterator[str]: - for dirpath, dirnames, filenames in os.walk(top_dir): - dirnames[:] = [d for d in dirnames if d[0] not in "._"] - for filename in filenames: - if os.path.splitext(filename)[1].lower() == ".java": - yield os.path.join(dirpath, filename).lstrip("./") - - - def md_prefix(i): - return f"{i * ' '}*" if i else "\n##" - - - def print_path(old_path: str, new_path: str) -> str: - global g_output - old_parts = old_path.split(os.sep) - mid_diff = False - new_parts = new_path.split(os.sep) - for i, new_part in enumerate(new_parts): - if i + 1 > len(old_parts) or old_parts[i] != new_part or mid_diff: - if i + 1 < len(new_parts): - mid_diff = True - if new_part: - g_output.append(f"{md_prefix(i)} {new_part.replace('_', ' ')}") - return new_path - - - def build_directory_md(top_dir: str = ".") -> str: - global g_output - old_path = "" - for filepath in sorted(good_filepaths(top_dir), key=str.lower): - filepath, filename = os.path.split(filepath) - if filepath != old_path: - old_path = print_path(old_path, filepath) - indent = (filepath.count(os.sep) + 1) if filepath else 0 - url = "/".join((URL_BASE, filepath, filename)).replace(" ", "%20") - filename = os.path.splitext(filename.replace("_", " "))[0] - g_output.append(f"{md_prefix(indent)} [{filename}]({url})") - return "\n".join(g_output) - - - with open("DIRECTORY.md", "w") as out_file: - out_file.write(build_directory_md(".") + "\n") - - - name: Update DIRECTORY.md - run: | - cat DIRECTORY.md - git config --global user.name "$GITHUB_ACTOR" - 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 "Update directory" || true - git push --force origin HEAD:$GITHUB_REF || true diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index 4b1885ffa388..4195f928d1bc 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -1,4 +1,4 @@ -FROM gitpod/workspace-java-21:2024-11-26-08-43-19 +FROM gitpod/workspace-java-21:2025-10-06-13-14-25 ENV LLVM_SCRIPT="tmp_llvm.sh" diff --git a/DIRECTORY.md b/DIRECTORY.md index 4fa1392a3c17..47833a3f59f2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -1,1360 +1,1526 @@ +# Project Structure ## src - * main - * java - * com - * thealgorithms - * audiofilters - * [EMAFilter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java) - * [IIRFilter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/audiofilters/IIRFilter.java) - * backtracking - * [AllPathsFromSourceToTarget](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java) - * [ArrayCombination](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java) - * [Combination](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/Combination.java) - * [CrosswordSolver](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java) - * [FloodFill](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/FloodFill.java) - * [KnightsTour](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/KnightsTour.java) - * [MazeRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java) - * [MColoring](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/MColoring.java) - * [NQueens](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/NQueens.java) - * [ParenthesesGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/ParenthesesGenerator.java) - * [Permutation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/Permutation.java) - * [PowerSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/PowerSum.java) - * [SubsequenceFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/SubsequenceFinder.java) - * [WordPatternMatcher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java) - * [WordSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/backtracking/WordSearch.java) - * bitmanipulation - * [BcdConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java) - * [BinaryPalindromeCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java) - * [BitSwap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java) - * [BooleanAlgebraGates](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java) - * [ClearLeftmostSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java) - * [CountLeadingZeros](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java) - * [CountSetBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java) - * [FindNthBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java) - * [FirstDifferentBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java) - * [GenerateSubsets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java) - * [GrayCodeConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java) - * [HammingDistance](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java) - * [HigherLowerPowerOfTwo](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java) - * [HighestSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java) - * [IndexOfRightMostSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java) - * [IsEven](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IsEven.java) - * [IsPowerTwo](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java) - * [LowestSetBit](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java) - * [ModuloPowerOfTwo](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java) - * [NextHigherSameBitCount](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java) - * [NonRepeatingNumberFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java) - * [NumberAppearingOddTimes](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java) - * [NumbersDifferentSigns](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java) - * [OneBitDifference](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java) - * [OnesComplement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java) - * [ParityCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java) - * [ReverseBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java) - * [SingleBitOperations](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java) - * [SingleElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java) - * [SwapAdjacentBits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java) - * [TwosComplement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java) - * [Xs3Conversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java) - * ciphers - * a5 - * [A5Cipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java) - * [A5KeyStreamGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java) - * [BaseLFSR](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/BaseLFSR.java) - * [CompositeLFSR](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java) - * [LFSR](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/LFSR.java) - * [Utils](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/a5/Utils.java) - * [ADFGVXCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java) - * [AES](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AES.java) - * [AESEncryption](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AESEncryption.java) - * [AffineCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AffineCipher.java) - * [AtbashCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/AtbashCipher.java) - * [Autokey](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Autokey.java) - * [BaconianCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/BaconianCipher.java) - * [Blowfish](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Blowfish.java) - * [Caesar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Caesar.java) - * [ColumnarTranspositionCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java) - * [DES](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/DES.java) - * [DiffieHellman](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/DiffieHellman.java) - * [ECC](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ECC.java) - * [HillCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/HillCipher.java) - * [MonoAlphabetic](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java) - * [PlayfairCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/PlayfairCipher.java) - * [Polybius](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Polybius.java) - * [ProductCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/ProductCipher.java) - * [RailFenceCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java) - * [RSA](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/RSA.java) - * [SimpleSubCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/SimpleSubCipher.java) - * [Vigenere](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/Vigenere.java) - * [XORCipher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/ciphers/XORCipher.java) - * conversions - * [AffineConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/AffineConverter.java) - * [AnyBaseToAnyBase](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java) - * [AnyBaseToDecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/AnyBaseToDecimal.java) - * [AnytoAny](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/AnytoAny.java) - * [BinaryToDecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java) - * [BinaryToHexadecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/BinaryToHexadecimal.java) - * [BinaryToOctal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/BinaryToOctal.java) - * [DecimalToAnyBase](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/DecimalToAnyBase.java) - * [DecimalToBinary](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/DecimalToBinary.java) - * [DecimalToHexadecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/DecimalToHexadecimal.java) - * [DecimalToOctal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/DecimalToOctal.java) - * [EndianConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/EndianConverter.java) - * [HexaDecimalToBinary](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java) - * [HexaDecimalToDecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java) - * [HexToOct](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/HexToOct.java) - * [IntegerToEnglish](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java) - * [IntegerToRoman](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IntegerToRoman.java) - * [IPConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IPConverter.java) - * [IPv6Converter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/IPv6Converter.java) - * [MorseCodeConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java) - * [NumberToWords](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/NumberToWords.java) - * [OctalToBinary](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/OctalToBinary.java) - * [OctalToDecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/OctalToDecimal.java) - * [OctalToHexadecimal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/OctalToHexadecimal.java) - * [PhoneticAlphabetConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java) - * [RgbHsvConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/RgbHsvConversion.java) - * [RomanToInteger](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/RomanToInteger.java) - * [TurkishToLatinConversion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java) - * [UnitConversions](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/UnitConversions.java) - * [UnitsConverter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/conversions/UnitsConverter.java) - * datastructures - * bags - * [Bag](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/bags/Bag.java) - * bloomfilter - * [BloomFilter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java) - * buffers - * [CircularBuffer](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java) - * caches - * [LFUCache](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java) - * [LRUCache](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java) - * [MRUCache](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java) - * crdt - * [GCounter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java) - * [GSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/GSet.java) - * [LWWElementSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java) - * [ORSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java) - * [PNCounter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/PNCounter.java) - * [TwoPSet](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/crdt/TwoPSet.java) - * disjointsetunion - * [DisjointSetUnion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java) - * [Node](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/disjointsetunion/Node.java) - * dynamicarray - * [DynamicArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java) - * graphs - * [AStar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java) - * [BellmanFord](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/BellmanFord.java) - * [BipartiteGraphDFS](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java) - * [BoruvkaAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithm.java) - * [ConnectedComponent](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/ConnectedComponent.java) - * [Cycles](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java) - * [DijkstraAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java) - * [DijkstraOptimizedAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java) - * [EdmondsBlossomAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java) - * [FloydWarshall](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java) - * [FordFulkerson](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java) - * [Graphs](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Graphs.java) - * [HamiltonianCycle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java) - * [JohnsonsAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java) - * [KahnsAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java) - * [Kosaraju](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java) - * [Kruskal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java) - * [MatrixGraphs](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java) - * [PrimMST](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java) - * [TarjansAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java) - * [WelshPowell](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java) - * hashmap - * hashing - * [GenericHashMapUsingArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java) - * [GenericHashMapUsingArrayList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java) - * [HashMap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java) - * [HashMapCuckooHashing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java) - * [Intersection](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java) - * [LinearProbingHashMap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java) - * [MainCuckooHashing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MainCuckooHashing.java) - * [MajorityElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java) - * [Map](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Map.java) - * heaps - * [EmptyHeapException](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/EmptyHeapException.java) - * [FibonacciHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java) - * [GenericHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java) - * [Heap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/Heap.java) - * [HeapElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java) - * [KthElementFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java) - * [LeftistHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java) - * [MaxHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java) - * [MedianFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java) - * [MergeKSortedArrays](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java) - * [MinHeap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java) - * [MinPriorityQueue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java) - * lists - * [CircleLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java) - * [CountSinglyLinkedListRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java) - * [CreateAndDetectLoop](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java) - * [CursorLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java) - * [DoublyLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/DoublyLinkedList.java) - * [MergeKSortedLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java) - * [MergeSortedArrayList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java) - * [MergeSortedSinglyLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java) - * [QuickSortLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java) - * [RandomNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/RandomNode.java) - * [ReverseKGroup](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java) - * [RotateSinglyLinkedLists](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java) - * [SearchSinglyLinkedListRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java) - * [SinglyLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java) - * [SkipList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/SkipList.java) - * [SortedLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java) - * [Node](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/Node.java) - * queues - * [CircularQueue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java) - * [Deque](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/Deque.java) - * [GenericArrayListQueue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java) - * [LinkedQueue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/LinkedQueue.java) - * [PriorityQueues](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/PriorityQueues.java) - * [Queue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/Queue.java) - * [QueueByTwoStacks](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java) - * [SlidingWindowMaximum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java) - * [TokenBucket](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java) - * stacks - * [NodeStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java) - * [ReverseStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java) - * [Stack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/stacks/Stack.java) - * [StackArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java) - * [StackArrayList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java) - * [StackOfLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java) - * trees - * [AVLSimple](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/AVLSimple.java) - * [AVLTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java) - * [BinaryTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java) - * [BoundaryTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java) - * [BSTFromSortedArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTFromSortedArray.java) - * [BSTIterative](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTIterative.java) - * [BSTRecursive](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursive.java) - * [BSTRecursiveGeneric](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursiveGeneric.java) - * [CeilInBinarySearchTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTree.java) - * [CheckBinaryTreeIsValidBST](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CheckBinaryTreeIsValidBST.java) - * [CheckIfBinaryTreeBalanced](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CheckIfBinaryTreeBalanced.java) - * [CheckTreeIsSymmetric](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CheckTreeIsSymmetric.java) - * [CreateBinaryTreeFromInorderPreorder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/CreateBinaryTreeFromInorderPreorder.java) - * [FenwickTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java) - * [GenericTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java) - * [InorderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/InorderTraversal.java) - * [KDTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/KDTree.java) - * [LazySegmentTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/LazySegmentTree.java) - * [LCA](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/LCA.java) - * [LevelOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/LevelOrderTraversal.java) - * [nearestRightKey](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/nearestRightKey.java) - * [PostOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/PostOrderTraversal.java) - * [PreOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/PreOrderTraversal.java) - * [PrintTopViewofTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/PrintTopViewofTree.java) - * [QuadTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java) - * [RedBlackBST](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/RedBlackBST.java) - * [SameTreesCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SameTreesCheck.java) - * [SegmentTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java) - * [SplayTree](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/SplayTree.java) - * [Treap](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/Treap.java) - * [TreeRandomNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/TreeRandomNode.java) - * [Trie](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/Trie.java) - * [VerticalOrderTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversal.java) - * [ZigzagTraversal](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/datastructures/trees/ZigzagTraversal.java) - * devutils - * entities - * [ProcessDetails](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/devutils/entities/ProcessDetails.java) - * nodes - * [LargeTreeNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/devutils/nodes/LargeTreeNode.java) - * [Node](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/devutils/nodes/Node.java) - * [SimpleNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/devutils/nodes/SimpleNode.java) - * [SimpleTreeNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/devutils/nodes/SimpleTreeNode.java) - * [TreeNode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java) - * searches - * [MatrixSearchAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/devutils/searches/MatrixSearchAlgorithm.java) - * [SearchAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/devutils/searches/SearchAlgorithm.java) - * divideandconquer - * [BinaryExponentiation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/BinaryExponentiation.java) - * [ClosestPair](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java) - * [CountingInversions](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java) - * [MedianOfTwoSortedArrays](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java) - * [SkylineAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/SkylineAlgorithm.java) - * [StrassenMatrixMultiplication](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplication.java) - * [TilingProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java) - * dynamicprogramming - * [Abbreviation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java) - * [AllConstruct](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java) - * [AssignmentUsingBitmask](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java) - * [BoardPath](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoardPath.java) - * [BoundaryFill](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java) - * [BruteForceKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java) - * [CatalanNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/CatalanNumber.java) - * [ClimbingStairs](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/ClimbingStairs.java) - * [CoinChange](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java) - * [CountFriendsPairing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/CountFriendsPairing.java) - * [DiceThrow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java) - * [EditDistance](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/EditDistance.java) - * [EggDropping](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/EggDropping.java) - * [Fibonacci](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java) - * [KadaneAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java) - * [Knapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java) - * [KnapsackMemoization](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java) - * [LevenshteinDistance](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LevenshteinDistance.java) - * [LongestAlternatingSubsequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LongestAlternatingSubsequence.java) - * [LongestArithmeticSubsequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequence.java) - * [LongestCommonSubsequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequence.java) - * [LongestIncreasingSubsequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequence.java) - * [LongestPalindromicSubsequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java) - * [LongestPalindromicSubstring](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubstring.java) - * [LongestValidParentheses](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/LongestValidParentheses.java) - * [MatrixChainMultiplication](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java) - * [MatrixChainRecursiveTopDownMemoisation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java) - * [MaximumSumOfNonAdjacentElements](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java) - * [MinimumPathSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MinimumPathSum.java) - * [MinimumSumPartition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/MinimumSumPartition.java) - * [NewManShanksPrime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java) - * [OptimalJobScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/OptimalJobScheduling.java) - * [PalindromicPartitioning](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/PalindromicPartitioning.java) - * [PartitionProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java) - * [RegexMatching](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/RegexMatching.java) - * [RodCutting](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java) - * [ShortestCommonSupersequenceLength](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/ShortestCommonSupersequenceLength.java) - * [SubsetCount](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/SubsetCount.java) - * [SubsetSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java) - * [SubsetSumSpaceOptimized](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java) - * [SumOfSubset](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/SumOfSubset.java) - * [Tribonacci](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/Tribonacci.java) - * [UniquePaths](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java) - * [UniqueSubsequencesCount](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java) - * [WildcardMatching](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java) - * [WineProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java) - * geometry - * [BresenhamLine](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/BresenhamLine.java) - * [ConvexHull](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/ConvexHull.java) - * [GrahamScan](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/GrahamScan.java) - * [MidpointCircle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/MidpointCircle.java) - * [MidpointEllipse](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/MidpointEllipse.java) - * [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/geometry/Point.java) - * graph - * [StronglyConnectedComponentOptimized](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java) - * greedyalgorithms - * [ActivitySelection](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java) - * [BandwidthAllocation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java) - * [BinaryAddition](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java) - * [CoinChange](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java) - * [DigitSeparation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/DigitSeparation.java) - * [EgyptianFraction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java) - * [FractionalKnapsack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java) - * [GaleShapley](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/GaleShapley.java) - * [JobSequencing](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/JobSequencing.java) - * [KCenters](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java) - * [MergeIntervals](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MergeIntervals.java) - * [MinimizingLateness](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MinimizingLateness.java) - * [MinimumWaitingTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java) - * [OptimalFileMerging](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java) - * [StockProfitCalculator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java) - * io - * [BufferedReader](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/io/BufferedReader.java) - * lineclipping - * [CohenSutherland](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/lineclipping/CohenSutherland.java) - * [LiangBarsky](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/lineclipping/LiangBarsky.java) - * utils - * [Line](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/lineclipping/utils/Line.java) - * [Point](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/lineclipping/utils/Point.java) - * maths - * [AbsoluteMax](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AbsoluteMax.java) - * [AbsoluteMin](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AbsoluteMin.java) - * [AbsoluteValue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AbsoluteValue.java) - * [ADTFraction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/ADTFraction.java) - * [AliquotSum](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AliquotSum.java) - * [AmicableNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AmicableNumber.java) - * [Area](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Area.java) - * [Armstrong](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Armstrong.java) - * [AutoCorrelation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AutoCorrelation.java) - * [AutomorphicNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/AutomorphicNumber.java) - * [Average](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Average.java) - * [BinaryPow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/BinaryPow.java) - * [BinomialCoefficient](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/BinomialCoefficient.java) - * [CatalanNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CatalanNumbers.java) - * [Ceil](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Ceil.java) - * [ChineseRemainderTheorem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java) - * [CircularConvolutionFFT](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java) - * [CollatzConjecture](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CollatzConjecture.java) - * [Combinations](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Combinations.java) - * [Convolution](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Convolution.java) - * [ConvolutionFFT](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/ConvolutionFFT.java) - * [CrossCorrelation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/CrossCorrelation.java) - * [DeterminantOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/DeterminantOfMatrix.java) - * [DigitalRoot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/DigitalRoot.java) - * [DistanceFormula](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/DistanceFormula.java) - * [DudeneyNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/DudeneyNumber.java) - * [EulerMethod](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/EulerMethod.java) - * [EulersFunction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/EulersFunction.java) - * [Factorial](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Factorial.java) - * [FactorialRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FactorialRecursion.java) - * [FastExponentiation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FastExponentiation.java) - * [FastInverseSqrt](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FastInverseSqrt.java) - * [FFT](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FFT.java) - * [FFTBluestein](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FFTBluestein.java) - * [FibonacciJavaStreams](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java) - * [FibonacciLoop](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FibonacciLoop.java) - * [FibonacciNumberCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java) - * [FibonacciNumberGoldenRation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java) - * [FindKthNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FindKthNumber.java) - * [FindMax](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FindMax.java) - * [FindMaxRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FindMaxRecursion.java) - * [FindMin](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FindMin.java) - * [FindMinRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FindMinRecursion.java) - * [Floor](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Floor.java) - * [FrizzyNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/FrizzyNumber.java) - * [Gaussian](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Gaussian.java) - * [GCD](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/GCD.java) - * [GCDRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/GCDRecursion.java) - * [GenericRoot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/GenericRoot.java) - * [GoldbachConjecture](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/GoldbachConjecture.java) - * [HarshadNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/HarshadNumber.java) - * [HeronsFormula](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/HeronsFormula.java) - * [JosephusProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/JosephusProblem.java) - * [JugglerSequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/JugglerSequence.java) - * [KaprekarNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/KaprekarNumbers.java) - * [KaratsubaMultiplication](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java) - * [KeithNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/KeithNumber.java) - * [KrishnamurthyNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java) - * [LeastCommonMultiple](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LeastCommonMultiple.java) - * [LeonardoNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LeonardoNumber.java) - * [LinearDiophantineEquationsSolver](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java) - * [LiouvilleLambdaFunction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LiouvilleLambdaFunction.java) - * [LongDivision](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LongDivision.java) - * [LucasSeries](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/LucasSeries.java) - * [MagicSquare](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MagicSquare.java) - * [MatrixRank](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MatrixRank.java) - * [MatrixUtil](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MatrixUtil.java) - * [MaxValue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MaxValue.java) - * [Means](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Means.java) - * [Median](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Median.java) - * [MillerRabinPrimalityCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MillerRabinPrimalityCheck.java) - * [MinValue](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MinValue.java) - * [MobiusFunction](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/MobiusFunction.java) - * [Mode](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Mode.java) - * [NonRepeatingElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/NonRepeatingElement.java) - * [NthUglyNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/NthUglyNumber.java) - * [NumberOfDigits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/NumberOfDigits.java) - * [PalindromeNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PalindromeNumber.java) - * [ParseInteger](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/ParseInteger.java) - * [PascalTriangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PascalTriangle.java) - * [PerfectCube](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PerfectCube.java) - * [PerfectNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PerfectNumber.java) - * [PerfectSquare](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PerfectSquare.java) - * [Perimeter](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Perimeter.java) - * [PiNilakantha](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PiNilakantha.java) - * [PollardRho](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PollardRho.java) - * [Pow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Pow.java) - * [PowerOfTwoOrNot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PowerOfTwoOrNot.java) - * [PowerUsingRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PowerUsingRecursion.java) - * [PrimeCheck](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PrimeCheck.java) - * [PrimeFactorization](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PrimeFactorization.java) - * [PronicNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PronicNumber.java) - * [PythagoreanTriple](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/PythagoreanTriple.java) - * [QuadraticEquationSolver](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java) - * [ReverseNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/ReverseNumber.java) - * [RomanNumeralUtil](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/RomanNumeralUtil.java) - * [SecondMinMax](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SecondMinMax.java) - * [SieveOfEratosthenes](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java) - * [SimpsonIntegration](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SimpsonIntegration.java) - * [SolovayStrassenPrimalityTest](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java) - * [SquareFreeInteger](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SquareFreeInteger.java) - * [SquareRootWithBabylonianMethod](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SquareRootWithBabylonianMethod.java) - * [SquareRootWithNewtonRaphsonMethod](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SquareRootWithNewtonRaphsonMethod.java) - * [StandardDeviation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/StandardDeviation.java) - * [StandardScore](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/StandardScore.java) - * [StrobogrammaticNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/StrobogrammaticNumber.java) - * [SumOfArithmeticSeries](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfArithmeticSeries.java) - * [SumOfDigits](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfDigits.java) - * [SumOfOddNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java) - * [SumWithoutArithmeticOperators](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/SumWithoutArithmeticOperators.java) - * [TrinomialTriangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/TrinomialTriangle.java) - * [TwinPrime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/TwinPrime.java) - * [UniformNumbers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/UniformNumbers.java) - * [VampireNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/VampireNumber.java) - * [VectorCrossProduct](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/VectorCrossProduct.java) - * [Volume](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/maths/Volume.java) - * matrix - * [InverseOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java) - * matrixexponentiation - * [Fibonacci](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java) - * [MatrixTranspose](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/MatrixTranspose.java) - * [MedianOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java) - * [MirrorOfMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java) - * [PrintAMatrixInSpiralOrder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java) - * [RotateMatrixBy90Degrees](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java) - * misc - * [ColorContrastRatio](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/ColorContrastRatio.java) - * [MapReduce](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MapReduce.java) - * [MedianOfRunningArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java) - * [MedianOfRunningArrayByte](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayByte.java) - * [MedianOfRunningArrayDouble](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayDouble.java) - * [MedianOfRunningArrayFloat](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayFloat.java) - * [MedianOfRunningArrayInteger](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayInteger.java) - * [MedianOfRunningArrayLong](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/MedianOfRunningArrayLong.java) - * [PalindromePrime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/PalindromePrime.java) - * [PalindromeSinglyLinkedList](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java) - * [RangeInSortedArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java) - * [ShuffleArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/ShuffleArray.java) - * [Sparsity](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/Sparsity.java) - * [ThreeSumProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/ThreeSumProblem.java) - * [TwoSumProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/TwoSumProblem.java) - * [WordBoggle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/misc/WordBoggle.java) - * others - * [ArrayLeftRotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ArrayLeftRotation.java) - * [ArrayRightRotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ArrayRightRotation.java) - * [BankersAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/BankersAlgorithm.java) - * [BFPRT](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/BFPRT.java) - * [BoyerMoore](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/BoyerMoore.java) - * [BrianKernighanAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/BrianKernighanAlgorithm.java) - * cn - * [HammingDistance](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/cn/HammingDistance.java) - * [Conway](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Conway.java) - * [CRC16](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/CRC16.java) - * [CRC32](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/CRC32.java) - * [CRCAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/CRCAlgorithm.java) - * [Damm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Damm.java) - * [Dijkstra](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Dijkstra.java) - * [FloydTriangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/FloydTriangle.java) - * [GaussLegendre](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/GaussLegendre.java) - * [HappyNumbersSeq](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java) - * [Huffman](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Huffman.java) - * [Implementing auto completing features using trie](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java) - * [InsertDeleteInArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java) - * [KochSnowflake](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/KochSnowflake.java) - * [Krishnamurthy](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Krishnamurthy.java) - * [LinearCongruentialGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/LinearCongruentialGenerator.java) - * [LineSweep](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/LineSweep.java) - * [LowestBasePalindrome](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java) - * [Luhn](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Luhn.java) - * [Mandelbrot](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Mandelbrot.java) - * [MaximumSlidingWindow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java) - * [MaximumSumOfDistinctSubarraysWithLengthK](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java) - * [MemoryManagementAlgorithms](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/MemoryManagementAlgorithms.java) - * [MiniMaxAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java) - * [PageRank](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/PageRank.java) - * [PasswordGen](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/PasswordGen.java) - * [PerlinNoise](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/PerlinNoise.java) - * [QueueUsingTwoStacks](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/QueueUsingTwoStacks.java) - * [RemoveDuplicateFromString](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/RemoveDuplicateFromString.java) - * [ReverseStackUsingRecursion](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/ReverseStackUsingRecursion.java) - * [SkylineProblem](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/SkylineProblem.java) - * [Sudoku](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Sudoku.java) - * [TowerOfHanoi](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/TowerOfHanoi.java) - * [TwoPointers](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/TwoPointers.java) - * [Verhoeff](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/others/Verhoeff.java) - * recursion - * [FibonacciSeries](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java) - * [GenerateSubsets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java) - * scheduling - * [AgingScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/AgingScheduling.java) - * diskscheduling - * [CircularLookScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java) - * [CircularScanScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java) - * [LookScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java) - * [ScanScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java) - * [SSFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java) - * [EDFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/EDFScheduling.java) - * [FairShareScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java) - * [FCFSScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/FCFSScheduling.java) - * [GangScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/GangScheduling.java) - * [HighestResponseRatioNextScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/HighestResponseRatioNextScheduling.java) - * [JobSchedulingWithDeadline](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java) - * [LotteryScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java) - * [MLFQScheduler](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java) - * [MultiAgentScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java) - * [NonPreemptivePriorityScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java) - * [PreemptivePriorityScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java) - * [ProportionalFairScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java) - * [RandomScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/RandomScheduling.java) - * [RRScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/RRScheduling.java) - * [SelfAdjustingScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java) - * [SJFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java) - * [SlackTimeScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java) - * [SRTFScheduling](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/scheduling/SRTFScheduling.java) - * searches - * [BinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/BinarySearch.java) - * [BinarySearch2dArray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/BinarySearch2dArray.java) - * [BM25InvertedIndex](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java) - * [BreadthFirstSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java) - * [DepthFirstSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/DepthFirstSearch.java) - * [ExponentalSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/ExponentalSearch.java) - * [FibonacciSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/FibonacciSearch.java) - * [HowManyTimesRotated](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/HowManyTimesRotated.java) - * [InterpolationSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/InterpolationSearch.java) - * [IterativeBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java) - * [IterativeTernarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/IterativeTernarySearch.java) - * [JumpSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/JumpSearch.java) - * [KMPSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/KMPSearch.java) - * [LinearSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/LinearSearch.java) - * [LinearSearchThread](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/LinearSearchThread.java) - * [LowerBound](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/LowerBound.java) - * [MonteCarloTreeSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/MonteCarloTreeSearch.java) - * [OrderAgnosticBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/OrderAgnosticBinarySearch.java) - * [PerfectBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java) - * [QuickSelect](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/QuickSelect.java) - * [RabinKarpAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/RabinKarpAlgorithm.java) - * [RandomSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/RandomSearch.java) - * [RecursiveBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java) - * [RowColumnWiseSorted2dArrayBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearch.java) - * [SaddlebackSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/SaddlebackSearch.java) - * [SearchInARowAndColWiseSortedMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java) - * [SortOrderAgnosticBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java) - * [SquareRootBinarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/SquareRootBinarySearch.java) - * [TernarySearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/TernarySearch.java) - * [UnionFind](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/UnionFind.java) - * [UpperBound](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/searches/UpperBound.java) - * slidingwindow - * [LongestSubarrayWithSumLessOrEqualToK](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToK.java) - * [LongestSubstringWithoutRepeatingCharacters](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java) - * [MaxSumKSizeSubarray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java) - * [MinSumKSizeSubarray](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarray.java) - * sorts - * [AdaptiveMergeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java) - * [BeadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BeadSort.java) - * [BinaryInsertionSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BinaryInsertionSort.java) - * [BitonicSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BitonicSort.java) - * [BogoSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BogoSort.java) - * [BubbleSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BubbleSort.java) - * [BubbleSortRecursive](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BubbleSortRecursive.java) - * [BucketSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/BucketSort.java) - * [CircleSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CircleSort.java) - * [CocktailShakerSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CocktailShakerSort.java) - * [CombSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CombSort.java) - * [CountingSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CountingSort.java) - * [CycleSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/CycleSort.java) - * [DualPivotQuickSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/DualPivotQuickSort.java) - * [DutchNationalFlagSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/DutchNationalFlagSort.java) - * [ExchangeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/ExchangeSort.java) - * [FlashSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/FlashSort.java) - * [GnomeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/GnomeSort.java) - * [HeapSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/HeapSort.java) - * [InsertionSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/InsertionSort.java) - * [IntrospectiveSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/IntrospectiveSort.java) - * [LinkListSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/LinkListSort.java) - * [MergeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/MergeSort.java) - * [MergeSortNoExtraSpace](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/MergeSortNoExtraSpace.java) - * [MergeSortRecursive](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/MergeSortRecursive.java) - * [OddEvenSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/OddEvenSort.java) - * [PancakeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/PancakeSort.java) - * [PatienceSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/PatienceSort.java) - * [PigeonholeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/PigeonholeSort.java) - * [QuickSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/QuickSort.java) - * [RadixSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/RadixSort.java) - * [SelectionSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SelectionSort.java) - * [SelectionSortRecursive](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SelectionSortRecursive.java) - * [ShellSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/ShellSort.java) - * [SimpleSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SimpleSort.java) - * [SlowSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SlowSort.java) - * [SortAlgorithm](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java) - * [SortUtils](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtils.java) - * [SortUtilsRandomGenerator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SortUtilsRandomGenerator.java) - * [SpreadSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SpreadSort.java) - * [StalinSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StalinSort.java) - * [StoogeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StoogeSort.java) - * [StrandSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/StrandSort.java) - * [SwapSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/SwapSort.java) - * [TimSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/TimSort.java) - * [TopologicalSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/TopologicalSort.java) - * [TreeSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/TreeSort.java) - * [WaveSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/WaveSort.java) - * [WiggleSort](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/sorts/WiggleSort.java) - * stacks - * [BalancedBrackets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/BalancedBrackets.java) - * [CelebrityFinder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/CelebrityFinder.java) - * [DecimalToAnyUsingStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java) - * [DuplicateBrackets](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/DuplicateBrackets.java) - * [GreatestElementConstantTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java) - * [InfixToPostfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java) - * [InfixToPrefix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/InfixToPrefix.java) - * [LargestRectangle](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/LargestRectangle.java) - * [MaximumMinimumWindow](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MaximumMinimumWindow.java) - * [MinStackUsingSingleStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java) - * [MinStackUsingTwoStacks](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java) - * [NextGreaterElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextGreaterElement.java) - * [NextSmallerElement](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/NextSmallerElement.java) - * [PalindromeWithStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java) - * [PostfixEvaluator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java) - * [PostfixToInfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PostfixToInfix.java) - * [PrefixEvaluator](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PrefixEvaluator.java) - * [PrefixToInfix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/PrefixToInfix.java) - * [SmallestElementConstantTime](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java) - * [SortStack](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/SortStack.java) - * [StackPostfixNotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/StackPostfixNotation.java) - * [StackUsingTwoQueues](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/stacks/StackUsingTwoQueues.java) - * strings - * [AhoCorasick](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/AhoCorasick.java) - * [Alphabetical](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Alphabetical.java) - * [Anagrams](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Anagrams.java) - * [CharactersSame](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CharactersSame.java) - * [CheckAnagrams](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CheckAnagrams.java) - * [CheckVowels](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CheckVowels.java) - * [CountChar](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CountChar.java) - * [CountWords](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/CountWords.java) - * [HammingDistance](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/HammingDistance.java) - * [HorspoolSearch](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/HorspoolSearch.java) - * [Isomorphic](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Isomorphic.java) - * [KMP](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/KMP.java) - * [LetterCombinationsOfPhoneNumber](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumber.java) - * [LongestCommonPrefix](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java) - * [LongestNonRepetitiveSubstring](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java) - * [LongestPalindromicSubstring](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java) - * [Lower](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Lower.java) - * [Manacher](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Manacher.java) - * [MyAtoi](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/MyAtoi.java) - * [Palindrome](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Palindrome.java) - * [Pangram](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Pangram.java) - * [PermuteString](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/PermuteString.java) - * [RabinKarp](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/RabinKarp.java) - * [ReturnSubsequence](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ReturnSubsequence.java) - * [ReverseString](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ReverseString.java) - * [ReverseStringRecursive](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ReverseStringRecursive.java) - * [ReverseWordsInString](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ReverseWordsInString.java) - * [Rotation](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Rotation.java) - * [StringCompression](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/StringCompression.java) - * [StringMatchFiniteAutomata](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/StringMatchFiniteAutomata.java) - * [Upper](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/Upper.java) - * [ValidParentheses](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/ValidParentheses.java) - * [WordLadder](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/WordLadder.java) - * zigZagPattern - * [ZigZagPattern](https://github.com/TheAlgorithms/Java/blob/master/src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java) - * test - * java - * com - * thealgorithms - * audiofilters - * [EMAFilterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/audiofilters/EMAFilterTest.java) - * [IIRFilterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/audiofilters/IIRFilterTest.java) - * backtracking - * [AllPathsFromSourceToTargetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/AllPathsFromSourceToTargetTest.java) - * [ArrayCombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java) - * [CombinationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/CombinationTest.java) - * [CrosswordSolverTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/CrosswordSolverTest.java) - * [FloodFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/FloodFillTest.java) - * [KnightsTourTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java) - * [MazeRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java) - * [MColoringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/MColoringTest.java) - * [NQueensTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/NQueensTest.java) - * [ParenthesesGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/ParenthesesGeneratorTest.java) - * [PermutationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/PermutationTest.java) - * [PowerSumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/PowerSumTest.java) - * [SubsequenceFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/SubsequenceFinderTest.java) - * [WordPatternMatcherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/WordPatternMatcherTest.java) - * [WordSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/backtracking/WordSearchTest.java) - * bitmanipulation - * [BcdConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BcdConversionTest.java) - * [BinaryPalindromeCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheckTest.java) - * [BitSwapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java) - * [BooleanAlgebraGatesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGatesTest.java) - * [ClearLeftmostSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBitTest.java) - * [CountLeadingZerosTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/CountLeadingZerosTest.java) - * [CountSetBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java) - * [FindNthBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/FindNthBitTest.java) - * [FirstDifferentBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/FirstDifferentBitTest.java) - * [GenerateSubsetsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/GenerateSubsetsTest.java) - * [GrayCodeConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/GrayCodeConversionTest.java) - * [HammingDistanceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/HammingDistanceTest.java) - * [HigherLowerPowerOfTwoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwoTest.java) - * [HighestSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/HighestSetBitTest.java) - * [IndexOfRightMostSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBitTest.java) - * [IsEvenTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IsEvenTest.java) - * [IsPowerTwoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java) - * [LowestSetBitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java) - * [ModuloPowerOfTwoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwoTest.java) - * [NextHigherSameBitCountTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCountTest.java) - * [NonRepeatingNumberFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java) - * [NumberAppearingOddTimesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimesTest.java) - * [NumbersDifferentSignsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java) - * [OneBitDifferenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/OneBitDifferenceTest.java) - * [OnesComplementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java) - * [ParityCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java) - * [ReverseBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java) - * [SingleBitOperationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java) - * [SingleElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SingleElementTest.java) - * [SwapAdjacentBitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/SwapAdjacentBitsTest.java) - * [TwosComplementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/TwosComplementTest.java) - * [Xs3ConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/bitmanipulation/Xs3ConversionTest.java) - * ciphers - * a5 - * [A5CipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java) - * [A5KeyStreamGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java) - * [LFSRTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java) - * [ADFGVXCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ADFGVXCipherTest.java) - * [AESEncryptionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java) - * [AffineCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java) - * [AtbashTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AtbashTest.java) - * [AutokeyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/AutokeyTest.java) - * [BaconianCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java) - * [BlowfishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/BlowfishTest.java) - * [CaesarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/CaesarTest.java) - * [ColumnarTranspositionCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java) - * [DESTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DESTest.java) - * [DiffieHellmanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java) - * [ECCTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/ECCTest.java) - * [HillCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/HillCipherTest.java) - * [MonoAlphabeticTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java) - * [PlayfairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java) - * [PolybiusTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/PolybiusTest.java) - * [RailFenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RailFenceTest.java) - * [RSATest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/RSATest.java) - * [SimpleSubCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/SimpleSubCipherTest.java) - * [VigenereTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/VigenereTest.java) - * [XORCipherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/ciphers/XORCipherTest.java) - * conversions - * [AffineConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java) - * [AnyBaseToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/AnyBaseToDecimalTest.java) - * [AnytoAnyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/AnytoAnyTest.java) - * [BinaryToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java) - * [BinaryToHexadecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/BinaryToHexadecimalTest.java) - * [BinaryToOctalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/BinaryToOctalTest.java) - * [DecimalToAnyBaseTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/DecimalToAnyBaseTest.java) - * [DecimalToBinaryTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/DecimalToBinaryTest.java) - * [DecimalToHexadecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/DecimalToHexadecimalTest.java) - * [DecimalToOctalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/DecimalToOctalTest.java) - * [EndianConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/EndianConverterTest.java) - * [HexaDecimalToBinaryTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexaDecimalToBinaryTest.java) - * [HexaDecimalToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexaDecimalToDecimalTest.java) - * [HexToOctTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/HexToOctTest.java) - * [IntegerToEnglishTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java) - * [IntegerToRomanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java) - * [IPConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IPConverterTest.java) - * [IPv6ConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/IPv6ConverterTest.java) - * [MorseCodeConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/MorseCodeConverterTest.java) - * [NumberToWordsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/NumberToWordsTest.java) - * [OctalToBinaryTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java) - * [OctalToDecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToDecimalTest.java) - * [OctalToHexadecimalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/OctalToHexadecimalTest.java) - * [PhoneticAlphabetConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java) - * [RomanToIntegerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java) - * [TurkishToLatinConversionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java) - * [UnitConversionsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java) - * [UnitsConverterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java) - * datastructures - * bag - * [BagTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java) - * bloomfilter - * [BloomFilterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java) - * buffers - * [CircularBufferTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java) - * caches - * [LFUCacheTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java) - * [LRUCacheTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java) - * [MRUCacheTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java) - * crdt - * [GCounterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java) - * [GSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/GSetTest.java) - * [LWWElementSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java) - * [ORSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/ORSetTest.java) - * [PNCounterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/PNCounterTest.java) - * [TwoPSetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/crdt/TwoPSetTest.java) - * disjointsetunion - * [DisjointSetUnionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java) - * dynamicarray - * [DynamicArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java) - * graphs - * [AStarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java) - * [BipartiteGraphDFSTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java) - * [BoruvkaAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java) - * [DijkstraAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java) - * [DijkstraOptimizedAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java) - * [EdmondsBlossomAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java) - * [FloydWarshallTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/FloydWarshallTest.java) - * [FordFulkersonTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java) - * [HamiltonianCycleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java) - * [JohnsonsAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java) - * [KahnsAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java) - * [KosarajuTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java) - * [KruskalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java) - * [MatrixGraphsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java) - * [PrimMSTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/PrimMSTTest.java) - * [TarjansAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java) - * [WelshPowellTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java) - * hashmap - * hashing - * [GenericHashMapUsingArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java) - * [GenericHashMapUsingArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java) - * [HashMapCuckooHashingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java) - * [HashMapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java) - * [IntersectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java) - * [LinearProbingHashMapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java) - * [MajorityElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java) - * [MapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java) - * heaps - * [FibonacciHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java) - * [GenericHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java) - * [HeapElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java) - * [KthElementFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/KthElementFinderTest.java) - * [LeftistHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java) - * [MaxHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java) - * [MedianFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MedianFinderTest.java) - * [MergeKSortedArraysTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MergeKSortedArraysTest.java) - * [MinHeapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MinHeapTest.java) - * [MinPriorityQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/heaps/MinPriorityQueueTest.java) - * lists - * [CircleLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) - * [CountSinglyLinkedListRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java) - * [CreateAndDetectLoopTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java) - * [CursorLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java) - * [MergeKSortedLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java) - * [MergeSortedArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java) - * [MergeSortedSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java) - * [QuickSortLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java) - * [ReverseKGroupTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java) - * [RotateSinglyLinkedListsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java) - * [SearchSinglyLinkedListRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursionTest.java) - * [SinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java) - * [SkipListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java) - * [SortedLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java) - * queues - * [CircularQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/CircularQueueTest.java) - * [DequeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java) - * [GenericArrayListQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/GenericArrayListQueueTest.java) - * [LinkedQueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/LinkedQueueTest.java) - * [PriorityQueuesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java) - * [QueueByTwoStacksTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java) - * [QueueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/QueueTest.java) - * [SlidingWindowMaximumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximumTest.java) - * [TokenBucketTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/queues/TokenBucketTest.java) - * stacks - * [NodeStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java) - * [ReverseStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java) - * [StackArrayListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayListTest.java) - * [StackArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java) - * [StackOfLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java) - * trees - * [AVLTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java) - * [BinaryTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java) - * [BoundaryTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BoundaryTraversalTest.java) - * [BSTFromSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BSTFromSortedArrayTest.java) - * [BSTIterativeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BSTIterativeTest.java) - * [BSTRecursiveTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/BSTRecursiveTest.java) - * [CeilInBinarySearchTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTreeTest.java) - * [CheckBinaryTreeIsValidBSTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/CheckBinaryTreeIsValidBSTTest.java) - * [CheckIfBinaryTreeBalancedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/CheckIfBinaryTreeBalancedTest.java) - * [CheckTreeIsSymmetricTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/CheckTreeIsSymmetricTest.java) - * [CreateBinaryTreeFromInorderPreorderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/CreateBinaryTreeFromInorderPreorderTest.java) - * [InorderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/InorderTraversalTest.java) - * [KDTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/KDTreeTest.java) - * [LazySegmentTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/LazySegmentTreeTest.java) - * [LevelOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/LevelOrderTraversalTest.java) - * [PostOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/PostOrderTraversalTest.java) - * [PreOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/PreOrderTraversalTest.java) - * [QuadTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/QuadTreeTest.java) - * [SameTreesCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SameTreesCheckTest.java) - * [SplayTreeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/SplayTreeTest.java) - * [TreapTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java) - * [TreeTestUtils](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TreeTestUtils.java) - * [TrieTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java) - * [VerticalOrderTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversalTest.java) - * [ZigzagTraversalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/trees/ZigzagTraversalTest.java) - * divideandconquer - * [BinaryExponentiationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/BinaryExponentiationTest.java) - * [ClosestPairTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java) - * [CountingInversionsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java) - * [MedianOfTwoSortedArraysTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArraysTest.java) - * [SkylineAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/SkylineAlgorithmTest.java) - * [StrassenMatrixMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplicationTest.java) - * [TilingProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/divideandconquer/TilingProblemTest.java) - * dynamicprogramming - * [AbbreviationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java) - * [AllConstructTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AllConstructTest.java) - * [AssignmentUsingBitmaskTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java) - * [BoardPathTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BoardPathTest.java) - * [BoundaryFillTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BoundaryFillTest.java) - * [BruteForceKnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsackTest.java) - * [CatalanNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/CatalanNumberTest.java) - * [ClimbStairsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/ClimbStairsTest.java) - * [CoinChangeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/CoinChangeTest.java) - * [CountFriendsPairingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/CountFriendsPairingTest.java) - * [DPTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/DPTest.java) - * [EditDistanceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/EditDistanceTest.java) - * [EggDroppingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/EggDroppingTest.java) - * [FibonacciTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/FibonacciTest.java) - * [KadaneAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithmTest.java) - * [KnapsackMemoizationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java) - * [KnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackTest.java) - * [LevenshteinDistanceTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LevenshteinDistanceTests.java) - * [LongestAlternatingSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestAlternatingSubsequenceTest.java) - * [LongestArithmeticSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequenceTest.java) - * [LongestCommonSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java) - * [LongestIncreasingSubsequenceTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceTests.java) - * [LongestPalindromicSubstringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubstringTest.java) - * [LongestValidParenthesesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/LongestValidParenthesesTest.java) - * [MatrixChainMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplicationTest.java) - * [MatrixChainRecursiveTopDownMemoisationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisationTest.java) - * [MaximumSumOfNonAdjacentElementsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElementsTest.java) - * [MinimumPathSumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MinimumPathSumTest.java) - * [MinimumSumPartitionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/MinimumSumPartitionTest.java) - * [NewManShanksPrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/NewManShanksPrimeTest.java) - * [OptimalJobSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/OptimalJobSchedulingTest.java) - * [PalindromicPartitioningTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/PalindromicPartitioningTest.java) - * [PartitionProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/PartitionProblemTest.java) - * [RegexMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/RegexMatchingTest.java) - * [RodCuttingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java) - * [ShortestCommonSupersequenceLengthTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/ShortestCommonSupersequenceLengthTest.java) - * [SubsetCountTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/SubsetCountTest.java) - * [SubsetSumSpaceOptimizedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimizedTest.java) - * [SubsetSumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumTest.java) - * [SumOfSubsetTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/SumOfSubsetTest.java) - * [TribonacciTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/TribonacciTest.java) - * [UniquePathsTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/UniquePathsTests.java) - * [UniqueSubsequencesCountTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java) - * [WildcardMatchingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java) - * [WineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java) - * geometry - * [BresenhamLineTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java) - * [ConvexHullTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java) - * [GrahamScanTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/GrahamScanTest.java) - * [MidpointCircleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/MidpointCircleTest.java) - * [MidpointEllipseTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/geometry/MidpointEllipseTest.java) - * graph - * [StronglyConnectedComponentOptimizedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java) - * greedyalgorithms - * [ActivitySelectionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java) - * [BandwidthAllocationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/BandwidthAllocationTest.java) - * [BinaryAdditionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java) - * [CoinChangeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java) - * [DigitSeparationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/DigitSeparationTest.java) - * [EgyptianFractionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java) - * [FractionalKnapsackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/FractionalKnapsackTest.java) - * [GaleShapleyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/GaleShapleyTest.java) - * [JobSequencingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/JobSequencingTest.java) - * [KCentersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/KCentersTest.java) - * [MergeIntervalsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MergeIntervalsTest.java) - * [MinimizingLatenessTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MinimizingLatenessTest.java) - * [MinimumWaitingTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java) - * [OptimalFileMergingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/OptimalFileMergingTest.java) - * [StockProfitCalculatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java) - * io - * [BufferedReaderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/io/BufferedReaderTest.java) - * lineclipping - * [CohenSutherlandTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/lineclipping/CohenSutherlandTest.java) - * [LiangBarskyTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/lineclipping/LiangBarskyTest.java) - * maths - * [AbsoluteMaxTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AbsoluteMaxTest.java) - * [AbsoluteMinTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java) - * [AbsoluteValueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java) - * [ADTFractionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ADTFractionTest.java) - * [AliquotSumTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AliquotSumTest.java) - * [AmicableNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AmicableNumberTest.java) - * [AreaTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AreaTest.java) - * [ArmstrongTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ArmstrongTest.java) - * [AutoCorrelationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AutoCorrelationTest.java) - * [AutomorphicNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AutomorphicNumberTest.java) - * [AverageTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/AverageTest.java) - * [BinaryPowTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/BinaryPowTest.java) - * [BinomialCoefficientTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/BinomialCoefficientTest.java) - * [CatalanNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CatalanNumbersTest.java) - * [CeilTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CeilTest.java) - * [ChineseRemainderTheoremTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java) - * [CollatzConjectureTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CollatzConjectureTest.java) - * [CombinationsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CombinationsTest.java) - * [ConvolutionFFTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java) - * [ConvolutionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ConvolutionTest.java) - * [CrossCorrelationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/CrossCorrelationTest.java) - * [DeterminantOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DeterminantOfMatrixTest.java) - * [DigitalRootTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DigitalRootTest.java) - * [DistanceFormulaTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DistanceFormulaTest.java) - * [DudeneyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/DudeneyNumberTest.java) - * [EulerMethodTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/EulerMethodTest.java) - * [EulersFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/EulersFunctionTest.java) - * [FactorialRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java) - * [FactorialTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FactorialTest.java) - * [FastExponentiationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FastExponentiationTest.java) - * [FastInverseSqrtTests](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FastInverseSqrtTests.java) - * [FFTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FFTTest.java) - * [FibonacciJavaStreamsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FibonacciJavaStreamsTest.java) - * [FibonacciLoopTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FibonacciLoopTest.java) - * [FibonacciNumberCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FibonacciNumberCheckTest.java) - * [FibonacciNumberGoldenRationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FibonacciNumberGoldenRationTest.java) - * [FindKthNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FindKthNumberTest.java) - * [FindMaxRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FindMaxRecursionTest.java) - * [FindMaxTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FindMaxTest.java) - * [FindMinRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FindMinRecursionTest.java) - * [FindMinTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FindMinTest.java) - * [FloorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FloorTest.java) - * [FrizzyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/FrizzyNumberTest.java) - * [GaussianTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GaussianTest.java) - * [GCDRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GCDRecursionTest.java) - * [GCDTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GCDTest.java) - * [GenericRootTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GenericRootTest.java) - * [GoldbachConjectureTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/GoldbachConjectureTest.java) - * [HarshadNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/HarshadNumberTest.java) - * [HeronsFormulaTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/HeronsFormulaTest.java) - * [JosephusProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/JosephusProblemTest.java) - * [KaprekarNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java) - * [KaratsubaMultiplicationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KaratsubaMultiplicationTest.java) - * [KrishnamurthyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java) - * [LeastCommonMultipleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LeastCommonMultipleTest.java) - * [LeonardoNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java) - * [LiouvilleLambdaFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LiouvilleLambdaFunctionTest.java) - * [LongDivisionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LongDivisionTest.java) - * [LucasSeriesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/LucasSeriesTest.java) - * [MatrixRankTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MatrixRankTest.java) - * [MatrixUtilTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MatrixUtilTest.java) - * [MaxValueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MaxValueTest.java) - * [MeansTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MeansTest.java) - * [MedianTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MedianTest.java) - * [MillerRabinPrimalityCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MillerRabinPrimalityCheckTest.java) - * [MinValueTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MinValueTest.java) - * [MobiusFunctionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/MobiusFunctionTest.java) - * [ModeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ModeTest.java) - * [NonRepeatingElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/NonRepeatingElementTest.java) - * [NthUglyNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java) - * [NumberOfDigitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/NumberOfDigitsTest.java) - * [PalindromeNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PalindromeNumberTest.java) - * [ParseIntegerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ParseIntegerTest.java) - * [PascalTriangleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PascalTriangleTest.java) - * [PerfectCubeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PerfectCubeTest.java) - * [PerfectNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PerfectNumberTest.java) - * [PerfectSquareTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PerfectSquareTest.java) - * [PerimeterTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PerimeterTest.java) - * [PollardRhoTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PollardRhoTest.java) - * [PowerOfTwoOrNotTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PowerOfTwoOrNotTest.java) - * [PowerUsingRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PowerUsingRecursionTest.java) - * [PowTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PowTest.java) - * [PrimeCheckTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PrimeCheckTest.java) - * [PrimeFactorizationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PrimeFactorizationTest.java) - * [PronicNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PronicNumberTest.java) - * [PythagoreanTripleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/PythagoreanTripleTest.java) - * [QuadraticEquationSolverTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java) - * [ReverseNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/ReverseNumberTest.java) - * [SecondMinMaxTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SecondMinMaxTest.java) - * [SieveOfEratosthenesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java) - * [SolovayStrassenPrimalityTestTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SolovayStrassenPrimalityTestTest.java) - * [SquareFreeIntegerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SquareFreeIntegerTest.java) - * [SquareRootwithBabylonianMethodTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SquareRootwithBabylonianMethodTest.java) - * [SquareRootWithNewtonRaphsonTestMethod](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SquareRootWithNewtonRaphsonTestMethod.java) - * [StandardDeviationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/StandardDeviationTest.java) - * [StandardScoreTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/StandardScoreTest.java) - * [StrobogrammaticNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/StrobogrammaticNumberTest.java) - * [SumOfArithmeticSeriesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfArithmeticSeriesTest.java) - * [SumOfDigitsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfDigitsTest.java) - * [SumOfOddNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java) - * [SumWithoutArithmeticOperatorsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/SumWithoutArithmeticOperatorsTest.java) - * [TestArmstrong](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/TestArmstrong.java) - * [TwinPrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/TwinPrimeTest.java) - * [UniformNumbersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/UniformNumbersTest.java) - * [VampireNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/VampireNumberTest.java) - * [VolumeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/maths/VolumeTest.java) - * matrix - * [InverseOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java) - * [MatrixTransposeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/MatrixTransposeTest.java) - * [MedianOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java) - * [MirrorOfMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/MirrorOfMatrixTest.java) - * [TestPrintMatrixInSpiralOrder](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java) - * misc - * [ColorContrastRatioTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ColorContrastRatioTest.java) - * [MapReduceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MapReduceTest.java) - * [MedianOfRunningArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java) - * [PalindromePrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java) - * [PalindromeSinglyLinkedListTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java) - * [RangeInSortedArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java) - * [ShuffleArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java) - * [SparsityTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/SparsityTest.java) - * [ThreeSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java) - * [TwoSumProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/TwoSumProblemTest.java) - * [WordBoggleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/misc/WordBoggleTest.java) - * others - * [ArrayLeftRotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ArrayLeftRotationTest.java) - * [ArrayRightRotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ArrayRightRotationTest.java) - * [BestFitCPUTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/BestFitCPUTest.java) - * [BFPRTTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/BFPRTTest.java) - * [BoyerMooreTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/BoyerMooreTest.java) - * cn - * [HammingDistanceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java) - * [ConwayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ConwayTest.java) - * [CountFriendsPairingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/CountFriendsPairingTest.java) - * [CRC16Test](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/CRC16Test.java) - * [CRCAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java) - * [FirstFitCPUTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/FirstFitCPUTest.java) - * [FloydTriangleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/FloydTriangleTest.java) - * [KadaneAlogrithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/KadaneAlogrithmTest.java) - * [LineSweepTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/LineSweepTest.java) - * [LinkListSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/LinkListSortTest.java) - * [LowestBasePalindromeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java) - * [MaximumSlidingWindowTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/MaximumSlidingWindowTest.java) - * [MaximumSumOfDistinctSubarraysWithLengthKTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java) - * [NewManShanksPrimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java) - * [NextFitTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/NextFitTest.java) - * [PasswordGenTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/PasswordGenTest.java) - * [QueueUsingTwoStacksTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/QueueUsingTwoStacksTest.java) - * [RemoveDuplicateFromStringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/RemoveDuplicateFromStringTest.java) - * [ReverseStackUsingRecursionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/ReverseStackUsingRecursionTest.java) - * [SkylineProblemTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/SkylineProblemTest.java) - * [SudokuTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/SudokuTest.java) - * [TowerOfHanoiTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/TowerOfHanoiTest.java) - * [TwoPointersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/TwoPointersTest.java) - * [WorstFitCPUTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/others/WorstFitCPUTest.java) - * recursion - * [FibonacciSeriesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/recursion/FibonacciSeriesTest.java) - * [GenerateSubsetsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java) - * scheduling - * [AgingSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/AgingSchedulingTest.java) - * diskscheduling - * [CircularLookSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularLookSchedulingTest.java) - * [CircularScanSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularScanSchedulingTest.java) - * [LookSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/LookSchedulingTest.java) - * [ScanSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/ScanSchedulingTest.java) - * [SSFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/diskscheduling/SSFSchedulingTest.java) - * [EDFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/EDFSchedulingTest.java) - * [FairShareSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/FairShareSchedulingTest.java) - * [FCFSSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/FCFSSchedulingTest.java) - * [GangSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/GangSchedulingTest.java) - * [HighestResponseRatioNextSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/HighestResponseRatioNextSchedulingTest.java) - * [JobSchedulingWithDeadlineTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/JobSchedulingWithDeadlineTest.java) - * [LotterySchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/LotterySchedulingTest.java) - * [MLFQSchedulerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java) - * [MultiAgentSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/MultiAgentSchedulingTest.java) - * [NonPreemptivePrioritySchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/NonPreemptivePrioritySchedulingTest.java) - * [PreemptivePrioritySchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java) - * [ProportionalFairSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/ProportionalFairSchedulingTest.java) - * [RandomSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/RandomSchedulingTest.java) - * [RRSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/RRSchedulingTest.java) - * [SelfAdjustingSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SelfAdjustingSchedulingTest.java) - * [SJFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SJFSchedulingTest.java) - * [SlackTimeSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SlackTimeSchedulingTest.java) - * [SRTFSchedulingTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/scheduling/SRTFSchedulingTest.java) - * searches - * [BinarySearch2dArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/BinarySearch2dArrayTest.java) - * [BinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/BinarySearchTest.java) - * [BM25InvertedIndexTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java) - * [BreadthFirstSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/BreadthFirstSearchTest.java) - * [DepthFirstSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/DepthFirstSearchTest.java) - * [ExponentialSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java) - * [FibonacciSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java) - * [HowManyTimesRotatedTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/HowManyTimesRotatedTest.java) - * [InterpolationSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java) - * [IterativeBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java) - * [IterativeTernarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/IterativeTernarySearchTest.java) - * [JumpSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/JumpSearchTest.java) - * [KMPSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/KMPSearchTest.java) - * [LinearSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/LinearSearchTest.java) - * [LinearSearchThreadTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java) - * [LowerBoundTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/LowerBoundTest.java) - * [MonteCarloTreeSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/MonteCarloTreeSearchTest.java) - * [OrderAgnosticBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/OrderAgnosticBinarySearchTest.java) - * [PerfectBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java) - * [QuickSelectTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/QuickSelectTest.java) - * [RabinKarpAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/RabinKarpAlgorithmTest.java) - * [RandomSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/RandomSearchTest.java) - * [RecursiveBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/RecursiveBinarySearchTest.java) - * [RowColumnWiseSorted2dArrayBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearchTest.java) - * [SaddlebackSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/SaddlebackSearchTest.java) - * [SearchInARowAndColWiseSortedMatrixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrixTest.java) - * [SortOrderAgnosticBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java) - * [SquareRootBinarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/SquareRootBinarySearchTest.java) - * [TernarySearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/TernarySearchTest.java) - * [TestSearchInARowAndColWiseSortedMatrix](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java) - * [UnionFindTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/UnionFindTest.java) - * [UpperBoundTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/searches/UpperBoundTest.java) - * slidingwindow - * [LongestSubarrayWithSumLessOrEqualToKTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToKTest.java) - * [LongestSubstringWithoutRepeatingCharactersTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java) - * [MaxSumKSizeSubarrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java) - * [MinSumKSizeSubarrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarrayTest.java) - * sorts - * [AdaptiveMergeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java) - * [BeadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BeadSortTest.java) - * [BinaryInsertionSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BinaryInsertionSortTest.java) - * [BitonicSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java) - * [BogoSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BogoSortTest.java) - * [BubbleSortRecursiveTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BubbleSortRecursiveTest.java) - * [BubbleSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BubbleSortTest.java) - * [BucketSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/BucketSortTest.java) - * [CircleSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/CircleSortTest.java) - * [CocktailShakerSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/CocktailShakerSortTest.java) - * [CombSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/CombSortTest.java) - * [CountingSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/CountingSortTest.java) - * [CycleSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/CycleSortTest.java) - * [DualPivotQuickSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/DualPivotQuickSortTest.java) - * [DutchNationalFlagSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/DutchNationalFlagSortTest.java) - * [ExchangeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/ExchangeSortTest.java) - * [FlashSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/FlashSortTest.java) - * [GnomeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/GnomeSortTest.java) - * [HeapSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/HeapSortTest.java) - * [InsertionSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/InsertionSortTest.java) - * [IntrospectiveSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/IntrospectiveSortTest.java) - * [MergeSortNoExtraSpaceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/MergeSortNoExtraSpaceTest.java) - * [MergeSortRecursiveTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/MergeSortRecursiveTest.java) - * [MergeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/MergeSortTest.java) - * [OddEvenSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/OddEvenSortTest.java) - * [PancakeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/PancakeSortTest.java) - * [PatienceSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/PatienceSortTest.java) - * [PigeonholeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/PigeonholeSortTest.java) - * [QuickSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/QuickSortTest.java) - * [RadixSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/RadixSortTest.java) - * [SelectionSortRecursiveTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SelectionSortRecursiveTest.java) - * [SelectionSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SelectionSortTest.java) - * [ShellSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/ShellSortTest.java) - * [SimpleSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SimpleSortTest.java) - * [SlowSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SlowSortTest.java) - * [SortingAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortingAlgorithmTest.java) - * [SortUtilsRandomGeneratorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsRandomGeneratorTest.java) - * [SortUtilsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SortUtilsTest.java) - * [SpreadSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java) - * [StalinSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StalinSortTest.java) - * [StoogeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StoogeSortTest.java) - * [StrandSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/StrandSortTest.java) - * [SwapSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/SwapSortTest.java) - * [TimSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/TimSortTest.java) - * [TopologicalSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java) - * [TreeSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/TreeSortTest.java) - * [WaveSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/WaveSortTest.java) - * [WiggleSortTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java) - * stacks - * [BalancedBracketsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/BalancedBracketsTest.java) - * [CelebrityFinderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java) - * [DecimalToAnyUsingStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/DecimalToAnyUsingStackTest.java) - * [DuplicateBracketsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java) - * [GreatestElementConstantTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java) - * [InfixToPostfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java) - * [InfixToPrefixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java) - * [LargestRectangleTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java) - * [MinStackUsingSingleStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java) - * [MinStackUsingTwoStacksTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java) - * [NextGreaterElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextGreaterElementTest.java) - * [NextSmallerElementTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/NextSmallerElementTest.java) - * [PalindromeWithStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java) - * [PostfixEvaluatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java) - * [PostfixToInfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PostfixToInfixTest.java) - * [PrefixEvaluatorTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java) - * [PrefixToInfixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/PrefixToInfixTest.java) - * [SmallestElementConstantTimeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java) - * [SortStackTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/SortStackTest.java) - * [StackPostfixNotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/StackPostfixNotationTest.java) - * [StackUsingTwoQueuesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/stacks/StackUsingTwoQueuesTest.java) - * strings - * [AhoCorasickTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/AhoCorasickTest.java) - * [AlphabeticalTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java) - * [AnagramsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/AnagramsTest.java) - * [CharacterSameTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CharacterSameTest.java) - * [CheckAnagramsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java) - * [CheckVowelsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CheckVowelsTest.java) - * [CountCharTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CountCharTest.java) - * [CountWordsTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/CountWordsTest.java) - * [HammingDistanceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/HammingDistanceTest.java) - * [HorspoolSearchTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/HorspoolSearchTest.java) - * [IsomorphicTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/IsomorphicTest.java) - * [LetterCombinationsOfPhoneNumberTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumberTest.java) - * [LongestCommonPrefixTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java) - * [LongestNonRepetitiveSubstringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LongestNonRepetitiveSubstringTest.java) - * [LongestPalindromicSubstringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java) - * [LowerTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/LowerTest.java) - * [ManacherTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ManacherTest.java) - * [MyAtoiTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/MyAtoiTest.java) - * [PalindromeTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/PalindromeTest.java) - * [PangramTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/PangramTest.java) - * [PermuteStringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/PermuteStringTest.java) - * [ReturnSubsequenceTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ReturnSubsequenceTest.java) - * [ReverseStringRecursiveTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ReverseStringRecursiveTest.java) - * [ReverseStringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ReverseStringTest.java) - * [ReverseWordsInStringTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ReverseWordsInStringTest.java) - * [RotationTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/RotationTest.java) - * [StringCompressionTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/StringCompressionTest.java) - * [StringMatchFiniteAutomataTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/StringMatchFiniteAutomataTest.java) - * [UpperTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/UpperTest.java) - * [ValidParenthesesTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java) - * [WordLadderTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/WordLadderTest.java) - * zigZagPattern - * [ZigZagPatternTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java) + +- ๐Ÿ“ **main** + - ๐Ÿ“ **java** + - ๐Ÿ“ **com** + - ๐Ÿ“ **thealgorithms** + - ๐Ÿ“ **audiofilters** + - ๐Ÿ“„ [EMAFilter](src/main/java/com/thealgorithms/audiofilters/EMAFilter.java) + - ๐Ÿ“„ [IIRFilter](src/main/java/com/thealgorithms/audiofilters/IIRFilter.java) + - ๐Ÿ“ **backtracking** + - ๐Ÿ“„ [AllPathsFromSourceToTarget](src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java) + - ๐Ÿ“„ [ArrayCombination](src/main/java/com/thealgorithms/backtracking/ArrayCombination.java) + - ๐Ÿ“„ [Combination](src/main/java/com/thealgorithms/backtracking/Combination.java) + - ๐Ÿ“„ [CrosswordSolver](src/main/java/com/thealgorithms/backtracking/CrosswordSolver.java) + - ๐Ÿ“„ [FloodFill](src/main/java/com/thealgorithms/backtracking/FloodFill.java) + - ๐Ÿ“„ [KnightsTour](src/main/java/com/thealgorithms/backtracking/KnightsTour.java) + - ๐Ÿ“„ [MColoring](src/main/java/com/thealgorithms/backtracking/MColoring.java) + - ๐Ÿ“„ [MazeRecursion](src/main/java/com/thealgorithms/backtracking/MazeRecursion.java) + - ๐Ÿ“„ [NQueens](src/main/java/com/thealgorithms/backtracking/NQueens.java) + - ๐Ÿ“„ [ParenthesesGenerator](src/main/java/com/thealgorithms/backtracking/ParenthesesGenerator.java) + - ๐Ÿ“„ [Permutation](src/main/java/com/thealgorithms/backtracking/Permutation.java) + - ๐Ÿ“„ [PowerSum](src/main/java/com/thealgorithms/backtracking/PowerSum.java) + - ๐Ÿ“„ [SubsequenceFinder](src/main/java/com/thealgorithms/backtracking/SubsequenceFinder.java) + - ๐Ÿ“„ [WordPatternMatcher](src/main/java/com/thealgorithms/backtracking/WordPatternMatcher.java) + - ๐Ÿ“„ [WordSearch](src/main/java/com/thealgorithms/backtracking/WordSearch.java) + - ๐Ÿ“ **bitmanipulation** + - ๐Ÿ“„ [BcdConversion](src/main/java/com/thealgorithms/bitmanipulation/BcdConversion.java) + - ๐Ÿ“„ [BinaryPalindromeCheck](src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java) + - ๐Ÿ“„ [BitSwap](src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java) + - ๐Ÿ“„ [BitwiseGCD](src/main/java/com/thealgorithms/bitmanipulation/BitwiseGCD.java) + - ๐Ÿ“„ [BooleanAlgebraGates](src/main/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGates.java) + - ๐Ÿ“„ [ClearLeftmostSetBit](src/main/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBit.java) + - ๐Ÿ“„ [CountBitsFlip](src/main/java/com/thealgorithms/bitmanipulation/CountBitsFlip.java) + - ๐Ÿ“„ [CountLeadingZeros](src/main/java/com/thealgorithms/bitmanipulation/CountLeadingZeros.java) + - ๐Ÿ“„ [CountSetBits](src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java) + - ๐Ÿ“„ [FindNthBit](src/main/java/com/thealgorithms/bitmanipulation/FindNthBit.java) + - ๐Ÿ“„ [FirstDifferentBit](src/main/java/com/thealgorithms/bitmanipulation/FirstDifferentBit.java) + - ๐Ÿ“„ [GenerateSubsets](src/main/java/com/thealgorithms/bitmanipulation/GenerateSubsets.java) + - ๐Ÿ“„ [GrayCodeConversion](src/main/java/com/thealgorithms/bitmanipulation/GrayCodeConversion.java) + - ๐Ÿ“„ [HammingDistance](src/main/java/com/thealgorithms/bitmanipulation/HammingDistance.java) + - ๐Ÿ“„ [HigherLowerPowerOfTwo](src/main/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwo.java) + - ๐Ÿ“„ [HighestSetBit](src/main/java/com/thealgorithms/bitmanipulation/HighestSetBit.java) + - ๐Ÿ“„ [IndexOfRightMostSetBit](src/main/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBit.java) + - ๐Ÿ“„ [IsEven](src/main/java/com/thealgorithms/bitmanipulation/IsEven.java) + - ๐Ÿ“„ [IsPowerTwo](src/main/java/com/thealgorithms/bitmanipulation/IsPowerTwo.java) + - ๐Ÿ“„ [LowestSetBit](src/main/java/com/thealgorithms/bitmanipulation/LowestSetBit.java) + - ๐Ÿ“„ [ModuloPowerOfTwo](src/main/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwo.java) + - ๐Ÿ“„ [NextHigherSameBitCount](src/main/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCount.java) + - ๐Ÿ“„ [NonRepeatingNumberFinder](src/main/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinder.java) + - ๐Ÿ“„ [NumberAppearingOddTimes](src/main/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimes.java) + - ๐Ÿ“„ [NumbersDifferentSigns](src/main/java/com/thealgorithms/bitmanipulation/NumbersDifferentSigns.java) + - ๐Ÿ“„ [OneBitDifference](src/main/java/com/thealgorithms/bitmanipulation/OneBitDifference.java) + - ๐Ÿ“„ [OnesComplement](src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java) + - ๐Ÿ“„ [ParityCheck](src/main/java/com/thealgorithms/bitmanipulation/ParityCheck.java) + - ๐Ÿ“„ [ReverseBits](src/main/java/com/thealgorithms/bitmanipulation/ReverseBits.java) + - ๐Ÿ“„ [SingleBitOperations](src/main/java/com/thealgorithms/bitmanipulation/SingleBitOperations.java) + - ๐Ÿ“„ [SingleElement](src/main/java/com/thealgorithms/bitmanipulation/SingleElement.java) + - ๐Ÿ“„ [SwapAdjacentBits](src/main/java/com/thealgorithms/bitmanipulation/SwapAdjacentBits.java) + - ๐Ÿ“„ [TwosComplement](src/main/java/com/thealgorithms/bitmanipulation/TwosComplement.java) + - ๐Ÿ“„ [Xs3Conversion](src/main/java/com/thealgorithms/bitmanipulation/Xs3Conversion.java) + - ๐Ÿ“ **ciphers** + - ๐Ÿ“„ [ADFGVXCipher](src/main/java/com/thealgorithms/ciphers/ADFGVXCipher.java) + - ๐Ÿ“„ [AES](src/main/java/com/thealgorithms/ciphers/AES.java) + - ๐Ÿ“„ [AESEncryption](src/main/java/com/thealgorithms/ciphers/AESEncryption.java) + - ๐Ÿ“„ [AffineCipher](src/main/java/com/thealgorithms/ciphers/AffineCipher.java) + - ๐Ÿ“„ [AtbashCipher](src/main/java/com/thealgorithms/ciphers/AtbashCipher.java) + - ๐Ÿ“„ [Autokey](src/main/java/com/thealgorithms/ciphers/Autokey.java) + - ๐Ÿ“„ [BaconianCipher](src/main/java/com/thealgorithms/ciphers/BaconianCipher.java) + - ๐Ÿ“„ [Blowfish](src/main/java/com/thealgorithms/ciphers/Blowfish.java) + - ๐Ÿ“„ [Caesar](src/main/java/com/thealgorithms/ciphers/Caesar.java) + - ๐Ÿ“„ [ColumnarTranspositionCipher](src/main/java/com/thealgorithms/ciphers/ColumnarTranspositionCipher.java) + - ๐Ÿ“„ [DES](src/main/java/com/thealgorithms/ciphers/DES.java) + - ๐Ÿ“„ [DiffieHellman](src/main/java/com/thealgorithms/ciphers/DiffieHellman.java) + - ๐Ÿ“„ [ECC](src/main/java/com/thealgorithms/ciphers/ECC.java) + - ๐Ÿ“„ [HillCipher](src/main/java/com/thealgorithms/ciphers/HillCipher.java) + - ๐Ÿ“„ [MonoAlphabetic](src/main/java/com/thealgorithms/ciphers/MonoAlphabetic.java) + - ๐Ÿ“„ [PermutationCipher](src/main/java/com/thealgorithms/ciphers/PermutationCipher.java) + - ๐Ÿ“„ [PlayfairCipher](src/main/java/com/thealgorithms/ciphers/PlayfairCipher.java) + - ๐Ÿ“„ [Polybius](src/main/java/com/thealgorithms/ciphers/Polybius.java) + - ๐Ÿ“„ [ProductCipher](src/main/java/com/thealgorithms/ciphers/ProductCipher.java) + - ๐Ÿ“„ [RSA](src/main/java/com/thealgorithms/ciphers/RSA.java) + - ๐Ÿ“„ [RailFenceCipher](src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java) + - ๐Ÿ“„ [SimpleSubCipher](src/main/java/com/thealgorithms/ciphers/SimpleSubCipher.java) + - ๐Ÿ“„ [Vigenere](src/main/java/com/thealgorithms/ciphers/Vigenere.java) + - ๐Ÿ“„ [XORCipher](src/main/java/com/thealgorithms/ciphers/XORCipher.java) + - ๐Ÿ“ **a5** + - ๐Ÿ“„ [A5Cipher](src/main/java/com/thealgorithms/ciphers/a5/A5Cipher.java) + - ๐Ÿ“„ [A5KeyStreamGenerator](src/main/java/com/thealgorithms/ciphers/a5/A5KeyStreamGenerator.java) + - ๐Ÿ“„ [BaseLFSR](src/main/java/com/thealgorithms/ciphers/a5/BaseLFSR.java) + - ๐Ÿ“„ [CompositeLFSR](src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java) + - ๐Ÿ“„ [LFSR](src/main/java/com/thealgorithms/ciphers/a5/LFSR.java) + - ๐Ÿ“„ [Utils](src/main/java/com/thealgorithms/ciphers/a5/Utils.java) + - ๐Ÿ“ **compression** + - ๐Ÿ“„ [RunLengthEncoding](src/main/java/com/thealgorithms/compression/RunLengthEncoding.java) + - ๐Ÿ“„ [ShannonFano](src/main/java/com/thealgorithms/compression/ShannonFano.java) + - ๐Ÿ“ **conversions** + - ๐Ÿ“„ [AffineConverter](src/main/java/com/thealgorithms/conversions/AffineConverter.java) + - ๐Ÿ“„ [AnyBaseToAnyBase](src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java) + - ๐Ÿ“„ [AnyBaseToDecimal](src/main/java/com/thealgorithms/conversions/AnyBaseToDecimal.java) + - ๐Ÿ“„ [AnytoAny](src/main/java/com/thealgorithms/conversions/AnytoAny.java) + - ๐Ÿ“„ [Base64](src/main/java/com/thealgorithms/conversions/Base64.java) + - ๐Ÿ“„ [BinaryToDecimal](src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java) + - ๐Ÿ“„ [BinaryToHexadecimal](src/main/java/com/thealgorithms/conversions/BinaryToHexadecimal.java) + - ๐Ÿ“„ [BinaryToOctal](src/main/java/com/thealgorithms/conversions/BinaryToOctal.java) + - ๐Ÿ“„ [CoordinateConverter](src/main/java/com/thealgorithms/conversions/CoordinateConverter.java) + - ๐Ÿ“„ [DecimalToAnyBase](src/main/java/com/thealgorithms/conversions/DecimalToAnyBase.java) + - ๐Ÿ“„ [DecimalToBinary](src/main/java/com/thealgorithms/conversions/DecimalToBinary.java) + - ๐Ÿ“„ [DecimalToHexadecimal](src/main/java/com/thealgorithms/conversions/DecimalToHexadecimal.java) + - ๐Ÿ“„ [DecimalToOctal](src/main/java/com/thealgorithms/conversions/DecimalToOctal.java) + - ๐Ÿ“„ [EndianConverter](src/main/java/com/thealgorithms/conversions/EndianConverter.java) + - ๐Ÿ“„ [HexToOct](src/main/java/com/thealgorithms/conversions/HexToOct.java) + - ๐Ÿ“„ [HexaDecimalToBinary](src/main/java/com/thealgorithms/conversions/HexaDecimalToBinary.java) + - ๐Ÿ“„ [HexaDecimalToDecimal](src/main/java/com/thealgorithms/conversions/HexaDecimalToDecimal.java) + - ๐Ÿ“„ [IPConverter](src/main/java/com/thealgorithms/conversions/IPConverter.java) + - ๐Ÿ“„ [IPv6Converter](src/main/java/com/thealgorithms/conversions/IPv6Converter.java) + - ๐Ÿ“„ [IntegerToEnglish](src/main/java/com/thealgorithms/conversions/IntegerToEnglish.java) + - ๐Ÿ“„ [IntegerToRoman](src/main/java/com/thealgorithms/conversions/IntegerToRoman.java) + - ๐Ÿ“„ [MorseCodeConverter](src/main/java/com/thealgorithms/conversions/MorseCodeConverter.java) + - ๐Ÿ“„ [NumberToWords](src/main/java/com/thealgorithms/conversions/NumberToWords.java) + - ๐Ÿ“„ [OctalToBinary](src/main/java/com/thealgorithms/conversions/OctalToBinary.java) + - ๐Ÿ“„ [OctalToDecimal](src/main/java/com/thealgorithms/conversions/OctalToDecimal.java) + - ๐Ÿ“„ [OctalToHexadecimal](src/main/java/com/thealgorithms/conversions/OctalToHexadecimal.java) + - ๐Ÿ“„ [PhoneticAlphabetConverter](src/main/java/com/thealgorithms/conversions/PhoneticAlphabetConverter.java) + - ๐Ÿ“„ [RgbHsvConversion](src/main/java/com/thealgorithms/conversions/RgbHsvConversion.java) + - ๐Ÿ“„ [RomanToInteger](src/main/java/com/thealgorithms/conversions/RomanToInteger.java) + - ๐Ÿ“„ [TimeConverter](src/main/java/com/thealgorithms/conversions/TimeConverter.java) + - ๐Ÿ“„ [TurkishToLatinConversion](src/main/java/com/thealgorithms/conversions/TurkishToLatinConversion.java) + - ๐Ÿ“„ [UnitConversions](src/main/java/com/thealgorithms/conversions/UnitConversions.java) + - ๐Ÿ“„ [UnitsConverter](src/main/java/com/thealgorithms/conversions/UnitsConverter.java) + - ๐Ÿ“„ [WordsToNumber](src/main/java/com/thealgorithms/conversions/WordsToNumber.java) + - ๐Ÿ“ **datastructures** + - ๐Ÿ“„ [Node](src/main/java/com/thealgorithms/datastructures/Node.java) + - ๐Ÿ“ **bags** + - ๐Ÿ“„ [Bag](src/main/java/com/thealgorithms/datastructures/bags/Bag.java) + - ๐Ÿ“ **bloomfilter** + - ๐Ÿ“„ [BloomFilter](src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java) + - ๐Ÿ“ **buffers** + - ๐Ÿ“„ [CircularBuffer](src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java) + - ๐Ÿ“ **caches** + - ๐Ÿ“„ [FIFOCache](src/main/java/com/thealgorithms/datastructures/caches/FIFOCache.java) + - ๐Ÿ“„ [LFUCache](src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java) + - ๐Ÿ“„ [LIFOCache](src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java) + - ๐Ÿ“„ [LRUCache](src/main/java/com/thealgorithms/datastructures/caches/LRUCache.java) + - ๐Ÿ“„ [MRUCache](src/main/java/com/thealgorithms/datastructures/caches/MRUCache.java) + - ๐Ÿ“„ [RRCache](src/main/java/com/thealgorithms/datastructures/caches/RRCache.java) + - ๐Ÿ“ **crdt** + - ๐Ÿ“„ [GCounter](src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java) + - ๐Ÿ“„ [GSet](src/main/java/com/thealgorithms/datastructures/crdt/GSet.java) + - ๐Ÿ“„ [LWWElementSet](src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java) + - ๐Ÿ“„ [ORSet](src/main/java/com/thealgorithms/datastructures/crdt/ORSet.java) + - ๐Ÿ“„ [PNCounter](src/main/java/com/thealgorithms/datastructures/crdt/PNCounter.java) + - ๐Ÿ“„ [TwoPSet](src/main/java/com/thealgorithms/datastructures/crdt/TwoPSet.java) + - ๐Ÿ“ **disjointsetunion** + - ๐Ÿ“„ [DisjointSetUnion](src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java) + - ๐Ÿ“„ [DisjointSetUnionBySize](src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySize.java) + - ๐Ÿ“„ [Node](src/main/java/com/thealgorithms/datastructures/disjointsetunion/Node.java) + - ๐Ÿ“ **dynamicarray** + - ๐Ÿ“„ [DynamicArray](src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java) + - ๐Ÿ“ **graphs** + - ๐Ÿ“„ [AStar](src/main/java/com/thealgorithms/datastructures/graphs/AStar.java) + - ๐Ÿ“„ [BellmanFord](src/main/java/com/thealgorithms/datastructures/graphs/BellmanFord.java) + - ๐Ÿ“„ [BipartiteGraphDFS](src/main/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFS.java) + - ๐Ÿ“„ [BoruvkaAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithm.java) + - ๐Ÿ“„ [ConnectedComponent](src/main/java/com/thealgorithms/datastructures/graphs/ConnectedComponent.java) + - ๐Ÿ“„ [Cycles](src/main/java/com/thealgorithms/datastructures/graphs/Cycles.java) + - ๐Ÿ“„ [DialsAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/DialsAlgorithm.java) + - ๐Ÿ“„ [DijkstraAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java) + - ๐Ÿ“„ [DijkstraOptimizedAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java) + - ๐Ÿ“„ [EdmondsBlossomAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithm.java) + - ๐Ÿ“„ [FloydWarshall](src/main/java/com/thealgorithms/datastructures/graphs/FloydWarshall.java) + - ๐Ÿ“„ [FordFulkerson](src/main/java/com/thealgorithms/datastructures/graphs/FordFulkerson.java) + - ๐Ÿ“„ [Graphs](src/main/java/com/thealgorithms/datastructures/graphs/Graphs.java) + - ๐Ÿ“„ [HamiltonianCycle](src/main/java/com/thealgorithms/datastructures/graphs/HamiltonianCycle.java) + - ๐Ÿ“„ [JohnsonsAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithm.java) + - ๐Ÿ“„ [KahnsAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java) + - ๐Ÿ“„ [Kosaraju](src/main/java/com/thealgorithms/datastructures/graphs/Kosaraju.java) + - ๐Ÿ“„ [Kruskal](src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java) + - ๐Ÿ“„ [MatrixGraphs](src/main/java/com/thealgorithms/datastructures/graphs/MatrixGraphs.java) + - ๐Ÿ“„ [PrimMST](src/main/java/com/thealgorithms/datastructures/graphs/PrimMST.java) + - ๐Ÿ“„ [TarjansAlgorithm](src/main/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithm.java) + - ๐Ÿ“„ [TwoSat](src/main/java/com/thealgorithms/datastructures/graphs/TwoSat.java) + - ๐Ÿ“„ [UndirectedAdjacencyListGraph](src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java) + - ๐Ÿ“„ [WelshPowell](src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java) + - ๐Ÿ“ **hashmap** + - ๐Ÿ“ **hashing** + - ๐Ÿ“„ [GenericHashMapUsingArray](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java) + - ๐Ÿ“„ [GenericHashMapUsingArrayList](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayList.java) + - ๐Ÿ“„ [HashMap](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java) + - ๐Ÿ“„ [HashMapCuckooHashing](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashing.java) + - ๐Ÿ“„ [Intersection](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java) + - ๐Ÿ“„ [LinearProbingHashMap](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java) + - ๐Ÿ“„ [MainCuckooHashing](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MainCuckooHashing.java) + - ๐Ÿ“„ [MajorityElement](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java) + - ๐Ÿ“„ [Map](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Map.java) + - ๐Ÿ“ **heaps** + - ๐Ÿ“„ [EmptyHeapException](src/main/java/com/thealgorithms/datastructures/heaps/EmptyHeapException.java) + - ๐Ÿ“„ [FibonacciHeap](src/main/java/com/thealgorithms/datastructures/heaps/FibonacciHeap.java) + - ๐Ÿ“„ [GenericHeap](src/main/java/com/thealgorithms/datastructures/heaps/GenericHeap.java) + - ๐Ÿ“„ [Heap](src/main/java/com/thealgorithms/datastructures/heaps/Heap.java) + - ๐Ÿ“„ [HeapElement](src/main/java/com/thealgorithms/datastructures/heaps/HeapElement.java) + - ๐Ÿ“„ [KthElementFinder](src/main/java/com/thealgorithms/datastructures/heaps/KthElementFinder.java) + - ๐Ÿ“„ [LeftistHeap](src/main/java/com/thealgorithms/datastructures/heaps/LeftistHeap.java) + - ๐Ÿ“„ [MaxHeap](src/main/java/com/thealgorithms/datastructures/heaps/MaxHeap.java) + - ๐Ÿ“„ [MedianFinder](src/main/java/com/thealgorithms/datastructures/heaps/MedianFinder.java) + - ๐Ÿ“„ [MergeKSortedArrays](src/main/java/com/thealgorithms/datastructures/heaps/MergeKSortedArrays.java) + - ๐Ÿ“„ [MinHeap](src/main/java/com/thealgorithms/datastructures/heaps/MinHeap.java) + - ๐Ÿ“„ [MinPriorityQueue](src/main/java/com/thealgorithms/datastructures/heaps/MinPriorityQueue.java) + - ๐Ÿ“ **lists** + - ๐Ÿ“„ [CircleLinkedList](src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java) + - ๐Ÿ“„ [CircularDoublyLinkedList](src/main/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedList.java) + - ๐Ÿ“„ [CountSinglyLinkedListRecursion](src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java) + - ๐Ÿ“„ [CreateAndDetectLoop](src/main/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoop.java) + - ๐Ÿ“„ [CursorLinkedList](src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java) + - ๐Ÿ“„ [DoublyLinkedList](src/main/java/com/thealgorithms/datastructures/lists/DoublyLinkedList.java) + - ๐Ÿ“„ [FlattenMultilevelLinkedList](src/main/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedList.java) + - ๐Ÿ“„ [MergeKSortedLinkedList](src/main/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedList.java) + - ๐Ÿ“„ [MergeSortedArrayList](src/main/java/com/thealgorithms/datastructures/lists/MergeSortedArrayList.java) + - ๐Ÿ“„ [MergeSortedSinglyLinkedList](src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java) + - ๐Ÿ“„ [QuickSortLinkedList](src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java) + - ๐Ÿ“„ [RandomNode](src/main/java/com/thealgorithms/datastructures/lists/RandomNode.java) + - ๐Ÿ“„ [ReverseKGroup](src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java) + - ๐Ÿ“„ [RotateSinglyLinkedLists](src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java) + - ๐Ÿ“„ [SearchSinglyLinkedListRecursion](src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java) + - ๐Ÿ“„ [SinglyLinkedList](src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java) + - ๐Ÿ“„ [SinglyLinkedListNode](src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java) + - ๐Ÿ“„ [SkipList](src/main/java/com/thealgorithms/datastructures/lists/SkipList.java) + - ๐Ÿ“„ [SortedLinkedList](src/main/java/com/thealgorithms/datastructures/lists/SortedLinkedList.java) + - ๐Ÿ“„ [TortoiseHareAlgo](src/main/java/com/thealgorithms/datastructures/lists/TortoiseHareAlgo.java) + - ๐Ÿ“ **queues** + - ๐Ÿ“„ [CircularQueue](src/main/java/com/thealgorithms/datastructures/queues/CircularQueue.java) + - ๐Ÿ“„ [Deque](src/main/java/com/thealgorithms/datastructures/queues/Deque.java) + - ๐Ÿ“„ [GenericArrayListQueue](src/main/java/com/thealgorithms/datastructures/queues/GenericArrayListQueue.java) + - ๐Ÿ“„ [LinkedQueue](src/main/java/com/thealgorithms/datastructures/queues/LinkedQueue.java) + - ๐Ÿ“„ [PriorityQueues](src/main/java/com/thealgorithms/datastructures/queues/PriorityQueues.java) + - ๐Ÿ“„ [Queue](src/main/java/com/thealgorithms/datastructures/queues/Queue.java) + - ๐Ÿ“„ [QueueByTwoStacks](src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java) + - ๐Ÿ“„ [SlidingWindowMaximum](src/main/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximum.java) + - ๐Ÿ“„ [TokenBucket](src/main/java/com/thealgorithms/datastructures/queues/TokenBucket.java) + - ๐Ÿ“ **stacks** + - ๐Ÿ“„ [NodeStack](src/main/java/com/thealgorithms/datastructures/stacks/NodeStack.java) + - ๐Ÿ“„ [ReverseStack](src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java) + - ๐Ÿ“„ [Stack](src/main/java/com/thealgorithms/datastructures/stacks/Stack.java) + - ๐Ÿ“„ [StackArray](src/main/java/com/thealgorithms/datastructures/stacks/StackArray.java) + - ๐Ÿ“„ [StackArrayList](src/main/java/com/thealgorithms/datastructures/stacks/StackArrayList.java) + - ๐Ÿ“„ [StackOfLinkedList](src/main/java/com/thealgorithms/datastructures/stacks/StackOfLinkedList.java) + - ๐Ÿ“ **trees** + - ๐Ÿ“„ [AVLSimple](src/main/java/com/thealgorithms/datastructures/trees/AVLSimple.java) + - ๐Ÿ“„ [AVLTree](src/main/java/com/thealgorithms/datastructures/trees/AVLTree.java) + - ๐Ÿ“„ [BSTFromSortedArray](src/main/java/com/thealgorithms/datastructures/trees/BSTFromSortedArray.java) + - ๐Ÿ“„ [BSTIterative](src/main/java/com/thealgorithms/datastructures/trees/BSTIterative.java) + - ๐Ÿ“„ [BSTRecursive](src/main/java/com/thealgorithms/datastructures/trees/BSTRecursive.java) + - ๐Ÿ“„ [BSTRecursiveGeneric](src/main/java/com/thealgorithms/datastructures/trees/BSTRecursiveGeneric.java) + - ๐Ÿ“„ [BTree](src/main/java/com/thealgorithms/datastructures/trees/BTree.java) + - ๐Ÿ“„ [BinaryTree](src/main/java/com/thealgorithms/datastructures/trees/BinaryTree.java) + - ๐Ÿ“„ [BoundaryTraversal](src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java) + - ๐Ÿ“„ [CeilInBinarySearchTree](src/main/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTree.java) + - ๐Ÿ“„ [CheckBinaryTreeIsValidBST](src/main/java/com/thealgorithms/datastructures/trees/CheckBinaryTreeIsValidBST.java) + - ๐Ÿ“„ [CheckIfBinaryTreeBalanced](src/main/java/com/thealgorithms/datastructures/trees/CheckIfBinaryTreeBalanced.java) + - ๐Ÿ“„ [CheckTreeIsSymmetric](src/main/java/com/thealgorithms/datastructures/trees/CheckTreeIsSymmetric.java) + - ๐Ÿ“„ [CreateBinaryTreeFromInorderPreorder](src/main/java/com/thealgorithms/datastructures/trees/CreateBinaryTreeFromInorderPreorder.java) + - ๐Ÿ“„ [FenwickTree](src/main/java/com/thealgorithms/datastructures/trees/FenwickTree.java) + - ๐Ÿ“„ [GenericTree](src/main/java/com/thealgorithms/datastructures/trees/GenericTree.java) + - ๐Ÿ“„ [InorderTraversal](src/main/java/com/thealgorithms/datastructures/trees/InorderTraversal.java) + - ๐Ÿ“„ [KDTree](src/main/java/com/thealgorithms/datastructures/trees/KDTree.java) + - ๐Ÿ“„ [LCA](src/main/java/com/thealgorithms/datastructures/trees/LCA.java) + - ๐Ÿ“„ [LazySegmentTree](src/main/java/com/thealgorithms/datastructures/trees/LazySegmentTree.java) + - ๐Ÿ“„ [LevelOrderTraversal](src/main/java/com/thealgorithms/datastructures/trees/LevelOrderTraversal.java) + - ๐Ÿ“„ [PostOrderTraversal](src/main/java/com/thealgorithms/datastructures/trees/PostOrderTraversal.java) + - ๐Ÿ“„ [PreOrderTraversal](src/main/java/com/thealgorithms/datastructures/trees/PreOrderTraversal.java) + - ๐Ÿ“„ [PrintTopViewofTree](src/main/java/com/thealgorithms/datastructures/trees/PrintTopViewofTree.java) + - ๐Ÿ“„ [QuadTree](src/main/java/com/thealgorithms/datastructures/trees/QuadTree.java) + - ๐Ÿ“„ [RedBlackBST](src/main/java/com/thealgorithms/datastructures/trees/RedBlackBST.java) + - ๐Ÿ“„ [SameTreesCheck](src/main/java/com/thealgorithms/datastructures/trees/SameTreesCheck.java) + - ๐Ÿ“„ [SegmentTree](src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java) + - ๐Ÿ“„ [SplayTree](src/main/java/com/thealgorithms/datastructures/trees/SplayTree.java) + - ๐Ÿ“„ [Treap](src/main/java/com/thealgorithms/datastructures/trees/Treap.java) + - ๐Ÿ“„ [TreeRandomNode](src/main/java/com/thealgorithms/datastructures/trees/TreeRandomNode.java) + - ๐Ÿ“„ [Trie](src/main/java/com/thealgorithms/datastructures/trees/Trie.java) + - ๐Ÿ“„ [VerticalOrderTraversal](src/main/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversal.java) + - ๐Ÿ“„ [ZigzagTraversal](src/main/java/com/thealgorithms/datastructures/trees/ZigzagTraversal.java) + - ๐Ÿ“„ [nearestRightKey](src/main/java/com/thealgorithms/datastructures/trees/nearestRightKey.java) + - ๐Ÿ“ **devutils** + - ๐Ÿ“ **entities** + - ๐Ÿ“„ [ProcessDetails](src/main/java/com/thealgorithms/devutils/entities/ProcessDetails.java) + - ๐Ÿ“ **nodes** + - ๐Ÿ“„ [LargeTreeNode](src/main/java/com/thealgorithms/devutils/nodes/LargeTreeNode.java) + - ๐Ÿ“„ [Node](src/main/java/com/thealgorithms/devutils/nodes/Node.java) + - ๐Ÿ“„ [SimpleNode](src/main/java/com/thealgorithms/devutils/nodes/SimpleNode.java) + - ๐Ÿ“„ [SimpleTreeNode](src/main/java/com/thealgorithms/devutils/nodes/SimpleTreeNode.java) + - ๐Ÿ“„ [TreeNode](src/main/java/com/thealgorithms/devutils/nodes/TreeNode.java) + - ๐Ÿ“ **searches** + - ๐Ÿ“„ [MatrixSearchAlgorithm](src/main/java/com/thealgorithms/devutils/searches/MatrixSearchAlgorithm.java) + - ๐Ÿ“„ [SearchAlgorithm](src/main/java/com/thealgorithms/devutils/searches/SearchAlgorithm.java) + - ๐Ÿ“ **divideandconquer** + - ๐Ÿ“„ [BinaryExponentiation](src/main/java/com/thealgorithms/divideandconquer/BinaryExponentiation.java) + - ๐Ÿ“„ [ClosestPair](src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java) + - ๐Ÿ“„ [CountingInversions](src/main/java/com/thealgorithms/divideandconquer/CountingInversions.java) + - ๐Ÿ“„ [MedianOfTwoSortedArrays](src/main/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArrays.java) + - ๐Ÿ“„ [SkylineAlgorithm](src/main/java/com/thealgorithms/divideandconquer/SkylineAlgorithm.java) + - ๐Ÿ“„ [StrassenMatrixMultiplication](src/main/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplication.java) + - ๐Ÿ“„ [TilingProblem](src/main/java/com/thealgorithms/divideandconquer/TilingProblem.java) + - ๐Ÿ“ **dynamicprogramming** + - ๐Ÿ“„ [Abbreviation](src/main/java/com/thealgorithms/dynamicprogramming/Abbreviation.java) + - ๐Ÿ“„ [AllConstruct](src/main/java/com/thealgorithms/dynamicprogramming/AllConstruct.java) + - ๐Ÿ“„ [AssignmentUsingBitmask](src/main/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmask.java) + - ๐Ÿ“„ [BoardPath](src/main/java/com/thealgorithms/dynamicprogramming/BoardPath.java) + - ๐Ÿ“„ [BoundaryFill](src/main/java/com/thealgorithms/dynamicprogramming/BoundaryFill.java) + - ๐Ÿ“„ [BruteForceKnapsack](src/main/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsack.java) + - ๐Ÿ“„ [CatalanNumber](src/main/java/com/thealgorithms/dynamicprogramming/CatalanNumber.java) + - ๐Ÿ“„ [ClimbingStairs](src/main/java/com/thealgorithms/dynamicprogramming/ClimbingStairs.java) + - ๐Ÿ“„ [CoinChange](src/main/java/com/thealgorithms/dynamicprogramming/CoinChange.java) + - ๐Ÿ“„ [CountFriendsPairing](src/main/java/com/thealgorithms/dynamicprogramming/CountFriendsPairing.java) + - ๐Ÿ“„ [DamerauLevenshteinDistance](src/main/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistance.java) + - ๐Ÿ“„ [DiceThrow](src/main/java/com/thealgorithms/dynamicprogramming/DiceThrow.java) + - ๐Ÿ“„ [EditDistance](src/main/java/com/thealgorithms/dynamicprogramming/EditDistance.java) + - ๐Ÿ“„ [EggDropping](src/main/java/com/thealgorithms/dynamicprogramming/EggDropping.java) + - ๐Ÿ“„ [Fibonacci](src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java) + - ๐Ÿ“„ [KadaneAlgorithm](src/main/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithm.java) + - ๐Ÿ“„ [Knapsack](src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java) + - ๐Ÿ“„ [KnapsackMemoization](src/main/java/com/thealgorithms/dynamicprogramming/KnapsackMemoization.java) + - ๐Ÿ“„ [KnapsackZeroOne](src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java) + - ๐Ÿ“„ [KnapsackZeroOneTabulation](src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java) + - ๐Ÿ“„ [LevenshteinDistance](src/main/java/com/thealgorithms/dynamicprogramming/LevenshteinDistance.java) + - ๐Ÿ“„ [LongestAlternatingSubsequence](src/main/java/com/thealgorithms/dynamicprogramming/LongestAlternatingSubsequence.java) + - ๐Ÿ“„ [LongestArithmeticSubsequence](src/main/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequence.java) + - ๐Ÿ“„ [LongestCommonSubsequence](src/main/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequence.java) + - ๐Ÿ“„ [LongestIncreasingSubsequence](src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequence.java) + - ๐Ÿ“„ [LongestIncreasingSubsequenceNLogN](src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java) + - ๐Ÿ“„ [LongestPalindromicSubsequence](src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java) + - ๐Ÿ“„ [LongestPalindromicSubstring](src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubstring.java) + - ๐Ÿ“„ [LongestValidParentheses](src/main/java/com/thealgorithms/dynamicprogramming/LongestValidParentheses.java) + - ๐Ÿ“„ [MatrixChainMultiplication](src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplication.java) + - ๐Ÿ“„ [MatrixChainRecursiveTopDownMemoisation](src/main/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisation.java) + - ๐Ÿ“„ [MaximumProductSubarray](src/main/java/com/thealgorithms/dynamicprogramming/MaximumProductSubarray.java) + - ๐Ÿ“„ [MaximumSumOfNonAdjacentElements](src/main/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElements.java) + - ๐Ÿ“„ [MinimumPathSum](src/main/java/com/thealgorithms/dynamicprogramming/MinimumPathSum.java) + - ๐Ÿ“„ [MinimumSumPartition](src/main/java/com/thealgorithms/dynamicprogramming/MinimumSumPartition.java) + - ๐Ÿ“„ [NeedlemanWunsch](src/main/java/com/thealgorithms/dynamicprogramming/NeedlemanWunsch.java) + - ๐Ÿ“„ [NewManShanksPrime](src/main/java/com/thealgorithms/dynamicprogramming/NewManShanksPrime.java) + - ๐Ÿ“„ [OptimalJobScheduling](src/main/java/com/thealgorithms/dynamicprogramming/OptimalJobScheduling.java) + - ๐Ÿ“„ [PalindromicPartitioning](src/main/java/com/thealgorithms/dynamicprogramming/PalindromicPartitioning.java) + - ๐Ÿ“„ [PartitionProblem](src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java) + - ๐Ÿ“„ [RegexMatching](src/main/java/com/thealgorithms/dynamicprogramming/RegexMatching.java) + - ๐Ÿ“„ [RodCutting](src/main/java/com/thealgorithms/dynamicprogramming/RodCutting.java) + - ๐Ÿ“„ [ShortestCommonSupersequenceLength](src/main/java/com/thealgorithms/dynamicprogramming/ShortestCommonSupersequenceLength.java) + - ๐Ÿ“„ [SmithWaterman](src/main/java/com/thealgorithms/dynamicprogramming/SmithWaterman.java) + - ๐Ÿ“„ [SubsetCount](src/main/java/com/thealgorithms/dynamicprogramming/SubsetCount.java) + - ๐Ÿ“„ [SubsetSum](src/main/java/com/thealgorithms/dynamicprogramming/SubsetSum.java) + - ๐Ÿ“„ [SubsetSumSpaceOptimized](src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java) + - ๐Ÿ“„ [SumOfSubset](src/main/java/com/thealgorithms/dynamicprogramming/SumOfSubset.java) + - ๐Ÿ“„ [TreeMatching](src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java) + - ๐Ÿ“„ [Tribonacci](src/main/java/com/thealgorithms/dynamicprogramming/Tribonacci.java) + - ๐Ÿ“„ [UniquePaths](src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java) + - ๐Ÿ“„ [UniqueSubsequencesCount](src/main/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCount.java) + - ๐Ÿ“„ [WildcardMatching](src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java) + - ๐Ÿ“„ [WineProblem](src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java) + - ๐Ÿ“ **geometry** + - ๐Ÿ“„ [BresenhamLine](src/main/java/com/thealgorithms/geometry/BresenhamLine.java) + - ๐Ÿ“„ [ConvexHull](src/main/java/com/thealgorithms/geometry/ConvexHull.java) + - ๐Ÿ“„ [GrahamScan](src/main/java/com/thealgorithms/geometry/GrahamScan.java) + - ๐Ÿ“„ [Haversine](src/main/java/com/thealgorithms/geometry/Haversine.java) + - ๐Ÿ“„ [MidpointCircle](src/main/java/com/thealgorithms/geometry/MidpointCircle.java) + - ๐Ÿ“„ [MidpointEllipse](src/main/java/com/thealgorithms/geometry/MidpointEllipse.java) + - ๐Ÿ“„ [Point](src/main/java/com/thealgorithms/geometry/Point.java) + - ๐Ÿ“„ [WusLine](src/main/java/com/thealgorithms/geometry/WusLine.java) + - ๐Ÿ“ **graph** + - ๐Ÿ“„ [BronKerbosch](src/main/java/com/thealgorithms/graph/BronKerbosch.java) + - ๐Ÿ“„ [ConstrainedShortestPath](src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java) + - ๐Ÿ“„ [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java) + - ๐Ÿ“„ [Edmonds](src/main/java/com/thealgorithms/graph/Edmonds.java) + - ๐Ÿ“„ [EdmondsKarp](src/main/java/com/thealgorithms/graph/EdmondsKarp.java) + - ๐Ÿ“„ [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java) + - ๐Ÿ“„ [HungarianAlgorithm](src/main/java/com/thealgorithms/graph/HungarianAlgorithm.java) + - ๐Ÿ“„ [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java) + - ๐Ÿ“„ [PushRelabel](src/main/java/com/thealgorithms/graph/PushRelabel.java) + - ๐Ÿ“„ [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java) + - ๐Ÿ“„ [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java) + - ๐Ÿ“„ [YensKShortestPaths](src/main/java/com/thealgorithms/graph/YensKShortestPaths.java) + - ๐Ÿ“„ [ZeroOneBfs](src/main/java/com/thealgorithms/graph/ZeroOneBfs.java) + - ๐Ÿ“ **greedyalgorithms** + - ๐Ÿ“„ [ActivitySelection](src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java) + - ๐Ÿ“„ [BandwidthAllocation](src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java) + - ๐Ÿ“„ [BinaryAddition](src/main/java/com/thealgorithms/greedyalgorithms/BinaryAddition.java) + - ๐Ÿ“„ [CoinChange](src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java) + - ๐Ÿ“„ [DigitSeparation](src/main/java/com/thealgorithms/greedyalgorithms/DigitSeparation.java) + - ๐Ÿ“„ [EgyptianFraction](src/main/java/com/thealgorithms/greedyalgorithms/EgyptianFraction.java) + - ๐Ÿ“„ [FractionalKnapsack](src/main/java/com/thealgorithms/greedyalgorithms/FractionalKnapsack.java) + - ๐Ÿ“„ [GaleShapley](src/main/java/com/thealgorithms/greedyalgorithms/GaleShapley.java) + - ๐Ÿ“„ [JobSequencing](src/main/java/com/thealgorithms/greedyalgorithms/JobSequencing.java) + - ๐Ÿ“„ [KCenters](src/main/java/com/thealgorithms/greedyalgorithms/KCenters.java) + - ๐Ÿ“„ [MergeIntervals](src/main/java/com/thealgorithms/greedyalgorithms/MergeIntervals.java) + - ๐Ÿ“„ [MinimizingLateness](src/main/java/com/thealgorithms/greedyalgorithms/MinimizingLateness.java) + - ๐Ÿ“„ [MinimumWaitingTime](src/main/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTime.java) + - ๐Ÿ“„ [OptimalFileMerging](src/main/java/com/thealgorithms/greedyalgorithms/OptimalFileMerging.java) + - ๐Ÿ“„ [StockProfitCalculator](src/main/java/com/thealgorithms/greedyalgorithms/StockProfitCalculator.java) + - ๐Ÿ“ **io** + - ๐Ÿ“„ [BufferedReader](src/main/java/com/thealgorithms/io/BufferedReader.java) + - ๐Ÿ“ **lineclipping** + - ๐Ÿ“„ [CohenSutherland](src/main/java/com/thealgorithms/lineclipping/CohenSutherland.java) + - ๐Ÿ“„ [LiangBarsky](src/main/java/com/thealgorithms/lineclipping/LiangBarsky.java) + - ๐Ÿ“ **utils** + - ๐Ÿ“„ [Line](src/main/java/com/thealgorithms/lineclipping/utils/Line.java) + - ๐Ÿ“„ [Point](src/main/java/com/thealgorithms/lineclipping/utils/Point.java) + - ๐Ÿ“ **maths** + - ๐Ÿ“„ [ADTFraction](src/main/java/com/thealgorithms/maths/ADTFraction.java) + - ๐Ÿ“„ [AbsoluteMax](src/main/java/com/thealgorithms/maths/AbsoluteMax.java) + - ๐Ÿ“„ [AbsoluteMin](src/main/java/com/thealgorithms/maths/AbsoluteMin.java) + - ๐Ÿ“„ [AbsoluteValue](src/main/java/com/thealgorithms/maths/AbsoluteValue.java) + - ๐Ÿ“„ [AliquotSum](src/main/java/com/thealgorithms/maths/AliquotSum.java) + - ๐Ÿ“„ [AmicableNumber](src/main/java/com/thealgorithms/maths/AmicableNumber.java) + - ๐Ÿ“„ [Area](src/main/java/com/thealgorithms/maths/Area.java) + - ๐Ÿ“„ [Armstrong](src/main/java/com/thealgorithms/maths/Armstrong.java) + - ๐Ÿ“„ [AutoCorrelation](src/main/java/com/thealgorithms/maths/AutoCorrelation.java) + - ๐Ÿ“„ [AutomorphicNumber](src/main/java/com/thealgorithms/maths/AutomorphicNumber.java) + - ๐Ÿ“„ [Average](src/main/java/com/thealgorithms/maths/Average.java) + - ๐Ÿ“„ [BinaryPow](src/main/java/com/thealgorithms/maths/BinaryPow.java) + - ๐Ÿ“„ [BinomialCoefficient](src/main/java/com/thealgorithms/maths/BinomialCoefficient.java) + - ๐Ÿ“„ [CatalanNumbers](src/main/java/com/thealgorithms/maths/CatalanNumbers.java) + - ๐Ÿ“„ [Ceil](src/main/java/com/thealgorithms/maths/Ceil.java) + - ๐Ÿ“„ [ChineseRemainderTheorem](src/main/java/com/thealgorithms/maths/ChineseRemainderTheorem.java) + - ๐Ÿ“„ [CircularConvolutionFFT](src/main/java/com/thealgorithms/maths/CircularConvolutionFFT.java) + - ๐Ÿ“„ [CollatzConjecture](src/main/java/com/thealgorithms/maths/CollatzConjecture.java) + - ๐Ÿ“„ [Combinations](src/main/java/com/thealgorithms/maths/Combinations.java) + - ๐Ÿ“„ [Convolution](src/main/java/com/thealgorithms/maths/Convolution.java) + - ๐Ÿ“„ [ConvolutionFFT](src/main/java/com/thealgorithms/maths/ConvolutionFFT.java) + - ๐Ÿ“„ [CrossCorrelation](src/main/java/com/thealgorithms/maths/CrossCorrelation.java) + - ๐Ÿ“„ [DeterminantOfMatrix](src/main/java/com/thealgorithms/maths/DeterminantOfMatrix.java) + - ๐Ÿ“„ [DigitalRoot](src/main/java/com/thealgorithms/maths/DigitalRoot.java) + - ๐Ÿ“„ [DistanceFormula](src/main/java/com/thealgorithms/maths/DistanceFormula.java) + - ๐Ÿ“„ [DudeneyNumber](src/main/java/com/thealgorithms/maths/DudeneyNumber.java) + - ๐Ÿ“„ [EulerMethod](src/main/java/com/thealgorithms/maths/EulerMethod.java) + - ๐Ÿ“„ [EulerPseudoprime](src/main/java/com/thealgorithms/maths/EulerPseudoprime.java) + - ๐Ÿ“„ [EulersFunction](src/main/java/com/thealgorithms/maths/EulersFunction.java) + - ๐Ÿ“„ [FFT](src/main/java/com/thealgorithms/maths/FFT.java) + - ๐Ÿ“„ [FFTBluestein](src/main/java/com/thealgorithms/maths/FFTBluestein.java) + - ๐Ÿ“„ [Factorial](src/main/java/com/thealgorithms/maths/Factorial.java) + - ๐Ÿ“„ [FactorialRecursion](src/main/java/com/thealgorithms/maths/FactorialRecursion.java) + - ๐Ÿ“„ [FastExponentiation](src/main/java/com/thealgorithms/maths/FastExponentiation.java) + - ๐Ÿ“„ [FastInverseSqrt](src/main/java/com/thealgorithms/maths/FastInverseSqrt.java) + - ๐Ÿ“„ [FibonacciJavaStreams](src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java) + - ๐Ÿ“„ [FibonacciLoop](src/main/java/com/thealgorithms/maths/FibonacciLoop.java) + - ๐Ÿ“„ [FibonacciNumberCheck](src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java) + - ๐Ÿ“„ [FibonacciNumberGoldenRation](src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java) + - ๐Ÿ“„ [FindKthNumber](src/main/java/com/thealgorithms/maths/FindKthNumber.java) + - ๐Ÿ“„ [FindMax](src/main/java/com/thealgorithms/maths/FindMax.java) + - ๐Ÿ“„ [FindMaxRecursion](src/main/java/com/thealgorithms/maths/FindMaxRecursion.java) + - ๐Ÿ“„ [FindMin](src/main/java/com/thealgorithms/maths/FindMin.java) + - ๐Ÿ“„ [FindMinRecursion](src/main/java/com/thealgorithms/maths/FindMinRecursion.java) + - ๐Ÿ“„ [Floor](src/main/java/com/thealgorithms/maths/Floor.java) + - ๐Ÿ“„ [FrizzyNumber](src/main/java/com/thealgorithms/maths/FrizzyNumber.java) + - ๐Ÿ“„ [GCD](src/main/java/com/thealgorithms/maths/GCD.java) + - ๐Ÿ“„ [GCDRecursion](src/main/java/com/thealgorithms/maths/GCDRecursion.java) + - ๐Ÿ“„ [Gaussian](src/main/java/com/thealgorithms/maths/Gaussian.java) + - ๐Ÿ“„ [GenericRoot](src/main/java/com/thealgorithms/maths/GenericRoot.java) + - ๐Ÿ“„ [GermainPrimeAndSafePrime](src/main/java/com/thealgorithms/maths/GermainPrimeAndSafePrime.java) + - ๐Ÿ“„ [GoldbachConjecture](src/main/java/com/thealgorithms/maths/GoldbachConjecture.java) + - ๐Ÿ“„ [HappyNumber](src/main/java/com/thealgorithms/maths/HappyNumber.java) + - ๐Ÿ“„ [HarshadNumber](src/main/java/com/thealgorithms/maths/HarshadNumber.java) + - ๐Ÿ“„ [HeronsFormula](src/main/java/com/thealgorithms/maths/HeronsFormula.java) + - ๐Ÿ“„ [JosephusProblem](src/main/java/com/thealgorithms/maths/JosephusProblem.java) + - ๐Ÿ“„ [JugglerSequence](src/main/java/com/thealgorithms/maths/JugglerSequence.java) + - ๐Ÿ“„ [KaprekarNumbers](src/main/java/com/thealgorithms/maths/KaprekarNumbers.java) + - ๐Ÿ“„ [KaratsubaMultiplication](src/main/java/com/thealgorithms/maths/KaratsubaMultiplication.java) + - ๐Ÿ“„ [KeithNumber](src/main/java/com/thealgorithms/maths/KeithNumber.java) + - ๐Ÿ“„ [KrishnamurthyNumber](src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java) + - ๐Ÿ“„ [LeastCommonMultiple](src/main/java/com/thealgorithms/maths/LeastCommonMultiple.java) + - ๐Ÿ“„ [LeonardoNumber](src/main/java/com/thealgorithms/maths/LeonardoNumber.java) + - ๐Ÿ“„ [LinearDiophantineEquationsSolver](src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java) + - ๐Ÿ“„ [LongDivision](src/main/java/com/thealgorithms/maths/LongDivision.java) + - ๐Ÿ“„ [LucasSeries](src/main/java/com/thealgorithms/maths/LucasSeries.java) + - ๐Ÿ“„ [MagicSquare](src/main/java/com/thealgorithms/maths/MagicSquare.java) + - ๐Ÿ“„ [MathBuilder](src/main/java/com/thealgorithms/maths/MathBuilder.java) + - ๐Ÿ“„ [MaxValue](src/main/java/com/thealgorithms/maths/MaxValue.java) + - ๐Ÿ“„ [Means](src/main/java/com/thealgorithms/maths/Means.java) + - ๐Ÿ“„ [Median](src/main/java/com/thealgorithms/maths/Median.java) + - ๐Ÿ“„ [MinValue](src/main/java/com/thealgorithms/maths/MinValue.java) + - ๐Ÿ“„ [Mode](src/main/java/com/thealgorithms/maths/Mode.java) + - ๐Ÿ“„ [NonRepeatingElement](src/main/java/com/thealgorithms/maths/NonRepeatingElement.java) + - ๐Ÿ“„ [NthUglyNumber](src/main/java/com/thealgorithms/maths/NthUglyNumber.java) + - ๐Ÿ“„ [NumberOfDigits](src/main/java/com/thealgorithms/maths/NumberOfDigits.java) + - ๐Ÿ“„ [NumberPersistence](src/main/java/com/thealgorithms/maths/NumberPersistence.java) + - ๐Ÿ“„ [PalindromeNumber](src/main/java/com/thealgorithms/maths/PalindromeNumber.java) + - ๐Ÿ“„ [ParseInteger](src/main/java/com/thealgorithms/maths/ParseInteger.java) + - ๐Ÿ“„ [PascalTriangle](src/main/java/com/thealgorithms/maths/PascalTriangle.java) + - ๐Ÿ“„ [PerfectCube](src/main/java/com/thealgorithms/maths/PerfectCube.java) + - ๐Ÿ“„ [PerfectNumber](src/main/java/com/thealgorithms/maths/PerfectNumber.java) + - ๐Ÿ“„ [PerfectSquare](src/main/java/com/thealgorithms/maths/PerfectSquare.java) + - ๐Ÿ“„ [Perimeter](src/main/java/com/thealgorithms/maths/Perimeter.java) + - ๐Ÿ“„ [PiApproximation](src/main/java/com/thealgorithms/maths/PiApproximation.java) + - ๐Ÿ“„ [PiNilakantha](src/main/java/com/thealgorithms/maths/PiNilakantha.java) + - ๐Ÿ“„ [PollardRho](src/main/java/com/thealgorithms/maths/PollardRho.java) + - ๐Ÿ“„ [Pow](src/main/java/com/thealgorithms/maths/Pow.java) + - ๐Ÿ“„ [PowerOfTwoOrNot](src/main/java/com/thealgorithms/maths/PowerOfTwoOrNot.java) + - ๐Ÿ“„ [PowerUsingRecursion](src/main/java/com/thealgorithms/maths/PowerUsingRecursion.java) + - ๐Ÿ“ **Prime** + - ๐Ÿ“„ [LiouvilleLambdaFunction](src/main/java/com/thealgorithms/maths/Prime/LiouvilleLambdaFunction.java) + - ๐Ÿ“„ [MillerRabinPrimalityCheck](src/main/java/com/thealgorithms/maths/Prime/MillerRabinPrimalityCheck.java) + - ๐Ÿ“„ [MobiusFunction](src/main/java/com/thealgorithms/maths/Prime/MobiusFunction.java) + - ๐Ÿ“„ [PrimeCheck](src/main/java/com/thealgorithms/maths/Prime/PrimeCheck.java) + - ๐Ÿ“„ [PrimeFactorization](src/main/java/com/thealgorithms/maths/Prime/PrimeFactorization.java) + - ๐Ÿ“„ [SquareFreeInteger](src/main/java/com/thealgorithms/maths/Prime/SquareFreeInteger.java) + - ๐Ÿ“„ [PronicNumber](src/main/java/com/thealgorithms/maths/PronicNumber.java) + - ๐Ÿ“„ [PythagoreanTriple](src/main/java/com/thealgorithms/maths/PythagoreanTriple.java) + - ๐Ÿ“„ [QuadraticEquationSolver](src/main/java/com/thealgorithms/maths/QuadraticEquationSolver.java) + - ๐Ÿ“„ [ReverseNumber](src/main/java/com/thealgorithms/maths/ReverseNumber.java) + - ๐Ÿ“„ [RomanNumeralUtil](src/main/java/com/thealgorithms/maths/RomanNumeralUtil.java) + - ๐Ÿ“„ [SecondMinMax](src/main/java/com/thealgorithms/maths/SecondMinMax.java) + - ๐Ÿ“„ [SieveOfAtkin](src/main/java/com/thealgorithms/maths/SieveOfAtkin.java) + - ๐Ÿ“„ [SieveOfEratosthenes](src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java) + - ๐Ÿ“„ [SimpsonIntegration](src/main/java/com/thealgorithms/maths/SimpsonIntegration.java) + - ๐Ÿ“„ [SolovayStrassenPrimalityTest](src/main/java/com/thealgorithms/maths/SolovayStrassenPrimalityTest.java) + - ๐Ÿ“„ [SquareRootWithBabylonianMethod](src/main/java/com/thealgorithms/maths/SquareRootWithBabylonianMethod.java) + - ๐Ÿ“„ [SquareRootWithNewtonRaphsonMethod](src/main/java/com/thealgorithms/maths/SquareRootWithNewtonRaphsonMethod.java) + - ๐Ÿ“„ [StandardDeviation](src/main/java/com/thealgorithms/maths/StandardDeviation.java) + - ๐Ÿ“„ [StandardScore](src/main/java/com/thealgorithms/maths/StandardScore.java) + - ๐Ÿ“„ [StrobogrammaticNumber](src/main/java/com/thealgorithms/maths/StrobogrammaticNumber.java) + - ๐Ÿ“„ [SumOfArithmeticSeries](src/main/java/com/thealgorithms/maths/SumOfArithmeticSeries.java) + - ๐Ÿ“„ [SumOfDigits](src/main/java/com/thealgorithms/maths/SumOfDigits.java) + - ๐Ÿ“„ [SumOfOddNumbers](src/main/java/com/thealgorithms/maths/SumOfOddNumbers.java) + - ๐Ÿ“„ [SumOfSquares](src/main/java/com/thealgorithms/maths/SumOfSquares.java) + - ๐Ÿ“„ [SumWithoutArithmeticOperators](src/main/java/com/thealgorithms/maths/SumWithoutArithmeticOperators.java) + - ๐Ÿ“„ [TrinomialTriangle](src/main/java/com/thealgorithms/maths/TrinomialTriangle.java) + - ๐Ÿ“„ [TwinPrime](src/main/java/com/thealgorithms/maths/TwinPrime.java) + - ๐Ÿ“„ [UniformNumbers](src/main/java/com/thealgorithms/maths/UniformNumbers.java) + - ๐Ÿ“„ [VampireNumber](src/main/java/com/thealgorithms/maths/VampireNumber.java) + - ๐Ÿ“„ [VectorCrossProduct](src/main/java/com/thealgorithms/maths/VectorCrossProduct.java) + - ๐Ÿ“„ [Volume](src/main/java/com/thealgorithms/maths/Volume.java) + - ๐Ÿ“„ [ZellersCongruence](src/main/java/com/thealgorithms/maths/ZellersCongruence.java) + - ๐Ÿ“ **matrix** + - ๐Ÿ“„ [InverseOfMatrix](src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java) + - ๐Ÿ“„ [MatrixMultiplication](src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java) + - ๐Ÿ“„ [MatrixRank](src/main/java/com/thealgorithms/matrix/MatrixRank.java) + - ๐Ÿ“„ [MatrixTranspose](src/main/java/com/thealgorithms/matrix/MatrixTranspose.java) + - ๐Ÿ“„ [MedianOfMatrix](src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java) + - ๐Ÿ“„ [MirrorOfMatrix](src/main/java/com/thealgorithms/matrix/MirrorOfMatrix.java) + - ๐Ÿ“„ [PrintAMatrixInSpiralOrder](src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java) + - ๐Ÿ“„ [RotateMatrixBy90Degrees](src/main/java/com/thealgorithms/matrix/RotateMatrixBy90Degrees.java) + - ๐Ÿ“„ [SolveSystem](src/main/java/com/thealgorithms/matrix/SolveSystem.java) + - ๐Ÿ“ **matrixexponentiation** + - ๐Ÿ“„ [Fibonacci](src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java) + - ๐Ÿ“ **utils** + - ๐Ÿ“„ [MatrixUtil](src/main/java/com/thealgorithms/matrix/utils/MatrixUtil.java) + - ๐Ÿ“ **misc** + - ๐Ÿ“„ [ColorContrastRatio](src/main/java/com/thealgorithms/misc/ColorContrastRatio.java) + - ๐Ÿ“„ [MapReduce](src/main/java/com/thealgorithms/misc/MapReduce.java) + - ๐Ÿ“„ [MedianOfRunningArray](src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java) + - ๐Ÿ“„ [MedianOfRunningArrayByte](src/main/java/com/thealgorithms/misc/MedianOfRunningArrayByte.java) + - ๐Ÿ“„ [MedianOfRunningArrayDouble](src/main/java/com/thealgorithms/misc/MedianOfRunningArrayDouble.java) + - ๐Ÿ“„ [MedianOfRunningArrayFloat](src/main/java/com/thealgorithms/misc/MedianOfRunningArrayFloat.java) + - ๐Ÿ“„ [MedianOfRunningArrayInteger](src/main/java/com/thealgorithms/misc/MedianOfRunningArrayInteger.java) + - ๐Ÿ“„ [MedianOfRunningArrayLong](src/main/java/com/thealgorithms/misc/MedianOfRunningArrayLong.java) + - ๐Ÿ“„ [PalindromePrime](src/main/java/com/thealgorithms/misc/PalindromePrime.java) + - ๐Ÿ“„ [PalindromeSinglyLinkedList](src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java) + - ๐Ÿ“„ [RangeInSortedArray](src/main/java/com/thealgorithms/misc/RangeInSortedArray.java) + - ๐Ÿ“„ [ShuffleArray](src/main/java/com/thealgorithms/misc/ShuffleArray.java) + - ๐Ÿ“„ [Sparsity](src/main/java/com/thealgorithms/misc/Sparsity.java) + - ๐Ÿ“„ [ThreeSumProblem](src/main/java/com/thealgorithms/misc/ThreeSumProblem.java) + - ๐Ÿ“„ [TwoSumProblem](src/main/java/com/thealgorithms/misc/TwoSumProblem.java) + - ๐Ÿ“ **others** + - ๐Ÿ“„ [ArrayLeftRotation](src/main/java/com/thealgorithms/others/ArrayLeftRotation.java) + - ๐Ÿ“„ [ArrayRightRotation](src/main/java/com/thealgorithms/others/ArrayRightRotation.java) + - ๐Ÿ“„ [BFPRT](src/main/java/com/thealgorithms/others/BFPRT.java) + - ๐Ÿ“„ [BankersAlgorithm](src/main/java/com/thealgorithms/others/BankersAlgorithm.java) + - ๐Ÿ“„ [BoyerMoore](src/main/java/com/thealgorithms/others/BoyerMoore.java) + - ๐Ÿ“„ [BrianKernighanAlgorithm](src/main/java/com/thealgorithms/others/BrianKernighanAlgorithm.java) + - ๐Ÿ“„ [CRC16](src/main/java/com/thealgorithms/others/CRC16.java) + - ๐Ÿ“„ [CRC32](src/main/java/com/thealgorithms/others/CRC32.java) + - ๐Ÿ“„ [CRCAlgorithm](src/main/java/com/thealgorithms/others/CRCAlgorithm.java) + - ๐Ÿ“„ [Conway](src/main/java/com/thealgorithms/others/Conway.java) + - ๐Ÿ“„ [Damm](src/main/java/com/thealgorithms/others/Damm.java) + - ๐Ÿ“„ [Dijkstra](src/main/java/com/thealgorithms/others/Dijkstra.java) + - ๐Ÿ“„ [FloydTriangle](src/main/java/com/thealgorithms/others/FloydTriangle.java) + - ๐Ÿ“„ [GaussLegendre](src/main/java/com/thealgorithms/others/GaussLegendre.java) + - ๐Ÿ“„ [Huffman](src/main/java/com/thealgorithms/others/Huffman.java) + - ๐Ÿ“„ [Implementing_auto_completing_features_using_trie](src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java) + - ๐Ÿ“„ [InsertDeleteInArray](src/main/java/com/thealgorithms/others/InsertDeleteInArray.java) + - ๐Ÿ“„ [IterativeFloodFill](src/main/java/com/thealgorithms/others/IterativeFloodFill.java) + - ๐Ÿ“„ [KochSnowflake](src/main/java/com/thealgorithms/others/KochSnowflake.java) + - ๐Ÿ“„ [LineSweep](src/main/java/com/thealgorithms/others/LineSweep.java) + - ๐Ÿ“„ [LinearCongruentialGenerator](src/main/java/com/thealgorithms/others/LinearCongruentialGenerator.java) + - ๐Ÿ“„ [LowestBasePalindrome](src/main/java/com/thealgorithms/others/LowestBasePalindrome.java) + - ๐Ÿ“„ [Luhn](src/main/java/com/thealgorithms/others/Luhn.java) + - ๐Ÿ“„ [Mandelbrot](src/main/java/com/thealgorithms/others/Mandelbrot.java) + - ๐Ÿ“„ [MaximumSumOfDistinctSubarraysWithLengthK](src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java) + - ๐Ÿ“„ [MemoryManagementAlgorithms](src/main/java/com/thealgorithms/others/MemoryManagementAlgorithms.java) + - ๐Ÿ“„ [MiniMaxAlgorithm](src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java) + - ๐Ÿ“„ [MosAlgorithm](src/main/java/com/thealgorithms/others/MosAlgorithm.java) + - ๐Ÿ“„ [PageRank](src/main/java/com/thealgorithms/others/PageRank.java) + - ๐Ÿ“„ [PasswordGen](src/main/java/com/thealgorithms/others/PasswordGen.java) + - ๐Ÿ“„ [PerlinNoise](src/main/java/com/thealgorithms/others/PerlinNoise.java) + - ๐Ÿ“„ [PrintAMatrixInSpiralOrder](src/main/java/com/thealgorithms/others/PrintAMatrixInSpiralOrder.java) + - ๐Ÿ“„ [QueueUsingTwoStacks](src/main/java/com/thealgorithms/others/QueueUsingTwoStacks.java) + - ๐Ÿ“„ [SkylineProblem](src/main/java/com/thealgorithms/others/SkylineProblem.java) + - ๐Ÿ“„ [TwoPointers](src/main/java/com/thealgorithms/others/TwoPointers.java) + - ๐Ÿ“„ [Verhoeff](src/main/java/com/thealgorithms/others/Verhoeff.java) + - ๐Ÿ“ **cn** + - ๐Ÿ“„ [HammingDistance](src/main/java/com/thealgorithms/others/cn/HammingDistance.java) + - ๐Ÿ“ **physics** + - ๐Ÿ“„ [GroundToGroundProjectileMotion](src/main/java/com/thealgorithms/physics/GroundToGroundProjectileMotion.java) + - ๐Ÿ“ **puzzlesandgames** + - ๐Ÿ“„ [Sudoku](src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java) + - ๐Ÿ“„ [TowerOfHanoi](src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java) + - ๐Ÿ“„ [WordBoggle](src/main/java/com/thealgorithms/puzzlesandgames/WordBoggle.java) + - ๐Ÿ“ **randomized** + - ๐Ÿ“„ [KargerMinCut](src/main/java/com/thealgorithms/randomized/KargerMinCut.java) + - ๐Ÿ“„ [MonteCarloIntegration](src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java) + - ๐Ÿ“„ [RandomizedClosestPair](src/main/java/com/thealgorithms/randomized/RandomizedClosestPair.java) + - ๐Ÿ“„ [RandomizedMatrixMultiplicationVerification](src/main/java/com/thealgorithms/randomized/RandomizedMatrixMultiplicationVerification.java) + - ๐Ÿ“„ [RandomizedQuickSort](src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java) + - ๐Ÿ“„ [ReservoirSampling](src/main/java/com/thealgorithms/randomized/ReservoirSampling.java) + - ๐Ÿ“ **recursion** + - ๐Ÿ“„ [DiceThrower](src/main/java/com/thealgorithms/recursion/DiceThrower.java) + - ๐Ÿ“„ [FibonacciSeries](src/main/java/com/thealgorithms/recursion/FibonacciSeries.java) + - ๐Ÿ“„ [GenerateSubsets](src/main/java/com/thealgorithms/recursion/GenerateSubsets.java) + - ๐Ÿ“„ [SylvesterSequence](src/main/java/com/thealgorithms/recursion/SylvesterSequence.java) + - ๐Ÿ“ **scheduling** + - ๐Ÿ“„ [AgingScheduling](src/main/java/com/thealgorithms/scheduling/AgingScheduling.java) + - ๐Ÿ“„ [EDFScheduling](src/main/java/com/thealgorithms/scheduling/EDFScheduling.java) + - ๐Ÿ“„ [FCFSScheduling](src/main/java/com/thealgorithms/scheduling/FCFSScheduling.java) + - ๐Ÿ“„ [FairShareScheduling](src/main/java/com/thealgorithms/scheduling/FairShareScheduling.java) + - ๐Ÿ“„ [GangScheduling](src/main/java/com/thealgorithms/scheduling/GangScheduling.java) + - ๐Ÿ“„ [HighestResponseRatioNextScheduling](src/main/java/com/thealgorithms/scheduling/HighestResponseRatioNextScheduling.java) + - ๐Ÿ“„ [JobSchedulingWithDeadline](src/main/java/com/thealgorithms/scheduling/JobSchedulingWithDeadline.java) + - ๐Ÿ“„ [LotteryScheduling](src/main/java/com/thealgorithms/scheduling/LotteryScheduling.java) + - ๐Ÿ“„ [MLFQScheduler](src/main/java/com/thealgorithms/scheduling/MLFQScheduler.java) + - ๐Ÿ“„ [MultiAgentScheduling](src/main/java/com/thealgorithms/scheduling/MultiAgentScheduling.java) + - ๐Ÿ“„ [NonPreemptivePriorityScheduling](src/main/java/com/thealgorithms/scheduling/NonPreemptivePriorityScheduling.java) + - ๐Ÿ“„ [PreemptivePriorityScheduling](src/main/java/com/thealgorithms/scheduling/PreemptivePriorityScheduling.java) + - ๐Ÿ“„ [ProportionalFairScheduling](src/main/java/com/thealgorithms/scheduling/ProportionalFairScheduling.java) + - ๐Ÿ“„ [RRScheduling](src/main/java/com/thealgorithms/scheduling/RRScheduling.java) + - ๐Ÿ“„ [RandomScheduling](src/main/java/com/thealgorithms/scheduling/RandomScheduling.java) + - ๐Ÿ“„ [SJFScheduling](src/main/java/com/thealgorithms/scheduling/SJFScheduling.java) + - ๐Ÿ“„ [SRTFScheduling](src/main/java/com/thealgorithms/scheduling/SRTFScheduling.java) + - ๐Ÿ“„ [SelfAdjustingScheduling](src/main/java/com/thealgorithms/scheduling/SelfAdjustingScheduling.java) + - ๐Ÿ“„ [SlackTimeScheduling](src/main/java/com/thealgorithms/scheduling/SlackTimeScheduling.java) + - ๐Ÿ“ **diskscheduling** + - ๐Ÿ“„ [CircularLookScheduling](src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularLookScheduling.java) + - ๐Ÿ“„ [CircularScanScheduling](src/main/java/com/thealgorithms/scheduling/diskscheduling/CircularScanScheduling.java) + - ๐Ÿ“„ [LookScheduling](src/main/java/com/thealgorithms/scheduling/diskscheduling/LookScheduling.java) + - ๐Ÿ“„ [SSFScheduling](src/main/java/com/thealgorithms/scheduling/diskscheduling/SSFScheduling.java) + - ๐Ÿ“„ [ScanScheduling](src/main/java/com/thealgorithms/scheduling/diskscheduling/ScanScheduling.java) + - ๐Ÿ“ **searches** + - ๐Ÿ“„ [BM25InvertedIndex](src/main/java/com/thealgorithms/searches/BM25InvertedIndex.java) + - ๐Ÿ“„ [BinarySearch](src/main/java/com/thealgorithms/searches/BinarySearch.java) + - ๐Ÿ“„ [BinarySearch2dArray](src/main/java/com/thealgorithms/searches/BinarySearch2dArray.java) + - ๐Ÿ“„ [BoyerMoore](src/main/java/com/thealgorithms/searches/BoyerMoore.java) + - ๐Ÿ“„ [BreadthFirstSearch](src/main/java/com/thealgorithms/searches/BreadthFirstSearch.java) + - ๐Ÿ“„ [DepthFirstSearch](src/main/java/com/thealgorithms/searches/DepthFirstSearch.java) + - ๐Ÿ“„ [ExponentalSearch](src/main/java/com/thealgorithms/searches/ExponentalSearch.java) + - ๐Ÿ“„ [FibonacciSearch](src/main/java/com/thealgorithms/searches/FibonacciSearch.java) + - ๐Ÿ“„ [HowManyTimesRotated](src/main/java/com/thealgorithms/searches/HowManyTimesRotated.java) + - ๐Ÿ“„ [InterpolationSearch](src/main/java/com/thealgorithms/searches/InterpolationSearch.java) + - ๐Ÿ“„ [IterativeBinarySearch](src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java) + - ๐Ÿ“„ [IterativeTernarySearch](src/main/java/com/thealgorithms/searches/IterativeTernarySearch.java) + - ๐Ÿ“„ [JumpSearch](src/main/java/com/thealgorithms/searches/JumpSearch.java) + - ๐Ÿ“„ [KMPSearch](src/main/java/com/thealgorithms/searches/KMPSearch.java) + - ๐Ÿ“„ [LinearSearch](src/main/java/com/thealgorithms/searches/LinearSearch.java) + - ๐Ÿ“„ [LinearSearchThread](src/main/java/com/thealgorithms/searches/LinearSearchThread.java) + - ๐Ÿ“„ [LowerBound](src/main/java/com/thealgorithms/searches/LowerBound.java) + - ๐Ÿ“„ [MonteCarloTreeSearch](src/main/java/com/thealgorithms/searches/MonteCarloTreeSearch.java) + - ๐Ÿ“„ [OrderAgnosticBinarySearch](src/main/java/com/thealgorithms/searches/OrderAgnosticBinarySearch.java) + - ๐Ÿ“„ [PerfectBinarySearch](src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java) + - ๐Ÿ“„ [QuickSelect](src/main/java/com/thealgorithms/searches/QuickSelect.java) + - ๐Ÿ“„ [RabinKarpAlgorithm](src/main/java/com/thealgorithms/searches/RabinKarpAlgorithm.java) + - ๐Ÿ“„ [RandomSearch](src/main/java/com/thealgorithms/searches/RandomSearch.java) + - ๐Ÿ“„ [RecursiveBinarySearch](src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java) + - ๐Ÿ“„ [RowColumnWiseSorted2dArrayBinarySearch](src/main/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearch.java) + - ๐Ÿ“„ [SaddlebackSearch](src/main/java/com/thealgorithms/searches/SaddlebackSearch.java) + - ๐Ÿ“„ [SearchInARowAndColWiseSortedMatrix](src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java) + - ๐Ÿ“„ [SentinelLinearSearch](src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java) + - ๐Ÿ“„ [SortOrderAgnosticBinarySearch](src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java) + - ๐Ÿ“„ [SquareRootBinarySearch](src/main/java/com/thealgorithms/searches/SquareRootBinarySearch.java) + - ๐Ÿ“„ [TernarySearch](src/main/java/com/thealgorithms/searches/TernarySearch.java) + - ๐Ÿ“„ [UnionFind](src/main/java/com/thealgorithms/searches/UnionFind.java) + - ๐Ÿ“„ [UpperBound](src/main/java/com/thealgorithms/searches/UpperBound.java) + - ๐Ÿ“ **slidingwindow** + - ๐Ÿ“„ [LongestSubarrayWithSumLessOrEqualToK](src/main/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToK.java) + - ๐Ÿ“„ [LongestSubstringWithoutRepeatingCharacters](src/main/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharacters.java) + - ๐Ÿ“„ [MaxSumKSizeSubarray](src/main/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarray.java) + - ๐Ÿ“„ [MaximumSlidingWindow](src/main/java/com/thealgorithms/slidingwindow/MaximumSlidingWindow.java) + - ๐Ÿ“„ [MinSumKSizeSubarray](src/main/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarray.java) + - ๐Ÿ“„ [MinimumWindowSubstring](src/main/java/com/thealgorithms/slidingwindow/MinimumWindowSubstring.java) + - ๐Ÿ“„ [ShortestCoprimeSegment](src/main/java/com/thealgorithms/slidingwindow/ShortestCoprimeSegment.java) + - ๐Ÿ“ **sorts** + - ๐Ÿ“„ [AdaptiveMergeSort](src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java) + - ๐Ÿ“„ [BeadSort](src/main/java/com/thealgorithms/sorts/BeadSort.java) + - ๐Ÿ“„ [BinaryInsertionSort](src/main/java/com/thealgorithms/sorts/BinaryInsertionSort.java) + - ๐Ÿ“„ [BitonicSort](src/main/java/com/thealgorithms/sorts/BitonicSort.java) + - ๐Ÿ“„ [BogoSort](src/main/java/com/thealgorithms/sorts/BogoSort.java) + - ๐Ÿ“„ [BubbleSort](src/main/java/com/thealgorithms/sorts/BubbleSort.java) + - ๐Ÿ“„ [BubbleSortRecursive](src/main/java/com/thealgorithms/sorts/BubbleSortRecursive.java) + - ๐Ÿ“„ [BucketSort](src/main/java/com/thealgorithms/sorts/BucketSort.java) + - ๐Ÿ“„ [CircleSort](src/main/java/com/thealgorithms/sorts/CircleSort.java) + - ๐Ÿ“„ [CocktailShakerSort](src/main/java/com/thealgorithms/sorts/CocktailShakerSort.java) + - ๐Ÿ“„ [CombSort](src/main/java/com/thealgorithms/sorts/CombSort.java) + - ๐Ÿ“„ [CountingSort](src/main/java/com/thealgorithms/sorts/CountingSort.java) + - ๐Ÿ“„ [CycleSort](src/main/java/com/thealgorithms/sorts/CycleSort.java) + - ๐Ÿ“„ [DarkSort](src/main/java/com/thealgorithms/sorts/DarkSort.java) + - ๐Ÿ“„ [DualPivotQuickSort](src/main/java/com/thealgorithms/sorts/DualPivotQuickSort.java) + - ๐Ÿ“„ [DutchNationalFlagSort](src/main/java/com/thealgorithms/sorts/DutchNationalFlagSort.java) + - ๐Ÿ“„ [ExchangeSort](src/main/java/com/thealgorithms/sorts/ExchangeSort.java) + - ๐Ÿ“„ [FlashSort](src/main/java/com/thealgorithms/sorts/FlashSort.java) + - ๐Ÿ“„ [GnomeSort](src/main/java/com/thealgorithms/sorts/GnomeSort.java) + - ๐Ÿ“„ [HeapSort](src/main/java/com/thealgorithms/sorts/HeapSort.java) + - ๐Ÿ“„ [InsertionSort](src/main/java/com/thealgorithms/sorts/InsertionSort.java) + - ๐Ÿ“„ [IntrospectiveSort](src/main/java/com/thealgorithms/sorts/IntrospectiveSort.java) + - ๐Ÿ“„ [LinkListSort](src/main/java/com/thealgorithms/sorts/LinkListSort.java) + - ๐Ÿ“„ [MergeSort](src/main/java/com/thealgorithms/sorts/MergeSort.java) + - ๐Ÿ“„ [MergeSortNoExtraSpace](src/main/java/com/thealgorithms/sorts/MergeSortNoExtraSpace.java) + - ๐Ÿ“„ [MergeSortRecursive](src/main/java/com/thealgorithms/sorts/MergeSortRecursive.java) + - ๐Ÿ“„ [OddEvenSort](src/main/java/com/thealgorithms/sorts/OddEvenSort.java) + - ๐Ÿ“„ [PancakeSort](src/main/java/com/thealgorithms/sorts/PancakeSort.java) + - ๐Ÿ“„ [PatienceSort](src/main/java/com/thealgorithms/sorts/PatienceSort.java) + - ๐Ÿ“„ [PigeonholeSort](src/main/java/com/thealgorithms/sorts/PigeonholeSort.java) + - ๐Ÿ“„ [PriorityQueueSort](src/main/java/com/thealgorithms/sorts/PriorityQueueSort.java) + - ๐Ÿ“„ [QuickSort](src/main/java/com/thealgorithms/sorts/QuickSort.java) + - ๐Ÿ“„ [RadixSort](src/main/java/com/thealgorithms/sorts/RadixSort.java) + - ๐Ÿ“„ [SelectionSort](src/main/java/com/thealgorithms/sorts/SelectionSort.java) + - ๐Ÿ“„ [SelectionSortRecursive](src/main/java/com/thealgorithms/sorts/SelectionSortRecursive.java) + - ๐Ÿ“„ [ShellSort](src/main/java/com/thealgorithms/sorts/ShellSort.java) + - ๐Ÿ“„ [SlowSort](src/main/java/com/thealgorithms/sorts/SlowSort.java) + - ๐Ÿ“„ [SortAlgorithm](src/main/java/com/thealgorithms/sorts/SortAlgorithm.java) + - ๐Ÿ“„ [SortUtils](src/main/java/com/thealgorithms/sorts/SortUtils.java) + - ๐Ÿ“„ [SortUtilsRandomGenerator](src/main/java/com/thealgorithms/sorts/SortUtilsRandomGenerator.java) + - ๐Ÿ“„ [SpreadSort](src/main/java/com/thealgorithms/sorts/SpreadSort.java) + - ๐Ÿ“„ [StalinSort](src/main/java/com/thealgorithms/sorts/StalinSort.java) + - ๐Ÿ“„ [StoogeSort](src/main/java/com/thealgorithms/sorts/StoogeSort.java) + - ๐Ÿ“„ [StrandSort](src/main/java/com/thealgorithms/sorts/StrandSort.java) + - ๐Ÿ“„ [SwapSort](src/main/java/com/thealgorithms/sorts/SwapSort.java) + - ๐Ÿ“„ [TimSort](src/main/java/com/thealgorithms/sorts/TimSort.java) + - ๐Ÿ“„ [TopologicalSort](src/main/java/com/thealgorithms/sorts/TopologicalSort.java) + - ๐Ÿ“„ [TreeSort](src/main/java/com/thealgorithms/sorts/TreeSort.java) + - ๐Ÿ“„ [WaveSort](src/main/java/com/thealgorithms/sorts/WaveSort.java) + - ๐Ÿ“„ [WiggleSort](src/main/java/com/thealgorithms/sorts/WiggleSort.java) + - ๐Ÿ“ **stacks** + - ๐Ÿ“„ [BalancedBrackets](src/main/java/com/thealgorithms/stacks/BalancedBrackets.java) + - ๐Ÿ“„ [CelebrityFinder](src/main/java/com/thealgorithms/stacks/CelebrityFinder.java) + - ๐Ÿ“„ [DecimalToAnyUsingStack](src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java) + - ๐Ÿ“„ [DuplicateBrackets](src/main/java/com/thealgorithms/stacks/DuplicateBrackets.java) + - ๐Ÿ“„ [GreatestElementConstantTime](src/main/java/com/thealgorithms/stacks/GreatestElementConstantTime.java) + - ๐Ÿ“„ [InfixToPostfix](src/main/java/com/thealgorithms/stacks/InfixToPostfix.java) + - ๐Ÿ“„ [InfixToPrefix](src/main/java/com/thealgorithms/stacks/InfixToPrefix.java) + - ๐Ÿ“„ [LargestRectangle](src/main/java/com/thealgorithms/stacks/LargestRectangle.java) + - ๐Ÿ“„ [MaximumMinimumWindow](src/main/java/com/thealgorithms/stacks/MaximumMinimumWindow.java) + - ๐Ÿ“„ [MinStackUsingSingleStack](src/main/java/com/thealgorithms/stacks/MinStackUsingSingleStack.java) + - ๐Ÿ“„ [MinStackUsingTwoStacks](src/main/java/com/thealgorithms/stacks/MinStackUsingTwoStacks.java) + - ๐Ÿ“„ [NextGreaterElement](src/main/java/com/thealgorithms/stacks/NextGreaterElement.java) + - ๐Ÿ“„ [NextSmallerElement](src/main/java/com/thealgorithms/stacks/NextSmallerElement.java) + - ๐Ÿ“„ [PalindromeWithStack](src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java) + - ๐Ÿ“„ [PostfixEvaluator](src/main/java/com/thealgorithms/stacks/PostfixEvaluator.java) + - ๐Ÿ“„ [PostfixToInfix](src/main/java/com/thealgorithms/stacks/PostfixToInfix.java) + - ๐Ÿ“„ [PrefixEvaluator](src/main/java/com/thealgorithms/stacks/PrefixEvaluator.java) + - ๐Ÿ“„ [PrefixToInfix](src/main/java/com/thealgorithms/stacks/PrefixToInfix.java) + - ๐Ÿ“„ [SmallestElementConstantTime](src/main/java/com/thealgorithms/stacks/SmallestElementConstantTime.java) + - ๐Ÿ“„ [SortStack](src/main/java/com/thealgorithms/stacks/SortStack.java) + - ๐Ÿ“„ [StackPostfixNotation](src/main/java/com/thealgorithms/stacks/StackPostfixNotation.java) + - ๐Ÿ“„ [StackUsingTwoQueues](src/main/java/com/thealgorithms/stacks/StackUsingTwoQueues.java) + - ๐Ÿ“ **strings** + - ๐Ÿ“„ [AhoCorasick](src/main/java/com/thealgorithms/strings/AhoCorasick.java) + - ๐Ÿ“„ [Alphabetical](src/main/java/com/thealgorithms/strings/Alphabetical.java) + - ๐Ÿ“„ [AlternativeStringArrange](src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java) + - ๐Ÿ“„ [Anagrams](src/main/java/com/thealgorithms/strings/Anagrams.java) + - ๐Ÿ“„ [CharactersSame](src/main/java/com/thealgorithms/strings/CharactersSame.java) + - ๐Ÿ“„ [CheckVowels](src/main/java/com/thealgorithms/strings/CheckVowels.java) + - ๐Ÿ“„ [CountChar](src/main/java/com/thealgorithms/strings/CountChar.java) + - ๐Ÿ“„ [CountWords](src/main/java/com/thealgorithms/strings/CountWords.java) + - ๐Ÿ“„ [HammingDistance](src/main/java/com/thealgorithms/strings/HammingDistance.java) + - ๐Ÿ“„ [HorspoolSearch](src/main/java/com/thealgorithms/strings/HorspoolSearch.java) + - ๐Ÿ“„ [Isogram](src/main/java/com/thealgorithms/strings/Isogram.java) + - ๐Ÿ“„ [Isomorphic](src/main/java/com/thealgorithms/strings/Isomorphic.java) + - ๐Ÿ“„ [KMP](src/main/java/com/thealgorithms/strings/KMP.java) + - ๐Ÿ“„ [LetterCombinationsOfPhoneNumber](src/main/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumber.java) + - ๐Ÿ“„ [LongestCommonPrefix](src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java) + - ๐Ÿ“„ [LongestNonRepetitiveSubstring](src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java) + - ๐Ÿ“„ [LongestPalindromicSubstring](src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java) + - ๐Ÿ“„ [Lower](src/main/java/com/thealgorithms/strings/Lower.java) + - ๐Ÿ“„ [Manacher](src/main/java/com/thealgorithms/strings/Manacher.java) + - ๐Ÿ“„ [MyAtoi](src/main/java/com/thealgorithms/strings/MyAtoi.java) + - ๐Ÿ“„ [Palindrome](src/main/java/com/thealgorithms/strings/Palindrome.java) + - ๐Ÿ“„ [Pangram](src/main/java/com/thealgorithms/strings/Pangram.java) + - ๐Ÿ“„ [PermuteString](src/main/java/com/thealgorithms/strings/PermuteString.java) + - ๐Ÿ“„ [RabinKarp](src/main/java/com/thealgorithms/strings/RabinKarp.java) + - ๐Ÿ“„ [RemoveDuplicateFromString](src/main/java/com/thealgorithms/strings/RemoveDuplicateFromString.java) + - ๐Ÿ“„ [ReturnSubsequence](src/main/java/com/thealgorithms/strings/ReturnSubsequence.java) + - ๐Ÿ“„ [ReverseString](src/main/java/com/thealgorithms/strings/ReverseString.java) + - ๐Ÿ“„ [ReverseWordsInString](src/main/java/com/thealgorithms/strings/ReverseWordsInString.java) + - ๐Ÿ“„ [Rotation](src/main/java/com/thealgorithms/strings/Rotation.java) + - ๐Ÿ“„ [StringCompression](src/main/java/com/thealgorithms/strings/StringCompression.java) + - ๐Ÿ“„ [StringMatchFiniteAutomata](src/main/java/com/thealgorithms/strings/StringMatchFiniteAutomata.java) + - ๐Ÿ“„ [SuffixArray](src/main/java/com/thealgorithms/strings/SuffixArray.java) + - ๐Ÿ“„ [Upper](src/main/java/com/thealgorithms/strings/Upper.java) + - ๐Ÿ“„ [ValidParentheses](src/main/java/com/thealgorithms/strings/ValidParentheses.java) + - ๐Ÿ“„ [WordLadder](src/main/java/com/thealgorithms/strings/WordLadder.java) + - ๐Ÿ“ **zigZagPattern** + - ๐Ÿ“„ [ZigZagPattern](src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java) + - ๐Ÿ“ **tree** + - ๐Ÿ“„ [HeavyLightDecomposition](src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java) +- ๐Ÿ“ **test** + - ๐Ÿ“ **java** + - ๐Ÿ“ **com** + - ๐Ÿ“ **thealgorithms** + - ๐Ÿ“ **audiofilters** + - ๐Ÿ“„ [EMAFilterTest](src/test/java/com/thealgorithms/audiofilters/EMAFilterTest.java) + - ๐Ÿ“„ [IIRFilterTest](src/test/java/com/thealgorithms/audiofilters/IIRFilterTest.java) + - ๐Ÿ“ **backtracking** + - ๐Ÿ“„ [AllPathsFromSourceToTargetTest](src/test/java/com/thealgorithms/backtracking/AllPathsFromSourceToTargetTest.java) + - ๐Ÿ“„ [ArrayCombinationTest](src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java) + - ๐Ÿ“„ [CombinationTest](src/test/java/com/thealgorithms/backtracking/CombinationTest.java) + - ๐Ÿ“„ [CrosswordSolverTest](src/test/java/com/thealgorithms/backtracking/CrosswordSolverTest.java) + - ๐Ÿ“„ [FloodFillTest](src/test/java/com/thealgorithms/backtracking/FloodFillTest.java) + - ๐Ÿ“„ [KnightsTourTest](src/test/java/com/thealgorithms/backtracking/KnightsTourTest.java) + - ๐Ÿ“„ [MColoringTest](src/test/java/com/thealgorithms/backtracking/MColoringTest.java) + - ๐Ÿ“„ [MazeRecursionTest](src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java) + - ๐Ÿ“„ [NQueensTest](src/test/java/com/thealgorithms/backtracking/NQueensTest.java) + - ๐Ÿ“„ [ParenthesesGeneratorTest](src/test/java/com/thealgorithms/backtracking/ParenthesesGeneratorTest.java) + - ๐Ÿ“„ [PermutationTest](src/test/java/com/thealgorithms/backtracking/PermutationTest.java) + - ๐Ÿ“„ [PowerSumTest](src/test/java/com/thealgorithms/backtracking/PowerSumTest.java) + - ๐Ÿ“„ [SubsequenceFinderTest](src/test/java/com/thealgorithms/backtracking/SubsequenceFinderTest.java) + - ๐Ÿ“„ [WordPatternMatcherTest](src/test/java/com/thealgorithms/backtracking/WordPatternMatcherTest.java) + - ๐Ÿ“„ [WordSearchTest](src/test/java/com/thealgorithms/backtracking/WordSearchTest.java) + - ๐Ÿ“ **bitmanipulation** + - ๐Ÿ“„ [BcdConversionTest](src/test/java/com/thealgorithms/bitmanipulation/BcdConversionTest.java) + - ๐Ÿ“„ [BinaryPalindromeCheckTest](src/test/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheckTest.java) + - ๐Ÿ“„ [BitSwapTest](src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java) + - ๐Ÿ“„ [BitwiseGCDTest](src/test/java/com/thealgorithms/bitmanipulation/BitwiseGCDTest.java) + - ๐Ÿ“„ [BooleanAlgebraGatesTest](src/test/java/com/thealgorithms/bitmanipulation/BooleanAlgebraGatesTest.java) + - ๐Ÿ“„ [ClearLeftmostSetBitTest](src/test/java/com/thealgorithms/bitmanipulation/ClearLeftmostSetBitTest.java) + - ๐Ÿ“„ [CountBitsFlipTest](src/test/java/com/thealgorithms/bitmanipulation/CountBitsFlipTest.java) + - ๐Ÿ“„ [CountLeadingZerosTest](src/test/java/com/thealgorithms/bitmanipulation/CountLeadingZerosTest.java) + - ๐Ÿ“„ [CountSetBitsTest](src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java) + - ๐Ÿ“„ [FindNthBitTest](src/test/java/com/thealgorithms/bitmanipulation/FindNthBitTest.java) + - ๐Ÿ“„ [FirstDifferentBitTest](src/test/java/com/thealgorithms/bitmanipulation/FirstDifferentBitTest.java) + - ๐Ÿ“„ [GenerateSubsetsTest](src/test/java/com/thealgorithms/bitmanipulation/GenerateSubsetsTest.java) + - ๐Ÿ“„ [GrayCodeConversionTest](src/test/java/com/thealgorithms/bitmanipulation/GrayCodeConversionTest.java) + - ๐Ÿ“„ [HammingDistanceTest](src/test/java/com/thealgorithms/bitmanipulation/HammingDistanceTest.java) + - ๐Ÿ“„ [HigherLowerPowerOfTwoTest](src/test/java/com/thealgorithms/bitmanipulation/HigherLowerPowerOfTwoTest.java) + - ๐Ÿ“„ [HighestSetBitTest](src/test/java/com/thealgorithms/bitmanipulation/HighestSetBitTest.java) + - ๐Ÿ“„ [IndexOfRightMostSetBitTest](src/test/java/com/thealgorithms/bitmanipulation/IndexOfRightMostSetBitTest.java) + - ๐Ÿ“„ [IsEvenTest](src/test/java/com/thealgorithms/bitmanipulation/IsEvenTest.java) + - ๐Ÿ“„ [IsPowerTwoTest](src/test/java/com/thealgorithms/bitmanipulation/IsPowerTwoTest.java) + - ๐Ÿ“„ [LowestSetBitTest](src/test/java/com/thealgorithms/bitmanipulation/LowestSetBitTest.java) + - ๐Ÿ“„ [ModuloPowerOfTwoTest](src/test/java/com/thealgorithms/bitmanipulation/ModuloPowerOfTwoTest.java) + - ๐Ÿ“„ [NextHigherSameBitCountTest](src/test/java/com/thealgorithms/bitmanipulation/NextHigherSameBitCountTest.java) + - ๐Ÿ“„ [NonRepeatingNumberFinderTest](src/test/java/com/thealgorithms/bitmanipulation/NonRepeatingNumberFinderTest.java) + - ๐Ÿ“„ [NumberAppearingOddTimesTest](src/test/java/com/thealgorithms/bitmanipulation/NumberAppearingOddTimesTest.java) + - ๐Ÿ“„ [NumbersDifferentSignsTest](src/test/java/com/thealgorithms/bitmanipulation/NumbersDifferentSignsTest.java) + - ๐Ÿ“„ [OneBitDifferenceTest](src/test/java/com/thealgorithms/bitmanipulation/OneBitDifferenceTest.java) + - ๐Ÿ“„ [OnesComplementTest](src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java) + - ๐Ÿ“„ [ParityCheckTest](src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java) + - ๐Ÿ“„ [ReverseBitsTest](src/test/java/com/thealgorithms/bitmanipulation/ReverseBitsTest.java) + - ๐Ÿ“„ [SingleBitOperationsTest](src/test/java/com/thealgorithms/bitmanipulation/SingleBitOperationsTest.java) + - ๐Ÿ“„ [SingleElementTest](src/test/java/com/thealgorithms/bitmanipulation/SingleElementTest.java) + - ๐Ÿ“„ [SwapAdjacentBitsTest](src/test/java/com/thealgorithms/bitmanipulation/SwapAdjacentBitsTest.java) + - ๐Ÿ“„ [TwosComplementTest](src/test/java/com/thealgorithms/bitmanipulation/TwosComplementTest.java) + - ๐Ÿ“„ [Xs3ConversionTest](src/test/java/com/thealgorithms/bitmanipulation/Xs3ConversionTest.java) + - ๐Ÿ“ **ciphers** + - ๐Ÿ“„ [ADFGVXCipherTest](src/test/java/com/thealgorithms/ciphers/ADFGVXCipherTest.java) + - ๐Ÿ“„ [AESEncryptionTest](src/test/java/com/thealgorithms/ciphers/AESEncryptionTest.java) + - ๐Ÿ“„ [AffineCipherTest](src/test/java/com/thealgorithms/ciphers/AffineCipherTest.java) + - ๐Ÿ“„ [AtbashTest](src/test/java/com/thealgorithms/ciphers/AtbashTest.java) + - ๐Ÿ“„ [AutokeyTest](src/test/java/com/thealgorithms/ciphers/AutokeyTest.java) + - ๐Ÿ“„ [BaconianCipherTest](src/test/java/com/thealgorithms/ciphers/BaconianCipherTest.java) + - ๐Ÿ“„ [BlowfishTest](src/test/java/com/thealgorithms/ciphers/BlowfishTest.java) + - ๐Ÿ“„ [CaesarTest](src/test/java/com/thealgorithms/ciphers/CaesarTest.java) + - ๐Ÿ“„ [ColumnarTranspositionCipherTest](src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java) + - ๐Ÿ“„ [DESTest](src/test/java/com/thealgorithms/ciphers/DESTest.java) + - ๐Ÿ“„ [DiffieHellmanTest](src/test/java/com/thealgorithms/ciphers/DiffieHellmanTest.java) + - ๐Ÿ“„ [ECCTest](src/test/java/com/thealgorithms/ciphers/ECCTest.java) + - ๐Ÿ“„ [HillCipherTest](src/test/java/com/thealgorithms/ciphers/HillCipherTest.java) + - ๐Ÿ“„ [MonoAlphabeticTest](src/test/java/com/thealgorithms/ciphers/MonoAlphabeticTest.java) + - ๐Ÿ“„ [PermutationCipherTest](src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java) + - ๐Ÿ“„ [PlayfairTest](src/test/java/com/thealgorithms/ciphers/PlayfairTest.java) + - ๐Ÿ“„ [PolybiusTest](src/test/java/com/thealgorithms/ciphers/PolybiusTest.java) + - ๐Ÿ“„ [RSATest](src/test/java/com/thealgorithms/ciphers/RSATest.java) + - ๐Ÿ“„ [RailFenceTest](src/test/java/com/thealgorithms/ciphers/RailFenceTest.java) + - ๐Ÿ“„ [SimpleSubCipherTest](src/test/java/com/thealgorithms/ciphers/SimpleSubCipherTest.java) + - ๐Ÿ“„ [VigenereTest](src/test/java/com/thealgorithms/ciphers/VigenereTest.java) + - ๐Ÿ“„ [XORCipherTest](src/test/java/com/thealgorithms/ciphers/XORCipherTest.java) + - ๐Ÿ“ **a5** + - ๐Ÿ“„ [A5CipherTest](src/test/java/com/thealgorithms/ciphers/a5/A5CipherTest.java) + - ๐Ÿ“„ [A5KeyStreamGeneratorTest](src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java) + - ๐Ÿ“„ [LFSRTest](src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java) + - ๐Ÿ“ **compression** + - ๐Ÿ“„ [RunLengthEncodingTest](src/test/java/com/thealgorithms/compression/RunLengthEncodingTest.java) + - ๐Ÿ“„ [ShannonFanoTest](src/test/java/com/thealgorithms/compression/ShannonFanoTest.java) + - ๐Ÿ“ **conversions** + - ๐Ÿ“„ [AffineConverterTest](src/test/java/com/thealgorithms/conversions/AffineConverterTest.java) + - ๐Ÿ“„ [AnyBaseToDecimalTest](src/test/java/com/thealgorithms/conversions/AnyBaseToDecimalTest.java) + - ๐Ÿ“„ [AnytoAnyTest](src/test/java/com/thealgorithms/conversions/AnytoAnyTest.java) + - ๐Ÿ“„ [Base64Test](src/test/java/com/thealgorithms/conversions/Base64Test.java) + - ๐Ÿ“„ [BinaryToDecimalTest](src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java) + - ๐Ÿ“„ [BinaryToHexadecimalTest](src/test/java/com/thealgorithms/conversions/BinaryToHexadecimalTest.java) + - ๐Ÿ“„ [BinaryToOctalTest](src/test/java/com/thealgorithms/conversions/BinaryToOctalTest.java) + - ๐Ÿ“„ [CoordinateConverterTest](src/test/java/com/thealgorithms/conversions/CoordinateConverterTest.java) + - ๐Ÿ“„ [DecimalToAnyBaseTest](src/test/java/com/thealgorithms/conversions/DecimalToAnyBaseTest.java) + - ๐Ÿ“„ [DecimalToBinaryTest](src/test/java/com/thealgorithms/conversions/DecimalToBinaryTest.java) + - ๐Ÿ“„ [DecimalToHexadecimalTest](src/test/java/com/thealgorithms/conversions/DecimalToHexadecimalTest.java) + - ๐Ÿ“„ [DecimalToOctalTest](src/test/java/com/thealgorithms/conversions/DecimalToOctalTest.java) + - ๐Ÿ“„ [EndianConverterTest](src/test/java/com/thealgorithms/conversions/EndianConverterTest.java) + - ๐Ÿ“„ [HexToOctTest](src/test/java/com/thealgorithms/conversions/HexToOctTest.java) + - ๐Ÿ“„ [HexaDecimalToBinaryTest](src/test/java/com/thealgorithms/conversions/HexaDecimalToBinaryTest.java) + - ๐Ÿ“„ [HexaDecimalToDecimalTest](src/test/java/com/thealgorithms/conversions/HexaDecimalToDecimalTest.java) + - ๐Ÿ“„ [IPConverterTest](src/test/java/com/thealgorithms/conversions/IPConverterTest.java) + - ๐Ÿ“„ [IPv6ConverterTest](src/test/java/com/thealgorithms/conversions/IPv6ConverterTest.java) + - ๐Ÿ“„ [IntegerToEnglishTest](src/test/java/com/thealgorithms/conversions/IntegerToEnglishTest.java) + - ๐Ÿ“„ [IntegerToRomanTest](src/test/java/com/thealgorithms/conversions/IntegerToRomanTest.java) + - ๐Ÿ“„ [MorseCodeConverterTest](src/test/java/com/thealgorithms/conversions/MorseCodeConverterTest.java) + - ๐Ÿ“„ [NumberToWordsTest](src/test/java/com/thealgorithms/conversions/NumberToWordsTest.java) + - ๐Ÿ“„ [OctalToBinaryTest](src/test/java/com/thealgorithms/conversions/OctalToBinaryTest.java) + - ๐Ÿ“„ [OctalToDecimalTest](src/test/java/com/thealgorithms/conversions/OctalToDecimalTest.java) + - ๐Ÿ“„ [OctalToHexadecimalTest](src/test/java/com/thealgorithms/conversions/OctalToHexadecimalTest.java) + - ๐Ÿ“„ [PhoneticAlphabetConverterTest](src/test/java/com/thealgorithms/conversions/PhoneticAlphabetConverterTest.java) + - ๐Ÿ“„ [RomanToIntegerTest](src/test/java/com/thealgorithms/conversions/RomanToIntegerTest.java) + - ๐Ÿ“„ [TimeConverterTest](src/test/java/com/thealgorithms/conversions/TimeConverterTest.java) + - ๐Ÿ“„ [TurkishToLatinConversionTest](src/test/java/com/thealgorithms/conversions/TurkishToLatinConversionTest.java) + - ๐Ÿ“„ [UnitConversionsTest](src/test/java/com/thealgorithms/conversions/UnitConversionsTest.java) + - ๐Ÿ“„ [UnitsConverterTest](src/test/java/com/thealgorithms/conversions/UnitsConverterTest.java) + - ๐Ÿ“„ [WordsToNumberTest](src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java) + - ๐Ÿ“ **datastructures** + - ๐Ÿ“ **bag** + - ๐Ÿ“„ [BagTest](src/test/java/com/thealgorithms/datastructures/bag/BagTest.java) + - ๐Ÿ“ **bloomfilter** + - ๐Ÿ“„ [BloomFilterTest](src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java) + - ๐Ÿ“ **buffers** + - ๐Ÿ“„ [CircularBufferTest](src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java) + - ๐Ÿ“ **caches** + - ๐Ÿ“„ [FIFOCacheTest](src/test/java/com/thealgorithms/datastructures/caches/FIFOCacheTest.java) + - ๐Ÿ“„ [LFUCacheTest](src/test/java/com/thealgorithms/datastructures/caches/LFUCacheTest.java) + - ๐Ÿ“„ [LIFOCacheTest](src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java) + - ๐Ÿ“„ [LRUCacheTest](src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java) + - ๐Ÿ“„ [MRUCacheTest](src/test/java/com/thealgorithms/datastructures/caches/MRUCacheTest.java) + - ๐Ÿ“„ [RRCacheTest](src/test/java/com/thealgorithms/datastructures/caches/RRCacheTest.java) + - ๐Ÿ“ **crdt** + - ๐Ÿ“„ [GCounterTest](src/test/java/com/thealgorithms/datastructures/crdt/GCounterTest.java) + - ๐Ÿ“„ [GSetTest](src/test/java/com/thealgorithms/datastructures/crdt/GSetTest.java) + - ๐Ÿ“„ [LWWElementSetTest](src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java) + - ๐Ÿ“„ [ORSetTest](src/test/java/com/thealgorithms/datastructures/crdt/ORSetTest.java) + - ๐Ÿ“„ [PNCounterTest](src/test/java/com/thealgorithms/datastructures/crdt/PNCounterTest.java) + - ๐Ÿ“„ [TwoPSetTest](src/test/java/com/thealgorithms/datastructures/crdt/TwoPSetTest.java) + - ๐Ÿ“ **disjointsetunion** + - ๐Ÿ“„ [DisjointSetUnionBySizeTest](src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySizeTest.java) + - ๐Ÿ“„ [DisjointSetUnionTest](src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java) + - ๐Ÿ“ **dynamicarray** + - ๐Ÿ“„ [DynamicArrayTest](src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java) + - ๐Ÿ“ **graphs** + - ๐Ÿ“„ [AStarTest](src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java) + - ๐Ÿ“„ [BipartiteGraphDFSTest](src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java) + - ๐Ÿ“„ [BoruvkaAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java) + - ๐Ÿ“„ [DialsAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/DialsAlgorithmTest.java) + - ๐Ÿ“„ [DijkstraAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java) + - ๐Ÿ“„ [DijkstraOptimizedAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java) + - ๐Ÿ“„ [EdmondsBlossomAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/EdmondsBlossomAlgorithmTest.java) + - ๐Ÿ“„ [FloydWarshallTest](src/test/java/com/thealgorithms/datastructures/graphs/FloydWarshallTest.java) + - ๐Ÿ“„ [FordFulkersonTest](src/test/java/com/thealgorithms/datastructures/graphs/FordFulkersonTest.java) + - ๐Ÿ“„ [HamiltonianCycleTest](src/test/java/com/thealgorithms/datastructures/graphs/HamiltonianCycleTest.java) + - ๐Ÿ“„ [JohnsonsAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/JohnsonsAlgorithmTest.java) + - ๐Ÿ“„ [KahnsAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithmTest.java) + - ๐Ÿ“„ [KosarajuTest](src/test/java/com/thealgorithms/datastructures/graphs/KosarajuTest.java) + - ๐Ÿ“„ [KruskalTest](src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java) + - ๐Ÿ“„ [MatrixGraphsTest](src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java) + - ๐Ÿ“„ [PrimMSTTest](src/test/java/com/thealgorithms/datastructures/graphs/PrimMSTTest.java) + - ๐Ÿ“„ [TarjansAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/TarjansAlgorithmTest.java) + - ๐Ÿ“„ [TwoSatTest](src/test/java/com/thealgorithms/datastructures/graphs/TwoSatTest.java) + - ๐Ÿ“„ [WelshPowellTest](src/test/java/com/thealgorithms/datastructures/graphs/WelshPowellTest.java) + - ๐Ÿ“ **hashmap** + - ๐Ÿ“ **hashing** + - ๐Ÿ“„ [GenericHashMapUsingArrayListTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayListTest.java) + - ๐Ÿ“„ [GenericHashMapUsingArrayTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java) + - ๐Ÿ“„ [HashMapCuckooHashingTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapCuckooHashingTest.java) + - ๐Ÿ“„ [HashMapTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/HashMapTest.java) + - ๐Ÿ“„ [IntersectionTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/IntersectionTest.java) + - ๐Ÿ“„ [LinearProbingHashMapTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMapTest.java) + - ๐Ÿ“„ [MajorityElementTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElementTest.java) + - ๐Ÿ“„ [MapTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java) + - ๐Ÿ“ **heaps** + - ๐Ÿ“„ [FibonacciHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/FibonacciHeapTest.java) + - ๐Ÿ“„ [GenericHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/GenericHeapTest.java) + - ๐Ÿ“„ [HeapElementTest](src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java) + - ๐Ÿ“„ [KthElementFinderTest](src/test/java/com/thealgorithms/datastructures/heaps/KthElementFinderTest.java) + - ๐Ÿ“„ [LeftistHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/LeftistHeapTest.java) + - ๐Ÿ“„ [MaxHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/MaxHeapTest.java) + - ๐Ÿ“„ [MedianFinderTest](src/test/java/com/thealgorithms/datastructures/heaps/MedianFinderTest.java) + - ๐Ÿ“„ [MergeKSortedArraysTest](src/test/java/com/thealgorithms/datastructures/heaps/MergeKSortedArraysTest.java) + - ๐Ÿ“„ [MinHeapTest](src/test/java/com/thealgorithms/datastructures/heaps/MinHeapTest.java) + - ๐Ÿ“„ [MinPriorityQueueTest](src/test/java/com/thealgorithms/datastructures/heaps/MinPriorityQueueTest.java) + - ๐Ÿ“ **lists** + - ๐Ÿ“„ [CircleLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/CircleLinkedListTest.java) + - ๐Ÿ“„ [CircularDoublyLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedListTest.java) + - ๐Ÿ“„ [CountSinglyLinkedListRecursionTest](src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java) + - ๐Ÿ“„ [CreateAndDetectLoopTest](src/test/java/com/thealgorithms/datastructures/lists/CreateAndDetectLoopTest.java) + - ๐Ÿ“„ [CursorLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java) + - ๐Ÿ“„ [FlattenMultilevelLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedListTest.java) + - ๐Ÿ“„ [MergeKSortedLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java) + - ๐Ÿ“„ [MergeSortedArrayListTest](src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java) + - ๐Ÿ“„ [MergeSortedSinglyLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedListTest.java) + - ๐Ÿ“„ [QuickSortLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/QuickSortLinkedListTest.java) + - ๐Ÿ“„ [ReverseKGroupTest](src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java) + - ๐Ÿ“„ [RotateSinglyLinkedListsTest](src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java) + - ๐Ÿ“„ [SearchSinglyLinkedListRecursionTest](src/test/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursionTest.java) + - ๐Ÿ“„ [SinglyLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java) + - ๐Ÿ“„ [SkipListTest](src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java) + - ๐Ÿ“„ [SortedLinkedListTest](src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java) + - ๐Ÿ“„ [TortoiseHareAlgoTest](src/test/java/com/thealgorithms/datastructures/lists/TortoiseHareAlgoTest.java) + - ๐Ÿ“ **queues** + - ๐Ÿ“„ [CircularQueueTest](src/test/java/com/thealgorithms/datastructures/queues/CircularQueueTest.java) + - ๐Ÿ“„ [DequeTest](src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java) + - ๐Ÿ“„ [GenericArrayListQueueTest](src/test/java/com/thealgorithms/datastructures/queues/GenericArrayListQueueTest.java) + - ๐Ÿ“„ [LinkedQueueTest](src/test/java/com/thealgorithms/datastructures/queues/LinkedQueueTest.java) + - ๐Ÿ“„ [PriorityQueuesTest](src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java) + - ๐Ÿ“„ [QueueByTwoStacksTest](src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java) + - ๐Ÿ“„ [QueueTest](src/test/java/com/thealgorithms/datastructures/queues/QueueTest.java) + - ๐Ÿ“„ [SlidingWindowMaximumTest](src/test/java/com/thealgorithms/datastructures/queues/SlidingWindowMaximumTest.java) + - ๐Ÿ“„ [TokenBucketTest](src/test/java/com/thealgorithms/datastructures/queues/TokenBucketTest.java) + - ๐Ÿ“ **stacks** + - ๐Ÿ“„ [NodeStackTest](src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java) + - ๐Ÿ“„ [ReverseStackTest](src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java) + - ๐Ÿ“„ [StackArrayListTest](src/test/java/com/thealgorithms/datastructures/stacks/StackArrayListTest.java) + - ๐Ÿ“„ [StackArrayTest](src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java) + - ๐Ÿ“„ [StackOfLinkedListTest](src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java) + - ๐Ÿ“ **trees** + - ๐Ÿ“„ [AVLTreeTest](src/test/java/com/thealgorithms/datastructures/trees/AVLTreeTest.java) + - ๐Ÿ“„ [BSTFromSortedArrayTest](src/test/java/com/thealgorithms/datastructures/trees/BSTFromSortedArrayTest.java) + - ๐Ÿ“„ [BSTIterativeTest](src/test/java/com/thealgorithms/datastructures/trees/BSTIterativeTest.java) + - ๐Ÿ“„ [BSTRecursiveGenericTest](src/test/java/com/thealgorithms/datastructures/trees/BSTRecursiveGenericTest.java) + - ๐Ÿ“„ [BSTRecursiveTest](src/test/java/com/thealgorithms/datastructures/trees/BSTRecursiveTest.java) + - ๐Ÿ“„ [BTreeTest](src/test/java/com/thealgorithms/datastructures/trees/BTreeTest.java) + - ๐Ÿ“„ [BinaryTreeTest](src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeTest.java) + - ๐Ÿ“„ [BoundaryTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/BoundaryTraversalTest.java) + - ๐Ÿ“„ [CeilInBinarySearchTreeTest](src/test/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTreeTest.java) + - ๐Ÿ“„ [CheckBinaryTreeIsValidBSTTest](src/test/java/com/thealgorithms/datastructures/trees/CheckBinaryTreeIsValidBSTTest.java) + - ๐Ÿ“„ [CheckIfBinaryTreeBalancedTest](src/test/java/com/thealgorithms/datastructures/trees/CheckIfBinaryTreeBalancedTest.java) + - ๐Ÿ“„ [CheckTreeIsSymmetricTest](src/test/java/com/thealgorithms/datastructures/trees/CheckTreeIsSymmetricTest.java) + - ๐Ÿ“„ [CreateBinaryTreeFromInorderPreorderTest](src/test/java/com/thealgorithms/datastructures/trees/CreateBinaryTreeFromInorderPreorderTest.java) + - ๐Ÿ“„ [InorderTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/InorderTraversalTest.java) + - ๐Ÿ“„ [KDTreeTest](src/test/java/com/thealgorithms/datastructures/trees/KDTreeTest.java) + - ๐Ÿ“„ [LazySegmentTreeTest](src/test/java/com/thealgorithms/datastructures/trees/LazySegmentTreeTest.java) + - ๐Ÿ“„ [LevelOrderTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/LevelOrderTraversalTest.java) + - ๐Ÿ“„ [PostOrderTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/PostOrderTraversalTest.java) + - ๐Ÿ“„ [PreOrderTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/PreOrderTraversalTest.java) + - ๐Ÿ“„ [QuadTreeTest](src/test/java/com/thealgorithms/datastructures/trees/QuadTreeTest.java) + - ๐Ÿ“„ [SameTreesCheckTest](src/test/java/com/thealgorithms/datastructures/trees/SameTreesCheckTest.java) + - ๐Ÿ“„ [SplayTreeTest](src/test/java/com/thealgorithms/datastructures/trees/SplayTreeTest.java) + - ๐Ÿ“„ [TreapTest](src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java) + - ๐Ÿ“„ [TreeTestUtils](src/test/java/com/thealgorithms/datastructures/trees/TreeTestUtils.java) + - ๐Ÿ“„ [TrieTest](src/test/java/com/thealgorithms/datastructures/trees/TrieTest.java) + - ๐Ÿ“„ [VerticalOrderTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/VerticalOrderTraversalTest.java) + - ๐Ÿ“„ [ZigzagTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/ZigzagTraversalTest.java) + - ๐Ÿ“ **devutils** + - ๐Ÿ“ **entities** + - ๐Ÿ“„ [ProcessDetailsTest](src/test/java/com/thealgorithms/devutils/entities/ProcessDetailsTest.java) + - ๐Ÿ“ **divideandconquer** + - ๐Ÿ“„ [BinaryExponentiationTest](src/test/java/com/thealgorithms/divideandconquer/BinaryExponentiationTest.java) + - ๐Ÿ“„ [ClosestPairTest](src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java) + - ๐Ÿ“„ [CountingInversionsTest](src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java) + - ๐Ÿ“„ [MedianOfTwoSortedArraysTest](src/test/java/com/thealgorithms/divideandconquer/MedianOfTwoSortedArraysTest.java) + - ๐Ÿ“„ [SkylineAlgorithmTest](src/test/java/com/thealgorithms/divideandconquer/SkylineAlgorithmTest.java) + - ๐Ÿ“„ [StrassenMatrixMultiplicationTest](src/test/java/com/thealgorithms/divideandconquer/StrassenMatrixMultiplicationTest.java) + - ๐Ÿ“„ [TilingProblemTest](src/test/java/com/thealgorithms/divideandconquer/TilingProblemTest.java) + - ๐Ÿ“ **dynamicprogramming** + - ๐Ÿ“„ [AbbreviationTest](src/test/java/com/thealgorithms/dynamicprogramming/AbbreviationTest.java) + - ๐Ÿ“„ [AllConstructTest](src/test/java/com/thealgorithms/dynamicprogramming/AllConstructTest.java) + - ๐Ÿ“„ [AssignmentUsingBitmaskTest](src/test/java/com/thealgorithms/dynamicprogramming/AssignmentUsingBitmaskTest.java) + - ๐Ÿ“„ [BoardPathTest](src/test/java/com/thealgorithms/dynamicprogramming/BoardPathTest.java) + - ๐Ÿ“„ [BoundaryFillTest](src/test/java/com/thealgorithms/dynamicprogramming/BoundaryFillTest.java) + - ๐Ÿ“„ [BruteForceKnapsackTest](src/test/java/com/thealgorithms/dynamicprogramming/BruteForceKnapsackTest.java) + - ๐Ÿ“„ [CatalanNumberTest](src/test/java/com/thealgorithms/dynamicprogramming/CatalanNumberTest.java) + - ๐Ÿ“„ [ClimbStairsTest](src/test/java/com/thealgorithms/dynamicprogramming/ClimbStairsTest.java) + - ๐Ÿ“„ [CoinChangeTest](src/test/java/com/thealgorithms/dynamicprogramming/CoinChangeTest.java) + - ๐Ÿ“„ [CountFriendsPairingTest](src/test/java/com/thealgorithms/dynamicprogramming/CountFriendsPairingTest.java) + - ๐Ÿ“„ [DPTest](src/test/java/com/thealgorithms/dynamicprogramming/DPTest.java) + - ๐Ÿ“„ [DamerauLevenshteinDistanceTest](src/test/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistanceTest.java) + - ๐Ÿ“„ [EditDistanceTest](src/test/java/com/thealgorithms/dynamicprogramming/EditDistanceTest.java) + - ๐Ÿ“„ [EggDroppingTest](src/test/java/com/thealgorithms/dynamicprogramming/EggDroppingTest.java) + - ๐Ÿ“„ [FibonacciTest](src/test/java/com/thealgorithms/dynamicprogramming/FibonacciTest.java) + - ๐Ÿ“„ [KadaneAlgorithmTest](src/test/java/com/thealgorithms/dynamicprogramming/KadaneAlgorithmTest.java) + - ๐Ÿ“„ [KnapsackMemoizationTest](src/test/java/com/thealgorithms/dynamicprogramming/KnapsackMemoizationTest.java) + - ๐Ÿ“„ [KnapsackTest](src/test/java/com/thealgorithms/dynamicprogramming/KnapsackTest.java) + - ๐Ÿ“„ [KnapsackZeroOneTabulationTest](src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java) + - ๐Ÿ“„ [KnapsackZeroOneTest](src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java) + - ๐Ÿ“„ [LevenshteinDistanceTests](src/test/java/com/thealgorithms/dynamicprogramming/LevenshteinDistanceTests.java) + - ๐Ÿ“„ [LongestAlternatingSubsequenceTest](src/test/java/com/thealgorithms/dynamicprogramming/LongestAlternatingSubsequenceTest.java) + - ๐Ÿ“„ [LongestArithmeticSubsequenceTest](src/test/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequenceTest.java) + - ๐Ÿ“„ [LongestCommonSubsequenceTest](src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java) + - ๐Ÿ“„ [LongestIncreasingSubsequenceNLogNTest](src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java) + - ๐Ÿ“„ [LongestIncreasingSubsequenceTests](src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceTests.java) + - ๐Ÿ“„ [LongestPalindromicSubstringTest](src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubstringTest.java) + - ๐Ÿ“„ [LongestValidParenthesesTest](src/test/java/com/thealgorithms/dynamicprogramming/LongestValidParenthesesTest.java) + - ๐Ÿ“„ [MatrixChainMultiplicationTest](src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainMultiplicationTest.java) + - ๐Ÿ“„ [MatrixChainRecursiveTopDownMemoisationTest](src/test/java/com/thealgorithms/dynamicprogramming/MatrixChainRecursiveTopDownMemoisationTest.java) + - ๐Ÿ“„ [MaximumProductSubarrayTest](src/test/java/com/thealgorithms/dynamicprogramming/MaximumProductSubarrayTest.java) + - ๐Ÿ“„ [MaximumSumOfNonAdjacentElementsTest](src/test/java/com/thealgorithms/dynamicprogramming/MaximumSumOfNonAdjacentElementsTest.java) + - ๐Ÿ“„ [MinimumPathSumTest](src/test/java/com/thealgorithms/dynamicprogramming/MinimumPathSumTest.java) + - ๐Ÿ“„ [MinimumSumPartitionTest](src/test/java/com/thealgorithms/dynamicprogramming/MinimumSumPartitionTest.java) + - ๐Ÿ“„ [NeedlemanWunschTest](src/test/java/com/thealgorithms/dynamicprogramming/NeedlemanWunschTest.java) + - ๐Ÿ“„ [NewManShanksPrimeTest](src/test/java/com/thealgorithms/dynamicprogramming/NewManShanksPrimeTest.java) + - ๐Ÿ“„ [OptimalJobSchedulingTest](src/test/java/com/thealgorithms/dynamicprogramming/OptimalJobSchedulingTest.java) + - ๐Ÿ“„ [PalindromicPartitioningTest](src/test/java/com/thealgorithms/dynamicprogramming/PalindromicPartitioningTest.java) + - ๐Ÿ“„ [PartitionProblemTest](src/test/java/com/thealgorithms/dynamicprogramming/PartitionProblemTest.java) + - ๐Ÿ“„ [RegexMatchingTest](src/test/java/com/thealgorithms/dynamicprogramming/RegexMatchingTest.java) + - ๐Ÿ“„ [RodCuttingTest](src/test/java/com/thealgorithms/dynamicprogramming/RodCuttingTest.java) + - ๐Ÿ“„ [ShortestCommonSupersequenceLengthTest](src/test/java/com/thealgorithms/dynamicprogramming/ShortestCommonSupersequenceLengthTest.java) + - ๐Ÿ“„ [SmithWatermanTest](src/test/java/com/thealgorithms/dynamicprogramming/SmithWatermanTest.java) + - ๐Ÿ“„ [SubsetCountTest](src/test/java/com/thealgorithms/dynamicprogramming/SubsetCountTest.java) + - ๐Ÿ“„ [SubsetSumSpaceOptimizedTest](src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimizedTest.java) + - ๐Ÿ“„ [SubsetSumTest](src/test/java/com/thealgorithms/dynamicprogramming/SubsetSumTest.java) + - ๐Ÿ“„ [SumOfSubsetTest](src/test/java/com/thealgorithms/dynamicprogramming/SumOfSubsetTest.java) + - ๐Ÿ“„ [TreeMatchingTest](src/test/java/com/thealgorithms/dynamicprogramming/TreeMatchingTest.java) + - ๐Ÿ“„ [TribonacciTest](src/test/java/com/thealgorithms/dynamicprogramming/TribonacciTest.java) + - ๐Ÿ“„ [UniquePathsTests](src/test/java/com/thealgorithms/dynamicprogramming/UniquePathsTests.java) + - ๐Ÿ“„ [UniqueSubsequencesCountTest](src/test/java/com/thealgorithms/dynamicprogramming/UniqueSubsequencesCountTest.java) + - ๐Ÿ“„ [WildcardMatchingTest](src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java) + - ๐Ÿ“„ [WineProblemTest](src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java) + - ๐Ÿ“ **geometry** + - ๐Ÿ“„ [BresenhamLineTest](src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java) + - ๐Ÿ“„ [ConvexHullTest](src/test/java/com/thealgorithms/geometry/ConvexHullTest.java) + - ๐Ÿ“„ [GrahamScanTest](src/test/java/com/thealgorithms/geometry/GrahamScanTest.java) + - ๐Ÿ“„ [HaversineTest](src/test/java/com/thealgorithms/geometry/HaversineTest.java) + - ๐Ÿ“„ [MidpointCircleTest](src/test/java/com/thealgorithms/geometry/MidpointCircleTest.java) + - ๐Ÿ“„ [MidpointEllipseTest](src/test/java/com/thealgorithms/geometry/MidpointEllipseTest.java) + - ๐Ÿ“„ [PointTest](src/test/java/com/thealgorithms/geometry/PointTest.java) + - ๐Ÿ“„ [WusLineTest](src/test/java/com/thealgorithms/geometry/WusLineTest.java) + - ๐Ÿ“ **graph** + - ๐Ÿ“„ [BronKerboschTest](src/test/java/com/thealgorithms/graph/BronKerboschTest.java) + - ๐Ÿ“„ [ConstrainedShortestPathTest](src/test/java/com/thealgorithms/graph/ConstrainedShortestPathTest.java) + - ๐Ÿ“„ [DinicTest](src/test/java/com/thealgorithms/graph/DinicTest.java) + - ๐Ÿ“„ [EdmondsKarpTest](src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java) + - ๐Ÿ“„ [EdmondsTest](src/test/java/com/thealgorithms/graph/EdmondsTest.java) + - ๐Ÿ“„ [HopcroftKarpTest](src/test/java/com/thealgorithms/graph/HopcroftKarpTest.java) + - ๐Ÿ“„ [PredecessorConstrainedDfsTest](src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java) + - ๐Ÿ“„ [PushRelabelTest](src/test/java/com/thealgorithms/graph/PushRelabelTest.java) + - ๐Ÿ“„ [StronglyConnectedComponentOptimizedTest](src/test/java/com/thealgorithms/graph/StronglyConnectedComponentOptimizedTest.java) + - ๐Ÿ“„ [TravelingSalesmanTest](src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java) + - ๐Ÿ“„ [YensKShortestPathsTest](src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java) + - ๐Ÿ“„ [ZeroOneBfsTest](src/test/java/com/thealgorithms/graph/ZeroOneBfsTest.java) + - ๐Ÿ“ **greedyalgorithms** + - ๐Ÿ“„ [ActivitySelectionTest](src/test/java/com/thealgorithms/greedyalgorithms/ActivitySelectionTest.java) + - ๐Ÿ“„ [BandwidthAllocationTest](src/test/java/com/thealgorithms/greedyalgorithms/BandwidthAllocationTest.java) + - ๐Ÿ“„ [BinaryAdditionTest](src/test/java/com/thealgorithms/greedyalgorithms/BinaryAdditionTest.java) + - ๐Ÿ“„ [CoinChangeTest](src/test/java/com/thealgorithms/greedyalgorithms/CoinChangeTest.java) + - ๐Ÿ“„ [DigitSeparationTest](src/test/java/com/thealgorithms/greedyalgorithms/DigitSeparationTest.java) + - ๐Ÿ“„ [EgyptianFractionTest](src/test/java/com/thealgorithms/greedyalgorithms/EgyptianFractionTest.java) + - ๐Ÿ“„ [FractionalKnapsackTest](src/test/java/com/thealgorithms/greedyalgorithms/FractionalKnapsackTest.java) + - ๐Ÿ“„ [GaleShapleyTest](src/test/java/com/thealgorithms/greedyalgorithms/GaleShapleyTest.java) + - ๐Ÿ“„ [JobSequencingTest](src/test/java/com/thealgorithms/greedyalgorithms/JobSequencingTest.java) + - ๐Ÿ“„ [KCentersTest](src/test/java/com/thealgorithms/greedyalgorithms/KCentersTest.java) + - ๐Ÿ“„ [MergeIntervalsTest](src/test/java/com/thealgorithms/greedyalgorithms/MergeIntervalsTest.java) + - ๐Ÿ“„ [MinimizingLatenessTest](src/test/java/com/thealgorithms/greedyalgorithms/MinimizingLatenessTest.java) + - ๐Ÿ“„ [MinimumWaitingTimeTest](src/test/java/com/thealgorithms/greedyalgorithms/MinimumWaitingTimeTest.java) + - ๐Ÿ“„ [OptimalFileMergingTest](src/test/java/com/thealgorithms/greedyalgorithms/OptimalFileMergingTest.java) + - ๐Ÿ“„ [StockProfitCalculatorTest](src/test/java/com/thealgorithms/greedyalgorithms/StockProfitCalculatorTest.java) + - ๐Ÿ“ **io** + - ๐Ÿ“„ [BufferedReaderTest](src/test/java/com/thealgorithms/io/BufferedReaderTest.java) + - ๐Ÿ“ **lineclipping** + - ๐Ÿ“„ [CohenSutherlandTest](src/test/java/com/thealgorithms/lineclipping/CohenSutherlandTest.java) + - ๐Ÿ“„ [LiangBarskyTest](src/test/java/com/thealgorithms/lineclipping/LiangBarskyTest.java) + - ๐Ÿ“ **maths** + - ๐Ÿ“„ [ADTFractionTest](src/test/java/com/thealgorithms/maths/ADTFractionTest.java) + - ๐Ÿ“„ [AbsoluteMaxTest](src/test/java/com/thealgorithms/maths/AbsoluteMaxTest.java) + - ๐Ÿ“„ [AbsoluteMinTest](src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java) + - ๐Ÿ“„ [AbsoluteValueTest](src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java) + - ๐Ÿ“„ [AliquotSumTest](src/test/java/com/thealgorithms/maths/AliquotSumTest.java) + - ๐Ÿ“„ [AmicableNumberTest](src/test/java/com/thealgorithms/maths/AmicableNumberTest.java) + - ๐Ÿ“„ [AreaTest](src/test/java/com/thealgorithms/maths/AreaTest.java) + - ๐Ÿ“„ [ArmstrongTest](src/test/java/com/thealgorithms/maths/ArmstrongTest.java) + - ๐Ÿ“„ [AutoCorrelationTest](src/test/java/com/thealgorithms/maths/AutoCorrelationTest.java) + - ๐Ÿ“„ [AutomorphicNumberTest](src/test/java/com/thealgorithms/maths/AutomorphicNumberTest.java) + - ๐Ÿ“„ [AverageTest](src/test/java/com/thealgorithms/maths/AverageTest.java) + - ๐Ÿ“„ [BinaryPowTest](src/test/java/com/thealgorithms/maths/BinaryPowTest.java) + - ๐Ÿ“„ [BinomialCoefficientTest](src/test/java/com/thealgorithms/maths/BinomialCoefficientTest.java) + - ๐Ÿ“„ [CatalanNumbersTest](src/test/java/com/thealgorithms/maths/CatalanNumbersTest.java) + - ๐Ÿ“„ [CeilTest](src/test/java/com/thealgorithms/maths/CeilTest.java) + - ๐Ÿ“„ [ChineseRemainderTheoremTest](src/test/java/com/thealgorithms/maths/ChineseRemainderTheoremTest.java) + - ๐Ÿ“„ [CollatzConjectureTest](src/test/java/com/thealgorithms/maths/CollatzConjectureTest.java) + - ๐Ÿ“„ [CombinationsTest](src/test/java/com/thealgorithms/maths/CombinationsTest.java) + - ๐Ÿ“„ [ConvolutionFFTTest](src/test/java/com/thealgorithms/maths/ConvolutionFFTTest.java) + - ๐Ÿ“„ [ConvolutionTest](src/test/java/com/thealgorithms/maths/ConvolutionTest.java) + - ๐Ÿ“„ [CrossCorrelationTest](src/test/java/com/thealgorithms/maths/CrossCorrelationTest.java) + - ๐Ÿ“„ [DeterminantOfMatrixTest](src/test/java/com/thealgorithms/maths/DeterminantOfMatrixTest.java) + - ๐Ÿ“„ [DigitalRootTest](src/test/java/com/thealgorithms/maths/DigitalRootTest.java) + - ๐Ÿ“„ [DistanceFormulaTest](src/test/java/com/thealgorithms/maths/DistanceFormulaTest.java) + - ๐Ÿ“„ [DudeneyNumberTest](src/test/java/com/thealgorithms/maths/DudeneyNumberTest.java) + - ๐Ÿ“„ [EulerMethodTest](src/test/java/com/thealgorithms/maths/EulerMethodTest.java) + - ๐Ÿ“„ [EulerPseudoprimeTest](src/test/java/com/thealgorithms/maths/EulerPseudoprimeTest.java) + - ๐Ÿ“„ [EulersFunctionTest](src/test/java/com/thealgorithms/maths/EulersFunctionTest.java) + - ๐Ÿ“„ [FFTTest](src/test/java/com/thealgorithms/maths/FFTTest.java) + - ๐Ÿ“„ [FactorialRecursionTest](src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java) + - ๐Ÿ“„ [FactorialTest](src/test/java/com/thealgorithms/maths/FactorialTest.java) + - ๐Ÿ“„ [FastExponentiationTest](src/test/java/com/thealgorithms/maths/FastExponentiationTest.java) + - ๐Ÿ“„ [FastInverseSqrtTests](src/test/java/com/thealgorithms/maths/FastInverseSqrtTests.java) + - ๐Ÿ“„ [FibonacciJavaStreamsTest](src/test/java/com/thealgorithms/maths/FibonacciJavaStreamsTest.java) + - ๐Ÿ“„ [FibonacciLoopTest](src/test/java/com/thealgorithms/maths/FibonacciLoopTest.java) + - ๐Ÿ“„ [FibonacciNumberCheckTest](src/test/java/com/thealgorithms/maths/FibonacciNumberCheckTest.java) + - ๐Ÿ“„ [FibonacciNumberGoldenRationTest](src/test/java/com/thealgorithms/maths/FibonacciNumberGoldenRationTest.java) + - ๐Ÿ“„ [FindKthNumberTest](src/test/java/com/thealgorithms/maths/FindKthNumberTest.java) + - ๐Ÿ“„ [FindMaxRecursionTest](src/test/java/com/thealgorithms/maths/FindMaxRecursionTest.java) + - ๐Ÿ“„ [FindMaxTest](src/test/java/com/thealgorithms/maths/FindMaxTest.java) + - ๐Ÿ“„ [FindMinRecursionTest](src/test/java/com/thealgorithms/maths/FindMinRecursionTest.java) + - ๐Ÿ“„ [FindMinTest](src/test/java/com/thealgorithms/maths/FindMinTest.java) + - ๐Ÿ“„ [FloorTest](src/test/java/com/thealgorithms/maths/FloorTest.java) + - ๐Ÿ“„ [FrizzyNumberTest](src/test/java/com/thealgorithms/maths/FrizzyNumberTest.java) + - ๐Ÿ“„ [GCDRecursionTest](src/test/java/com/thealgorithms/maths/GCDRecursionTest.java) + - ๐Ÿ“„ [GCDTest](src/test/java/com/thealgorithms/maths/GCDTest.java) + - ๐Ÿ“„ [GaussianTest](src/test/java/com/thealgorithms/maths/GaussianTest.java) + - ๐Ÿ“„ [GenericRootTest](src/test/java/com/thealgorithms/maths/GenericRootTest.java) + - ๐Ÿ“„ [GermainPrimeAndSafePrimeTest](src/test/java/com/thealgorithms/maths/GermainPrimeAndSafePrimeTest.java) + - ๐Ÿ“„ [GoldbachConjectureTest](src/test/java/com/thealgorithms/maths/GoldbachConjectureTest.java) + - ๐Ÿ“„ [HappyNumberTest](src/test/java/com/thealgorithms/maths/HappyNumberTest.java) + - ๐Ÿ“„ [HarshadNumberTest](src/test/java/com/thealgorithms/maths/HarshadNumberTest.java) + - ๐Ÿ“„ [HeronsFormulaTest](src/test/java/com/thealgorithms/maths/HeronsFormulaTest.java) + - ๐Ÿ“„ [JosephusProblemTest](src/test/java/com/thealgorithms/maths/JosephusProblemTest.java) + - ๐Ÿ“„ [KaprekarNumbersTest](src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java) + - ๐Ÿ“„ [KaratsubaMultiplicationTest](src/test/java/com/thealgorithms/maths/KaratsubaMultiplicationTest.java) + - ๐Ÿ“„ [KeithNumberTest](src/test/java/com/thealgorithms/maths/KeithNumberTest.java) + - ๐Ÿ“„ [KrishnamurthyNumberTest](src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java) + - ๐Ÿ“„ [LeastCommonMultipleTest](src/test/java/com/thealgorithms/maths/LeastCommonMultipleTest.java) + - ๐Ÿ“„ [LeonardoNumberTest](src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java) + - ๐Ÿ“„ [LinearDiophantineEquationsSolverTest](src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java) + - ๐Ÿ“„ [LongDivisionTest](src/test/java/com/thealgorithms/maths/LongDivisionTest.java) + - ๐Ÿ“„ [LucasSeriesTest](src/test/java/com/thealgorithms/maths/LucasSeriesTest.java) + - ๐Ÿ“„ [MathBuilderTest](src/test/java/com/thealgorithms/maths/MathBuilderTest.java) + - ๐Ÿ“„ [MaxValueTest](src/test/java/com/thealgorithms/maths/MaxValueTest.java) + - ๐Ÿ“„ [MeansTest](src/test/java/com/thealgorithms/maths/MeansTest.java) + - ๐Ÿ“„ [MedianTest](src/test/java/com/thealgorithms/maths/MedianTest.java) + - ๐Ÿ“„ [MinValueTest](src/test/java/com/thealgorithms/maths/MinValueTest.java) + - ๐Ÿ“„ [ModeTest](src/test/java/com/thealgorithms/maths/ModeTest.java) + - ๐Ÿ“„ [NonRepeatingElementTest](src/test/java/com/thealgorithms/maths/NonRepeatingElementTest.java) + - ๐Ÿ“„ [NthUglyNumberTest](src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java) + - ๐Ÿ“„ [NumberOfDigitsTest](src/test/java/com/thealgorithms/maths/NumberOfDigitsTest.java) + - ๐Ÿ“„ [NumberPersistenceTest](src/test/java/com/thealgorithms/maths/NumberPersistenceTest.java) + - ๐Ÿ“„ [PalindromeNumberTest](src/test/java/com/thealgorithms/maths/PalindromeNumberTest.java) + - ๐Ÿ“„ [ParseIntegerTest](src/test/java/com/thealgorithms/maths/ParseIntegerTest.java) + - ๐Ÿ“„ [PascalTriangleTest](src/test/java/com/thealgorithms/maths/PascalTriangleTest.java) + - ๐Ÿ“„ [PerfectCubeTest](src/test/java/com/thealgorithms/maths/PerfectCubeTest.java) + - ๐Ÿ“„ [PerfectNumberTest](src/test/java/com/thealgorithms/maths/PerfectNumberTest.java) + - ๐Ÿ“„ [PerfectSquareTest](src/test/java/com/thealgorithms/maths/PerfectSquareTest.java) + - ๐Ÿ“„ [PerimeterTest](src/test/java/com/thealgorithms/maths/PerimeterTest.java) + - ๐Ÿ“„ [PiApproximationTest](src/test/java/com/thealgorithms/maths/PiApproximationTest.java) + - ๐Ÿ“„ [PollardRhoTest](src/test/java/com/thealgorithms/maths/PollardRhoTest.java) + - ๐Ÿ“„ [PowTest](src/test/java/com/thealgorithms/maths/PowTest.java) + - ๐Ÿ“„ [PowerOfTwoOrNotTest](src/test/java/com/thealgorithms/maths/PowerOfTwoOrNotTest.java) + - ๐Ÿ“„ [PowerUsingRecursionTest](src/test/java/com/thealgorithms/maths/PowerUsingRecursionTest.java) + - ๐Ÿ“„ [PronicNumberTest](src/test/java/com/thealgorithms/maths/PronicNumberTest.java) + - ๐Ÿ“„ [PythagoreanTripleTest](src/test/java/com/thealgorithms/maths/PythagoreanTripleTest.java) + - ๐Ÿ“„ [QuadraticEquationSolverTest](src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java) + - ๐Ÿ“„ [ReverseNumberTest](src/test/java/com/thealgorithms/maths/ReverseNumberTest.java) + - ๐Ÿ“„ [SecondMinMaxTest](src/test/java/com/thealgorithms/maths/SecondMinMaxTest.java) + - ๐Ÿ“„ [SieveOfAtkinTest](src/test/java/com/thealgorithms/maths/SieveOfAtkinTest.java) + - ๐Ÿ“„ [SieveOfEratosthenesTest](src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java) + - ๐Ÿ“„ [SolovayStrassenPrimalityTestTest](src/test/java/com/thealgorithms/maths/SolovayStrassenPrimalityTestTest.java) + - ๐Ÿ“„ [SquareFreeIntegerTest](src/test/java/com/thealgorithms/maths/SquareFreeIntegerTest.java) + - ๐Ÿ“„ [SquareRootWithNewtonRaphsonTestMethod](src/test/java/com/thealgorithms/maths/SquareRootWithNewtonRaphsonTestMethod.java) + - ๐Ÿ“„ [SquareRootwithBabylonianMethodTest](src/test/java/com/thealgorithms/maths/SquareRootwithBabylonianMethodTest.java) + - ๐Ÿ“„ [StandardDeviationTest](src/test/java/com/thealgorithms/maths/StandardDeviationTest.java) + - ๐Ÿ“„ [StandardScoreTest](src/test/java/com/thealgorithms/maths/StandardScoreTest.java) + - ๐Ÿ“„ [StrobogrammaticNumberTest](src/test/java/com/thealgorithms/maths/StrobogrammaticNumberTest.java) + - ๐Ÿ“„ [SumOfArithmeticSeriesTest](src/test/java/com/thealgorithms/maths/SumOfArithmeticSeriesTest.java) + - ๐Ÿ“„ [SumOfDigitsTest](src/test/java/com/thealgorithms/maths/SumOfDigitsTest.java) + - ๐Ÿ“„ [SumOfOddNumbersTest](src/test/java/com/thealgorithms/maths/SumOfOddNumbersTest.java) + - ๐Ÿ“„ [SumOfSquaresTest](src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java) + - ๐Ÿ“„ [SumWithoutArithmeticOperatorsTest](src/test/java/com/thealgorithms/maths/SumWithoutArithmeticOperatorsTest.java) + - ๐Ÿ“„ [TestArmstrong](src/test/java/com/thealgorithms/maths/TestArmstrong.java) + - ๐Ÿ“„ [TwinPrimeTest](src/test/java/com/thealgorithms/maths/TwinPrimeTest.java) + - ๐Ÿ“„ [UniformNumbersTest](src/test/java/com/thealgorithms/maths/UniformNumbersTest.java) + - ๐Ÿ“„ [VampireNumberTest](src/test/java/com/thealgorithms/maths/VampireNumberTest.java) + - ๐Ÿ“„ [VolumeTest](src/test/java/com/thealgorithms/maths/VolumeTest.java) + - ๐Ÿ“„ [ZellersCongruenceTest](src/test/java/com/thealgorithms/maths/ZellersCongruenceTest.java) + - ๐Ÿ“ **prime** + - ๐Ÿ“„ [LiouvilleLambdaFunctionTest](src/test/java/com/thealgorithms/maths/prime/LiouvilleLambdaFunctionTest.java) + - ๐Ÿ“„ [MillerRabinPrimalityCheckTest](src/test/java/com/thealgorithms/maths/prime/MillerRabinPrimalityCheckTest.java) + - ๐Ÿ“„ [MobiusFunctionTest](src/test/java/com/thealgorithms/maths/prime/MobiusFunctionTest.java) + - ๐Ÿ“„ [PrimeCheckTest](src/test/java/com/thealgorithms/maths/prime/PrimeCheckTest.java) + - ๐Ÿ“„ [PrimeFactorizationTest](src/test/java/com/thealgorithms/maths/prime/PrimeFactorizationTest.java) + - ๐Ÿ“ **matrix** + - ๐Ÿ“„ [InverseOfMatrixTest](src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java) + - ๐Ÿ“„ [MatrixMultiplicationTest](src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java) + - ๐Ÿ“„ [MatrixRankTest](src/test/java/com/thealgorithms/matrix/MatrixRankTest.java) + - ๐Ÿ“„ [MatrixTransposeTest](src/test/java/com/thealgorithms/matrix/MatrixTransposeTest.java) + - ๐Ÿ“„ [MatrixUtilTest](src/test/java/com/thealgorithms/matrix/MatrixUtilTest.java) + - ๐Ÿ“„ [MedianOfMatrixTest](src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java) + - ๐Ÿ“„ [MirrorOfMatrixTest](src/test/java/com/thealgorithms/matrix/MirrorOfMatrixTest.java) + - ๐Ÿ“„ [SolveSystemTest](src/test/java/com/thealgorithms/matrix/SolveSystemTest.java) + - ๐Ÿ“„ [TestPrintMatrixInSpiralOrder](src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java) + - ๐Ÿ“ **misc** + - ๐Ÿ“„ [ColorContrastRatioTest](src/test/java/com/thealgorithms/misc/ColorContrastRatioTest.java) + - ๐Ÿ“„ [MapReduceTest](src/test/java/com/thealgorithms/misc/MapReduceTest.java) + - ๐Ÿ“„ [MedianOfRunningArrayTest](src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java) + - ๐Ÿ“„ [PalindromePrimeTest](src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java) + - ๐Ÿ“„ [PalindromeSinglyLinkedListTest](src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java) + - ๐Ÿ“„ [RangeInSortedArrayTest](src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java) + - ๐Ÿ“„ [ShuffleArrayTest](src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java) + - ๐Ÿ“„ [SparsityTest](src/test/java/com/thealgorithms/misc/SparsityTest.java) + - ๐Ÿ“„ [ThreeSumProblemTest](src/test/java/com/thealgorithms/misc/ThreeSumProblemTest.java) + - ๐Ÿ“„ [TwoSumProblemTest](src/test/java/com/thealgorithms/misc/TwoSumProblemTest.java) + - ๐Ÿ“ **others** + - ๐Ÿ“„ [ArrayLeftRotationTest](src/test/java/com/thealgorithms/others/ArrayLeftRotationTest.java) + - ๐Ÿ“„ [ArrayRightRotationTest](src/test/java/com/thealgorithms/others/ArrayRightRotationTest.java) + - ๐Ÿ“„ [BFPRTTest](src/test/java/com/thealgorithms/others/BFPRTTest.java) + - ๐Ÿ“„ [BestFitCPUTest](src/test/java/com/thealgorithms/others/BestFitCPUTest.java) + - ๐Ÿ“„ [BoyerMooreTest](src/test/java/com/thealgorithms/others/BoyerMooreTest.java) + - ๐Ÿ“„ [CRC16Test](src/test/java/com/thealgorithms/others/CRC16Test.java) + - ๐Ÿ“„ [CRCAlgorithmTest](src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java) + - ๐Ÿ“„ [ConwayTest](src/test/java/com/thealgorithms/others/ConwayTest.java) + - ๐Ÿ“„ [CountFriendsPairingTest](src/test/java/com/thealgorithms/others/CountFriendsPairingTest.java) + - ๐Ÿ“„ [FirstFitCPUTest](src/test/java/com/thealgorithms/others/FirstFitCPUTest.java) + - ๐Ÿ“„ [FloydTriangleTest](src/test/java/com/thealgorithms/others/FloydTriangleTest.java) + - ๐Ÿ“„ [HuffmanTest](src/test/java/com/thealgorithms/others/HuffmanTest.java) + - ๐Ÿ“„ [InsertDeleteInArrayTest](src/test/java/com/thealgorithms/others/InsertDeleteInArrayTest.java) + - ๐Ÿ“„ [IterativeFloodFillTest](src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java) + - ๐Ÿ“„ [KadaneAlogrithmTest](src/test/java/com/thealgorithms/others/KadaneAlogrithmTest.java) + - ๐Ÿ“„ [LineSweepTest](src/test/java/com/thealgorithms/others/LineSweepTest.java) + - ๐Ÿ“„ [LinkListSortTest](src/test/java/com/thealgorithms/others/LinkListSortTest.java) + - ๐Ÿ“„ [LowestBasePalindromeTest](src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java) + - ๐Ÿ“„ [MaximumSumOfDistinctSubarraysWithLengthKTest](src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java) + - ๐Ÿ“„ [MiniMaxAlgorithmTest](src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java) + - ๐Ÿ“„ [MosAlgorithmTest](src/test/java/com/thealgorithms/others/MosAlgorithmTest.java) + - ๐Ÿ“„ [NewManShanksPrimeTest](src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java) + - ๐Ÿ“„ [NextFitTest](src/test/java/com/thealgorithms/others/NextFitTest.java) + - ๐Ÿ“„ [PageRankTest](src/test/java/com/thealgorithms/others/PageRankTest.java) + - ๐Ÿ“„ [PasswordGenTest](src/test/java/com/thealgorithms/others/PasswordGenTest.java) + - ๐Ÿ“„ [PerlinNoiseTest](src/test/java/com/thealgorithms/others/PerlinNoiseTest.java) + - ๐Ÿ“„ [QueueUsingTwoStacksTest](src/test/java/com/thealgorithms/others/QueueUsingTwoStacksTest.java) + - ๐Ÿ“„ [SkylineProblemTest](src/test/java/com/thealgorithms/others/SkylineProblemTest.java) + - ๐Ÿ“„ [TestPrintMatrixInSpiralOrder](src/test/java/com/thealgorithms/others/TestPrintMatrixInSpiralOrder.java) + - ๐Ÿ“„ [TwoPointersTest](src/test/java/com/thealgorithms/others/TwoPointersTest.java) + - ๐Ÿ“„ [WorstFitCPUTest](src/test/java/com/thealgorithms/others/WorstFitCPUTest.java) + - ๐Ÿ“ **cn** + - ๐Ÿ“„ [HammingDistanceTest](src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java) + - ๐Ÿ“ **physics** + - ๐Ÿ“„ [GroundToGroundProjectileMotionTest](src/test/java/com/thealgorithms/physics/GroundToGroundProjectileMotionTest.java) + - ๐Ÿ“ **puzzlesandgames** + - ๐Ÿ“„ [SudokuTest](src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java) + - ๐Ÿ“„ [TowerOfHanoiTest](src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java) + - ๐Ÿ“„ [WordBoggleTest](src/test/java/com/thealgorithms/puzzlesandgames/WordBoggleTest.java) + - ๐Ÿ“ **randomized** + - ๐Ÿ“„ [KargerMinCutTest](src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java) + - ๐Ÿ“„ [MonteCarloIntegrationTest](src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java) + - ๐Ÿ“„ [RandomizedClosestPairTest](src/test/java/com/thealgorithms/randomized/RandomizedClosestPairTest.java) + - ๐Ÿ“„ [RandomizedMatrixMultiplicationVerificationTest](src/test/java/com/thealgorithms/randomized/RandomizedMatrixMultiplicationVerificationTest.java) + - ๐Ÿ“„ [RandomizedQuickSortTest](src/test/java/com/thealgorithms/randomized/RandomizedQuickSortTest.java) + - ๐Ÿ“„ [ReservoirSamplingTest](src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java) + - ๐Ÿ“ **recursion** + - ๐Ÿ“„ [DiceThrowerTest](src/test/java/com/thealgorithms/recursion/DiceThrowerTest.java) + - ๐Ÿ“„ [FibonacciSeriesTest](src/test/java/com/thealgorithms/recursion/FibonacciSeriesTest.java) + - ๐Ÿ“„ [GenerateSubsetsTest](src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java) + - ๐Ÿ“„ [SylvesterSequenceTest](src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java) + - ๐Ÿ“ **scheduling** + - ๐Ÿ“„ [AgingSchedulingTest](src/test/java/com/thealgorithms/scheduling/AgingSchedulingTest.java) + - ๐Ÿ“„ [EDFSchedulingTest](src/test/java/com/thealgorithms/scheduling/EDFSchedulingTest.java) + - ๐Ÿ“„ [FCFSSchedulingTest](src/test/java/com/thealgorithms/scheduling/FCFSSchedulingTest.java) + - ๐Ÿ“„ [FairShareSchedulingTest](src/test/java/com/thealgorithms/scheduling/FairShareSchedulingTest.java) + - ๐Ÿ“„ [GangSchedulingTest](src/test/java/com/thealgorithms/scheduling/GangSchedulingTest.java) + - ๐Ÿ“„ [HighestResponseRatioNextSchedulingTest](src/test/java/com/thealgorithms/scheduling/HighestResponseRatioNextSchedulingTest.java) + - ๐Ÿ“„ [JobSchedulingWithDeadlineTest](src/test/java/com/thealgorithms/scheduling/JobSchedulingWithDeadlineTest.java) + - ๐Ÿ“„ [LotterySchedulingTest](src/test/java/com/thealgorithms/scheduling/LotterySchedulingTest.java) + - ๐Ÿ“„ [MLFQSchedulerTest](src/test/java/com/thealgorithms/scheduling/MLFQSchedulerTest.java) + - ๐Ÿ“„ [MultiAgentSchedulingTest](src/test/java/com/thealgorithms/scheduling/MultiAgentSchedulingTest.java) + - ๐Ÿ“„ [NonPreemptivePrioritySchedulingTest](src/test/java/com/thealgorithms/scheduling/NonPreemptivePrioritySchedulingTest.java) + - ๐Ÿ“„ [PreemptivePrioritySchedulingTest](src/test/java/com/thealgorithms/scheduling/PreemptivePrioritySchedulingTest.java) + - ๐Ÿ“„ [ProportionalFairSchedulingTest](src/test/java/com/thealgorithms/scheduling/ProportionalFairSchedulingTest.java) + - ๐Ÿ“„ [RRSchedulingTest](src/test/java/com/thealgorithms/scheduling/RRSchedulingTest.java) + - ๐Ÿ“„ [RandomSchedulingTest](src/test/java/com/thealgorithms/scheduling/RandomSchedulingTest.java) + - ๐Ÿ“„ [SJFSchedulingTest](src/test/java/com/thealgorithms/scheduling/SJFSchedulingTest.java) + - ๐Ÿ“„ [SRTFSchedulingTest](src/test/java/com/thealgorithms/scheduling/SRTFSchedulingTest.java) + - ๐Ÿ“„ [SelfAdjustingSchedulingTest](src/test/java/com/thealgorithms/scheduling/SelfAdjustingSchedulingTest.java) + - ๐Ÿ“„ [SlackTimeSchedulingTest](src/test/java/com/thealgorithms/scheduling/SlackTimeSchedulingTest.java) + - ๐Ÿ“ **diskscheduling** + - ๐Ÿ“„ [CircularLookSchedulingTest](src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularLookSchedulingTest.java) + - ๐Ÿ“„ [CircularScanSchedulingTest](src/test/java/com/thealgorithms/scheduling/diskscheduling/CircularScanSchedulingTest.java) + - ๐Ÿ“„ [LookSchedulingTest](src/test/java/com/thealgorithms/scheduling/diskscheduling/LookSchedulingTest.java) + - ๐Ÿ“„ [SSFSchedulingTest](src/test/java/com/thealgorithms/scheduling/diskscheduling/SSFSchedulingTest.java) + - ๐Ÿ“„ [ScanSchedulingTest](src/test/java/com/thealgorithms/scheduling/diskscheduling/ScanSchedulingTest.java) + - ๐Ÿ“ **searches** + - ๐Ÿ“„ [BM25InvertedIndexTest](src/test/java/com/thealgorithms/searches/BM25InvertedIndexTest.java) + - ๐Ÿ“„ [BinarySearch2dArrayTest](src/test/java/com/thealgorithms/searches/BinarySearch2dArrayTest.java) + - ๐Ÿ“„ [BinarySearchTest](src/test/java/com/thealgorithms/searches/BinarySearchTest.java) + - ๐Ÿ“„ [BoyerMooreTest](src/test/java/com/thealgorithms/searches/BoyerMooreTest.java) + - ๐Ÿ“„ [BreadthFirstSearchTest](src/test/java/com/thealgorithms/searches/BreadthFirstSearchTest.java) + - ๐Ÿ“„ [DepthFirstSearchTest](src/test/java/com/thealgorithms/searches/DepthFirstSearchTest.java) + - ๐Ÿ“„ [ExponentialSearchTest](src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java) + - ๐Ÿ“„ [FibonacciSearchTest](src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java) + - ๐Ÿ“„ [HowManyTimesRotatedTest](src/test/java/com/thealgorithms/searches/HowManyTimesRotatedTest.java) + - ๐Ÿ“„ [InterpolationSearchTest](src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java) + - ๐Ÿ“„ [IterativeBinarySearchTest](src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java) + - ๐Ÿ“„ [IterativeTernarySearchTest](src/test/java/com/thealgorithms/searches/IterativeTernarySearchTest.java) + - ๐Ÿ“„ [JumpSearchTest](src/test/java/com/thealgorithms/searches/JumpSearchTest.java) + - ๐Ÿ“„ [KMPSearchTest](src/test/java/com/thealgorithms/searches/KMPSearchTest.java) + - ๐Ÿ“„ [LinearSearchTest](src/test/java/com/thealgorithms/searches/LinearSearchTest.java) + - ๐Ÿ“„ [LinearSearchThreadTest](src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java) + - ๐Ÿ“„ [LowerBoundTest](src/test/java/com/thealgorithms/searches/LowerBoundTest.java) + - ๐Ÿ“„ [MonteCarloTreeSearchTest](src/test/java/com/thealgorithms/searches/MonteCarloTreeSearchTest.java) + - ๐Ÿ“„ [OrderAgnosticBinarySearchTest](src/test/java/com/thealgorithms/searches/OrderAgnosticBinarySearchTest.java) + - ๐Ÿ“„ [PerfectBinarySearchTest](src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java) + - ๐Ÿ“„ [QuickSelectTest](src/test/java/com/thealgorithms/searches/QuickSelectTest.java) + - ๐Ÿ“„ [RabinKarpAlgorithmTest](src/test/java/com/thealgorithms/searches/RabinKarpAlgorithmTest.java) + - ๐Ÿ“„ [RandomSearchTest](src/test/java/com/thealgorithms/searches/RandomSearchTest.java) + - ๐Ÿ“„ [RecursiveBinarySearchTest](src/test/java/com/thealgorithms/searches/RecursiveBinarySearchTest.java) + - ๐Ÿ“„ [RowColumnWiseSorted2dArrayBinarySearchTest](src/test/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearchTest.java) + - ๐Ÿ“„ [SaddlebackSearchTest](src/test/java/com/thealgorithms/searches/SaddlebackSearchTest.java) + - ๐Ÿ“„ [SearchInARowAndColWiseSortedMatrixTest](src/test/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrixTest.java) + - ๐Ÿ“„ [SentinelLinearSearchTest](src/test/java/com/thealgorithms/searches/SentinelLinearSearchTest.java) + - ๐Ÿ“„ [SortOrderAgnosticBinarySearchTest](src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java) + - ๐Ÿ“„ [SquareRootBinarySearchTest](src/test/java/com/thealgorithms/searches/SquareRootBinarySearchTest.java) + - ๐Ÿ“„ [TernarySearchTest](src/test/java/com/thealgorithms/searches/TernarySearchTest.java) + - ๐Ÿ“„ [TestSearchInARowAndColWiseSortedMatrix](src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java) + - ๐Ÿ“„ [UnionFindTest](src/test/java/com/thealgorithms/searches/UnionFindTest.java) + - ๐Ÿ“„ [UpperBoundTest](src/test/java/com/thealgorithms/searches/UpperBoundTest.java) + - ๐Ÿ“ **slidingwindow** + - ๐Ÿ“„ [LongestSubarrayWithSumLessOrEqualToKTest](src/test/java/com/thealgorithms/slidingwindow/LongestSubarrayWithSumLessOrEqualToKTest.java) + - ๐Ÿ“„ [LongestSubstringWithoutRepeatingCharactersTest](src/test/java/com/thealgorithms/slidingwindow/LongestSubstringWithoutRepeatingCharactersTest.java) + - ๐Ÿ“„ [MaxSumKSizeSubarrayTest](src/test/java/com/thealgorithms/slidingwindow/MaxSumKSizeSubarrayTest.java) + - ๐Ÿ“„ [MaximumSlidingWindowTest](src/test/java/com/thealgorithms/slidingwindow/MaximumSlidingWindowTest.java) + - ๐Ÿ“„ [MinSumKSizeSubarrayTest](src/test/java/com/thealgorithms/slidingwindow/MinSumKSizeSubarrayTest.java) + - ๐Ÿ“„ [MinimumWindowSubstringTest](src/test/java/com/thealgorithms/slidingwindow/MinimumWindowSubstringTest.java) + - ๐Ÿ“„ [ShortestCoprimeSegmentTest](src/test/java/com/thealgorithms/slidingwindow/ShortestCoprimeSegmentTest.java) + - ๐Ÿ“ **sorts** + - ๐Ÿ“„ [AdaptiveMergeSortTest](src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java) + - ๐Ÿ“„ [BeadSortTest](src/test/java/com/thealgorithms/sorts/BeadSortTest.java) + - ๐Ÿ“„ [BinaryInsertionSortTest](src/test/java/com/thealgorithms/sorts/BinaryInsertionSortTest.java) + - ๐Ÿ“„ [BitonicSortTest](src/test/java/com/thealgorithms/sorts/BitonicSortTest.java) + - ๐Ÿ“„ [BogoSortTest](src/test/java/com/thealgorithms/sorts/BogoSortTest.java) + - ๐Ÿ“„ [BubbleSortRecursiveTest](src/test/java/com/thealgorithms/sorts/BubbleSortRecursiveTest.java) + - ๐Ÿ“„ [BubbleSortTest](src/test/java/com/thealgorithms/sorts/BubbleSortTest.java) + - ๐Ÿ“„ [BucketSortTest](src/test/java/com/thealgorithms/sorts/BucketSortTest.java) + - ๐Ÿ“„ [CircleSortTest](src/test/java/com/thealgorithms/sorts/CircleSortTest.java) + - ๐Ÿ“„ [CocktailShakerSortTest](src/test/java/com/thealgorithms/sorts/CocktailShakerSortTest.java) + - ๐Ÿ“„ [CombSortTest](src/test/java/com/thealgorithms/sorts/CombSortTest.java) + - ๐Ÿ“„ [CountingSortTest](src/test/java/com/thealgorithms/sorts/CountingSortTest.java) + - ๐Ÿ“„ [CycleSortTest](src/test/java/com/thealgorithms/sorts/CycleSortTest.java) + - ๐Ÿ“„ [DarkSortTest](src/test/java/com/thealgorithms/sorts/DarkSortTest.java) + - ๐Ÿ“„ [DualPivotQuickSortTest](src/test/java/com/thealgorithms/sorts/DualPivotQuickSortTest.java) + - ๐Ÿ“„ [DutchNationalFlagSortTest](src/test/java/com/thealgorithms/sorts/DutchNationalFlagSortTest.java) + - ๐Ÿ“„ [ExchangeSortTest](src/test/java/com/thealgorithms/sorts/ExchangeSortTest.java) + - ๐Ÿ“„ [FlashSortTest](src/test/java/com/thealgorithms/sorts/FlashSortTest.java) + - ๐Ÿ“„ [GnomeSortTest](src/test/java/com/thealgorithms/sorts/GnomeSortTest.java) + - ๐Ÿ“„ [HeapSortTest](src/test/java/com/thealgorithms/sorts/HeapSortTest.java) + - ๐Ÿ“„ [InsertionSortTest](src/test/java/com/thealgorithms/sorts/InsertionSortTest.java) + - ๐Ÿ“„ [IntrospectiveSortTest](src/test/java/com/thealgorithms/sorts/IntrospectiveSortTest.java) + - ๐Ÿ“„ [MergeSortNoExtraSpaceTest](src/test/java/com/thealgorithms/sorts/MergeSortNoExtraSpaceTest.java) + - ๐Ÿ“„ [MergeSortRecursiveTest](src/test/java/com/thealgorithms/sorts/MergeSortRecursiveTest.java) + - ๐Ÿ“„ [MergeSortTest](src/test/java/com/thealgorithms/sorts/MergeSortTest.java) + - ๐Ÿ“„ [OddEvenSortTest](src/test/java/com/thealgorithms/sorts/OddEvenSortTest.java) + - ๐Ÿ“„ [PancakeSortTest](src/test/java/com/thealgorithms/sorts/PancakeSortTest.java) + - ๐Ÿ“„ [PatienceSortTest](src/test/java/com/thealgorithms/sorts/PatienceSortTest.java) + - ๐Ÿ“„ [PigeonholeSortTest](src/test/java/com/thealgorithms/sorts/PigeonholeSortTest.java) + - ๐Ÿ“„ [PriorityQueueSortTest](src/test/java/com/thealgorithms/sorts/PriorityQueueSortTest.java) + - ๐Ÿ“„ [QuickSortTest](src/test/java/com/thealgorithms/sorts/QuickSortTest.java) + - ๐Ÿ“„ [RadixSortTest](src/test/java/com/thealgorithms/sorts/RadixSortTest.java) + - ๐Ÿ“„ [SelectionSortRecursiveTest](src/test/java/com/thealgorithms/sorts/SelectionSortRecursiveTest.java) + - ๐Ÿ“„ [SelectionSortTest](src/test/java/com/thealgorithms/sorts/SelectionSortTest.java) + - ๐Ÿ“„ [ShellSortTest](src/test/java/com/thealgorithms/sorts/ShellSortTest.java) + - ๐Ÿ“„ [SlowSortTest](src/test/java/com/thealgorithms/sorts/SlowSortTest.java) + - ๐Ÿ“„ [SortUtilsRandomGeneratorTest](src/test/java/com/thealgorithms/sorts/SortUtilsRandomGeneratorTest.java) + - ๐Ÿ“„ [SortUtilsTest](src/test/java/com/thealgorithms/sorts/SortUtilsTest.java) + - ๐Ÿ“„ [SortingAlgorithmTest](src/test/java/com/thealgorithms/sorts/SortingAlgorithmTest.java) + - ๐Ÿ“„ [SpreadSortTest](src/test/java/com/thealgorithms/sorts/SpreadSortTest.java) + - ๐Ÿ“„ [StalinSortTest](src/test/java/com/thealgorithms/sorts/StalinSortTest.java) + - ๐Ÿ“„ [StoogeSortTest](src/test/java/com/thealgorithms/sorts/StoogeSortTest.java) + - ๐Ÿ“„ [StrandSortTest](src/test/java/com/thealgorithms/sorts/StrandSortTest.java) + - ๐Ÿ“„ [SwapSortTest](src/test/java/com/thealgorithms/sorts/SwapSortTest.java) + - ๐Ÿ“„ [TimSortTest](src/test/java/com/thealgorithms/sorts/TimSortTest.java) + - ๐Ÿ“„ [TopologicalSortTest](src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java) + - ๐Ÿ“„ [TreeSortTest](src/test/java/com/thealgorithms/sorts/TreeSortTest.java) + - ๐Ÿ“„ [WaveSortTest](src/test/java/com/thealgorithms/sorts/WaveSortTest.java) + - ๐Ÿ“„ [WiggleSortTest](src/test/java/com/thealgorithms/sorts/WiggleSortTest.java) + - ๐Ÿ“ **stacks** + - ๐Ÿ“„ [BalancedBracketsTest](src/test/java/com/thealgorithms/stacks/BalancedBracketsTest.java) + - ๐Ÿ“„ [CelebrityFinderTest](src/test/java/com/thealgorithms/stacks/CelebrityFinderTest.java) + - ๐Ÿ“„ [DecimalToAnyUsingStackTest](src/test/java/com/thealgorithms/stacks/DecimalToAnyUsingStackTest.java) + - ๐Ÿ“„ [DuplicateBracketsTest](src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java) + - ๐Ÿ“„ [GreatestElementConstantTimeTest](src/test/java/com/thealgorithms/stacks/GreatestElementConstantTimeTest.java) + - ๐Ÿ“„ [InfixToPostfixTest](src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java) + - ๐Ÿ“„ [InfixToPrefixTest](src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java) + - ๐Ÿ“„ [LargestRectangleTest](src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java) + - ๐Ÿ“„ [MinStackUsingSingleStackTest](src/test/java/com/thealgorithms/stacks/MinStackUsingSingleStackTest.java) + - ๐Ÿ“„ [MinStackUsingTwoStacksTest](src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java) + - ๐Ÿ“„ [NextGreaterElementTest](src/test/java/com/thealgorithms/stacks/NextGreaterElementTest.java) + - ๐Ÿ“„ [NextSmallerElementTest](src/test/java/com/thealgorithms/stacks/NextSmallerElementTest.java) + - ๐Ÿ“„ [PalindromeWithStackTest](src/test/java/com/thealgorithms/stacks/PalindromeWithStackTest.java) + - ๐Ÿ“„ [PostfixEvaluatorTest](src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java) + - ๐Ÿ“„ [PostfixToInfixTest](src/test/java/com/thealgorithms/stacks/PostfixToInfixTest.java) + - ๐Ÿ“„ [PrefixEvaluatorTest](src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java) + - ๐Ÿ“„ [PrefixToInfixTest](src/test/java/com/thealgorithms/stacks/PrefixToInfixTest.java) + - ๐Ÿ“„ [SmallestElementConstantTimeTest](src/test/java/com/thealgorithms/stacks/SmallestElementConstantTimeTest.java) + - ๐Ÿ“„ [SortStackTest](src/test/java/com/thealgorithms/stacks/SortStackTest.java) + - ๐Ÿ“„ [StackPostfixNotationTest](src/test/java/com/thealgorithms/stacks/StackPostfixNotationTest.java) + - ๐Ÿ“„ [StackUsingTwoQueuesTest](src/test/java/com/thealgorithms/stacks/StackUsingTwoQueuesTest.java) + - ๐Ÿ“ **strings** + - ๐Ÿ“„ [AhoCorasickTest](src/test/java/com/thealgorithms/strings/AhoCorasickTest.java) + - ๐Ÿ“„ [AlphabeticalTest](src/test/java/com/thealgorithms/strings/AlphabeticalTest.java) + - ๐Ÿ“„ [AlternativeStringArrangeTest](src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java) + - ๐Ÿ“„ [AnagramsTest](src/test/java/com/thealgorithms/strings/AnagramsTest.java) + - ๐Ÿ“„ [CharactersSameTest](src/test/java/com/thealgorithms/strings/CharactersSameTest.java) + - ๐Ÿ“„ [CheckVowelsTest](src/test/java/com/thealgorithms/strings/CheckVowelsTest.java) + - ๐Ÿ“„ [CountCharTest](src/test/java/com/thealgorithms/strings/CountCharTest.java) + - ๐Ÿ“„ [CountWordsTest](src/test/java/com/thealgorithms/strings/CountWordsTest.java) + - ๐Ÿ“„ [HammingDistanceTest](src/test/java/com/thealgorithms/strings/HammingDistanceTest.java) + - ๐Ÿ“„ [HorspoolSearchTest](src/test/java/com/thealgorithms/strings/HorspoolSearchTest.java) + - ๐Ÿ“„ [IsogramTest](src/test/java/com/thealgorithms/strings/IsogramTest.java) + - ๐Ÿ“„ [IsomorphicTest](src/test/java/com/thealgorithms/strings/IsomorphicTest.java) + - ๐Ÿ“„ [LetterCombinationsOfPhoneNumberTest](src/test/java/com/thealgorithms/strings/LetterCombinationsOfPhoneNumberTest.java) + - ๐Ÿ“„ [LongestCommonPrefixTest](src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java) + - ๐Ÿ“„ [LongestNonRepetitiveSubstringTest](src/test/java/com/thealgorithms/strings/LongestNonRepetitiveSubstringTest.java) + - ๐Ÿ“„ [LongestPalindromicSubstringTest](src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java) + - ๐Ÿ“„ [LowerTest](src/test/java/com/thealgorithms/strings/LowerTest.java) + - ๐Ÿ“„ [ManacherTest](src/test/java/com/thealgorithms/strings/ManacherTest.java) + - ๐Ÿ“„ [MyAtoiTest](src/test/java/com/thealgorithms/strings/MyAtoiTest.java) + - ๐Ÿ“„ [PalindromeTest](src/test/java/com/thealgorithms/strings/PalindromeTest.java) + - ๐Ÿ“„ [PangramTest](src/test/java/com/thealgorithms/strings/PangramTest.java) + - ๐Ÿ“„ [PermuteStringTest](src/test/java/com/thealgorithms/strings/PermuteStringTest.java) + - ๐Ÿ“„ [RemoveDuplicateFromStringTest](src/test/java/com/thealgorithms/strings/RemoveDuplicateFromStringTest.java) + - ๐Ÿ“„ [ReturnSubsequenceTest](src/test/java/com/thealgorithms/strings/ReturnSubsequenceTest.java) + - ๐Ÿ“„ [ReverseStringTest](src/test/java/com/thealgorithms/strings/ReverseStringTest.java) + - ๐Ÿ“„ [ReverseWordsInStringTest](src/test/java/com/thealgorithms/strings/ReverseWordsInStringTest.java) + - ๐Ÿ“„ [RotationTest](src/test/java/com/thealgorithms/strings/RotationTest.java) + - ๐Ÿ“„ [StringCompressionTest](src/test/java/com/thealgorithms/strings/StringCompressionTest.java) + - ๐Ÿ“„ [StringMatchFiniteAutomataTest](src/test/java/com/thealgorithms/strings/StringMatchFiniteAutomataTest.java) + - ๐Ÿ“„ [SuffixArrayTest](src/test/java/com/thealgorithms/strings/SuffixArrayTest.java) + - ๐Ÿ“„ [UpperTest](src/test/java/com/thealgorithms/strings/UpperTest.java) + - ๐Ÿ“„ [ValidParenthesesTest](src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java) + - ๐Ÿ“„ [WordLadderTest](src/test/java/com/thealgorithms/strings/WordLadderTest.java) + - ๐Ÿ“ **zigZagPattern** + - ๐Ÿ“„ [ZigZagPatternTest](src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java) + - ๐Ÿ“ **tree** + - ๐Ÿ“„ [HeavyLightDecompositionTest](src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java) diff --git a/pmd-custom_ruleset.xml b/pmd-custom_ruleset.xml new file mode 100644 index 000000000000..19bb1c7968f0 --- /dev/null +++ b/pmd-custom_ruleset.xml @@ -0,0 +1,28 @@ + + + + Custom PMD checks for TheAlgorithms/Java + + + + Avoid using the main method. + + 3 + + + + + + + + + diff --git a/pmd-exclude.properties b/pmd-exclude.properties index 5bf31455e190..a3c95b12fa4b 100644 --- a/pmd-exclude.properties +++ b/pmd-exclude.properties @@ -1,89 +1,122 @@ -com.thealgorithms.bitmanipulation.SingleBitOperations=UselessParentheses +com.thealgorithms.ciphers.AES=UselessMainMethod +com.thealgorithms.ciphers.AESEncryption=UselessMainMethod com.thealgorithms.ciphers.AffineCipher=UselessParentheses -com.thealgorithms.ciphers.ColumnarTranspositionCipher=UnnecessaryFullyQualifiedName com.thealgorithms.ciphers.DES=UselessParentheses -com.thealgorithms.ciphers.HillCipher=UselessParentheses +com.thealgorithms.ciphers.ProductCipher=UselessMainMethod com.thealgorithms.ciphers.RSA=UselessParentheses -com.thealgorithms.conversions.AnyBaseToAnyBase=UselessParentheses +com.thealgorithms.conversions.AnyBaseToAnyBase=UselessMainMethod,UselessParentheses com.thealgorithms.conversions.AnytoAny=UselessParentheses -com.thealgorithms.conversions.HexToOct=UselessParentheses -com.thealgorithms.conversions.IntegerToRoman=UnnecessaryFullyQualifiedName -com.thealgorithms.datastructures.crdt.LWWElementSet=UselessParentheses +com.thealgorithms.conversions.RgbHsvConversion=UselessMainMethod com.thealgorithms.datastructures.crdt.Pair=UnusedPrivateField com.thealgorithms.datastructures.graphs.AStar=UselessParentheses com.thealgorithms.datastructures.graphs.AdjacencyMatrixGraph=CollapsibleIfStatements,UnnecessaryFullyQualifiedName,UselessParentheses +com.thealgorithms.datastructures.graphs.BellmanFord=UselessMainMethod com.thealgorithms.datastructures.graphs.BipartiteGraphDFS=CollapsibleIfStatements -com.thealgorithms.datastructures.graphs.Kruskal=UselessParentheses +com.thealgorithms.datastructures.graphs.ConnectedComponent=UselessMainMethod +com.thealgorithms.datastructures.graphs.Cycles=UselessMainMethod +com.thealgorithms.datastructures.graphs.Graphs=UselessMainMethod +com.thealgorithms.datastructures.graphs.KahnsAlgorithm=UselessMainMethod +com.thealgorithms.datastructures.graphs.MatrixGraphs=UselessMainMethod com.thealgorithms.datastructures.hashmap.hashing.HashMapCuckooHashing=UselessParentheses +com.thealgorithms.datastructures.hashmap.hashing.MainCuckooHashing=UselessMainMethod com.thealgorithms.datastructures.heaps.FibonacciHeap=UselessParentheses -com.thealgorithms.datastructures.heaps.HeapElement=UselessParentheses com.thealgorithms.datastructures.heaps.HeapNode=UselessParentheses com.thealgorithms.datastructures.lists.DoublyLinkedList=UselessParentheses +com.thealgorithms.datastructures.lists.Link=UselessMainMethod +com.thealgorithms.datastructures.lists.RandomNode=UselessMainMethod com.thealgorithms.datastructures.lists.SearchSinglyLinkedListRecursion=UselessParentheses -com.thealgorithms.datastructures.lists.SinglyLinkedList=UnusedLocalVariable +com.thealgorithms.datastructures.lists.SinglyLinkedList=UnusedLocalVariable,UselessMainMethod +com.thealgorithms.datastructures.queues.Deque=UselessMainMethod com.thealgorithms.datastructures.queues.PriorityQueue=UselessParentheses -com.thealgorithms.datastructures.stacks.NodeStack=UnnecessaryFullyQualifiedName,UnusedFormalParameter -com.thealgorithms.datastructures.stacks.StackArray=UselessParentheses +com.thealgorithms.datastructures.trees.BSTRecursiveGeneric=UselessMainMethod com.thealgorithms.datastructures.trees.CheckBinaryTreeIsValidBST=UselessParentheses +com.thealgorithms.datastructures.trees.LCA=UselessMainMethod +com.thealgorithms.datastructures.trees.NearestRightKey=UselessMainMethod +com.thealgorithms.datastructures.trees.PrintTopViewofTree=UselessMainMethod com.thealgorithms.datastructures.trees.SegmentTree=UselessParentheses com.thealgorithms.devutils.nodes.LargeTreeNode=UselessParentheses com.thealgorithms.devutils.nodes.SimpleNode=UselessParentheses com.thealgorithms.devutils.nodes.SimpleTreeNode=UselessParentheses com.thealgorithms.devutils.nodes.TreeNode=UselessParentheses -com.thealgorithms.divideandconquer.ClosestPair=UnnecessaryFullyQualifiedName,UselessParentheses +com.thealgorithms.divideandconquer.ClosestPair=UnnecessaryFullyQualifiedName,UselessMainMethod,UselessParentheses com.thealgorithms.divideandconquer.Point=UselessParentheses -com.thealgorithms.dynamicprogramming.MatrixChainMultiplication=UselessParentheses -com.thealgorithms.dynamicprogramming.ShortestSuperSequence=UselessParentheses -com.thealgorithms.dynamicprogramming.UniquePaths=UnnecessarySemicolon +com.thealgorithms.dynamicprogramming.CatalanNumber=UselessMainMethod +com.thealgorithms.dynamicprogramming.EggDropping=UselessMainMethod +com.thealgorithms.dynamicprogramming.LongestPalindromicSubsequence=UselessMainMethod com.thealgorithms.dynamicprogramming.WineProblem=UselessParentheses com.thealgorithms.maths.BinomialCoefficient=UselessParentheses com.thealgorithms.maths.Complex=UselessParentheses com.thealgorithms.maths.DistanceFormulaTest=UnnecessaryFullyQualifiedName +com.thealgorithms.maths.EulerMethod=UselessMainMethod +com.thealgorithms.maths.GCDRecursion=UselessMainMethod com.thealgorithms.maths.Gaussian=UselessParentheses com.thealgorithms.maths.GcdSolutionWrapper=UselessParentheses com.thealgorithms.maths.HeronsFormula=UselessParentheses +com.thealgorithms.maths.JugglerSequence=UselessMainMethod com.thealgorithms.maths.KaprekarNumbers=UselessParentheses -com.thealgorithms.maths.KeithNumber=UselessParentheses +com.thealgorithms.maths.KeithNumber=UselessMainMethod,UselessParentheses com.thealgorithms.maths.LeonardoNumber=UselessParentheses -com.thealgorithms.maths.LinearDiophantineEquationsSolver=UselessParentheses -com.thealgorithms.maths.MatrixUtil=UselessParentheses +com.thealgorithms.maths.LinearDiophantineEquationsSolver=UselessMainMethod,UselessParentheses +com.thealgorithms.maths.MagicSquare=UselessMainMethod +com.thealgorithms.maths.PiNilakantha=UselessMainMethod +com.thealgorithms.maths.Prime.PrimeCheck=UselessMainMethod +com.thealgorithms.maths.PythagoreanTriple=UselessMainMethod com.thealgorithms.maths.RomanNumeralUtil=UselessParentheses com.thealgorithms.maths.SecondMinMax=UselessParentheses com.thealgorithms.maths.SecondMinMaxTest=UnnecessaryFullyQualifiedName +com.thealgorithms.maths.SimpsonIntegration=UselessMainMethod com.thealgorithms.maths.StandardDeviation=UselessParentheses com.thealgorithms.maths.SumOfArithmeticSeries=UselessParentheses -com.thealgorithms.maths.TrinomialTriangle=UselessParentheses -com.thealgorithms.maths.VampireNumber=CollapsibleIfStatements +com.thealgorithms.maths.TrinomialTriangle=UselessMainMethod,UselessParentheses +com.thealgorithms.maths.VectorCrossProduct=UselessMainMethod com.thealgorithms.maths.Volume=UselessParentheses +com.thealgorithms.matrix.RotateMatrixBy90Degrees=UselessMainMethod com.thealgorithms.misc.Sparsity=UselessParentheses -com.thealgorithms.misc.ThreeSumProblem=UselessParentheses -com.thealgorithms.misc.WordBoggle=UselessParentheses -com.thealgorithms.others.CRC16=UselessParentheses -com.thealgorithms.others.Damm=UnnecessaryFullyQualifiedName -com.thealgorithms.others.Luhn=UnnecessaryFullyQualifiedName -com.thealgorithms.others.Mandelbrot=UselessParentheses -com.thealgorithms.others.MaximumSumOfDistinctSubarraysWithLengthK=CollapsibleIfStatements -com.thealgorithms.others.MiniMaxAlgorithm=UselessParentheses -com.thealgorithms.others.PageRank=UselessParentheses -com.thealgorithms.others.PerlinNoise=UselessParentheses +com.thealgorithms.others.BankersAlgorithm=UselessMainMethod +com.thealgorithms.others.BrianKernighanAlgorithm=UselessMainMethod +com.thealgorithms.others.CRC16=UselessMainMethod,UselessParentheses +com.thealgorithms.others.CRC32=UselessMainMethod +com.thealgorithms.others.Damm=UnnecessaryFullyQualifiedName,UselessMainMethod +com.thealgorithms.others.Dijkstra=UselessMainMethod +com.thealgorithms.others.GaussLegendre=UselessMainMethod +com.thealgorithms.others.HappyNumbersSeq=UselessMainMethod +com.thealgorithms.others.Huffman=UselessMainMethod +com.thealgorithms.others.InsertDeleteInArray=UselessMainMethod +com.thealgorithms.others.KochSnowflake=UselessMainMethod +com.thealgorithms.others.Krishnamurthy=UselessMainMethod +com.thealgorithms.others.LinearCongruentialGenerator=UselessMainMethod +com.thealgorithms.others.Luhn=UnnecessaryFullyQualifiedName,UselessMainMethod +com.thealgorithms.others.Mandelbrot=UselessMainMethod,UselessParentheses +com.thealgorithms.others.MiniMaxAlgorithm=UselessMainMethod,UselessParentheses +com.thealgorithms.others.MosAlgorithm=UselessMainMethod +com.thealgorithms.others.PageRank=UselessMainMethod,UselessParentheses +com.thealgorithms.others.PerlinNoise=UselessMainMethod,UselessParentheses com.thealgorithms.others.QueueUsingTwoStacks=UselessParentheses -com.thealgorithms.others.QueueWithStack=UselessParentheses -com.thealgorithms.others.Trieac=UselessParentheses -com.thealgorithms.others.Verhoeff=UnnecessaryFullyQualifiedName +com.thealgorithms.others.Trieac=UselessMainMethod,UselessParentheses +com.thealgorithms.others.Verhoeff=UnnecessaryFullyQualifiedName,UselessMainMethod +com.thealgorithms.puzzlesandgames.Sudoku=UselessMainMethod +com.thealgorithms.recursion.DiceThrower=UselessMainMethod +com.thealgorithms.searches.HowManyTimesRotated=UselessMainMethod com.thealgorithms.searches.InterpolationSearch=UselessParentheses com.thealgorithms.searches.KMPSearch=UselessParentheses -com.thealgorithms.searches.LinearSearchThread=EmptyCatchBlock com.thealgorithms.searches.RabinKarpAlgorithm=UselessParentheses +com.thealgorithms.searches.RecursiveBinarySearch=UselessMainMethod +com.thealgorithms.sorts.BogoSort=UselessMainMethod com.thealgorithms.sorts.CircleSort=EmptyControlStatement -com.thealgorithms.sorts.CombSort=UselessParentheses com.thealgorithms.sorts.DutchNationalFlagSort=UselessParentheses -com.thealgorithms.sorts.LinkListSort=EmptyControlStatement,UnusedLocalVariable com.thealgorithms.sorts.MergeSortNoExtraSpace=UselessParentheses -com.thealgorithms.sorts.PigeonholeSort=UselessParentheses com.thealgorithms.sorts.RadixSort=UselessParentheses +com.thealgorithms.sorts.TreeSort=UselessMainMethod com.thealgorithms.sorts.WiggleSort=UselessParentheses +com.thealgorithms.stacks.LargestRectangle=UselessMainMethod +com.thealgorithms.stacks.MaximumMinimumWindow=UselessMainMethod com.thealgorithms.stacks.PostfixToInfix=UselessParentheses +com.thealgorithms.strings.Alphabetical=UselessMainMethod com.thealgorithms.strings.HorspoolSearch=UnnecessaryFullyQualifiedName,UselessParentheses -com.thealgorithms.strings.MyAtoi=UselessParentheses +com.thealgorithms.strings.KMP=UselessMainMethod +com.thealgorithms.strings.Lower=UselessMainMethod com.thealgorithms.strings.Palindrome=UselessParentheses -com.thealgorithms.strings.Solution=CollapsibleIfStatements +com.thealgorithms.strings.Pangram=UselessMainMethod +com.thealgorithms.strings.RabinKarp=UselessMainMethod +com.thealgorithms.strings.Rotation=UselessMainMethod +com.thealgorithms.strings.Upper=UselessMainMethod diff --git a/pom.xml b/pom.xml index 7900c6f2d956..116c7bb27e40 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ UTF-8 21 21 - 3.27.3 + 3.27.6 @@ -20,7 +20,7 @@ org.junit junit-bom - 5.11.4 + 6.0.0 pom import @@ -31,7 +31,6 @@ org.junit.jupiter junit-jupiter - 5.11.4 test @@ -43,26 +42,18 @@ org.mockito mockito-core - 5.15.2 - test - - - - - org.junit.jupiter - junit-jupiter-api - 5.11.4 + 5.20.0 test org.apache.commons commons-lang3 - 3.17.0 + 3.19.0 org.apache.commons commons-collections4 - 4.5.0-M3 + 4.5.0 @@ -70,7 +61,7 @@ maven-surefire-plugin - 3.5.2 + 3.5.4 @@ -78,16 +69,12 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.14.1 - 21 - 21 + 21 -Xlint:all -Xlint:-auxiliaryclass - -Xlint:-rawtypes - -Xlint:-unchecked - -Xlint:-lossy-conversions -Werror @@ -95,7 +82,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.14 @@ -125,14 +112,14 @@ com.puppycrawl.tools checkstyle - 10.21.2 + 12.1.0 com.github.spotbugs spotbugs-maven-plugin - 4.8.6.6 + 4.9.8.1 spotbugs-exclude.xml true @@ -140,12 +127,12 @@ com.mebigfatguy.fb-contrib fb-contrib - 7.6.9 + 7.6.15 com.h3xstream.findsecbugs findsecbugs-plugin - 1.13.0 + 1.14.0 @@ -153,8 +140,13 @@ org.apache.maven.plugins maven-pmd-plugin - 3.26.0 + 3.28.0 + + /rulesets/java/maven-pmd-plugin-default.xml + /category/java/security.xml + file://${basedir}/pmd-custom_ruleset.xml + true true false diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index d3eff458ea45..d2e094556d61 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -81,7 +81,10 @@ - + + + + @@ -193,10 +196,10 @@ - + - + diff --git a/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java b/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java index 6f93b704ffb2..c35a36d97a57 100644 --- a/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java +++ b/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java @@ -9,6 +9,7 @@ * * @author Siddhant Swarup Mallick */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class AllPathsFromSourceToTarget { // No. of vertices in graph diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java b/src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java index 40b3097b1276..634c9e7b3b44 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/BitSwap.java @@ -1,13 +1,31 @@ package com.thealgorithms.bitmanipulation; +/** + * Utility class for performing bit-swapping operations on integers. + * This class cannot be instantiated. + */ public final class BitSwap { private BitSwap() { } - /* - * @brief Swaps the bits at the position posA and posB from data + + /** + * Swaps two bits at specified positions in an integer. + * + * @param data The input integer whose bits need to be swapped + * @param posA The position of the first bit (0-based, from least significant) + * @param posB The position of the second bit (0-based, from least significant) + * @return The modified value with swapped bits + * @throws IllegalArgumentException if either position is negative or โ‰ฅ 32 */ + public static int bitSwap(int data, final int posA, final int posB) { - if (SingleBitOperations.getBit(data, posA) != SingleBitOperations.getBit(data, posB)) { + if (posA < 0 || posA >= Integer.SIZE || posB < 0 || posB >= Integer.SIZE) { + throw new IllegalArgumentException("Bit positions must be between 0 and 31"); + } + + boolean bitA = ((data >> posA) & 1) != 0; + boolean bitB = ((data >> posB) & 1) != 0; + if (bitA != bitB) { data ^= (1 << posA) ^ (1 << posB); } return data; diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BitwiseGCD.java b/src/main/java/com/thealgorithms/bitmanipulation/BitwiseGCD.java new file mode 100644 index 000000000000..516563459256 --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/BitwiseGCD.java @@ -0,0 +1,147 @@ +package com.thealgorithms.bitmanipulation; + +import java.math.BigInteger; + +/** + * Bitwise GCD implementation with full-range support utilities. + * + *

This class provides a fast binary (Stein's) GCD implementation for {@code long} + * inputs and a BigInteger-backed API for full 2's-complement range support (including + * {@code Long.MIN_VALUE}). The {@code long} implementation is efficient and avoids + * division/modulo operations. For edge-cases that overflow signed-64-bit ranges + * (e.g., gcd(Long.MIN_VALUE, 0) = 2^63), use the BigInteger API {@code gcdBig}. + * + *

Behaviour: + *

    + *
  • {@code gcd(long,long)} : returns non-negative {@code long} gcd for inputs whose + * absolute values fit in signed {@code long} (i.e., not causing an unsigned 2^63 result). + * If the true gcd does not fit in a signed {@code long} (for example gcd(Long.MIN_VALUE,0) = 2^63) + * this method will delegate to BigInteger and throw {@link ArithmeticException} if the + * BigInteger result does not fit into a signed {@code long}.
  • + *
  • {@code gcdBig(BigInteger, BigInteger)} : returns the exact gcd as a {@link BigInteger} + * and works for the full signed-64-bit range and beyond.
  • + *
+ */ +public final class BitwiseGCD { + + private BitwiseGCD() { + } + + /** + * Computes GCD of two long values using Stein's algorithm (binary GCD). + *

Handles negative inputs. If either input is {@code Long.MIN_VALUE} the + * method delegates to the BigInteger implementation and will throw {@link ArithmeticException} + * if the result cannot be represented as a signed {@code long}. + * + * @param a first value (may be negative) + * @param b second value (may be negative) + * @return non-negative gcd as a {@code long} + * @throws ArithmeticException when the exact gcd does not fit into a signed {@code long} + */ + public static long gcd(long a, long b) { + // Trivial cases + if (a == 0L) { + return absOrThrowIfOverflow(b); + } + if (b == 0L) { + return absOrThrowIfOverflow(a); + } + + // If either is Long.MIN_VALUE, absolute value doesn't fit into signed long. + if (a == Long.MIN_VALUE || b == Long.MIN_VALUE) { + // Delegate to BigInteger and try to return a long if it fits + BigInteger g = gcdBig(BigInteger.valueOf(a), BigInteger.valueOf(b)); + return g.longValueExact(); + } + + // Work with non-negative long values now (safe because we excluded Long.MIN_VALUE) + a = (a < 0) ? -a : a; + b = (b < 0) ? -b : b; + + // Count common factors of 2 + int commonTwos = Long.numberOfTrailingZeros(a | b); + + // Remove all factors of 2 from a + a >>= Long.numberOfTrailingZeros(a); + + while (b != 0L) { + // Remove all factors of 2 from b + b >>= Long.numberOfTrailingZeros(b); + + // Now both a and b are odd. Ensure a <= b + if (a > b) { + long tmp = a; + a = b; + b = tmp; + } + + // b >= a; subtract a from b (result is even) + b = b - a; + } + + // Restore common powers of two + return a << commonTwos; + } + + /** + * Helper to return absolute value of x unless x == Long.MIN_VALUE, in which + * case we delegate to BigInteger and throw to indicate overflow. + */ + private static long absOrThrowIfOverflow(long x) { + if (x == Long.MIN_VALUE) { + // |Long.MIN_VALUE| = 2^63 which does not fit into signed long + throw new ArithmeticException("Absolute value of Long.MIN_VALUE does not fit into signed long. Use gcdBig() for full-range support."); + } + return (x < 0) ? -x : x; + } + + /** + * Computes GCD for an array of {@code long} values. Returns 0 for empty/null arrays. + * If any intermediate gcd cannot be represented in signed long (rare), an ArithmeticException + * will be thrown. + */ + public static long gcd(long... values) { + + if (values == null || values.length == 0) { + return 0L; + } + long result = values[0]; + for (int i = 1; i < values.length; i++) { + result = gcd(result, values[i]); + if (result == 1L) { + return 1L; // early exit + } + } + return result; + } + + /** + * BigInteger-backed gcd that works for the full integer range (and beyond). + * This is the recommended method when inputs may be Long.MIN_VALUE or when you + * need an exact result even if it is greater than Long.MAX_VALUE. + * @param a first value (may be negative) + * @param b second value (may be negative) + * @return non-negative gcd as a {@link BigInteger} + */ + public static BigInteger gcdBig(BigInteger a, BigInteger b) { + + if (a == null || b == null) { + throw new NullPointerException("Arguments must not be null"); + } + return a.abs().gcd(b.abs()); + } + + /** + * Convenience overload that accepts signed-64 inputs and returns BigInteger gcd. + */ + public static BigInteger gcdBig(long a, long b) { + return gcdBig(BigInteger.valueOf(a), BigInteger.valueOf(b)); + } + + /** + * int overload for convenience. + */ + public static int gcd(int a, int b) { + return (int) gcd((long) a, (long) b); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountBitsFlip.java b/src/main/java/com/thealgorithms/bitmanipulation/CountBitsFlip.java new file mode 100644 index 000000000000..8d2c757e5e0a --- /dev/null +++ b/src/main/java/com/thealgorithms/bitmanipulation/CountBitsFlip.java @@ -0,0 +1,63 @@ +package com.thealgorithms.bitmanipulation; + +/** + * Implementation to count number of bits to be flipped to convert A to B + * + * Problem: Given two numbers A and B, count the number of bits needed to be + * flipped to convert A to B. + * + * Example: + * A = 10 (01010 in binary) + * B = 20 (10100 in binary) + * XOR = 30 (11110 in binary) - positions where bits differ + * Answer: 4 bits need to be flipped + * + * Time Complexity: O(log n) - where n is the number of set bits + * Space Complexity: O(1) + * + *@author [Yash Rajput](https://github.com/the-yash-rajput) + */ +public final class CountBitsFlip { + + private CountBitsFlip() { + throw new AssertionError("No instances."); + } + + /** + * Counts the number of bits that need to be flipped to convert a to b + * + * Algorithm: + * 1. XOR a and b to get positions where bits differ + * 2. Count the number of set bits in the XOR result + * 3. Use Brian Kernighan's algorithm: n & (n-1) removes rightmost set bit + * + * @param a the source number + * @param b the target number + * @return the number of bits to flip to convert A to B + */ + public static long countBitsFlip(long a, long b) { + int count = 0; + + // XOR gives us positions where bits differ + long xorResult = a ^ b; + + // Count set bits using Brian Kernighan's algorithm + while (xorResult != 0) { + xorResult = xorResult & (xorResult - 1); // Remove rightmost set bit + count++; + } + + return count; + } + + /** + * Alternative implementation using Long.bitCount(). + * + * @param a the source number + * @param b the target number + * @return the number of bits to flip to convert a to b + */ + public static long countBitsFlipAlternative(long a, long b) { + return Long.bitCount(a ^ b); + } +} diff --git a/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java b/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java index c5c068422113..aae3a996e49d 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/OnesComplement.java @@ -12,15 +12,24 @@ public final class OnesComplement { private OnesComplement() { } - // Function to get the 1's complement of a binary number + /** + * Returns the 1's complement of a binary string. + * + * @param binary A string representing a binary number (e.g., "1010"). + * @return A string representing the 1's complement. + * @throws IllegalArgumentException if the input is null or contains characters other than '0' or '1'. + */ public static String onesComplement(String binary) { - StringBuilder complement = new StringBuilder(); - // Invert each bit to get the 1's complement - for (int i = 0; i < binary.length(); i++) { - if (binary.charAt(i) == '0') { - complement.append('1'); - } else { - complement.append('0'); + if (binary == null || binary.isEmpty()) { + throw new IllegalArgumentException("Input must be a non-empty binary string."); + } + + StringBuilder complement = new StringBuilder(binary.length()); + for (char bit : binary.toCharArray()) { + switch (bit) { + case '0' -> complement.append('1'); + case '1' -> complement.append('0'); + default -> throw new IllegalArgumentException("Input must contain only '0' and '1'. Found: " + bit); } } return complement.toString(); diff --git a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java index b82681372f2b..979f18532eaa 100644 --- a/src/main/java/com/thealgorithms/ciphers/AffineCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/AffineCipher.java @@ -68,6 +68,7 @@ static String decryptCipher(String cipher) { // then i will be the multiplicative inverse of a if (flag == 1) { aInv = i; + break; } } for (int i = 0; i < cipher.length(); i++) { @@ -83,16 +84,4 @@ static String decryptCipher(String cipher) { return msg.toString(); } - - // Driver code - public static void main(String[] args) { - String msg = "AFFINE CIPHER"; - - // Calling encryption function - String cipherText = encryptMessage(msg.toCharArray()); - System.out.println("Encrypted Message is : " + cipherText); - - // Calling Decryption function - System.out.println("Decrypted Message is: " + decryptCipher(cipherText)); - } } diff --git a/src/main/java/com/thealgorithms/ciphers/Caesar.java b/src/main/java/com/thealgorithms/ciphers/Caesar.java index 61c444cf6463..23535bc2b5d2 100644 --- a/src/main/java/com/thealgorithms/ciphers/Caesar.java +++ b/src/main/java/com/thealgorithms/ciphers/Caesar.java @@ -9,6 +9,9 @@ * @author khalil2535 */ public class Caesar { + private static char normalizeShift(final int shift) { + return (char) (shift % 26); + } /** * Encrypt text by shifting every Latin char by add number shift for ASCII @@ -19,7 +22,7 @@ public class Caesar { public String encode(String message, int shift) { StringBuilder encoded = new StringBuilder(); - shift %= 26; + final char shiftChar = normalizeShift(shift); final int length = message.length(); for (int i = 0; i < length; i++) { @@ -29,10 +32,10 @@ public String encode(String message, int shift) { char current = message.charAt(i); // Java law : char + int = char if (isCapitalLatinLetter(current)) { - current += shift; + current += shiftChar; encoded.append((char) (current > 'Z' ? current - 26 : current)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current += shift; + current += shiftChar; encoded.append((char) (current > 'z' ? current - 26 : current)); // 26 = number of latin letters } else { encoded.append(current); @@ -50,16 +53,16 @@ public String encode(String message, int shift) { public String decode(String encryptedMessage, int shift) { StringBuilder decoded = new StringBuilder(); - shift %= 26; + final char shiftChar = normalizeShift(shift); final int length = encryptedMessage.length(); for (int i = 0; i < length; i++) { char current = encryptedMessage.charAt(i); if (isCapitalLatinLetter(current)) { - current -= shift; + current -= shiftChar; decoded.append((char) (current < 'A' ? current + 26 : current)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current -= shift; + current -= shiftChar; decoded.append((char) (current < 'a' ? current + 26 : current)); // 26 = number of latin letters } else { decoded.append(current); diff --git a/src/main/java/com/thealgorithms/ciphers/PermutationCipher.java b/src/main/java/com/thealgorithms/ciphers/PermutationCipher.java new file mode 100644 index 000000000000..ce443545db1d --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/PermutationCipher.java @@ -0,0 +1,194 @@ +package com.thealgorithms.ciphers; + +import java.util.HashSet; +import java.util.Set; + +/** + * A Java implementation of Permutation Cipher. + * It is a type of transposition cipher in which the plaintext is divided into blocks + * and the characters within each block are rearranged according to a fixed permutation key. + * + * For example, with key {3, 1, 2} and plaintext "HELLO", the text is divided into blocks + * of 3 characters: "HEL" and "LO" (with padding). The characters are then rearranged + * according to the key positions. + * + * @author GitHub Copilot + */ +public class PermutationCipher { + + private static final char PADDING_CHAR = 'X'; + + /** + * Encrypts the given plaintext using the permutation cipher with the specified key. + * + * @param plaintext the text to encrypt + * @param key the permutation key (array of integers representing positions) + * @return the encrypted text + * @throws IllegalArgumentException if the key is invalid + */ + public String encrypt(String plaintext, int[] key) { + validateKey(key); + + if (plaintext == null || plaintext.isEmpty()) { + return plaintext; + } + + // Remove spaces and convert to uppercase for consistent processing + String cleanText = plaintext.replaceAll("\\s+", "").toUpperCase(); + + // Pad the text to make it divisible by key length + String paddedText = padText(cleanText, key.length); + + StringBuilder encrypted = new StringBuilder(); + + // Process text in blocks of key length + for (int i = 0; i < paddedText.length(); i += key.length) { + String block = paddedText.substring(i, Math.min(i + key.length, paddedText.length())); + encrypted.append(permuteBlock(block, key)); + } + + return encrypted.toString(); + } + + /** + * Decrypts the given ciphertext using the permutation cipher with the specified key. + * + * @param ciphertext the text to decrypt + * @param key the permutation key (array of integers representing positions) + * @return the decrypted text + * @throws IllegalArgumentException if the key is invalid + */ + public String decrypt(String ciphertext, int[] key) { + validateKey(key); + + if (ciphertext == null || ciphertext.isEmpty()) { + return ciphertext; + } + + // Create the inverse permutation + int[] inverseKey = createInverseKey(key); + + StringBuilder decrypted = new StringBuilder(); + + // Process text in blocks of key length + for (int i = 0; i < ciphertext.length(); i += key.length) { + String block = ciphertext.substring(i, Math.min(i + key.length, ciphertext.length())); + decrypted.append(permuteBlock(block, inverseKey)); + } + + // Remove padding characters from the end + return removePadding(decrypted.toString()); + } + /** + * Validates that the permutation key is valid. + * A valid key must contain all integers from 1 to n exactly once, where n is the key length. + * + * @param key the permutation key to validate + * @throws IllegalArgumentException if the key is invalid + */ + private void validateKey(int[] key) { + if (key == null || key.length == 0) { + throw new IllegalArgumentException("Key cannot be null or empty"); + } + + Set keySet = new HashSet<>(); + for (int position : key) { + if (position < 1 || position > key.length) { + throw new IllegalArgumentException("Key must contain integers from 1 to " + key.length); + } + if (!keySet.add(position)) { + throw new IllegalArgumentException("Key must contain each position exactly once"); + } + } + } + + /** + * Pads the text with padding characters to make its length divisible by the block size. + * + * @param text the text to pad + * @param blockSize the size of each block + * @return the padded text + */ + private String padText(String text, int blockSize) { + int remainder = text.length() % blockSize; + if (remainder == 0) { + return text; + } + + int paddingNeeded = blockSize - remainder; + StringBuilder padded = new StringBuilder(text); + for (int i = 0; i < paddingNeeded; i++) { + padded.append(PADDING_CHAR); + } + + return padded.toString(); + } + /** + * Applies the permutation to a single block of text. + * + * @param block the block to permute + * @param key the permutation key + * @return the permuted block + */ + private String permuteBlock(String block, int[] key) { + if (block.length() != key.length) { + // Handle case where block is shorter than key (shouldn't happen with proper padding) + block = padText(block, key.length); + } + + char[] result = new char[key.length]; + char[] blockChars = block.toCharArray(); + + for (int i = 0; i < key.length; i++) { + // Key positions are 1-based, so subtract 1 for 0-based array indexing + result[i] = blockChars[key[i] - 1]; + } + + return new String(result); + } + + /** + * Creates the inverse permutation key for decryption. + * + * @param key the original permutation key + * @return the inverse key + */ + private int[] createInverseKey(int[] key) { + int[] inverse = new int[key.length]; + + for (int i = 0; i < key.length; i++) { + // The inverse key maps each position to where it should go + inverse[key[i] - 1] = i + 1; + } + + return inverse; + } + + /** + * Removes padding characters from the end of the decrypted text. + * + * @param text the text to remove padding from + * @return the text without padding + */ + private String removePadding(String text) { + if (text.isEmpty()) { + return text; + } + + int i = text.length() - 1; + while (i >= 0 && text.charAt(i) == PADDING_CHAR) { + i--; + } + + return text.substring(0, i + 1); + } + + /** + * Gets the padding character used by this cipher. + * + * @return the padding character + */ + public char getPaddingChar() { + return PADDING_CHAR; + } +} diff --git a/src/main/java/com/thealgorithms/compression/ArithmeticCoding.java b/src/main/java/com/thealgorithms/compression/ArithmeticCoding.java new file mode 100644 index 000000000000..b5ccf359d1be --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/ArithmeticCoding.java @@ -0,0 +1,157 @@ +package com.thealgorithms.compression; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An implementation of the Arithmetic Coding algorithm. + * + *

+ * Arithmetic coding is a form of entropy encoding used in lossless data + * compression. It encodes an entire message into a single number, a fraction n + * where (0.0 <= n < 1.0). Unlike Huffman coding, which assigns a specific + * bit sequence to each symbol, arithmetic coding represents the message as a + * sub-interval of the [0, 1) interval. + *

+ * + *

+ * This implementation uses BigDecimal for precision to handle the shrinking + * intervals, making it suitable for educational purposes to demonstrate the + * core logic. + *

+ * + *

+ * Time Complexity: O(n*m) for compression and decompression where n is the + * length of the input and m is the number of unique symbols, due to the need + * to calculate symbol probabilities. + *

+ * + *

+ * References: + *

+ *

+ */ +public final class ArithmeticCoding { + + private ArithmeticCoding() { + } + + /** + * Compresses a string using the Arithmetic Coding algorithm. + * + * @param uncompressed The string to be compressed. + * @return The compressed representation as a BigDecimal number. + * @throws IllegalArgumentException if the input string is null or empty. + */ + public static BigDecimal compress(String uncompressed) { + if (uncompressed == null || uncompressed.isEmpty()) { + throw new IllegalArgumentException("Input string cannot be null or empty."); + } + + Map probabilityTable = calculateProbabilities(uncompressed); + + BigDecimal low = BigDecimal.ZERO; + BigDecimal high = BigDecimal.ONE; + + for (char symbol : uncompressed.toCharArray()) { + BigDecimal range = high.subtract(low); + Symbol sym = probabilityTable.get(symbol); + + high = low.add(range.multiply(sym.high())); + low = low.add(range.multiply(sym.low())); + } + + return low; // Return the lower bound of the final interval + } + + /** + * Decompresses a BigDecimal number back into the original string. + * + * @param compressed The compressed BigDecimal number. + * @param length The length of the original uncompressed string. + * @param probabilityTable The probability table used during compression. + * @return The original, uncompressed string. + */ + public static String decompress(BigDecimal compressed, int length, Map probabilityTable) { + StringBuilder decompressed = new StringBuilder(); + + // Create a sorted list of symbols for deterministic decompression, matching the + // order used in calculateProbabilities + List> sortedSymbols = new ArrayList<>(probabilityTable.entrySet()); + sortedSymbols.sort(Map.Entry.comparingByKey()); + + BigDecimal low = BigDecimal.ZERO; + BigDecimal high = BigDecimal.ONE; + + for (int i = 0; i < length; i++) { + BigDecimal range = high.subtract(low); + + // Find which symbol the compressed value falls into + for (Map.Entry entry : sortedSymbols) { + Symbol sym = entry.getValue(); + + // Calculate the actual range for this symbol in the current interval + BigDecimal symLow = low.add(range.multiply(sym.low())); + BigDecimal symHigh = low.add(range.multiply(sym.high())); + + // Check if the compressed value falls within this symbol's range + if (compressed.compareTo(symLow) >= 0 && compressed.compareTo(symHigh) < 0) { + decompressed.append(entry.getKey()); + + // Update the interval for the next iteration + low = symLow; + high = symHigh; + break; + } + } + } + + return decompressed.toString(); + } + + /** + * Calculates the frequency and probability range for each character in the + * input string in a deterministic order. + * + * @param text The input string. + * @return A map from each character to a Symbol object containing its + * probability range. + */ + public static Map calculateProbabilities(String text) { + Map frequencies = new HashMap<>(); + for (char c : text.toCharArray()) { + frequencies.put(c, frequencies.getOrDefault(c, 0) + 1); + } + + // Sort the characters to ensure a deterministic order for the probability table + List sortedKeys = new ArrayList<>(frequencies.keySet()); + Collections.sort(sortedKeys); + + Map probabilityTable = new HashMap<>(); + BigDecimal currentLow = BigDecimal.ZERO; + int total = text.length(); + + for (char symbol : sortedKeys) { + BigDecimal probability = BigDecimal.valueOf(frequencies.get(symbol)).divide(BigDecimal.valueOf(total), MathContext.DECIMAL128); + BigDecimal high = currentLow.add(probability); + probabilityTable.put(symbol, new Symbol(currentLow, high)); + currentLow = high; + } + + return probabilityTable; + } + + /** + * Helper class to store the probability range [low, high) for a symbol. + */ + public record Symbol(BigDecimal low, BigDecimal high) { + } +} diff --git a/src/main/java/com/thealgorithms/compression/BurrowsWheelerTransform.java b/src/main/java/com/thealgorithms/compression/BurrowsWheelerTransform.java new file mode 100644 index 000000000000..a148517e5b55 --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/BurrowsWheelerTransform.java @@ -0,0 +1,220 @@ +package com.thealgorithms.compression; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation of the Burrows-Wheeler Transform (BWT) and its inverse. + *

+ * BWT is a reversible data transformation algorithm that rearranges a string into runs of + * similar characters. While not a compression algorithm itself, it significantly improves + * the compressibility of data for subsequent algorithms like Move-to-Front encoding and + * Run-Length Encoding. + *

+ * + *

The transform works by: + *

    + *
  1. Generating all rotations of the input string
  2. + *
  3. Sorting these rotations lexicographically
  4. + *
  5. Taking the last column of the sorted matrix as output
  6. + *
  7. Recording the index of the original string in the sorted matrix
  8. + *
+ *

+ * + *

Important: The input string should end with a unique end-of-string marker + * (typically '$') that: + *

    + *
  • Does not appear anywhere else in the text
  • + *
  • Is lexicographically smaller than all other characters
  • + *
  • Ensures unique rotations and enables correct inverse transformation
  • + *
+ * Without this marker, the inverse transform may not correctly reconstruct the original string. + *

+ * + *

Time Complexity: + *

    + *
  • Forward transform: O(nยฒ log n) where n is the string length
  • + *
  • Inverse transform: O(n) using the LF-mapping technique
  • + *
+ *

+ * + *

Example:

+ *
+ * Input:  "banana$"
+ * Output: BWTResult("annb$aa", 4)
+ *         - "annb$aa" is the transformed string (groups similar characters)
+ *         - 4 is the index of the original string in the sorted rotations
+ * 
+ * + * @see Burrowsโ€“Wheeler transform (Wikipedia) + */ +public final class BurrowsWheelerTransform { + + private BurrowsWheelerTransform() { + } + + /** + * A container for the result of the forward BWT. + *

+ * Contains the transformed string and the index of the original string + * in the sorted rotations matrix, both of which are required for the + * inverse transformation. + *

+ */ + public static class BWTResult { + /** The transformed string (last column of the sorted rotation matrix) */ + public final String transformed; + + /** The index of the original string in the sorted rotations matrix */ + public final int originalIndex; + + /** + * Constructs a BWTResult with the transformed string and original index. + * + * @param transformed the transformed string (L-column) + * @param originalIndex the index of the original string in sorted rotations + */ + public BWTResult(String transformed, int originalIndex) { + this.transformed = transformed; + this.originalIndex = originalIndex; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BWTResult bwtResult = (BWTResult) obj; + return originalIndex == bwtResult.originalIndex && transformed.equals(bwtResult.transformed); + } + + @Override + public int hashCode() { + return 31 * transformed.hashCode() + originalIndex; + } + + @Override + public String toString() { + return "BWTResult[transformed=" + transformed + ", originalIndex=" + originalIndex + "]"; + } + } + + /** + * Performs the forward Burrows-Wheeler Transform on the input string. + *

+ * The algorithm generates all cyclic rotations of the input, sorts them + * lexicographically, and returns the last column of this sorted matrix + * along with the position of the original string. + *

+ * + *

Note: It is strongly recommended that the input string ends with + * a unique end-of-string marker (e.g., '$') that is lexicographically smaller + * than any other character in the string. This ensures correct inversion.

+ * + * @param text the input string to transform; must not be {@code null} + * @return a {@link BWTResult} object containing the transformed string (L-column) + * and the index of the original string in the sorted rotations matrix; + * returns {@code BWTResult("", -1)} for empty input + * @throws NullPointerException if {@code text} is {@code null} + */ + public static BWTResult transform(String text) { + if (text == null || text.isEmpty()) { + return new BWTResult("", -1); + } + + int n = text.length(); + + // Generate all rotations of the input string + String[] rotations = new String[n]; + for (int i = 0; i < n; i++) { + rotations[i] = text.substring(i) + text.substring(0, i); + } + + // Sort rotations lexicographically + Arrays.sort(rotations); + int originalIndex = Arrays.binarySearch(rotations, text); + StringBuilder lastColumn = new StringBuilder(n); + for (int i = 0; i < n; i++) { + lastColumn.append(rotations[i].charAt(n - 1)); + } + + return new BWTResult(lastColumn.toString(), originalIndex); + } + + /** + * Performs the inverse Burrows-Wheeler Transform using the LF-mapping technique. + *

+ * The LF-mapping (Last-First mapping) is an efficient method to reconstruct + * the original string from the BWT output without explicitly reconstructing + * the entire sorted rotations matrix. + *

+ * + *

The algorithm works by: + *

    + *
  1. Creating the first column by sorting the BWT string
  2. + *
  3. Building a mapping from first column indices to last column indices
  4. + *
  5. Following this mapping starting from the original index to reconstruct the string
  6. + *
+ *

+ * + * @param bwtString the transformed string (L-column) from the forward transform; must not be {@code null} + * @param originalIndex the index of the original string row from the forward transform; + * use -1 for empty strings + * @return the original, untransformed string; returns empty string if input is empty or {@code originalIndex} is -1 + * @throws NullPointerException if {@code bwtString} is {@code null} + * @throws IllegalArgumentException if {@code originalIndex} is out of valid range (except -1) + */ + public static String inverseTransform(String bwtString, int originalIndex) { + if (bwtString == null || bwtString.isEmpty() || originalIndex == -1) { + return ""; + } + + int n = bwtString.length(); + if (originalIndex < 0 || originalIndex >= n) { + throw new IllegalArgumentException("Original index must be between 0 and " + (n - 1) + ", got: " + originalIndex); + } + + char[] lastColumn = bwtString.toCharArray(); + char[] firstColumn = bwtString.toCharArray(); + Arrays.sort(firstColumn); + + // Create the "next" array for LF-mapping. + // next[i] stores the row index in the last column that corresponds to firstColumn[i] + int[] next = new int[n]; + + // Track the count of each character seen so far in the last column + Map countMap = new HashMap<>(); + + // Store the first occurrence index of each character in the first column + Map firstOccurrence = new HashMap<>(); + + for (int i = 0; i < n; i++) { + if (!firstOccurrence.containsKey(firstColumn[i])) { + firstOccurrence.put(firstColumn[i], i); + } + } + + // Build the LF-mapping + for (int i = 0; i < n; i++) { + char c = lastColumn[i]; + int count = countMap.getOrDefault(c, 0); + int firstIndex = firstOccurrence.get(c); + next[firstIndex + count] = i; + countMap.put(c, count + 1); + } + + // Reconstruct the original string by following the LF-mapping + StringBuilder originalString = new StringBuilder(n); + int currentRow = originalIndex; + for (int i = 0; i < n; i++) { + originalString.append(firstColumn[currentRow]); + currentRow = next[currentRow]; + } + + return originalString.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/compression/LZ77.java b/src/main/java/com/thealgorithms/compression/LZ77.java new file mode 100644 index 000000000000..d02307aa57b5 --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/LZ77.java @@ -0,0 +1,168 @@ +package com.thealgorithms.compression; + +import java.util.ArrayList; +import java.util.List; + +/** + * An implementation of the Lempel-Ziv 77 (LZ77) compression algorithm. + *

+ * LZ77 is a lossless data compression algorithm that works by finding repeated + * occurrences of data in a sliding window. It replaces subsequent occurrences + * with references (offset, length) to the first occurrence within the window. + *

+ *

+ * This implementation uses a simple sliding window and lookahead buffer approach. + * Output format is a sequence of tuples (offset, length, next_character). + *

+ *

+ * Time Complexity: O(n*W) in this naive implementation, where n is the input length + * and W is the window size, due to the search for the longest match. More advanced + * data structures (like suffix trees) can improve this. + *

+ *

+ * References: + *

+ *

+ */ +public final class LZ77 { + + private static final int DEFAULT_WINDOW_SIZE = 4096; + private static final int DEFAULT_LOOKAHEAD_BUFFER_SIZE = 16; + private static final char END_OF_STREAM = '\u0000'; + private LZ77() { + } + + /** + * Represents a token in the LZ77 compressed output. + * Stores the offset back into the window, the length of the match, + * and the next character after the match (or END_OF_STREAM if at end). + */ + public record Token(int offset, int length, char nextChar) { + } + + /** + * Compresses the input text using the LZ77 algorithm. + * + * @param text The input string to compress. Must not be null. + * @param windowSize The size of the sliding window (search buffer). Must be positive. + * @param lookaheadBufferSize The size of the lookahead buffer. Must be positive. + * @return A list of {@link Token} objects representing the compressed data. + * @throws IllegalArgumentException if windowSize or lookaheadBufferSize are not positive. + */ + public static List compress(String text, int windowSize, int lookaheadBufferSize) { + if (text == null) { + return new ArrayList<>(); + } + if (windowSize <= 0 || lookaheadBufferSize <= 0) { + throw new IllegalArgumentException("Window size and lookahead buffer size must be positive."); + } + + List compressedOutput = new ArrayList<>(); + int currentPosition = 0; + + while (currentPosition < text.length()) { + int bestMatchDistance = 0; + int bestMatchLength = 0; + + // Define the start of the search window + int searchBufferStart = Math.max(0, currentPosition - windowSize); + // Define the end of the lookahead buffer (don't go past text length) + int lookaheadEnd = Math.min(currentPosition + lookaheadBufferSize, text.length()); + + // Search for the longest match in the window + for (int i = searchBufferStart; i < currentPosition; i++) { + int currentMatchLength = 0; + + // Check how far the match extends into the lookahead buffer + // This allows for overlapping matches (e.g., "aaa" can match with offset 1) + while (currentPosition + currentMatchLength < lookaheadEnd) { + int sourceIndex = i + currentMatchLength; + + // Handle overlapping matches (run-length encoding within LZ77) + // When we've matched beyond our starting position, wrap around using modulo + if (sourceIndex >= currentPosition) { + int offset = currentPosition - i; + sourceIndex = i + (currentMatchLength % offset); + } + + if (text.charAt(sourceIndex) == text.charAt(currentPosition + currentMatchLength)) { + currentMatchLength++; + } else { + break; + } + } + + // If this match is longer than the best found so far + if (currentMatchLength > bestMatchLength) { + bestMatchLength = currentMatchLength; + bestMatchDistance = currentPosition - i; // Calculate offset from current position + } + } + + char nextChar; + if (currentPosition + bestMatchLength < text.length()) { + nextChar = text.charAt(currentPosition + bestMatchLength); + } else { + nextChar = END_OF_STREAM; + } + + // Add the token to the output + compressedOutput.add(new Token(bestMatchDistance, bestMatchLength, nextChar)); + + // Move the current position forward + // If we're at the end and had a match, just move by the match length + if (nextChar == END_OF_STREAM) { + currentPosition += bestMatchLength; + } else { + currentPosition += bestMatchLength + 1; + } + } + + return compressedOutput; + } + + /** + * Compresses the input text using the LZ77 algorithm with default buffer sizes. + * + * @param text The input string to compress. Must not be null. + * @return A list of {@link Token} objects representing the compressed data. + */ + public static List compress(String text) { + return compress(text, DEFAULT_WINDOW_SIZE, DEFAULT_LOOKAHEAD_BUFFER_SIZE); + } + + /** + * Decompresses a list of LZ77 tokens back into the original string. + * + * @param compressedData The list of {@link Token} objects. Must not be null. + * @return The original, uncompressed string. + */ + public static String decompress(List compressedData) { + if (compressedData == null) { + return ""; + } + + StringBuilder decompressedText = new StringBuilder(); + + for (Token token : compressedData) { + // Copy matched characters from the sliding window + if (token.length > 0) { + int startIndex = decompressedText.length() - token.offset; + + // Handle overlapping matches (e.g., when length > offset) + for (int i = 0; i < token.length; i++) { + decompressedText.append(decompressedText.charAt(startIndex + i)); + } + } + + // Append the next character (if not END_OF_STREAM) + if (token.nextChar != END_OF_STREAM) { + decompressedText.append(token.nextChar); + } + } + + return decompressedText.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/compression/LZ78.java b/src/main/java/com/thealgorithms/compression/LZ78.java new file mode 100644 index 000000000000..904c379cc2a2 --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/LZ78.java @@ -0,0 +1,136 @@ +package com.thealgorithms.compression; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An implementation of the Lempel-Ziv 78 (LZ78) compression algorithm. + *

+ * LZ78 is a dictionary-based lossless data compression algorithm. It processes + * input data sequentially, building a dictionary of phrases encountered so far. + * It outputs pairs (dictionary_index, next_character), representing + * the longest match found in the dictionary plus the character that follows it. + *

+ *

+ * This implementation builds the dictionary dynamically during compression. + * The dictionary index 0 represents the empty string (no prefix). + *

+ *

+ * Time Complexity: O(n) on average for compression and decompression, assuming + * efficient dictionary lookups (using a HashMap), where n is the + * length of the input string. + *

+ *

+ * References: + *

+ *

+ */ +public final class LZ78 { + + /** + * Special character used to mark end of stream when needed. + */ + private static final char END_OF_STREAM = '\u0000'; + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private LZ78() { + } + + /** + * Represents a token in the LZ78 compressed output. + * Stores the index of the matching prefix in the dictionary and the next character. + * Index 0 represents the empty string (no prefix). + */ + public record Token(int index, char nextChar) { + } + + /** + * A node in the dictionary trie structure. + * Each node represents a phrase and can have child nodes for extended phrases. + */ + private static final class TrieNode { + Map children = new HashMap<>(); + int index = -1; // -1 means not assigned yet + } + + /** + * Compresses the input text using the LZ78 algorithm. + * + * @param text The input string to compress. Must not be null. + * @return A list of {@link Token} objects representing the compressed data. + */ + public static List compress(String text) { + if (text == null || text.isEmpty()) { + return new ArrayList<>(); + } + + List compressedOutput = new ArrayList<>(); + TrieNode root = new TrieNode(); + int nextDictionaryIndex = 1; + + TrieNode currentNode = root; + int lastMatchedIndex = 0; + + for (int i = 0; i < text.length(); i++) { + char currentChar = text.charAt(i); + + if (currentNode.children.containsKey(currentChar)) { + currentNode = currentNode.children.get(currentChar); + lastMatchedIndex = currentNode.index; + } else { + // Output: (index of longest matching prefix, current character) + compressedOutput.add(new Token(lastMatchedIndex, currentChar)); + + TrieNode newNode = new TrieNode(); + newNode.index = nextDictionaryIndex++; + currentNode.children.put(currentChar, newNode); + + currentNode = root; + lastMatchedIndex = 0; + } + } + + // Handle remaining phrase at end of input + if (currentNode != root) { + compressedOutput.add(new Token(lastMatchedIndex, END_OF_STREAM)); + } + + return compressedOutput; + } + + /** + * Decompresses a list of LZ78 tokens back into the original string. + * + * @param compressedData The list of {@link Token} objects. Must not be null. + * @return The original, uncompressed string. + */ + public static String decompress(List compressedData) { + if (compressedData == null || compressedData.isEmpty()) { + return ""; + } + + StringBuilder decompressedText = new StringBuilder(); + Map dictionary = new HashMap<>(); + int nextDictionaryIndex = 1; + + for (Token token : compressedData) { + String prefix = (token.index == 0) ? "" : dictionary.get(token.index); + + if (token.nextChar == END_OF_STREAM) { + decompressedText.append(prefix); + } else { + String currentPhrase = prefix + token.nextChar; + decompressedText.append(currentPhrase); + dictionary.put(nextDictionaryIndex++, currentPhrase); + } + } + + return decompressedText.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/compression/LZW.java b/src/main/java/com/thealgorithms/compression/LZW.java new file mode 100644 index 000000000000..c8383815ad4f --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/LZW.java @@ -0,0 +1,136 @@ +package com.thealgorithms.compression; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An implementation of the Lempel-Ziv-Welch (LZW) algorithm. + * + *

+ * LZW is a universal lossless data compression algorithm created by Abraham + * Lempel, Jacob Ziv, and Terry Welch. It works by building a dictionary of + * strings encountered during compression and replacing occurrences of those + * strings with a shorter code. + *

+ * + *

+ * This implementation handles standard ASCII characters and provides methods for + * both compression and decompression. + *

    + *
  • Compressing "TOBEORNOTTOBEORTOBEORNOT" results in a list of integer + * codes.
  • + *
  • Decompressing that list of codes results back in the original + * string.
  • + *
+ *

+ * + *

+ * Time Complexity: O(n) for both compression and decompression, where n is the + * length of the input string. + *

+ * + *

+ * References: + *

+ *

+ */ +public final class LZW { + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private LZW() { + } + + /** + * Compresses a string using the LZW algorithm. + * + * @param uncompressed The string to be compressed. Can be null. + * @return A list of integers representing the compressed data. Returns an empty + * list if the input is null or empty. + */ + public static List compress(String uncompressed) { + if (uncompressed == null || uncompressed.isEmpty()) { + return new ArrayList<>(); + } + + // Initialize dictionary with single characters (ASCII 0-255) + int dictSize = 256; + Map dictionary = new HashMap<>(); + for (int i = 0; i < dictSize; i++) { + dictionary.put("" + (char) i, i); + } + + String w = ""; + List result = new ArrayList<>(); + for (char c : uncompressed.toCharArray()) { + String wc = w + c; + if (dictionary.containsKey(wc)) { + // If the new string is in the dictionary, extend the current string + w = wc; + } else { + // Otherwise, output the code for the current string + result.add(dictionary.get(w)); + // Add the new string to the dictionary + dictionary.put(wc, dictSize++); + // Start a new current string + w = "" + c; + } + } + + // Output the code for the last remaining string + result.add(dictionary.get(w)); + return result; + } + + /** + * Decompresses a list of integers back into a string using the LZW algorithm. + * + * @param compressed A list of integers representing the compressed data. Can be + * null. + * @return The original, uncompressed string. Returns an empty string if the + * input is null or empty. + */ + public static String decompress(List compressed) { + if (compressed == null || compressed.isEmpty()) { + return ""; + } + + // Initialize dictionary with single characters (ASCII 0-255) + int dictSize = 256; + Map dictionary = new HashMap<>(); + for (int i = 0; i < dictSize; i++) { + dictionary.put(i, "" + (char) i); + } + + // Decompress the first code + String w = "" + (char) (int) compressed.removeFirst(); + StringBuilder result = new StringBuilder(w); + + for (int k : compressed) { + String entry; + if (dictionary.containsKey(k)) { + // The code is in the dictionary + entry = dictionary.get(k); + } else if (k == dictSize) { + // Special case for sequences like "ababab" + entry = w + w.charAt(0); + } else { + throw new IllegalArgumentException("Bad compressed k: " + k); + } + + result.append(entry); + + // Add new sequence to the dictionary + dictionary.put(dictSize++, w + entry.charAt(0)); + + w = entry; + } + return result.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/compression/MoveToFront.java b/src/main/java/com/thealgorithms/compression/MoveToFront.java new file mode 100644 index 000000000000..fa8976df8262 --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/MoveToFront.java @@ -0,0 +1,164 @@ +package com.thealgorithms.compression; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Implementation of the Move-to-Front (MTF) transform and its inverse. + *

+ * MTF is a data transformation algorithm that encodes each symbol in the input + * as its current position in a dynamically-maintained list, then moves that symbol + * to the front of the list. This transformation is particularly effective when used + * after the Burrows-Wheeler Transform (BWT), as BWT groups similar characters together. + *

+ * + *

The transform converts runs of repeated characters into sequences of small integers + * (often zeros), which are highly compressible by subsequent entropy encoding algorithms + * like Run-Length Encoding (RLE) or Huffman coding. This technique is used in the + * bzip2 compression algorithm. + *

+ * + *

How it works: + *

    + *
  1. Maintain a list of symbols (the alphabet), initially in a fixed order
  2. + *
  3. For each input symbol: + *
      + *
    • Output its current index in the list
    • + *
    • Move that symbol to the front of the list
    • + *
    + *
  4. + *
+ * This means frequently occurring symbols quickly move to the front and are encoded + * with small indices (often 0), while rare symbols remain near the back. + *

+ * + *

Time Complexity: + *

    + *
  • Forward transform: O(n ร— m) where n is input length and m is alphabet size
  • + *
  • Inverse transform: O(n ร— m)
  • + *
+ * Note: Using {@link LinkedList} for O(1) insertions and O(m) search operations. + *

+ * + *

Example:

+ *
+ * Input:    "annb$aa"
+ * Alphabet: "$abn" (initial order)
+ * Output:   [1, 3, 0, 3, 3, 3, 0]
+ *
+ * Step-by-step:
+ * - 'a': index 1 in [$,a,b,n] โ†’ output 1, list becomes [a,$,b,n]
+ * - 'n': index 3 in [a,$,b,n] โ†’ output 3, list becomes [n,a,$,b]
+ * - 'n': index 0 in [n,a,$,b] โ†’ output 0, list stays [n,a,$,b]
+ * - 'b': index 3 in [n,a,$,b] โ†’ output 3, list becomes [b,n,a,$]
+ * - etc.
+ *
+ * Notice how repeated 'n' characters produce zeros after the first occurrence!
+ * 
+ * + * @see Move-to-front transform (Wikipedia) + */ +public final class MoveToFront { + + private MoveToFront() { + } + + /** + * Performs the forward Move-to-Front transform. + *

+ * Converts the input string into a list of integers, where each integer represents + * the position of the corresponding character in a dynamically-maintained alphabet list. + *

+ * + *

Note: All characters in the input text must exist in the provided alphabet, + * otherwise an {@link IllegalArgumentException} is thrown. The alphabet should contain + * all unique characters that may appear in the input.

+ * + * @param text the input string to transform; if empty, returns an empty list + * @param initialAlphabet a string containing the initial ordered set of symbols + * (e.g., "$abn" or the full ASCII set); must not be empty + * when {@code text} is non-empty + * @return a list of integers representing the transformed data, where each integer + * is the index of the corresponding input character in the current alphabet state + * @throws IllegalArgumentException if {@code text} is non-empty and {@code initialAlphabet} + * is {@code null} or empty + * @throws IllegalArgumentException if any character in {@code text} is not found in + * {@code initialAlphabet} + */ + public static List transform(String text, String initialAlphabet) { + if (text == null || text.isEmpty()) { + return new ArrayList<>(); + } + if (initialAlphabet == null || initialAlphabet.isEmpty()) { + throw new IllegalArgumentException("Alphabet cannot be null or empty when text is not empty."); + } + + List output = new ArrayList<>(text.length()); + + // Use LinkedList for O(1) add-to-front and O(n) remove operations + // This is more efficient than ArrayList for the move-to-front pattern + List alphabet = initialAlphabet.chars().mapToObj(c -> (char) c).collect(Collectors.toCollection(LinkedList::new)); + + for (char c : text.toCharArray()) { + int index = alphabet.indexOf(c); + if (index == -1) { + throw new IllegalArgumentException("Symbol '" + c + "' not found in the initial alphabet."); + } + + output.add(index); + + // Move the character to the front + Character symbol = alphabet.remove(index); + alphabet.addFirst(symbol); + } + return output; + } + + /** + * Performs the inverse Move-to-Front transform. + *

+ * Reconstructs the original string from the list of indices produced by the + * forward transform. This requires the exact same initial alphabet that was + * used in the forward transform. + *

+ * + *

Important: The {@code initialAlphabet} parameter must be identical + * to the one used in the forward transform, including character order, or the + * output will be incorrect.

+ * + * @param indices The list of integers from the forward transform. + * @param initialAlphabet the exact same initial alphabet string used for the forward transform; + * if {@code null} or empty, returns an empty string + * @return the original, untransformed string + * @throws IllegalArgumentException if any index in {@code indices} is negative or + * exceeds the current alphabet size + */ + public static String inverseTransform(Collection indices, String initialAlphabet) { + if (indices == null || indices.isEmpty() || initialAlphabet == null || initialAlphabet.isEmpty()) { + return ""; + } + + StringBuilder output = new StringBuilder(indices.size()); + + // Use LinkedList for O(1) add-to-front and O(n) remove operations + List alphabet = initialAlphabet.chars().mapToObj(c -> (char) c).collect(Collectors.toCollection(LinkedList::new)); + + for (int index : indices) { + if (index < 0 || index >= alphabet.size()) { + throw new IllegalArgumentException("Index " + index + " is out of bounds for the current alphabet of size " + alphabet.size() + "."); + } + + // Get the symbol at the index + char symbol = alphabet.get(index); + output.append(symbol); + + // Move the symbol to the front (mirroring the forward transform) + alphabet.remove(index); + alphabet.addFirst(symbol); + } + return output.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/compression/RunLengthEncoding.java b/src/main/java/com/thealgorithms/compression/RunLengthEncoding.java new file mode 100644 index 000000000000..8d065f4648df --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/RunLengthEncoding.java @@ -0,0 +1,87 @@ +package com.thealgorithms.compression; + +/** + * An implementation of the Run-Length Encoding (RLE) algorithm. + * + *

Run-Length Encoding is a simple form of lossless data compression in which + * runs of data (sequences in which the same data value occurs in many + * consecutive data elements) are stored as a single data value and count, + * rather than as the original run. + * + *

This implementation provides methods for both compressing and decompressing + * a string. For example: + *

    + *
  • Compressing "AAAABBBCCDAA" results in "4A3B2C1D2A".
  • + *
  • Decompressing "4A3B2C1D2A" results in "AAAABBBCCDAA".
  • + *
+ * + *

Time Complexity: O(n) for both compression and decompression, where n is the + * length of the input string. + * + *

References: + *

+ */ +public final class RunLengthEncoding { + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private RunLengthEncoding() { + } + + /** + * Compresses a string using the Run-Length Encoding algorithm. + * + * @param text The string to be compressed. Must not be null. + * @return The compressed string. Returns an empty string if the input is empty. + */ + public static String compress(String text) { + if (text == null || text.isEmpty()) { + return ""; + } + + StringBuilder compressed = new StringBuilder(); + int count = 1; + + for (int i = 0; i < text.length(); i++) { + // Check if it's the last character or if the next character is different + if (i == text.length() - 1 || text.charAt(i) != text.charAt(i + 1)) { + compressed.append(count); + compressed.append(text.charAt(i)); + count = 1; // Reset count for the new character + } else { + count++; + } + } + return compressed.toString(); + } + + /** + * Decompresses a string that was compressed using the Run-Length Encoding algorithm. + * + * @param compressedText The compressed string. Must not be null. + * @return The original, uncompressed string. + */ + public static String decompress(String compressedText) { + if (compressedText == null || compressedText.isEmpty()) { + return ""; + } + + StringBuilder decompressed = new StringBuilder(); + int count = 0; + + for (char ch : compressedText.toCharArray()) { + if (Character.isDigit(ch)) { + // Build the number for runs of 10 or more (e.g., "12A") + count = count * 10 + ch - '0'; + } else { + // Append the character 'count' times + decompressed.append(String.valueOf(ch).repeat(Math.max(0, count))); + count = 0; // Reset count for the next sequence + } + } + return decompressed.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/compression/ShannonFano.java b/src/main/java/com/thealgorithms/compression/ShannonFano.java new file mode 100644 index 000000000000..aa5d7ad91b2f --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/ShannonFano.java @@ -0,0 +1,159 @@ +package com.thealgorithms.compression; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * An implementation of the Shannon-Fano algorithm for generating prefix codes. + * + *

Shannon-Fano coding is an entropy encoding technique for lossless data + * compression. It assigns variable-length codes to symbols based on their + * frequencies of occurrence. It is a precursor to Huffman coding and works by + * recursively partitioning a sorted list of symbols into two sub-lists with + * nearly equal total frequencies. + * + *

The algorithm works as follows: + *

    + *
  1. Count the frequency of each symbol in the input data.
  2. + *
  3. Sort the symbols in descending order of their frequencies.
  4. + *
  5. Recursively divide the list of symbols into two parts with sums of + * frequencies as close as possible to each other.
  6. + *
  7. Assign a '0' bit to the codes in the first part and a '1' bit to the codes + * in the second part.
  8. + *
  9. Repeat the process for each part until a part contains only one symbol.
  10. + *
+ * + *

Time Complexity: O(n^2) in this implementation due to the partitioning logic, + * or O(n log n) if a more optimized partitioning strategy is used. + * Sorting takes O(n log n), where n is the number of unique symbols. + * + *

References: + *

+ */ +public final class ShannonFano { + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private ShannonFano() { + } + + /** + * A private inner class to represent a symbol and its frequency. + * Implements Comparable to allow sorting based on frequency. + */ + private static class Symbol implements Comparable { + final char character; + final int frequency; + String code = ""; + + Symbol(char character, int frequency) { + this.character = character; + this.frequency = frequency; + } + + @Override + public int compareTo(Symbol other) { + return Integer.compare(other.frequency, this.frequency); // Sort descending + } + } + + /** + * Generates Shannon-Fano codes for the symbols in a given text. + * + * @param text The input string for which to generate codes. Must not be null. + * @return A map where keys are characters and values are their corresponding Shannon-Fano codes. + */ + public static Map generateCodes(String text) { + if (text == null || text.isEmpty()) { + return Collections.emptyMap(); + } + + Map frequencyMap = new HashMap<>(); + for (char c : text.toCharArray()) { + frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1); + } + + List symbols = new ArrayList<>(); + for (Map.Entry entry : frequencyMap.entrySet()) { + symbols.add(new Symbol(entry.getKey(), entry.getValue())); + } + + Collections.sort(symbols); + + // Special case: only one unique symbol + if (symbols.size() == 1) { + symbols.getFirst().code = "0"; + } else { + buildCodeTree(symbols, 0, symbols.size() - 1, ""); + } + + return symbols.stream().collect(Collectors.toMap(s -> s.character, s -> s.code)); + } + + /** + * Recursively builds the Shannon-Fano code tree by partitioning the list of symbols. + * Uses index-based approach to avoid sublist creation issues. + * + * @param symbols The sorted list of symbols to be processed. + * @param start The start index of the current partition. + * @param end The end index of the current partition (inclusive). + * @param prefix The current prefix code being built for the symbols in this partition. + */ + private static void buildCodeTree(List symbols, int start, int end, String prefix) { + // The initial check in generateCodes ensures start <= end is always true here. + // The base case is when a partition has only one symbol. + if (start == end) { + symbols.get(start).code = prefix; + return; + } + + // Find the optimal split point + int splitIndex = findSplitIndex(symbols, start, end); + + // Recursively process left and right partitions with updated prefixes + buildCodeTree(symbols, start, splitIndex, prefix + "0"); + buildCodeTree(symbols, splitIndex + 1, end, prefix + "1"); + } + + /** + * Finds the index that splits the range into two parts with the most balanced frequency sums. + * This method tries every possible split point and returns the index that minimizes the + * absolute difference between the two partition sums. + * + * @param symbols The sorted list of symbols. + * @param start The start index of the range. + * @param end The end index of the range (inclusive). + * @return The index of the last element in the first partition. + */ + private static int findSplitIndex(List symbols, int start, int end) { + // Calculate total frequency for the entire range + long totalFrequency = 0; + for (int i = start; i <= end; i++) { + totalFrequency += symbols.get(i).frequency; + } + + long leftSum = 0; + long minDifference = Long.MAX_VALUE; + int splitIndex = start; + + // Try every possible split point and find the one with minimum difference + for (int i = start; i < end; i++) { + leftSum += symbols.get(i).frequency; + long rightSum = totalFrequency - leftSum; + long difference = Math.abs(leftSum - rightSum); + + if (difference < minDifference) { + minDifference = difference; + splitIndex = i; + } + } + return splitIndex; + } +} diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java new file mode 100644 index 000000000000..5219c4ba7f4e --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/Base64.java @@ -0,0 +1,196 @@ +package com.thealgorithms.conversions; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Base64 is a group of binary-to-text encoding schemes that represent binary data + * in an ASCII string format by translating it into a radix-64 representation. + * Each base64 digit represents exactly 6 bits of data. + * + * Base64 encoding is commonly used when there is a need to encode binary data + * that needs to be stored and transferred over media that are designed to deal + * with textual data. + * + * Wikipedia Reference: https://en.wikipedia.org/wiki/Base64 + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + */ + +public final class Base64 { + + // Base64 character set + private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private static final char PADDING_CHAR = '='; + + private Base64() { + } + + /** + * Encodes the given byte array to a Base64 encoded string. + * + * @param input the byte array to encode + * @return the Base64 encoded string + * @throws IllegalArgumentException if input is null + */ + public static String encode(byte[] input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + if (input.length == 0) { + return ""; + } + + StringBuilder result = new StringBuilder(); + int padding = 0; + + // Process input in groups of 3 bytes + for (int i = 0; i < input.length; i += 3) { + // Get up to 3 bytes + int byte1 = input[i] & 0xFF; + int byte2 = (i + 1 < input.length) ? (input[i + 1] & 0xFF) : 0; + int byte3 = (i + 2 < input.length) ? (input[i + 2] & 0xFF) : 0; + + // Calculate padding needed + if (i + 1 >= input.length) { + padding = 2; + } else if (i + 2 >= input.length) { + padding = 1; + } + + // Combine 3 bytes into a 24-bit number + int combined = (byte1 << 16) | (byte2 << 8) | byte3; + + // Extract four 6-bit groups + result.append(BASE64_CHARS.charAt((combined >> 18) & 0x3F)); + result.append(BASE64_CHARS.charAt((combined >> 12) & 0x3F)); + result.append(BASE64_CHARS.charAt((combined >> 6) & 0x3F)); + result.append(BASE64_CHARS.charAt(combined & 0x3F)); + } + + // Replace padding characters + if (padding > 0) { + result.setLength(result.length() - padding); + for (int i = 0; i < padding; i++) { + result.append(PADDING_CHAR); + } + } + + return result.toString(); + } + + /** + * Encodes the given string to a Base64 encoded string using UTF-8 encoding. + * + * @param input the string to encode + * @return the Base64 encoded string + * @throws IllegalArgumentException if input is null + */ + public static String encode(String input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + return encode(input.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Decodes the given Base64 encoded string to a byte array. + * + * @param input the Base64 encoded string to decode + * @return the decoded byte array + * @throws IllegalArgumentException if input is null or contains invalid Base64 characters + */ + public static byte[] decode(String input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + if (input.isEmpty()) { + return new byte[0]; + } + + // Strict RFC 4648 compliance: length must be a multiple of 4 + if (input.length() % 4 != 0) { + throw new IllegalArgumentException("Invalid Base64 input length; must be multiple of 4"); + } + + // Validate padding: '=' can only appear at the end (last 1 or 2 chars) + int firstPadding = input.indexOf('='); + if (firstPadding != -1 && firstPadding < input.length() - 2) { + throw new IllegalArgumentException("Padding '=' can only appear at the end (last 1 or 2 characters)"); + } + + List result = new ArrayList<>(); + + // Process input in groups of 4 characters + for (int i = 0; i < input.length(); i += 4) { + // Get up to 4 characters + int char1 = getBase64Value(input.charAt(i)); + int char2 = getBase64Value(input.charAt(i + 1)); + int char3 = input.charAt(i + 2) == '=' ? 0 : getBase64Value(input.charAt(i + 2)); + int char4 = input.charAt(i + 3) == '=' ? 0 : getBase64Value(input.charAt(i + 3)); + + // Combine four 6-bit groups into a 24-bit number + int combined = (char1 << 18) | (char2 << 12) | (char3 << 6) | char4; + + // Extract three 8-bit bytes + result.add((byte) ((combined >> 16) & 0xFF)); + if (input.charAt(i + 2) != '=') { + result.add((byte) ((combined >> 8) & 0xFF)); + } + if (input.charAt(i + 3) != '=') { + result.add((byte) (combined & 0xFF)); + } + } + + // Convert List to byte[] + byte[] resultArray = new byte[result.size()]; + for (int i = 0; i < result.size(); i++) { + resultArray[i] = result.get(i); + } + + return resultArray; + } + + /** + * Decodes the given Base64 encoded string to a string using UTF-8 encoding. + * + * @param input the Base64 encoded string to decode + * @return the decoded string + * @throws IllegalArgumentException if input is null or contains invalid Base64 characters + */ + public static String decodeToString(String input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + byte[] decodedBytes = decode(input); + return new String(decodedBytes, StandardCharsets.UTF_8); + } + + /** + * Gets the numeric value of a Base64 character. + * + * @param c the Base64 character + * @return the numeric value (0-63) + * @throws IllegalArgumentException if character is not a valid Base64 character + */ + private static int getBase64Value(char c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return c - 'a' + 26; + } else if (c >= '0' && c <= '9') { + return c - '0' + 52; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } else { + throw new IllegalArgumentException("Invalid Base64 character: " + c); + } + } +} diff --git a/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java b/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java index 36c0790e565f..b3783ab284b2 100644 --- a/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java +++ b/src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java @@ -30,4 +30,30 @@ public static long binaryToDecimal(long binaryNumber) { } return decimalValue; } + + /** + * Converts a binary String to its decimal equivalent using bitwise operators. + * + * @param binary The binary number to convert. + * @return The decimal equivalent of the binary number. + * @throws IllegalArgumentException If the binary number contains digits other than 0 and 1. + */ + public static long binaryStringToDecimal(String binary) { + boolean isNegative = binary.charAt(0) == '-'; + if (isNegative) { + binary = binary.substring(1); + } + + long decimalValue = 0; + + for (char bit : binary.toCharArray()) { + if (bit != '0' && bit != '1') { + throw new IllegalArgumentException("Incorrect binary digit: " + bit); + } + // shift left by 1 (multiply by 2) and add bit value + decimalValue = (decimalValue << 1) | (bit - '0'); + } + + return isNegative ? -decimalValue : decimalValue; + } } diff --git a/src/main/java/com/thealgorithms/conversions/CoordinateConverter.java b/src/main/java/com/thealgorithms/conversions/CoordinateConverter.java new file mode 100644 index 000000000000..2766a3a1cf89 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/CoordinateConverter.java @@ -0,0 +1,57 @@ +package com.thealgorithms.conversions; + +/** + * A utility class to convert between Cartesian and Polar coordinate systems. + * + *

This class provides methods to perform the following conversions: + *

    + *
  • Cartesian to Polar coordinates
  • + *
  • Polar to Cartesian coordinates
  • + *
+ * + *

The class is final and cannot be instantiated. + */ +public final class CoordinateConverter { + + private CoordinateConverter() { + // Prevent instantiation + } + + /** + * Converts Cartesian coordinates to Polar coordinates. + * + * @param x the x-coordinate in the Cartesian system; must be a finite number + * @param y the y-coordinate in the Cartesian system; must be a finite number + * @return an array where the first element is the radius (r) and the second element is the angle (theta) in degrees + * @throws IllegalArgumentException if x or y is not a finite number + */ + public static double[] cartesianToPolar(double x, double y) { + if (!Double.isFinite(x) || !Double.isFinite(y)) { + throw new IllegalArgumentException("x and y must be finite numbers."); + } + double r = Math.sqrt(x * x + y * y); + double theta = Math.toDegrees(Math.atan2(y, x)); + return new double[] {r, theta}; + } + + /** + * Converts Polar coordinates to Cartesian coordinates. + * + * @param r the radius in the Polar system; must be non-negative + * @param thetaDegrees the angle (theta) in degrees in the Polar system; must be a finite number + * @return an array where the first element is the x-coordinate and the second element is the y-coordinate in the Cartesian system + * @throws IllegalArgumentException if r is negative or thetaDegrees is not a finite number + */ + public static double[] polarToCartesian(double r, double thetaDegrees) { + if (r < 0) { + throw new IllegalArgumentException("Radius (r) must be non-negative."); + } + if (!Double.isFinite(thetaDegrees)) { + throw new IllegalArgumentException("Theta (angle) must be a finite number."); + } + double theta = Math.toRadians(thetaDegrees); + double x = r * Math.cos(theta); + double y = r * Math.sin(theta); + return new double[] {x, y}; + } +} diff --git a/src/main/java/com/thealgorithms/conversions/TimeConverter.java b/src/main/java/com/thealgorithms/conversions/TimeConverter.java new file mode 100644 index 000000000000..41cae37d7ad1 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/TimeConverter.java @@ -0,0 +1,97 @@ +package com.thealgorithms.conversions; + +import java.util.Locale; +import java.util.Map; + +/** + * A utility class to convert between different units of time. + * + *

This class supports conversions between the following units: + *

    + *
  • seconds
  • + *
  • minutes
  • + *
  • hours
  • + *
  • days
  • + *
  • weeks
  • + *
  • months (approximated as 30.44 days)
  • + *
  • years (approximated as 365.25 days)
  • + *
+ * + *

The conversion is based on predefined constants in seconds. + * Results are rounded to three decimal places for consistency. + * + *

This class is final and cannot be instantiated. + * + * @see Wikipedia: Unit of time + */ +public final class TimeConverter { + + private TimeConverter() { + // Prevent instantiation + } + + /** + * Supported time units with their equivalent in seconds. + */ + private enum TimeUnit { + SECONDS(1.0), + MINUTES(60.0), + HOURS(3600.0), + DAYS(86400.0), + WEEKS(604800.0), + MONTHS(2629800.0), // 30.44 days + YEARS(31557600.0); // 365.25 days + + private final double seconds; + + TimeUnit(double seconds) { + this.seconds = seconds; + } + + public double toSeconds(double value) { + return value * seconds; + } + + public double fromSeconds(double secondsValue) { + return secondsValue / seconds; + } + } + + private static final Map UNIT_LOOKUP + = Map.ofEntries(Map.entry("seconds", TimeUnit.SECONDS), Map.entry("minutes", TimeUnit.MINUTES), Map.entry("hours", TimeUnit.HOURS), Map.entry("days", TimeUnit.DAYS), Map.entry("weeks", TimeUnit.WEEKS), Map.entry("months", TimeUnit.MONTHS), Map.entry("years", TimeUnit.YEARS)); + + /** + * Converts a time value from one unit to another. + * + * @param timeValue the numeric value of time to convert; must be non-negative + * @param unitFrom the unit of the input value (e.g., "minutes", "hours") + * @param unitTo the unit to convert into (e.g., "seconds", "days") + * @return the converted value in the target unit, rounded to three decimals + * @throws IllegalArgumentException if {@code timeValue} is negative + * @throws IllegalArgumentException if either {@code unitFrom} or {@code unitTo} is not supported + */ + public static double convertTime(double timeValue, String unitFrom, String unitTo) { + if (timeValue < 0) { + throw new IllegalArgumentException("timeValue must be a non-negative number."); + } + + TimeUnit from = resolveUnit(unitFrom); + TimeUnit to = resolveUnit(unitTo); + + double secondsValue = from.toSeconds(timeValue); + double converted = to.fromSeconds(secondsValue); + + return Math.round(converted * 1000.0) / 1000.0; + } + + private static TimeUnit resolveUnit(String unit) { + if (unit == null) { + throw new IllegalArgumentException("Unit cannot be null."); + } + TimeUnit resolved = UNIT_LOOKUP.get(unit.toLowerCase(Locale.ROOT)); + if (resolved == null) { + throw new IllegalArgumentException("Invalid unit '" + unit + "'. Supported units are: " + UNIT_LOOKUP.keySet()); + } + return resolved; + } +} diff --git a/src/main/java/com/thealgorithms/conversions/WordsToNumber.java b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java new file mode 100644 index 000000000000..e2b81a0f4b47 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/WordsToNumber.java @@ -0,0 +1,343 @@ +package com.thealgorithms.conversions; + +import java.io.Serial; +import java.math.BigDecimal; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + A Java-based utility for converting English word representations of numbers + into their numeric form. This utility supports whole numbers, decimals, + large values up to trillions, and even scientific notation where applicable. + It ensures accurate parsing while handling edge cases like negative numbers, + improper word placements, and ambiguous inputs. + * + */ + +public final class WordsToNumber { + + private WordsToNumber() { + } + + private enum NumberWord { + ZERO("zero", 0), + ONE("one", 1), + TWO("two", 2), + THREE("three", 3), + FOUR("four", 4), + FIVE("five", 5), + SIX("six", 6), + SEVEN("seven", 7), + EIGHT("eight", 8), + NINE("nine", 9), + TEN("ten", 10), + ELEVEN("eleven", 11), + TWELVE("twelve", 12), + THIRTEEN("thirteen", 13), + FOURTEEN("fourteen", 14), + FIFTEEN("fifteen", 15), + SIXTEEN("sixteen", 16), + SEVENTEEN("seventeen", 17), + EIGHTEEN("eighteen", 18), + NINETEEN("nineteen", 19), + TWENTY("twenty", 20), + THIRTY("thirty", 30), + FORTY("forty", 40), + FIFTY("fifty", 50), + SIXTY("sixty", 60), + SEVENTY("seventy", 70), + EIGHTY("eighty", 80), + NINETY("ninety", 90); + + private final String word; + private final int value; + + NumberWord(String word, int value) { + this.word = word; + this.value = value; + } + + public static Integer getValue(String word) { + for (NumberWord num : values()) { + if (word.equals(num.word)) { + return num.value; + } + } + return null; + } + } + + private enum PowerOfTen { + THOUSAND("thousand", new BigDecimal("1000")), + MILLION("million", new BigDecimal("1000000")), + BILLION("billion", new BigDecimal("1000000000")), + TRILLION("trillion", new BigDecimal("1000000000000")); + + private final String word; + private final BigDecimal value; + + PowerOfTen(String word, BigDecimal value) { + this.word = word; + this.value = value; + } + + public static BigDecimal getValue(String word) { + for (PowerOfTen power : values()) { + if (word.equals(power.word)) { + return power.value; + } + } + return null; + } + } + + public static String convert(String numberInWords) { + if (numberInWords == null) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, ""); + } + + ArrayDeque wordDeque = preprocessWords(numberInWords); + BigDecimal completeNumber = convertWordQueueToBigDecimal(wordDeque); + + return completeNumber.toString(); + } + + public static BigDecimal convertToBigDecimal(String numberInWords) { + String conversionResult = convert(numberInWords); + return new BigDecimal(conversionResult); + } + + private static ArrayDeque preprocessWords(String numberInWords) { + String[] wordSplitArray = numberInWords.trim().split("[ ,-]"); + ArrayDeque wordDeque = new ArrayDeque<>(); + for (String word : wordSplitArray) { + if (word.isEmpty()) { + continue; + } + wordDeque.add(word.toLowerCase()); + } + if (wordDeque.isEmpty()) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.NULL_INPUT, ""); + } + return wordDeque; + } + + private static void handleConjunction(boolean prevNumWasHundred, boolean prevNumWasPowerOfTen, ArrayDeque wordDeque) { + if (wordDeque.isEmpty()) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, ""); + } + + String nextWord = wordDeque.pollFirst(); + String afterNextWord = wordDeque.peekFirst(); + + wordDeque.addFirst(nextWord); + + Integer number = NumberWord.getValue(nextWord); + + boolean isPrevWordValid = prevNumWasHundred || prevNumWasPowerOfTen; + boolean isNextWordValid = number != null && (number >= 10 || afterNextWord == null || "point".equals(afterNextWord)); + + if (!isPrevWordValid || !isNextWordValid) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, ""); + } + } + + private static BigDecimal handleHundred(BigDecimal currentChunk, String word, boolean prevNumWasPowerOfTen) { + boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0; + if (currentChunk.compareTo(BigDecimal.TEN) >= 0 || prevNumWasPowerOfTen) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + if (currentChunkIsZero) { + currentChunk = currentChunk.add(BigDecimal.ONE); + } + return currentChunk.multiply(BigDecimal.valueOf(100)); + } + + private static void handlePowerOfTen(List chunks, BigDecimal currentChunk, BigDecimal powerOfTen, String word, boolean prevNumWasPowerOfTen) { + boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0; + if (currentChunkIsZero || prevNumWasPowerOfTen) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + BigDecimal nextChunk = currentChunk.multiply(powerOfTen); + + if (!(chunks.isEmpty() || isAdditionSafe(chunks.getLast(), nextChunk))) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + chunks.add(nextChunk); + } + + private static BigDecimal handleNumber(Collection chunks, BigDecimal currentChunk, String word, Integer number) { + boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0; + if (number == 0 && !(currentChunkIsZero && chunks.isEmpty())) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + BigDecimal bigDecimalNumber = BigDecimal.valueOf(number); + + if (!currentChunkIsZero && !isAdditionSafe(currentChunk, bigDecimalNumber)) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD, word); + } + return currentChunk.add(bigDecimalNumber); + } + + private static void handlePoint(Collection chunks, BigDecimal currentChunk, ArrayDeque wordDeque) { + boolean currentChunkIsZero = currentChunk.compareTo(BigDecimal.ZERO) == 0; + if (!currentChunkIsZero) { + chunks.add(currentChunk); + } + + String decimalPart = convertDecimalPart(wordDeque); + chunks.add(new BigDecimal(decimalPart)); + } + + private static void handleNegative(boolean isNegative) { + if (isNegative) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, ""); + } + throw new WordsToNumberException(WordsToNumberException.ErrorType.INVALID_NEGATIVE, ""); + } + + private static BigDecimal convertWordQueueToBigDecimal(ArrayDeque wordDeque) { + BigDecimal currentChunk = BigDecimal.ZERO; + List chunks = new ArrayList<>(); + + boolean isNegative = "negative".equals(wordDeque.peek()); + if (isNegative) { + wordDeque.poll(); + } + + boolean prevNumWasHundred = false; + boolean prevNumWasPowerOfTen = false; + + while (!wordDeque.isEmpty()) { + String word = wordDeque.poll(); + + switch (word) { + case "and" -> { + handleConjunction(prevNumWasHundred, prevNumWasPowerOfTen, wordDeque); + continue; + } + case "hundred" -> { + currentChunk = handleHundred(currentChunk, word, prevNumWasPowerOfTen); + prevNumWasHundred = true; + continue; + } + default -> { + + } + } + prevNumWasHundred = false; + + BigDecimal powerOfTen = PowerOfTen.getValue(word); + if (powerOfTen != null) { + handlePowerOfTen(chunks, currentChunk, powerOfTen, word, prevNumWasPowerOfTen); + currentChunk = BigDecimal.ZERO; + prevNumWasPowerOfTen = true; + continue; + } + prevNumWasPowerOfTen = false; + + Integer number = NumberWord.getValue(word); + if (number != null) { + currentChunk = handleNumber(chunks, currentChunk, word, number); + continue; + } + + switch (word) { + case "point" -> { + handlePoint(chunks, currentChunk, wordDeque); + currentChunk = BigDecimal.ZERO; + continue; + } + case "negative" -> { + handleNegative(isNegative); + } + default -> { + + } + } + + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNKNOWN_WORD, word); + } + + if (currentChunk.compareTo(BigDecimal.ZERO) != 0) { + chunks.add(currentChunk); + } + + BigDecimal completeNumber = combineChunks(chunks); + return isNegative ? completeNumber.multiply(BigDecimal.valueOf(-1)) + : + completeNumber; + } + + private static boolean isAdditionSafe(BigDecimal currentChunk, BigDecimal number) { + int chunkDigitCount = currentChunk.toString().length(); + int numberDigitCount = number.toString().length(); + return chunkDigitCount > numberDigitCount; + } + + private static String convertDecimalPart(ArrayDeque wordDeque) { + StringBuilder decimalPart = new StringBuilder("."); + + while (!wordDeque.isEmpty()) { + String word = wordDeque.poll(); + Integer number = NumberWord.getValue(word); + if (number == null) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, word); + } + decimalPart.append(number); + } + + boolean missingNumbers = decimalPart.length() == 1; + if (missingNumbers) { + throw new WordsToNumberException(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, ""); + } + return decimalPart.toString(); + } + + private static BigDecimal combineChunks(List chunks) { + BigDecimal completeNumber = BigDecimal.ZERO; + for (BigDecimal chunk : chunks) { + completeNumber = completeNumber.add(chunk); + } + return completeNumber; + } + } + + class WordsToNumberException extends RuntimeException { + + @Serial private static final long serialVersionUID = 1L; + + enum ErrorType { + NULL_INPUT("'null' or empty input provided"), + UNKNOWN_WORD("Unknown Word: "), + UNEXPECTED_WORD("Unexpected Word: "), + UNEXPECTED_WORD_AFTER_POINT("Unexpected Word (after Point): "), + MISSING_DECIMAL_NUMBERS("Decimal part is missing numbers."), + MULTIPLE_NEGATIVES("Multiple 'Negative's detected."), + INVALID_NEGATIVE("Incorrect 'negative' placement"), + INVALID_CONJUNCTION("Incorrect 'and' placement"); + + private final String message; + + ErrorType(String message) { + this.message = message; + } + + public String formatMessage(String details) { + return "Invalid Input. " + message + (details.isEmpty() ? "" : details); + } + } + + public final ErrorType errorType; + + WordsToNumberException(ErrorType errorType, String details) { + super(errorType.formatMessage(details)); + this.errorType = errorType; + } + + public ErrorType getErrorType() { + return errorType; + } + } diff --git a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java index a2edd3db2d8e..90625ad1c902 100644 --- a/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java +++ b/src/main/java/com/thealgorithms/datastructures/bloomfilter/BloomFilter.java @@ -1,17 +1,21 @@ package com.thealgorithms.datastructures.bloomfilter; +import java.util.Arrays; import java.util.BitSet; /** * A generic BloomFilter implementation for probabilistic membership checking. *

- * Bloom filters are space-efficient data structures that provide a fast way to test whether an - * element is a member of a set. They may produce false positives, indicating an element is + * Bloom filters are space-efficient data structures that provide a fast way to + * test whether an + * element is a member of a set. They may produce false positives, indicating an + * element is * in the set when it is not, but they will never produce false negatives. *

* * @param The type of elements to be stored in the Bloom filter. */ +@SuppressWarnings("rawtypes") public class BloomFilter { private final int numberOfHashFunctions; @@ -19,11 +23,14 @@ public class BloomFilter { private final Hash[] hashFunctions; /** - * Constructs a BloomFilter with a specified number of hash functions and bit array size. + * Constructs a BloomFilter with a specified number of hash functions and bit + * array size. * * @param numberOfHashFunctions the number of hash functions to use - * @param bitArraySize the size of the bit array, which determines the capacity of the filter - * @throws IllegalArgumentException if numberOfHashFunctions or bitArraySize is less than 1 + * @param bitArraySize the size of the bit array, which determines the + * capacity of the filter + * @throws IllegalArgumentException if numberOfHashFunctions or bitArraySize is + * less than 1 */ @SuppressWarnings("unchecked") public BloomFilter(int numberOfHashFunctions, int bitArraySize) { @@ -37,7 +44,8 @@ public BloomFilter(int numberOfHashFunctions, int bitArraySize) { } /** - * Initializes the hash functions with unique indices to ensure different hashing. + * Initializes the hash functions with unique indices to ensure different + * hashing. */ private void initializeHashFunctions() { for (int i = 0; i < numberOfHashFunctions; i++) { @@ -48,7 +56,8 @@ private void initializeHashFunctions() { /** * Inserts an element into the Bloom filter. *

- * This method hashes the element using all defined hash functions and sets the corresponding + * This method hashes the element using all defined hash functions and sets the + * corresponding * bits in the bit array. *

* @@ -64,13 +73,16 @@ public void insert(T key) { /** * Checks if an element might be in the Bloom filter. *

- * This method checks the bits at the positions computed by each hash function. If any of these - * bits are not set, the element is definitely not in the filter. If all bits are set, the element + * This method checks the bits at the positions computed by each hash function. + * If any of these + * bits are not set, the element is definitely not in the filter. If all bits + * are set, the element * might be in the filter. *

* * @param key the element to check for membership in the Bloom filter - * @return {@code true} if the element might be in the Bloom filter, {@code false} if it is definitely not + * @return {@code true} if the element might be in the Bloom filter, + * {@code false} if it is definitely not */ public boolean contains(T key) { for (Hash hash : hashFunctions) { @@ -85,7 +97,8 @@ public boolean contains(T key) { /** * Inner class representing a hash function used by the Bloom filter. *

- * Each instance of this class represents a different hash function based on its index. + * Each instance of this class represents a different hash function based on its + * index. *

* * @param The type of elements to be hashed. @@ -114,7 +127,7 @@ private static class Hash { * @return the computed hash value */ public int compute(T key) { - return index * asciiString(String.valueOf(key)); + return index * contentHash(key); } /** @@ -134,5 +147,31 @@ private int asciiString(String word) { } return sum; } + + /** + * Computes a content-based hash for arrays; falls back to ASCII-sum of String value otherwise. + */ + private int contentHash(Object key) { + if (key instanceof int[]) { + return Arrays.hashCode((int[]) key); + } else if (key instanceof long[]) { + return Arrays.hashCode((long[]) key); + } else if (key instanceof byte[]) { + return Arrays.hashCode((byte[]) key); + } else if (key instanceof short[]) { + return Arrays.hashCode((short[]) key); + } else if (key instanceof char[]) { + return Arrays.hashCode((char[]) key); + } else if (key instanceof boolean[]) { + return Arrays.hashCode((boolean[]) key); + } else if (key instanceof float[]) { + return Arrays.hashCode((float[]) key); + } else if (key instanceof double[]) { + return Arrays.hashCode((double[]) key); + } else if (key instanceof Object[]) { + return Arrays.deepHashCode((Object[]) key); + } + return asciiString(String.valueOf(key)); + } } } diff --git a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java index b709e16fd1f6..3b89c2119ae0 100644 --- a/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java +++ b/src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java @@ -10,6 +10,7 @@ * * @param The type of elements stored in the circular buffer. */ +@SuppressWarnings("unchecked") public class CircularBuffer { private final Item[] buffer; private final CircularPointer putPointer; diff --git a/src/main/java/com/thealgorithms/datastructures/caches/FIFOCache.java b/src/main/java/com/thealgorithms/datastructures/caches/FIFOCache.java new file mode 100644 index 000000000000..fa048434a187 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/caches/FIFOCache.java @@ -0,0 +1,549 @@ +package com.thealgorithms.datastructures.caches; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiConsumer; + +/** + * A thread-safe generic cache implementation using the First-In-First-Out eviction policy. + *

+ * The cache holds a fixed number of entries, defined by its capacity. When the cache is full and a + * new entry is added, the oldest entry in the cache is selected and evicted to make space. + *

+ * Optionally, entries can have a time-to-live (TTL) in milliseconds. If a TTL is set, entries will + * automatically expire and be removed upon access or insertion attempts. + *

+ * Features: + *

    + *
  • Removes oldest entry when capacity is exceeded
  • + *
  • Optional TTL (time-to-live in milliseconds) per entry or default TTL for all entries
  • + *
  • Thread-safe access using locking
  • + *
  • Hit and miss counters for cache statistics
  • + *
  • Eviction listener callback support
  • + *
+ * + * @param the type of keys maintained by this cache + * @param the type of mapped values + * See FIFO + * @author Kevin Babu (GitHub) + */ +public final class FIFOCache { + + private final int capacity; + private final long defaultTTL; + private final Map> cache; + private final Lock lock; + + private long hits = 0; + private long misses = 0; + private final BiConsumer evictionListener; + private final EvictionStrategy evictionStrategy; + + /** + * Internal structure to store value + expiry timestamp. + * + * @param the type of the value being cached + */ + private static class CacheEntry { + V value; + long expiryTime; + + /** + * Constructs a new {@code CacheEntry} with the specified value and time-to-live (TTL). + * If TTL is 0, the entry is kept indefinitely, that is, unless it is the first value, + * then it will be removed according to the FIFO principle + * + * @param value the value to cache + * @param ttlMillis the time-to-live in milliseconds + */ + CacheEntry(V value, long ttlMillis) { + this.value = value; + if (ttlMillis == 0) { + this.expiryTime = Long.MAX_VALUE; + } else { + this.expiryTime = System.currentTimeMillis() + ttlMillis; + } + } + + /** + * Checks if the cache entry has expired. + * + * @return {@code true} if the current time is past the expiration time; {@code false} otherwise + */ + boolean isExpired() { + return System.currentTimeMillis() > expiryTime; + } + } + + /** + * Constructs a new {@code FIFOCache} instance using the provided {@link Builder}. + * + *

This constructor initializes the cache with the specified capacity and default TTL, + * sets up internal data structures (a {@code LinkedHashMap} for cache entries and configures eviction. + * + * @param builder the {@code Builder} object containing configuration parameters + */ + private FIFOCache(Builder builder) { + this.capacity = builder.capacity; + this.defaultTTL = builder.defaultTTL; + this.cache = new LinkedHashMap<>(); + this.lock = new ReentrantLock(); + this.evictionListener = builder.evictionListener; + this.evictionStrategy = builder.evictionStrategy; + } + + /** + * Retrieves the value associated with the specified key from the cache. + * + *

If the key is not present or the corresponding entry has expired, this method + * returns {@code null}. If an expired entry is found, it will be removed and the + * eviction listener (if any) will be notified. Cache hit-and-miss statistics are + * also updated accordingly. + * + * @param key the key whose associated value is to be returned; must not be {@code null} + * @return the cached value associated with the key, or {@code null} if not present or expired + * @throws IllegalArgumentException if {@code key} is {@code null} + */ + public V get(K key) { + if (key == null) { + throw new IllegalArgumentException("Key must not be null"); + } + + lock.lock(); + try { + evictionStrategy.onAccess(this); + + CacheEntry entry = cache.get(key); + if (entry == null || entry.isExpired()) { + if (entry != null) { + cache.remove(key); + notifyEviction(key, entry.value); + } + misses++; + return null; + } + hits++; + return entry.value; + } finally { + lock.unlock(); + } + } + + /** + * Adds a key-value pair to the cache using the default time-to-live (TTL). + * + *

The key may overwrite an existing entry. The actual insertion is delegated + * to the overloaded {@link #put(K, V, long)} method. + * + * @param key the key to cache the value under + * @param value the value to be cached + */ + public void put(K key, V value) { + put(key, value, defaultTTL); + } + + /** + * Adds a key-value pair to the cache with a specified time-to-live (TTL). + * + *

If the key already exists, its value is removed, re-inserted at tail and its TTL is reset. + * If the key does not exist and the cache is full, the oldest entry is evicted to make space. + * Expired entries are also cleaned up prior to any eviction. The eviction listener + * is notified when an entry gets evicted. + * + * @param key the key to associate with the cached value; must not be {@code null} + * @param value the value to be cached; must not be {@code null} + * @param ttlMillis the time-to-live for this entry in milliseconds; must be >= 0 + * @throws IllegalArgumentException if {@code key} or {@code value} is {@code null}, or if {@code ttlMillis} is negative + */ + public void put(K key, V value, long ttlMillis) { + if (key == null || value == null) { + throw new IllegalArgumentException("Key and value must not be null"); + } + if (ttlMillis < 0) { + throw new IllegalArgumentException("TTL must be >= 0"); + } + + lock.lock(); + try { + // If key already exists, remove it + CacheEntry oldEntry = cache.remove(key); + if (oldEntry != null && !oldEntry.isExpired()) { + notifyEviction(key, oldEntry.value); + } + + // Evict expired entries to make space for new entry + evictExpired(); + + // If no expired entry was removed, remove the oldest + if (cache.size() >= capacity) { + Iterator>> it = cache.entrySet().iterator(); + if (it.hasNext()) { + Map.Entry> eldest = it.next(); + it.remove(); + notifyEviction(eldest.getKey(), eldest.getValue().value); + } + } + + // Insert new entry at tail + cache.put(key, new CacheEntry<>(value, ttlMillis)); + } finally { + lock.unlock(); + } + } + + /** + * Removes all expired entries from the cache. + * + *

This method iterates through the list of cached keys and checks each associated + * entry for expiration. Expired entries are removed the cache map. For each eviction, + * the eviction listener is notified. + */ + private int evictExpired() { + int count = 0; + Iterator>> it = cache.entrySet().iterator(); + + while (it.hasNext()) { + Map.Entry> entry = it.next(); + if (entry != null && entry.getValue().isExpired()) { + it.remove(); + notifyEviction(entry.getKey(), entry.getValue().value); + count++; + } + } + + return count; + } + + /** + * Removes the specified key and its associated entry from the cache. + * + * @param key the key to remove from the cache; + * @return the value associated with the key; or {@code null} if no such key exists + */ + public V removeKey(K key) { + if (key == null) { + throw new IllegalArgumentException("Key cannot be null"); + } + CacheEntry entry = cache.remove(key); + + // No such key in cache + if (entry == null) { + return null; + } + + notifyEviction(key, entry.value); + return entry.value; + } + + /** + * Notifies the eviction listener, if one is registered, that a key-value pair has been evicted. + * + *

If the {@code evictionListener} is not {@code null}, it is invoked with the provided key + * and value. Any exceptions thrown by the listener are caught and logged to standard error, + * preventing them from disrupting cache operations. + * + * @param key the key that was evicted + * @param value the value that was associated with the evicted key + */ + private void notifyEviction(K key, V value) { + if (evictionListener != null) { + try { + evictionListener.accept(key, value); + } catch (Exception e) { + System.err.println("Eviction listener failed: " + e.getMessage()); + } + } + } + + /** + * Returns the number of successful cache lookups (hits). + * + * @return the number of cache hits + */ + public long getHits() { + lock.lock(); + try { + return hits; + } finally { + lock.unlock(); + } + } + + /** + * Returns the number of failed cache lookups (misses), including expired entries. + * + * @return the number of cache misses + */ + public long getMisses() { + lock.lock(); + try { + return misses; + } finally { + lock.unlock(); + } + } + + /** + * Returns the current number of entries in the cache, excluding expired ones. + * + * @return the current cache size + */ + public int size() { + lock.lock(); + try { + evictionStrategy.onAccess(this); + + int count = 0; + for (CacheEntry entry : cache.values()) { + if (!entry.isExpired()) { + ++count; + } + } + return count; + } finally { + lock.unlock(); + } + } + + /** + * Removes all entries from the cache, regardless of their expiration status. + * + *

This method clears the internal cache map entirely, resets the hit-and-miss counters, + * and notifies the eviction listener (if any) for each removed entry. + * Note that expired entries are treated the same as active ones for the purpose of clearing. + * + *

This operation acquires the internal lock to ensure thread safety. + */ + public void clear() { + lock.lock(); + try { + for (Map.Entry> entry : cache.entrySet()) { + notifyEviction(entry.getKey(), entry.getValue().value); + } + cache.clear(); + hits = 0; + misses = 0; + } finally { + lock.unlock(); + } + } + + /** + * Returns a set of all keys currently stored in the cache that have not expired. + * + *

This method iterates through the cache and collects the keys of all non-expired entries. + * Expired entries are ignored but not removed. If you want to ensure expired entries are cleaned up, + * consider invoking {@link EvictionStrategy#onAccess(FIFOCache)} or calling {@link #evictExpired()} manually. + * + *

This operation acquires the internal lock to ensure thread safety. + * + * @return a set containing all non-expired keys currently in the cache + */ + public Set getAllKeys() { + lock.lock(); + try { + Set keys = new LinkedHashSet<>(); + + for (Map.Entry> entry : cache.entrySet()) { + if (!entry.getValue().isExpired()) { + keys.add(entry.getKey()); + } + } + + return keys; + } finally { + lock.unlock(); + } + } + + /** + * Returns the current {@link EvictionStrategy} used by this cache instance. + + * @return the eviction strategy currently assigned to this cache + */ + public EvictionStrategy getEvictionStrategy() { + return evictionStrategy; + } + + /** + * Returns a string representation of the cache, including metadata and current non-expired entries. + * + *

The returned string includes the cache's capacity, current size (excluding expired entries), + * hit-and-miss counts, and a map of all non-expired key-value pairs. This method acquires a lock + * to ensure thread-safe access. + * + * @return a string summarizing the state of the cache + */ + @Override + public String toString() { + lock.lock(); + try { + Map visible = new LinkedHashMap<>(); + for (Map.Entry> entry : cache.entrySet()) { + if (!entry.getValue().isExpired()) { + visible.put(entry.getKey(), entry.getValue().value); + } + } + return String.format("Cache(capacity=%d, size=%d, hits=%d, misses=%d, entries=%s)", capacity, visible.size(), hits, misses, visible); + } finally { + lock.unlock(); + } + } + + /** + * A strategy interface for controlling when expired entries are evicted from the cache. + * + *

Implementations decide whether and when to trigger {@link FIFOCache#evictExpired()} based + * on cache usage patterns. This allows for flexible eviction behaviour such as periodic cleanup, + * or no automatic cleanup. + * + * @param the type of keys maintained by the cache + * @param the type of cached values + */ + public interface EvictionStrategy { + /** + * Called on each cache access (e.g., {@link FIFOCache#get(Object)}) to optionally trigger eviction. + * + * @param cache the cache instance on which this strategy is applied + * @return the number of expired entries evicted during this access + */ + int onAccess(FIFOCache cache); + } + + /** + * An eviction strategy that performs eviction of expired entries on each call. + * + * @param the type of keys + * @param the type of values + */ + public static class ImmediateEvictionStrategy implements EvictionStrategy { + @Override + public int onAccess(FIFOCache cache) { + return cache.evictExpired(); + } + } + + /** + * An eviction strategy that triggers eviction on every fixed number of accesses. + * + *

This deterministic strategy ensures cleanup occurs at predictable intervals, + * ideal for moderately active caches where memory usage is a concern. + * + * @param the type of keys + * @param the type of values + */ + public static class PeriodicEvictionStrategy implements EvictionStrategy { + private final int interval; + private final AtomicInteger counter = new AtomicInteger(); + + /** + * Constructs a periodic eviction strategy. + * + * @param interval the number of accesses between evictions; must be > 0 + * @throws IllegalArgumentException if {@code interval} is less than or equal to 0 + */ + public PeriodicEvictionStrategy(int interval) { + if (interval <= 0) { + throw new IllegalArgumentException("Interval must be > 0"); + } + this.interval = interval; + } + + @Override + public int onAccess(FIFOCache cache) { + if (counter.incrementAndGet() % interval == 0) { + return cache.evictExpired(); + } + + return 0; + } + } + + /** + * A builder for constructing a {@link FIFOCache} instance with customizable settings. + * + *

Allows configuring capacity, default TTL, eviction listener, and a pluggable eviction + * strategy. Call {@link #build()} to create the configured cache instance. + * + * @param the type of keys maintained by the cache + * @param the type of values stored in the cache + */ + public static class Builder { + private final int capacity; + private long defaultTTL = 0; + private BiConsumer evictionListener; + private EvictionStrategy evictionStrategy = new FIFOCache.ImmediateEvictionStrategy<>(); + /** + * Creates a new {@code Builder} with the specified cache capacity. + * + * @param capacity the maximum number of entries the cache can hold; must be > 0 + * @throws IllegalArgumentException if {@code capacity} is less than or equal to 0 + */ + public Builder(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be > 0"); + } + this.capacity = capacity; + } + + /** + * Sets the default time-to-live (TTL) in milliseconds for cache entries. + * + * @param ttlMillis the TTL duration in milliseconds; must be >= 0 + * @return this builder instance for chaining + * @throws IllegalArgumentException if {@code ttlMillis} is negative + */ + public Builder defaultTTL(long ttlMillis) { + if (ttlMillis < 0) { + throw new IllegalArgumentException("Default TTL must be >= 0"); + } + this.defaultTTL = ttlMillis; + return this; + } + + /** + * Sets an eviction listener to be notified when entries are evicted from the cache. + * + * @param listener a {@link BiConsumer} that accepts evicted keys and values; must not be {@code null} + * @return this builder instance for chaining + * @throws IllegalArgumentException if {@code listener} is {@code null} + */ + public Builder evictionListener(BiConsumer listener) { + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + this.evictionListener = listener; + return this; + } + + /** + * Builds and returns a new {@link FIFOCache} instance with the configured parameters. + * + * @return a fully configured {@code FIFOCache} instance + */ + public FIFOCache build() { + return new FIFOCache<>(this); + } + + /** + * Sets the eviction strategy used to determine when to clean up expired entries. + * + * @param strategy an {@link EvictionStrategy} implementation; must not be {@code null} + * @return this builder instance + * @throws IllegalArgumentException if {@code strategy} is {@code null} + */ + public Builder evictionStrategy(EvictionStrategy strategy) { + if (strategy == null) { + throw new IllegalArgumentException("Eviction strategy must not be null"); + } + this.evictionStrategy = strategy; + return this; + } + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java index f0d8ea8f7ff3..bf2b928ec33c 100644 --- a/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java +++ b/src/main/java/com/thealgorithms/datastructures/caches/LFUCache.java @@ -132,7 +132,7 @@ public void put(K key, V value) { private void addNodeWithUpdatedFrequency(Node node) { if (tail != null && head != null) { Node temp = this.head; - while (temp != null) { + while (true) { if (temp.frequency > node.frequency) { if (temp == head) { node.next = temp; diff --git a/src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java b/src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java new file mode 100644 index 000000000000..df3d4da912fe --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/caches/LIFOCache.java @@ -0,0 +1,563 @@ +package com.thealgorithms.datastructures.caches; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiConsumer; + +/** + * A thread-safe generic cache implementation using the Last-In-First-Out eviction policy. + *

+ * The cache holds a fixed number of entries, defined by its capacity. When the cache is full and a + * new entry is added, the youngest entry in the cache is selected and evicted to make space. + *

+ * Optionally, entries can have a time-to-live (TTL) in milliseconds. If a TTL is set, entries will + * automatically expire and be removed upon access or insertion attempts. + *

+ * Features: + *

    + *
  • Removes youngest entry when capacity is exceeded
  • + *
  • Optional TTL (time-to-live in milliseconds) per entry or default TTL for all entries
  • + *
  • Thread-safe access using locking
  • + *
  • Hit and miss counters for cache statistics
  • + *
  • Eviction listener callback support
  • + *
+ * + * @param the type of keys maintained by this cache + * @param the type of mapped values + * See LIFO + * @author Kevin Babu (GitHub) + */ +public final class LIFOCache { + + private final int capacity; + private final long defaultTTL; + private final Map> cache; + private final Lock lock; + private final Deque keys; + private long hits = 0; + private long misses = 0; + private final BiConsumer evictionListener; + private final EvictionStrategy evictionStrategy; + + /** + * Internal structure to store value + expiry timestamp. + * + * @param the type of the value being cached + */ + private static class CacheEntry { + V value; + long expiryTime; + + /** + * Constructs a new {@code CacheEntry} with the specified value and time-to-live (TTL). + * If TTL is 0, the entry is kept indefinitely, that is, unless it is the first value, + * then it will be removed according to the LIFO principle + * + * @param value the value to cache + * @param ttlMillis the time-to-live in milliseconds + */ + CacheEntry(V value, long ttlMillis) { + this.value = value; + if (ttlMillis == 0) { + this.expiryTime = Long.MAX_VALUE; + } else { + this.expiryTime = System.currentTimeMillis() + ttlMillis; + } + } + + /** + * Checks if the cache entry has expired. + * + * @return {@code true} if the current time is past the expiration time; {@code false} otherwise + */ + boolean isExpired() { + return System.currentTimeMillis() > expiryTime; + } + } + + /** + * Constructs a new {@code LIFOCache} instance using the provided {@link Builder}. + * + *

This constructor initializes the cache with the specified capacity and default TTL, + * sets up internal data structures (a {@code HashMap} for cache entries, + * {an @code ArrayDeque}, for key storage, and configures eviction. + * + * @param builder the {@code Builder} object containing configuration parameters + */ + private LIFOCache(Builder builder) { + this.capacity = builder.capacity; + this.defaultTTL = builder.defaultTTL; + this.cache = new HashMap<>(builder.capacity); + this.keys = new ArrayDeque<>(builder.capacity); + this.lock = new ReentrantLock(); + this.evictionListener = builder.evictionListener; + this.evictionStrategy = builder.evictionStrategy; + } + + /** + * Retrieves the value associated with the specified key from the cache. + * + *

If the key is not present or the corresponding entry has expired, this method + * returns {@code null}. If an expired entry is found, it will be removed and the + * eviction listener (if any) will be notified. Cache hit-and-miss statistics are + * also updated accordingly. + * + * @param key the key whose associated value is to be returned; must not be {@code null} + * @return the cached value associated with the key, or {@code null} if not present or expired + * @throws IllegalArgumentException if {@code key} is {@code null} + */ + public V get(K key) { + if (key == null) { + throw new IllegalArgumentException("Key must not be null"); + } + + lock.lock(); + try { + evictionStrategy.onAccess(this); + + final CacheEntry entry = cache.get(key); + if (entry == null || entry.isExpired()) { + if (entry != null) { + cache.remove(key); + keys.remove(key); + notifyEviction(key, entry.value); + } + misses++; + return null; + } + hits++; + return entry.value; + } finally { + lock.unlock(); + } + } + + /** + * Adds a key-value pair to the cache using the default time-to-live (TTL). + * + *

The key may overwrite an existing entry. The actual insertion is delegated + * to the overloaded {@link #put(K, V, long)} method. + * + * @param key the key to cache the value under + * @param value the value to be cached + */ + public void put(K key, V value) { + put(key, value, defaultTTL); + } + + /** + * Adds a key-value pair to the cache with a specified time-to-live (TTL). + * + *

If the key already exists, its value is removed, re-inserted at tail and its TTL is reset. + * If the key does not exist and the cache is full, the youngest entry is evicted to make space. + * Expired entries are also cleaned up prior to any eviction. The eviction listener + * is notified when an entry gets evicted. + * + * @param key the key to associate with the cached value; must not be {@code null} + * @param value the value to be cached; must not be {@code null} + * @param ttlMillis the time-to-live for this entry in milliseconds; must be >= 0 + * @throws IllegalArgumentException if {@code key} or {@code value} is {@code null}, or if {@code ttlMillis} is negative + */ + public void put(K key, V value, long ttlMillis) { + if (key == null || value == null) { + throw new IllegalArgumentException("Key and value must not be null"); + } + if (ttlMillis < 0) { + throw new IllegalArgumentException("TTL must be >= 0"); + } + + lock.lock(); + try { + // If key already exists, remove it. It will later be re-inserted at top of stack + keys.remove(key); + final CacheEntry oldEntry = cache.remove(key); + if (oldEntry != null && !oldEntry.isExpired()) { + notifyEviction(key, oldEntry.value); + } + + // Evict expired entries to make space for new entry + evictExpired(); + + // If no expired entry was removed, remove the youngest + if (cache.size() >= capacity) { + final K youngestKey = keys.pollLast(); + final CacheEntry youngestEntry = cache.remove(youngestKey); + notifyEviction(youngestKey, youngestEntry.value); + } + + // Insert new entry at tail + keys.add(key); + cache.put(key, new CacheEntry<>(value, ttlMillis)); + } finally { + lock.unlock(); + } + } + + /** + * Removes all expired entries from the cache. + * + *

This method iterates through the list of cached keys and checks each associated + * entry for expiration. Expired entries are removed the cache map. For each eviction, + * the eviction listener is notified. + */ + private int evictExpired() { + int count = 0; + final Iterator it = keys.iterator(); + + while (it.hasNext()) { + final K k = it.next(); + final CacheEntry entry = cache.get(k); + if (entry != null && entry.isExpired()) { + it.remove(); + cache.remove(k); + notifyEviction(k, entry.value); + count++; + } + } + + return count; + } + + /** + * Removes the specified key and its associated entry from the cache. + * + * @param key the key to remove from the cache; + * @return the value associated with the key; or {@code null} if no such key exists + */ + public V removeKey(K key) { + if (key == null) { + throw new IllegalArgumentException("Key cannot be null"); + } + lock.lock(); + try { + final CacheEntry entry = cache.remove(key); + keys.remove(key); + + // No such key in cache + if (entry == null) { + return null; + } + + notifyEviction(key, entry.value); + return entry.value; + } finally { + lock.unlock(); + } + } + + /** + * Notifies the eviction listener, if one is registered, that a key-value pair has been evicted. + * + *

If the {@code evictionListener} is not {@code null}, it is invoked with the provided key + * and value. Any exceptions thrown by the listener are caught and logged to standard error, + * preventing them from disrupting cache operations. + * + * @param key the key that was evicted + * @param value the value that was associated with the evicted key + */ + private void notifyEviction(K key, V value) { + if (evictionListener != null) { + try { + evictionListener.accept(key, value); + } catch (Exception e) { + System.err.println("Eviction listener failed: " + e.getMessage()); + } + } + } + + /** + * Returns the number of successful cache lookups (hits). + * + * @return the number of cache hits + */ + public long getHits() { + lock.lock(); + try { + return hits; + } finally { + lock.unlock(); + } + } + + /** + * Returns the number of failed cache lookups (misses), including expired entries. + * + * @return the number of cache misses + */ + public long getMisses() { + lock.lock(); + try { + return misses; + } finally { + lock.unlock(); + } + } + + /** + * Returns the current number of entries in the cache, excluding expired ones. + * + * @return the current cache size + */ + public int size() { + lock.lock(); + try { + evictionStrategy.onAccess(this); + + int count = 0; + for (CacheEntry entry : cache.values()) { + if (!entry.isExpired()) { + ++count; + } + } + return count; + } finally { + lock.unlock(); + } + } + + /** + * Removes all entries from the cache, regardless of their expiration status. + * + *

This method clears the internal cache map entirely, resets the hit-and-miss counters, + * and notifies the eviction listener (if any) for each removed entry. + * Note that expired entries are treated the same as active ones for the purpose of clearing. + * + *

This operation acquires the internal lock to ensure thread safety. + */ + public void clear() { + lock.lock(); + try { + for (Map.Entry> entry : cache.entrySet()) { + notifyEviction(entry.getKey(), entry.getValue().value); + } + keys.clear(); + cache.clear(); + hits = 0; + misses = 0; + } finally { + lock.unlock(); + } + } + + /** + * Returns a set of all keys currently stored in the cache that have not expired. + * + *

This method iterates through the cache and collects the keys of all non-expired entries. + * Expired entries are ignored but not removed. If you want to ensure expired entries are cleaned up, + * consider invoking {@link EvictionStrategy#onAccess(LIFOCache)} or calling {@link #evictExpired()} manually. + * + *

This operation acquires the internal lock to ensure thread safety. + * + * @return a set containing all non-expired keys currently in the cache + */ + public Set getAllKeys() { + lock.lock(); + try { + final Set result = new HashSet<>(); + + for (Map.Entry> entry : cache.entrySet()) { + if (!entry.getValue().isExpired()) { + result.add(entry.getKey()); + } + } + + return result; + } finally { + lock.unlock(); + } + } + + /** + * Returns the current {@link EvictionStrategy} used by this cache instance. + + * @return the eviction strategy currently assigned to this cache + */ + public EvictionStrategy getEvictionStrategy() { + return evictionStrategy; + } + + /** + * Returns a string representation of the cache, including metadata and current non-expired entries. + * + *

The returned string includes the cache's capacity, current size (excluding expired entries), + * hit-and-miss counts, and a map of all non-expired key-value pairs. This method acquires a lock + * to ensure thread-safe access. + * + * @return a string summarizing the state of the cache + */ + @Override + public String toString() { + lock.lock(); + try { + final Map visible = new LinkedHashMap<>(); + for (Map.Entry> entry : cache.entrySet()) { + if (!entry.getValue().isExpired()) { + visible.put(entry.getKey(), entry.getValue().value); + } + } + return String.format("Cache(capacity=%d, size=%d, hits=%d, misses=%d, entries=%s)", capacity, visible.size(), hits, misses, visible); + } finally { + lock.unlock(); + } + } + + /** + * A strategy interface for controlling when expired entries are evicted from the cache. + * + *

Implementations decide whether and when to trigger {@link LIFOCache#evictExpired()} based + * on cache usage patterns. This allows for flexible eviction behaviour such as periodic cleanup, + * or no automatic cleanup. + * + * @param the type of keys maintained by the cache + * @param the type of cached values + */ + public interface EvictionStrategy { + /** + * Called on each cache access (e.g., {@link LIFOCache#get(Object)}) to optionally trigger eviction. + * + * @param cache the cache instance on which this strategy is applied + * @return the number of expired entries evicted during this access + */ + int onAccess(LIFOCache cache); + } + + /** + * An eviction strategy that performs eviction of expired entries on each call. + * + * @param the type of keys + * @param the type of values + */ + public static class ImmediateEvictionStrategy implements EvictionStrategy { + @Override + public int onAccess(LIFOCache cache) { + return cache.evictExpired(); + } + } + + /** + * An eviction strategy that triggers eviction on every fixed number of accesses. + * + *

This deterministic strategy ensures cleanup occurs at predictable intervals, + * ideal for moderately active caches where memory usage is a concern. + * + * @param the type of keys + * @param the type of values + */ + public static class PeriodicEvictionStrategy implements EvictionStrategy { + private final int interval; + private final AtomicInteger counter = new AtomicInteger(); + + /** + * Constructs a periodic eviction strategy. + * + * @param interval the number of accesses between evictions; must be > 0 + * @throws IllegalArgumentException if {@code interval} is less than or equal to 0 + */ + public PeriodicEvictionStrategy(int interval) { + if (interval <= 0) { + throw new IllegalArgumentException("Interval must be > 0"); + } + this.interval = interval; + } + + @Override + public int onAccess(LIFOCache cache) { + if (counter.incrementAndGet() % interval == 0) { + return cache.evictExpired(); + } + + return 0; + } + } + + /** + * A builder for constructing a {@link LIFOCache} instance with customizable settings. + * + *

Allows configuring capacity, default TTL, eviction listener, and a pluggable eviction + * strategy. Call {@link #build()} to create the configured cache instance. + * + * @param the type of keys maintained by the cache + * @param the type of values stored in the cache + */ + public static class Builder { + private final int capacity; + private long defaultTTL = 0; + private BiConsumer evictionListener; + private EvictionStrategy evictionStrategy = new LIFOCache.ImmediateEvictionStrategy<>(); + /** + * Creates a new {@code Builder} with the specified cache capacity. + * + * @param capacity the maximum number of entries the cache can hold; must be > 0 + * @throws IllegalArgumentException if {@code capacity} is less than or equal to 0 + */ + public Builder(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be > 0"); + } + this.capacity = capacity; + } + + /** + * Sets the default time-to-live (TTL) in milliseconds for cache entries. + * + * @param ttlMillis the TTL duration in milliseconds; must be >= 0 + * @return this builder instance for chaining + * @throws IllegalArgumentException if {@code ttlMillis} is negative + */ + public Builder defaultTTL(long ttlMillis) { + if (ttlMillis < 0) { + throw new IllegalArgumentException("Default TTL must be >= 0"); + } + this.defaultTTL = ttlMillis; + return this; + } + + /** + * Sets an eviction listener to be notified when entries are evicted from the cache. + * + * @param listener a {@link BiConsumer} that accepts evicted keys and values; must not be {@code null} + * @return this builder instance for chaining + * @throws IllegalArgumentException if {@code listener} is {@code null} + */ + public Builder evictionListener(BiConsumer listener) { + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + this.evictionListener = listener; + return this; + } + + /** + * Builds and returns a new {@link LIFOCache} instance with the configured parameters. + * + * @return a fully configured {@code LIFOCache} instance + */ + public LIFOCache build() { + return new LIFOCache<>(this); + } + + /** + * Sets the eviction strategy used to determine when to clean up expired entries. + * + * @param strategy an {@link EvictionStrategy} implementation; must not be {@code null} + * @return this builder instance + * @throws IllegalArgumentException if {@code strategy} is {@code null} + */ + public Builder evictionStrategy(EvictionStrategy strategy) { + if (strategy == null) { + throw new IllegalArgumentException("Eviction strategy must not be null"); + } + this.evictionStrategy = strategy; + return this; + } + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/caches/RRCache.java b/src/main/java/com/thealgorithms/datastructures/caches/RRCache.java new file mode 100644 index 000000000000..1821872be9cd --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/caches/RRCache.java @@ -0,0 +1,505 @@ +package com.thealgorithms.datastructures.caches; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiConsumer; + +/** + * A thread-safe generic cache implementation using the Random Replacement (RR) eviction policy. + *

+ * The cache holds a fixed number of entries, defined by its capacity. When the cache is full and a + * new entry is added, one of the existing entries is selected at random and evicted to make space. + *

+ * Optionally, entries can have a time-to-live (TTL) in milliseconds. If a TTL is set, entries will + * automatically expire and be removed upon access or insertion attempts. + *

+ * Features: + *

    + *
  • Random eviction when capacity is exceeded
  • + *
  • Optional TTL (time-to-live in milliseconds) per entry or default TTL for all entries
  • + *
  • Thread-safe access using locking
  • + *
  • Hit and miss counters for cache statistics
  • + *
  • Eviction listener callback support
  • + *
+ * + * @param the type of keys maintained by this cache + * @param the type of mapped values + * See Random Replacement + * @author Kevin Babu (GitHub) + */ +public final class RRCache { + + private final int capacity; + private final long defaultTTL; + private final Map> cache; + private final List keys; + private final Random random; + private final Lock lock; + + private long hits = 0; + private long misses = 0; + private final BiConsumer evictionListener; + private final EvictionStrategy evictionStrategy; + + /** + * Internal structure to store value + expiry timestamp. + * + * @param the type of the value being cached + */ + private static class CacheEntry { + V value; + long expiryTime; + + /** + * Constructs a new {@code CacheEntry} with the specified value and time-to-live (TTL). + * + * @param value the value to cache + * @param ttlMillis the time-to-live in milliseconds + */ + CacheEntry(V value, long ttlMillis) { + this.value = value; + this.expiryTime = System.currentTimeMillis() + ttlMillis; + } + + /** + * Checks if the cache entry has expired. + * + * @return {@code true} if the current time is past the expiration time; {@code false} otherwise + */ + boolean isExpired() { + return System.currentTimeMillis() > expiryTime; + } + } + + /** + * Constructs a new {@code RRCache} instance using the provided {@link Builder}. + * + *

This constructor initializes the cache with the specified capacity and default TTL, + * sets up internal data structures (a {@code HashMap} for cache entries and an {@code ArrayList} + * for key tracking), and configures eviction and randomization behavior. + * + * @param builder the {@code Builder} object containing configuration parameters + */ + private RRCache(Builder builder) { + this.capacity = builder.capacity; + this.defaultTTL = builder.defaultTTL; + this.cache = new HashMap<>(builder.capacity); + this.keys = new ArrayList<>(builder.capacity); + this.random = builder.random != null ? builder.random : new Random(); + this.lock = new ReentrantLock(); + this.evictionListener = builder.evictionListener; + this.evictionStrategy = builder.evictionStrategy; + } + + /** + * Retrieves the value associated with the specified key from the cache. + * + *

If the key is not present or the corresponding entry has expired, this method + * returns {@code null}. If an expired entry is found, it will be removed and the + * eviction listener (if any) will be notified. Cache hit-and-miss statistics are + * also updated accordingly. + * + * @param key the key whose associated value is to be returned; must not be {@code null} + * @return the cached value associated with the key, or {@code null} if not present or expired + * @throws IllegalArgumentException if {@code key} is {@code null} + */ + public V get(K key) { + if (key == null) { + throw new IllegalArgumentException("Key must not be null"); + } + + lock.lock(); + try { + evictionStrategy.onAccess(this); + + CacheEntry entry = cache.get(key); + if (entry == null || entry.isExpired()) { + if (entry != null) { + removeKey(key); + notifyEviction(key, entry.value); + } + misses++; + return null; + } + hits++; + return entry.value; + } finally { + lock.unlock(); + } + } + + /** + * Adds a key-value pair to the cache using the default time-to-live (TTL). + * + *

The key may overwrite an existing entry. The actual insertion is delegated + * to the overloaded {@link #put(K, V, long)} method. + * + * @param key the key to cache the value under + * @param value the value to be cached + */ + public void put(K key, V value) { + put(key, value, defaultTTL); + } + + /** + * Adds a key-value pair to the cache with a specified time-to-live (TTL). + * + *

If the key already exists, its value is updated and its TTL is reset. If the key + * does not exist and the cache is full, a random entry is evicted to make space. + * Expired entries are also cleaned up prior to any eviction. The eviction listener + * is notified when an entry gets evicted. + * + * @param key the key to associate with the cached value; must not be {@code null} + * @param value the value to be cached; must not be {@code null} + * @param ttlMillis the time-to-live for this entry in milliseconds; must be >= 0 + * @throws IllegalArgumentException if {@code key} or {@code value} is {@code null}, or if {@code ttlMillis} is negative + */ + public void put(K key, V value, long ttlMillis) { + if (key == null || value == null) { + throw new IllegalArgumentException("Key and value must not be null"); + } + if (ttlMillis < 0) { + throw new IllegalArgumentException("TTL must be >= 0"); + } + + lock.lock(); + try { + if (cache.containsKey(key)) { + cache.put(key, new CacheEntry<>(value, ttlMillis)); + return; + } + + evictExpired(); + + if (cache.size() >= capacity) { + int idx = random.nextInt(keys.size()); + K evictKey = keys.remove(idx); + CacheEntry evictVal = cache.remove(evictKey); + notifyEviction(evictKey, evictVal.value); + } + + cache.put(key, new CacheEntry<>(value, ttlMillis)); + keys.add(key); + } finally { + lock.unlock(); + } + } + + /** + * Removes all expired entries from the cache. + * + *

This method iterates through the list of cached keys and checks each associated + * entry for expiration. Expired entries are removed from both the key tracking list + * and the cache map. For each eviction, the eviction listener is notified. + */ + private int evictExpired() { + Iterator it = keys.iterator(); + int expiredCount = 0; + + while (it.hasNext()) { + K k = it.next(); + CacheEntry entry = cache.get(k); + if (entry != null && entry.isExpired()) { + it.remove(); + cache.remove(k); + ++expiredCount; + notifyEviction(k, entry.value); + } + } + return expiredCount; + } + + /** + * Removes the specified key and its associated entry from the cache. + * + *

This method deletes the key from both the cache map and the key tracking list. + * + * @param key the key to remove from the cache + */ + private void removeKey(K key) { + cache.remove(key); + keys.remove(key); + } + + /** + * Notifies the eviction listener, if one is registered, that a key-value pair has been evicted. + * + *

If the {@code evictionListener} is not {@code null}, it is invoked with the provided key + * and value. Any exceptions thrown by the listener are caught and logged to standard error, + * preventing them from disrupting cache operations. + * + * @param key the key that was evicted + * @param value the value that was associated with the evicted key + */ + private void notifyEviction(K key, V value) { + if (evictionListener != null) { + try { + evictionListener.accept(key, value); + } catch (Exception e) { + System.err.println("Eviction listener failed: " + e.getMessage()); + } + } + } + + /** + * Returns the number of successful cache lookups (hits). + * + * @return the number of cache hits + */ + public long getHits() { + lock.lock(); + try { + return hits; + } finally { + lock.unlock(); + } + } + + /** + * Returns the number of failed cache lookups (misses), including expired entries. + * + * @return the number of cache misses + */ + public long getMisses() { + lock.lock(); + try { + return misses; + } finally { + lock.unlock(); + } + } + + /** + * Returns the current number of entries in the cache, excluding expired ones. + * + * @return the current cache size + */ + public int size() { + lock.lock(); + try { + int cachedSize = cache.size(); + int evictedCount = evictionStrategy.onAccess(this); + if (evictedCount > 0) { + return cachedSize - evictedCount; + } + + // This runs if periodic eviction does not occur + int count = 0; + for (Map.Entry> entry : cache.entrySet()) { + if (!entry.getValue().isExpired()) { + ++count; + } + } + return count; + } finally { + lock.unlock(); + } + } + + /** + * Returns the current {@link EvictionStrategy} used by this cache instance. + + * @return the eviction strategy currently assigned to this cache + */ + public EvictionStrategy getEvictionStrategy() { + return evictionStrategy; + } + + /** + * Returns a string representation of the cache, including metadata and current non-expired entries. + * + *

The returned string includes the cache's capacity, current size (excluding expired entries), + * hit-and-miss counts, and a map of all non-expired key-value pairs. This method acquires a lock + * to ensure thread-safe access. + * + * @return a string summarizing the state of the cache + */ + @Override + public String toString() { + lock.lock(); + try { + Map visible = new HashMap<>(); + for (Map.Entry> entry : cache.entrySet()) { + if (!entry.getValue().isExpired()) { + visible.put(entry.getKey(), entry.getValue().value); + } + } + return String.format("Cache(capacity=%d, size=%d, hits=%d, misses=%d, entries=%s)", capacity, visible.size(), hits, misses, visible); + } finally { + lock.unlock(); + } + } + + /** + * A strategy interface for controlling when expired entries are evicted from the cache. + * + *

Implementations decide whether and when to trigger {@link RRCache#evictExpired()} based + * on cache usage patterns. This allows for flexible eviction behaviour such as periodic cleanup, + * or no automatic cleanup. + * + * @param the type of keys maintained by the cache + * @param the type of cached values + */ + public interface EvictionStrategy { + /** + * Called on each cache access (e.g., {@link RRCache#get(Object)}) to optionally trigger eviction. + * + * @param cache the cache instance on which this strategy is applied + * @return the number of expired entries evicted during this access + */ + int onAccess(RRCache cache); + } + + /** + * An eviction strategy that performs eviction of expired entries on each call. + * + * @param the type of keys + * @param the type of values + */ + public static class NoEvictionStrategy implements EvictionStrategy { + @Override + public int onAccess(RRCache cache) { + return cache.evictExpired(); + } + } + + /** + * An eviction strategy that triggers eviction every fixed number of accesses. + * + *

This deterministic strategy ensures cleanup occurs at predictable intervals, + * ideal for moderately active caches where memory usage is a concern. + * + * @param the type of keys + * @param the type of values + */ + public static class PeriodicEvictionStrategy implements EvictionStrategy { + private final int interval; + private int counter = 0; + + /** + * Constructs a periodic eviction strategy. + * + * @param interval the number of accesses between evictions; must be > 0 + * @throws IllegalArgumentException if {@code interval} is less than or equal to 0 + */ + public PeriodicEvictionStrategy(int interval) { + if (interval <= 0) { + throw new IllegalArgumentException("Interval must be > 0"); + } + this.interval = interval; + } + + @Override + public int onAccess(RRCache cache) { + if (++counter % interval == 0) { + return cache.evictExpired(); + } + + return 0; + } + } + + /** + * A builder for constructing an {@link RRCache} instance with customizable settings. + * + *

Allows configuring capacity, default TTL, random eviction behavior, eviction listener, + * and a pluggable eviction strategy. Call {@link #build()} to create the configured cache instance. + * + * @param the type of keys maintained by the cache + * @param the type of values stored in the cache + */ + public static class Builder { + private final int capacity; + private long defaultTTL = 0; + private Random random; + private BiConsumer evictionListener; + private EvictionStrategy evictionStrategy = new RRCache.PeriodicEvictionStrategy<>(100); + /** + * Creates a new {@code Builder} with the specified cache capacity. + * + * @param capacity the maximum number of entries the cache can hold; must be > 0 + * @throws IllegalArgumentException if {@code capacity} is less than or equal to 0 + */ + public Builder(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be > 0"); + } + this.capacity = capacity; + } + + /** + * Sets the default time-to-live (TTL) in milliseconds for cache entries. + * + * @param ttlMillis the TTL duration in milliseconds; must be >= 0 + * @return this builder instance for chaining + * @throws IllegalArgumentException if {@code ttlMillis} is negative + */ + public Builder defaultTTL(long ttlMillis) { + if (ttlMillis < 0) { + throw new IllegalArgumentException("Default TTL must be >= 0"); + } + this.defaultTTL = ttlMillis; + return this; + } + + /** + * Sets the {@link Random} instance to be used for random eviction selection. + * + * @param r a non-null {@code Random} instance + * @return this builder instance for chaining + * @throws IllegalArgumentException if {@code r} is {@code null} + */ + public Builder random(Random r) { + if (r == null) { + throw new IllegalArgumentException("Random must not be null"); + } + this.random = r; + return this; + } + + /** + * Sets an eviction listener to be notified when entries are evicted from the cache. + * + * @param listener a {@link BiConsumer} that accepts evicted keys and values; must not be {@code null} + * @return this builder instance for chaining + * @throws IllegalArgumentException if {@code listener} is {@code null} + */ + public Builder evictionListener(BiConsumer listener) { + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + this.evictionListener = listener; + return this; + } + + /** + * Builds and returns a new {@link RRCache} instance with the configured parameters. + * + * @return a fully configured {@code RRCache} instance + */ + public RRCache build() { + return new RRCache<>(this); + } + + /** + * Sets the eviction strategy used to determine when to clean up expired entries. + * + * @param strategy an {@link EvictionStrategy} implementation; must not be {@code null} + * @return this builder instance + * @throws IllegalArgumentException if {@code strategy} is {@code null} + */ + public Builder evictionStrategy(EvictionStrategy strategy) { + if (strategy == null) { + throw new IllegalArgumentException("Eviction strategy must not be null"); + } + this.evictionStrategy = strategy; + return this; + } + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java b/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java index 2c6ce8a427d1..d33bd3ee84d9 100644 --- a/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java +++ b/src/main/java/com/thealgorithms/datastructures/crdt/LWWElementSet.java @@ -1,53 +1,33 @@ package com.thealgorithms.datastructures.crdt; +import java.time.Instant; import java.util.HashMap; import java.util.Map; /** - * Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data Type) - * designed for managing sets in a distributed and concurrent environment. It supports the addition and removal - * of elements, using timestamps to determine the order of operations. The set is split into two subsets: - * the add set for elements to be added and the remove set for elements to be removed. + * Last-Write-Wins Element Set (LWWElementSet) is a state-based CRDT (Conflict-free Replicated Data + * Type) designed for managing sets in a distributed and concurrent environment. It supports the + * addition and removal of elements, using timestamps to determine the order of operations. The set + * is split into two subsets: the add set for elements to be added and the remove set for elements + * to be removed. The LWWElementSet ensures that the most recent operation (based on the timestamp) + * wins in the case of concurrent operations. * - * @author itakurah (Niklas Hoefflin) (https://github.com/itakurah) - * @see Conflict-free_replicated_data_type - * @see itakurah (Niklas Hoefflin) + * @param The type of the elements in the LWWElementSet. + * @author itakurah (GitHub), Niklas Hoefflin (LinkedIn) + * @see Conflict free + * replicated data type (Wikipedia) + * @see A comprehensive study of + * Convergent and Commutative Replicated Data Types */ - -class Element { - String key; - int timestamp; - Bias bias; +class LWWElementSet { + final Map> addSet; + final Map> removeSet; /** - * Constructs a new Element with the specified key, timestamp and bias. - * - * @param key The key of the element. - * @param timestamp The timestamp associated with the element. - * @param bias The bias of the element (ADDS or REMOVALS). - */ - Element(String key, int timestamp, Bias bias) { - this.key = key; - this.timestamp = timestamp; - this.bias = bias; - } -} - -enum Bias { - /** - * ADDS bias for the add set. - * REMOVALS bias for the remove set. - */ - ADDS, - REMOVALS -} - -class LWWElementSet { - private final Map addSet; - private final Map removeSet; - - /** - * Constructs an empty LWWElementSet. + * Constructs an empty LWWElementSet. This constructor initializes the addSet and removeSet as + * empty HashMaps. The addSet stores elements that are added, and the removeSet stores elements + * that are removed. */ LWWElementSet() { this.addSet = new HashMap<>(); @@ -55,84 +35,92 @@ class LWWElementSet { } /** - * Adds an element to the addSet. + * Adds an element to the addSet with the current timestamp. This method stores the element in the + * addSet, ensuring that the element is added to the set with an associated timestamp that + * represents the time of the addition. * - * @param e The element to be added. + * @param key The key of the element to be added. */ - public void add(Element e) { - addSet.put(e.key, e); + public void add(T key) { + addSet.put(key, new Element<>(key, Instant.now())); } /** - * Removes an element from the removeSet. + * Removes an element by adding it to the removeSet with the current timestamp. This method adds + * the element to the removeSet, marking it as removed with the current timestamp. * - * @param e The element to be removed. + * @param key The key of the element to be removed. */ - public void remove(Element e) { - if (lookup(e)) { - removeSet.put(e.key, e); - } + public void remove(T key) { + removeSet.put(key, new Element<>(key, Instant.now())); } /** - * Checks if an element is in the LWWElementSet by comparing timestamps in the addSet and removeSet. + * Checks if an element is in the LWWElementSet. An element is considered present if it exists in + * the addSet and either does not exist in the removeSet, or its add timestamp is later than any + * corresponding remove timestamp. * - * @param e The element to be checked. - * @return True if the element is present, false otherwise. + * @param key The key of the element to be checked. + * @return {@code true} if the element is present in the set (i.e., its add timestamp is later + * than its remove timestamp, or it is not in the remove set), {@code false} otherwise (i.e., + * the element has been removed or its remove timestamp is later than its add timestamp). */ - public boolean lookup(Element e) { - Element inAddSet = addSet.get(e.key); - Element inRemoveSet = removeSet.get(e.key); + public boolean lookup(T key) { + Element inAddSet = addSet.get(key); + Element inRemoveSet = removeSet.get(key); - return (inAddSet != null && (inRemoveSet == null || inAddSet.timestamp > inRemoveSet.timestamp)); + return inAddSet != null && (inRemoveSet == null || inAddSet.timestamp.isAfter(inRemoveSet.timestamp)); } /** - * Compares the LWWElementSet with another LWWElementSet to check if addSet and removeSet are a subset. + * Merges another LWWElementSet into this set. This method takes the union of both the add-sets + * and remove-sets from the two sets, resolving conflicts by keeping the element with the latest + * timestamp. If an element appears in both the add-set and remove-set of both sets, the one with + * the later timestamp will be retained. * - * @param other The LWWElementSet to compare. - * @return True if the set is subset, false otherwise. + * @param other The LWWElementSet to merge with the current set. */ - public boolean compare(LWWElementSet other) { - return other.addSet.keySet().containsAll(addSet.keySet()) && other.removeSet.keySet().containsAll(removeSet.keySet()); + public void merge(LWWElementSet other) { + for (Map.Entry> entry : other.addSet.entrySet()) { + addSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict); + } + for (Map.Entry> entry : other.removeSet.entrySet()) { + removeSet.merge(entry.getKey(), entry.getValue(), this::resolveConflict); + } } /** - * Merges another LWWElementSet into this set by resolving conflicts based on timestamps. + * Resolves conflicts between two elements by selecting the one with the later timestamp. This + * method is used when merging two LWWElementSets to ensure that the most recent operation (based + * on timestamps) is kept. * - * @param other The LWWElementSet to merge. + * @param e1 The first element. + * @param e2 The second element. + * @return The element with the later timestamp. */ - public void merge(LWWElementSet other) { - for (Element e : other.addSet.values()) { - if (!addSet.containsKey(e.key) || compareTimestamps(addSet.get(e.key), e)) { - addSet.put(e.key, e); - } - } - - for (Element e : other.removeSet.values()) { - if (!removeSet.containsKey(e.key) || compareTimestamps(removeSet.get(e.key), e)) { - removeSet.put(e.key, e); - } - } + private Element resolveConflict(Element e1, Element e2) { + return e1.timestamp.isAfter(e2.timestamp) ? e1 : e2; } +} + +/** + * Represents an element in the LWWElementSet, consisting of a key and a timestamp. This class is + * used to store the elements in both the add and remove sets with their respective timestamps. + * + * @param The type of the key associated with the element. + */ +class Element { + T key; + Instant timestamp; /** - * Compares timestamps of two elements based on their bias (ADDS or REMOVALS). + * Constructs a new Element with the specified key and timestamp. * - * @param e The first element. - * @param other The second element. - * @return True if the first element's timestamp is greater or the bias is ADDS and timestamps are equal. + * @param key The key of the element. + * @param timestamp The timestamp associated with the element. */ - public boolean compareTimestamps(Element e, Element other) { - if (e.bias != other.bias) { - throw new IllegalArgumentException("Invalid bias value"); - } - Bias bias = e.bias; - int timestampComparison = Integer.compare(e.timestamp, other.timestamp); - - if (timestampComparison == 0) { - return bias != Bias.ADDS; - } - return timestampComparison < 0; + Element(T key, Instant timestamp) { + this.key = key; + this.timestamp = timestamp; } } diff --git a/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java b/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java index 583800998c81..55951be82c8a 100644 --- a/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java +++ b/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnion.java @@ -1,53 +1,65 @@ package com.thealgorithms.datastructures.disjointsetunion; /** - * Disjoint Set Union or DSU is useful for solving problems related to connected components, - * cycle detection in graphs, and maintaining relationships in disjoint sets of data. - * It is commonly employed in graph algorithms and problems. + * Disjoint Set Union (DSU), also known as Union-Find, is a data structure that tracks a set of elements + * partitioned into disjoint (non-overlapping) subsets. It supports two primary operations efficiently: * - * @see Disjoint Set Union + *

    + *
  • Find: Determine which subset a particular element belongs to.
  • + *
  • Union: Merge two subsets into a single subset.
  • + *
+ * + * @see Disjoint Set Union (Wikipedia) */ public class DisjointSetUnion { /** - * Creates a new node of DSU with parent initialised as same node + * Creates a new disjoint set containing the single specified element. + * + * @param value the element to be placed in a new singleton set + * @return a node representing the new set */ - public Node makeSet(final T x) { - return new Node(x); + public Node makeSet(final T value) { + return new Node<>(value); } /** - * Finds and returns the representative (root) element of the set to which a given element belongs. - * This operation uses path compression to optimize future findSet operations. + * Finds and returns the representative (root) of the set containing the given node. + * This method applies path compression to flatten the tree structure for future efficiency. + * + * @param node the node whose set representative is to be found + * @return the representative (root) node of the set */ public Node findSet(Node node) { - while (node != node.parent) { - node = node.parent; + if (node != node.parent) { + node.parent = findSet(node.parent); } - return node; + return node.parent; } /** - * Unions two sets by merging their representative elements. The merge is performed based on the rank of each set - * to ensure efficient merging and path compression to optimize future findSet operations. + * Merges the sets containing the two given nodes. Union by rank is used to attach the smaller tree under the larger one. + * If both sets have the same rank, one becomes the parent and its rank is incremented. + * + * @param x a node in the first set + * @param y a node in the second set */ - public void unionSets(final Node x, final Node y) { - Node nx = findSet(x); - Node ny = findSet(y); + public void unionSets(Node x, Node y) { + Node rootX = findSet(x); + Node rootY = findSet(y); - if (nx == ny) { - return; // Both elements already belong to the same set. + if (rootX == rootY) { + return; // They are already in the same set } // Merging happens based on rank of node, this is done to avoid long chaining of nodes and reduce time // to find root of the component. Idea is to attach small components in big, instead of other way around. - if (nx.rank > ny.rank) { - ny.parent = nx; - } else if (ny.rank > nx.rank) { - nx.parent = ny; + if (rootX.rank > rootY.rank) { + rootY.parent = rootX; + } else if (rootY.rank > rootX.rank) { + rootX.parent = rootY; } else { - // Both sets have the same rank; choose one as the parent and increment the rank. - ny.parent = nx; - nx.rank++; + rootY.parent = rootX; + rootX.rank++; } } } diff --git a/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySize.java b/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySize.java new file mode 100644 index 000000000000..71951f67dfc8 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySize.java @@ -0,0 +1,83 @@ +package com.thealgorithms.datastructures.disjointsetunion; + +/** + * Disjoint Set Union (DSU) with Union by Size. + * This data structure tracks a set of elements partitioned into disjoint (non-overlapping) subsets. + * It supports two primary operations efficiently: + * + *
    + *
  • Find: Determine which subset a particular element belongs to.
  • + *
  • Union: Merge two subsets into a single subset using union by size.
  • + *
+ * + * Union by size always attaches the smaller tree under the root of the larger tree. + * This helps keep the tree shallow, improving the efficiency of find operations. + * + * @see Disjoint Set Union (Wikipedia) + */ +public class DisjointSetUnionBySize { + /** + * Node class for DSU by size. + * Each node keeps track of its parent and the size of the set it represents. + */ + public static class Node { + public T value; + public Node parent; + public int size; // size of the set + + public Node(T value) { + this.value = value; + this.parent = this; + this.size = 1; // initially, the set size is 1 + } + } + + /** + * Creates a new disjoint set containing the single specified element. + * @param value the element to be placed in a new singleton set + * @return a node representing the new set + */ + public Node makeSet(final T value) { + return new Node<>(value); + } + + /** + * Finds and returns the representative (root) of the set containing the given node. + * This method applies path compression to flatten the tree structure for future efficiency. + * @param node the node whose set representative is to be found + * @return the representative (root) node of the set + */ + public Node findSet(Node node) { + if (node != node.parent) { + node.parent = findSet(node.parent); // path compression + } + return node.parent; + } + + /** + * Merges the sets containing the two given nodes using union by size. + * The root of the smaller set is attached to the root of the larger set. + * @param x a node in the first set + * @param y a node in the second set + */ + public void unionSets(Node x, Node y) { + Node rootX = findSet(x); + Node rootY = findSet(y); + + if (rootX == rootY) { + return; // They are already in the same set + } + // Union by size: attach smaller tree under the larger one + if (rootX.size < rootY.size) { + rootX.parent = rootY; + rootY.size += rootX.size; // update size + } else { + rootY.parent = rootX; + rootX.size += rootY.size; // update size + } + } +} +// This implementation uses union by size instead of union by rank. +// The size field tracks the number of elements in each set. +// When two sets are merged, the smaller set is always attached to the larger set's root. +// This helps keep the tree shallow and improves the efficiency of find operations. diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DialsAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DialsAlgorithm.java new file mode 100644 index 000000000000..da3fba88c618 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/DialsAlgorithm.java @@ -0,0 +1,114 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * An implementation of Dial's Algorithm for the single-source shortest path problem. + * This algorithm is an optimization of Dijkstra's algorithm and is particularly + * efficient for graphs with small, non-negative integer edge weights. + * + * It uses a bucket queue (implemented here as a List of HashSets) to store vertices, + * where each bucket corresponds to a specific distance from the source. This is more + * efficient than a standard priority queue when the range of edge weights is small. + * + * Time Complexity: O(E + W * V), where E is the number of edges, V is the number + * of vertices, and W is the maximum weight of any edge. + * + * @see Wikipedia - Dial's Algorithm + */ +public final class DialsAlgorithm { + /** + * Private constructor to prevent instantiation of this utility class. + */ + private DialsAlgorithm() { + } + /** + * Represents an edge in the graph, connecting to a destination vertex with a given weight. + */ + public static class Edge { + private final int destination; + private final int weight; + + public Edge(int destination, int weight) { + this.destination = destination; + this.weight = weight; + } + + public int getDestination() { + return destination; + } + + public int getWeight() { + return weight; + } + } + /** + * Finds the shortest paths from a source vertex to all other vertices in a weighted graph. + * + * @param graph The graph represented as an adjacency list. + * @param source The source vertex to start from (0-indexed). + * @param maxEdgeWeight The maximum weight of any single edge in the graph. + * @return An array of integers where the value at each index `i` is the + * shortest distance from the source to vertex `i`. Unreachable vertices + * will have a value of Integer.MAX_VALUE. + * @throws IllegalArgumentException if the source vertex is out of bounds. + */ + public static int[] run(List> graph, int source, int maxEdgeWeight) { + int numVertices = graph.size(); + if (source < 0 || source >= numVertices) { + throw new IllegalArgumentException("Source vertex is out of bounds."); + } + + // Initialize distances array + int[] distances = new int[numVertices]; + Arrays.fill(distances, Integer.MAX_VALUE); + distances[source] = 0; + + // The bucket queue. Size is determined by the max possible path length. + int maxPathWeight = maxEdgeWeight * (numVertices > 0 ? numVertices - 1 : 0); + List> buckets = new ArrayList<>(maxPathWeight + 1); + for (int i = 0; i <= maxPathWeight; i++) { + buckets.add(new HashSet<>()); + } + + // Add the source vertex to the first bucket + buckets.get(0).add(source); + + // Process buckets in increasing order of distance + for (int d = 0; d <= maxPathWeight; d++) { + // Process all vertices in the current bucket + while (!buckets.get(d).isEmpty()) { + // Get and remove a vertex from the current bucket + int u = buckets.get(d).iterator().next(); + buckets.get(d).remove(u); + + // If we've found a shorter path already, skip + if (d > distances[u]) { + continue; + } + + // Relax all adjacent edges + for (Edge edge : graph.get(u)) { + int v = edge.getDestination(); + int weight = edge.getWeight(); + + // If a shorter path to v is found + if (distances[u] != Integer.MAX_VALUE && distances[u] + weight < distances[v]) { + // If v was already in a bucket, remove it from the old one + if (distances[v] != Integer.MAX_VALUE) { + buckets.get(distances[v]).remove(v); + } + // Update distance and move v to the new bucket + distances[v] = distances[u] + weight; + buckets.get(distances[v]).add(v); + } + } + } + } + return distances; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java b/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java index 25c4548daa7a..331d7196b61c 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/Kruskal.java @@ -19,6 +19,7 @@ * *

Time Complexity: O(E log V), where E is the number of edges and V is the number of vertices.

*/ +@SuppressWarnings({"rawtypes", "unchecked"}) public class Kruskal { /** diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/TwoSat.java b/src/main/java/com/thealgorithms/datastructures/graphs/TwoSat.java new file mode 100644 index 000000000000..835e3880428f --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/TwoSat.java @@ -0,0 +1,265 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Stack; + +/** + * This class implements a solution to the 2-SAT (2-Satisfiability) problem + * using Kosaraju's algorithm to find strongly connected components (SCCs) + * in the implication graph. + * + *

+ * Brief Idea: + *

+ * + *
+ * 1. From each clause (a โˆจ b), we can derive implications:
+ *      (ยฌa โ†’ b) and (ยฌb โ†’ a)
+ *
+ * 2. We construct an implication graph using these implications.
+ *
+ * 3. For each variable x, its negation ยฌx is also represented as a node.
+ *    If x and ยฌx belong to the same SCC, the expression is unsatisfiable.
+ *
+ * 4. Otherwise, we assign truth values based on the SCC order:
+ *    If SCC(x) > SCC(ยฌx), then x = true; otherwise, x = false.
+ * 
+ * + *

+ * Complexities: + *

+ *
    + *
  • Time Complexity: O(n + m)
  • + *
  • Space Complexity: O(n + m)
  • + *
+ * where {@code n} is the number of variables and {@code m} is the number of + * clauses. + * + *

+ * Usage Example: + *

+ * + *
+ * TwoSat twoSat = new TwoSat(5); // Initialize with 5 variables: x1, x2, x3, x4, x5
+ *
+ * // Add clauses
+ * twoSat.addClause(1, false, 2, false); // (x1 โˆจ x2)
+ * twoSat.addClause(3, true, 2, false); // (ยฌx3 โˆจ x2)
+ * twoSat.addClause(4, false, 5, true); // (x4 โˆจ ยฌx5)
+ *
+ * twoSat.solve(); // Solve the problem
+ *
+ * if (twoSat.isSolutionExists()) {
+ *     boolean[] solution = twoSat.getSolutions();
+ *     for (int i = 1; i <= 5; i++) {
+ *         System.out.println("x" + i + " = " + solution[i]);
+ *     }
+ * }
+ * 
+ *

Reference

+ * CP Algorithm

+ * Wikipedia - 2 SAT + * @author Shoyeb Ansari + * + * @see Kosaraju + */ +class TwoSat { + + /** Number of variables in the boolean expression. */ + private final int numberOfVariables; + + /** Implication graph built from the boolean clauses. */ + private final ArrayList[] graph; + + /** Transposed implication graph used in Kosaraju's algorithm. */ + private final ArrayList[] graphTranspose; + + /** Stores one valid truth assignment for all variables (1-indexed). */ + private final boolean[] variableAssignments; + + /** Indicates whether a valid solution exists. */ + private boolean hasSolution = true; + + /** Tracks whether the {@code solve()} method has been called. */ + private boolean isSolved = false; + + /** + * Initializes the TwoSat solver with the given number of variables. + * + * @param numberOfVariables the number of boolean variables + * @throws IllegalArgumentException if the number of variables is negative + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + TwoSat(int numberOfVariables) { + if (numberOfVariables < 0) { + throw new IllegalArgumentException("Number of variables cannot be negative."); + } + this.numberOfVariables = numberOfVariables; + int n = 2 * numberOfVariables + 1; + + graph = (ArrayList[]) new ArrayList[n]; + graphTranspose = (ArrayList[]) new ArrayList[n]; + for (int i = 0; i < n; i++) { + graph[i] = new ArrayList<>(); + graphTranspose[i] = new ArrayList<>(); + } + variableAssignments = new boolean[numberOfVariables + 1]; + } + + /** + * Adds a clause of the form (a โˆจ b) to the boolean expression. + * + *

+ * Example: To add (ยฌxโ‚ โˆจ xโ‚‚), call: + *

+ * + *
{@code
+     * addClause(1, true, 2, false);
+     * }
+ * + * @param a the first variable (1 โ‰ค a โ‰ค numberOfVariables) + * @param isNegateA {@code true} if variable {@code a} is negated + * @param b the second variable (1 โ‰ค b โ‰ค numberOfVariables) + * @param isNegateB {@code true} if variable {@code b} is negated + * @throws IllegalArgumentException if {@code a} or {@code b} are out of range + */ + void addClause(int a, boolean isNegateA, int b, boolean isNegateB) { + if (a <= 0 || a > numberOfVariables) { + throw new IllegalArgumentException("Variable number must be between 1 and " + numberOfVariables); + } + if (b <= 0 || b > numberOfVariables) { + throw new IllegalArgumentException("Variable number must be between 1 and " + numberOfVariables); + } + + a = isNegateA ? negate(a) : a; + b = isNegateB ? negate(b) : b; + int notA = negate(a); + int notB = negate(b); + + // Add implications: (ยฌa โ†’ b) and (ยฌb โ†’ a) + graph[notA].add(b); + graph[notB].add(a); + + // Build transpose graph + graphTranspose[b].add(notA); + graphTranspose[a].add(notB); + } + + /** + * Solves the 2-SAT problem using Kosaraju's algorithm to find SCCs + * and determines whether a satisfying assignment exists. + */ + void solve() { + isSolved = true; + int n = 2 * numberOfVariables + 1; + + boolean[] visited = new boolean[n]; + int[] component = new int[n]; + Stack topologicalOrder = new Stack<>(); + + // Step 1: Perform DFS to get topological order + for (int i = 1; i < n; i++) { + if (!visited[i]) { + dfsForTopologicalOrder(i, visited, topologicalOrder); + } + } + + Arrays.fill(visited, false); + int sccId = 0; + + // Step 2: Find SCCs on transposed graph + while (!topologicalOrder.isEmpty()) { + int node = topologicalOrder.pop(); + if (!visited[node]) { + dfsForScc(node, visited, component, sccId); + sccId++; + } + } + + // Step 3: Check for contradictions and assign values + for (int i = 1; i <= numberOfVariables; i++) { + int notI = negate(i); + if (component[i] == component[notI]) { + hasSolution = false; + return; + } + // If SCC(i) > SCC(ยฌi), then variable i is true. + variableAssignments[i] = component[i] > component[notI]; + } + } + + /** + * Returns whether the given boolean formula is satisfiable. + * + * @return {@code true} if a solution exists; {@code false} otherwise + * @throws Error if called before {@link #solve()} + */ + boolean isSolutionExists() { + if (!isSolved) { + throw new Error("Please call solve() before checking for a solution."); + } + return hasSolution; + } + + /** + * Returns one valid assignment of variables that satisfies the boolean formula. + * + * @return a boolean array where {@code result[i]} represents the truth value of + * variable {@code xแตข} + * @throws Error if called before {@link #solve()} or if no solution exists + */ + boolean[] getSolutions() { + if (!isSolved) { + throw new Error("Please call solve() before fetching the solution."); + } + if (!hasSolution) { + throw new Error("No satisfying assignment exists for the given expression."); + } + return variableAssignments.clone(); + } + + /** Performs DFS to compute topological order. */ + private void dfsForTopologicalOrder(int u, boolean[] visited, Stack topologicalOrder) { + visited[u] = true; + for (int v : graph[u]) { + if (!visited[v]) { + dfsForTopologicalOrder(v, visited, topologicalOrder); + } + } + topologicalOrder.push(u); + } + + /** Performs DFS on the transposed graph to identify SCCs. */ + private void dfsForScc(int u, boolean[] visited, int[] component, int sccId) { + visited[u] = true; + component[u] = sccId; + for (int v : graphTranspose[u]) { + if (!visited[v]) { + dfsForScc(v, visited, component, sccId); + } + } + } + + /** + * Returns the index representing the negation of the given variable. + * + *

+ * Mapping rule: + *

+ * + *
+     * For a variable i:
+     *     negate(i) = i + n
+     * For a negated variable (i + n):
+     *     negate(i + n) = i
+     * where n = numberOfVariables
+     * 
+ * + * @param a the variable index + * @return the index representing its negation + */ + private int negate(int a) { + return a <= numberOfVariables ? a + numberOfVariables : a - numberOfVariables; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java b/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java new file mode 100644 index 000000000000..8aafc1ef3368 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/UndirectedAdjacencyListGraph.java @@ -0,0 +1,69 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class UndirectedAdjacencyListGraph { + private ArrayList> adjacencyList = new ArrayList<>(); + + /** + * Adds a new node to the graph by adding an empty HashMap for its neighbors. + * @return the index of the newly added node in the adjacency list + */ + public int addNode() { + adjacencyList.add(new HashMap<>()); + return adjacencyList.size() - 1; + } + + /** + * Adds an undirected edge between the origin node (@orig) and the destination node (@dest) with the specified weight. + * If the edge already exists, no changes are made. + * @param orig the index of the origin node + * @param dest the index of the destination node + * @param weight the weight of the edge between @orig and @dest + * @return true if the edge was successfully added, false if the edge already exists or if any node index is invalid + */ + public boolean addEdge(int orig, int dest, int weight) { + int numNodes = adjacencyList.size(); + if (orig >= numNodes || dest >= numNodes || orig < 0 || dest < 0) { + return false; + } + + if (adjacencyList.get(orig).containsKey(dest)) { + return false; + } + + adjacencyList.get(orig).put(dest, weight); + adjacencyList.get(dest).put(orig, weight); + return true; + } + + /** + * Returns the set of all adjacent nodes (neighbors) for the given node. + * @param node the index of the node whose neighbors are to be retrieved + * @return a HashSet containing the indices of all neighboring nodes + */ + public HashSet getNeighbors(int node) { + return new HashSet<>(adjacencyList.get(node).keySet()); + } + + /** + * Returns the weight of the edge between the origin node (@orig) and the destination node (@dest). + * If no edge exists, returns null. + * @param orig the index of the origin node + * @param dest the index of the destination node + * @return the weight of the edge between @orig and @dest, or null if no edge exists + */ + public Integer getEdgeWeight(int orig, int dest) { + return adjacencyList.get(orig).getOrDefault(dest, null); + } + + /** + * Returns the number of nodes currently in the graph. + * @return the number of nodes in the graph + */ + public int size() { + return adjacencyList.size(); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java b/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java index 26ca97736fe9..4bf21c7ed4c1 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/WelshPowell.java @@ -22,6 +22,7 @@ * For more information, see Graph Coloring. *

*/ +@SuppressWarnings({"rawtypes", "unchecked"}) public final class WelshPowell { private static final int BLANK_COLOR = -1; // Constant representing an uncolored state diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java index 3637e323f097..36d2cc8df160 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArray.java @@ -23,6 +23,7 @@ * @param the type of keys maintained by this hash map * @param the type of mapped values */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class GenericHashMapUsingArray { private int size; // Total number of key-value pairs diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java index aed39c941430..1b0792b8a738 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/HashMap.java @@ -8,6 +8,7 @@ * @param the type of keys maintained by this map * @param the type of mapped values */ +@SuppressWarnings("rawtypes") public class HashMap { private final int hashSize; private final LinkedList[] buckets; diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java index 0e49218d6348..5760d39f1df7 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/Intersection.java @@ -8,60 +8,66 @@ /** * The {@code Intersection} class provides a method to compute the intersection of two integer arrays. - * The intersection is defined as the set of common elements present in both arrays. *

- * This class utilizes a HashMap to efficiently count occurrences of elements in the first array, - * allowing for an efficient lookup of common elements in the second array. + * This intersection includes duplicate values โ€” meaning elements are included in the result + * as many times as they appear in both arrays (i.e., multiset intersection). *

* *

- * Example: - *

+ * The algorithm uses a {@link java.util.HashMap} to count occurrences of elements in the first array,
+ * then iterates through the second array to collect common elements based on these counts.
+ * 

+ * + *

+ * Example usage: + *

{@code
  * int[] array1 = {1, 2, 2, 1};
  * int[] array2 = {2, 2};
- * List result = Intersection.intersection(array1, array2); // result will contain [2, 2]
- * 
+ * List result = Intersection.intersection(array1, array2); // result: [2, 2] + * }
*

* *

- * Note: The order of the returned list may vary since it depends on the order of elements - * in the input arrays. + * Note: The order of elements in the returned list depends on the order in the second input array. *

*/ public final class Intersection { + private Intersection() { + // Utility class; prevent instantiation + } + /** - * Computes the intersection of two integer arrays. + * Computes the intersection of two integer arrays, preserving element frequency. + * For example, given [1,2,2,3] and [2,2,4], the result will be [2,2]. + * * Steps: - * 1. Count the occurrences of each element in the first array using a HashMap. - * 2. Iterate over the second array and check if the element is present in the HashMap. - * If it is, add it to the result list and decrement the count in the HashMap. - * 3. Return the result list containing the intersection of the two arrays. + * 1. Count the occurrences of each element in the first array using a map. + * 2. Iterate over the second array and collect common elements. * * @param arr1 the first array of integers * @param arr2 the second array of integers - * @return a list containing the intersection of the two arrays, or an empty list if either array is null or empty + * @return a list containing the intersection of the two arrays (with duplicates), + * or an empty list if either array is null or empty */ public static List intersection(int[] arr1, int[] arr2) { if (arr1 == null || arr2 == null || arr1.length == 0 || arr2.length == 0) { return Collections.emptyList(); } - Map cnt = new HashMap<>(16); - for (int v : arr1) { - cnt.put(v, cnt.getOrDefault(v, 0) + 1); + Map countMap = new HashMap<>(); + for (int num : arr1) { + countMap.put(num, countMap.getOrDefault(num, 0) + 1); } - List res = new ArrayList<>(); - for (int v : arr2) { - if (cnt.containsKey(v) && cnt.get(v) > 0) { - res.add(v); - cnt.put(v, cnt.get(v) - 1); + List result = new ArrayList<>(); + for (int num : arr2) { + if (countMap.getOrDefault(num, 0) > 0) { + result.add(num); + countMap.computeIfPresent(num, (k, v) -> v - 1); } } - return res; - } - private Intersection() { + return result; } } diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java index 10d5dc7decae..761a5fe83d18 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/LinearProbingHashMap.java @@ -34,6 +34,7 @@ * @param the type of keys maintained by this map * @param the type of mapped values */ +@SuppressWarnings("rawtypes") public class LinearProbingHashMap, Value> extends Map { private int hsize; // size of the hash table private Key[] keys; // array to store keys diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java index 915e4228b618..5fd6b2e7f3cb 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/MajorityElement.java @@ -1,8 +1,10 @@ package com.thealgorithms.datastructures.hashmap.hashing; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * This class provides a method to find the majority element(s) in an array of integers. @@ -18,13 +20,18 @@ private MajorityElement() { * Returns a list of majority element(s) from the given array of integers. * * @param nums an array of integers - * @return a list containing the majority element(s); returns an empty list if none exist + * @return a list containing the majority element(s); returns an empty list if none exist or input is null/empty */ public static List majority(int[] nums) { - HashMap numToCount = new HashMap<>(); + if (nums == null || nums.length == 0) { + return Collections.emptyList(); + } + + Map numToCount = new HashMap<>(); for (final var num : nums) { numToCount.merge(num, 1, Integer::sum); } + List majorityElements = new ArrayList<>(); for (final var entry : numToCount.entrySet()) { if (entry.getValue() >= nums.length / 2) { diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java index 422e8953625f..72a12cd58401 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CircleLinkedList.java @@ -10,6 +10,7 @@ * * @param the type of elements held in this list */ +@SuppressWarnings("rawtypes") public class CircleLinkedList { /** diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedList.java new file mode 100644 index 000000000000..fbb48854c449 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedList.java @@ -0,0 +1,118 @@ +package com.thealgorithms.datastructures.lists; + +/** + * This class is a circular doubly linked list implementation. In a circular + * doubly linked list, + * the last node points back to the first node and the first node points back to + * the last node, + * creating a circular chain in both directions. + * + * This implementation includes basic operations such as appending elements to + * the end, + * removing elements from a specified position, and converting the list to a + * string representation. + * + * @param the type of elements held in this list + */ +public class CircularDoublyLinkedList { + static final class Node { + Node next; + Node prev; + E value; + + private Node(E value, Node next, Node prev) { + this.value = value; + this.next = next; + this.prev = prev; + } + } + + private int size; + Node head = null; + + /** + * Initializes a new circular doubly linked list. A dummy head node is used for + * simplicity, + * pointing initially to itself to ensure the list is never empty. + */ + public CircularDoublyLinkedList() { + head = new Node<>(null, null, null); + head.next = head; + head.prev = head; + size = 0; + } + + /** + * Returns the current size of the list. + * + * @return the number of elements in the list + */ + public int getSize() { + return size; + } + + /** + * Appends a new element to the end of the list. Throws a NullPointerException + * if + * a null value is provided. + * + * @param value the value to append to the list + * @throws NullPointerException if the value is null + */ + public void append(E value) { + if (value == null) { + throw new NullPointerException("Cannot add null element to the list"); + } + Node newNode = new Node<>(value, head, head.prev); + head.prev.next = newNode; + head.prev = newNode; + size++; + } + + /** + * Returns a string representation of the list in the format "[ element1, + * element2, ... ]". + * An empty list is represented as "[]". + * + * @return the string representation of the list + */ + public String toString() { + if (size == 0) { + return "[]"; + } + StringBuilder sb = new StringBuilder("[ "); + Node current = head.next; + while (current != head) { + sb.append(current.value); + if (current.next != head) { + sb.append(", "); + } + current = current.next; + } + sb.append(" ]"); + return sb.toString(); + } + + /** + * Removes and returns the element at the specified position in the list. + * Throws an IndexOutOfBoundsException if the position is invalid. + * + * @param pos the position of the element to remove + * @return the value of the removed element - pop operation + * @throws IndexOutOfBoundsException if the position is out of range + */ + public E remove(int pos) { + if (pos >= size || pos < 0) { + throw new IndexOutOfBoundsException("Position out of bounds"); + } + Node current = head.next; + for (int i = 0; i < pos; i++) { + current = current.next; + } + current.prev.next = current.next; + current.next.prev = current.prev; + E removedValue = current.value; + size--; + return removedValue; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java b/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java index 4c1ffe9d3ea4..b58d51e7e5fe 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursion.java @@ -12,7 +12,7 @@ public class CountSinglyLinkedListRecursion extends SinglyLinkedList { * @param head the head node of the list segment being counted. * @return the count of nodes from the given head node onward. */ - private int countRecursion(Node head) { + private int countRecursion(SinglyLinkedListNode head) { return head == null ? 0 : 1 + countRecursion(head.next); } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java index ff3d39115c3b..63bb29034df2 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/CursorLinkedList.java @@ -10,6 +10,7 @@ * * @param the type of elements in this list */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class CursorLinkedList { /** diff --git a/src/main/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedList.java new file mode 100644 index 000000000000..3c4106f178b9 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedList.java @@ -0,0 +1,92 @@ +package com.thealgorithms.datastructures.lists; +/** + * Implements an algorithm to flatten a multilevel linked list. + * + * In this specific problem structure, each node has a `next` pointer (to the + * next node at the same level) and a `child` pointer (which points to the head + * of another sorted linked list). The goal is to merge all these lists into a + * single, vertically sorted linked list using the `child` pointer. + * + * The approach is a recursive one that leverages a merge utility, similar to + * the merge step in Merge Sort. It recursively flattens the list starting from + * the rightmost node and merges each node's child list with the already + * flattened list to its right. + * @see GeeksforGeeks: Flattening a Linked List + */ +public final class FlattenMultilevelLinkedList { + /** + * Private constructor to prevent instantiation of this utility class. + */ + private FlattenMultilevelLinkedList() { + } + /** + * Node represents an element in the multilevel linked list. It contains the + * integer data, a reference to the next node at the same level, and a + * reference to the head of a child list. + */ + static class Node { + int data; + Node next; + Node child; + + Node(int data) { + this.data = data; + this.next = null; + this.child = null; + } + } + + /** + * Merges two sorted linked lists (connected via the `child` pointer). + * This is a helper function for the main flatten algorithm. + * + * @param a The head of the first sorted list. + * @param b The head of the second sorted list. + * @return The head of the merged sorted list. + */ + private static Node merge(Node a, Node b) { + // If one of the lists is empty, return the other. + if (a == null) { + return b; + } + if (b == null) { + return a; + } + + Node result; + + // Choose the smaller value as the new head. + if (a.data < b.data) { + result = a; + result.child = merge(a.child, b); + } else { + result = b; + result.child = merge(a, b.child); + } + result.next = null; // Ensure the merged list has no `next` pointers. + return result; + } + + /** + * Flattens a multilevel linked list into a single sorted list. + * The flattened list is connected using the `child` pointers. + * + * @param head The head of the top-level list (connected via `next` pointers). + * @return The head of the fully flattened and sorted list. + */ + public static Node flatten(Node head) { + // Base case: if the list is empty or has only one node, it's already flattened. + if (head == null || head.next == null) { + return head; + } + + // Recursively flatten the list starting from the next node. + head.next = flatten(head.next); + + // Now, merge the current list (head's child list) with the flattened rest of the list. + head = merge(head, head.next); + + // Return the head of the fully merged list. + return head; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java index a16b202c4505..4e99642fccd8 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/MergeSortedSinglyLinkedList.java @@ -42,12 +42,12 @@ public static SinglyLinkedList merge(SinglyLinkedList listA, SinglyLinkedList li throw new NullPointerException("Input lists must not be null."); } - Node headA = listA.getHead(); - Node headB = listB.getHead(); + SinglyLinkedListNode headA = listA.getHead(); + SinglyLinkedListNode headB = listB.getHead(); int size = listA.size() + listB.size(); - Node head = new Node(); - Node tail = head; + SinglyLinkedListNode head = new SinglyLinkedListNode(); + SinglyLinkedListNode tail = head; while (headA != null && headB != null) { if (headA.value <= headB.value) { tail.next = headA; diff --git a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java index 08fe674b47f4..f018781ada70 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/QuickSortLinkedList.java @@ -105,7 +105,7 @@ public class QuickSortLinkedList { private final SinglyLinkedList list; // The linked list to be sorted - private Node head; // Head of the list + private SinglyLinkedListNode head; // Head of the list /** * Constructor that initializes the QuickSortLinkedList with a given linked list. @@ -136,19 +136,19 @@ public void sortList() { * @param head The head node of the list to sort * @return The head node of the sorted linked list */ - private Node sortList(Node head) { + private SinglyLinkedListNode sortList(SinglyLinkedListNode head) { if (head == null || head.next == null) { return head; } - Node pivot = head; + SinglyLinkedListNode pivot = head; head = head.next; pivot.next = null; - Node lessHead = new Node(); - Node lessTail = lessHead; - Node greaterHead = new Node(); - Node greaterTail = greaterHead; + SinglyLinkedListNode lessHead = new SinglyLinkedListNode(); + SinglyLinkedListNode lessTail = lessHead; + SinglyLinkedListNode greaterHead = new SinglyLinkedListNode(); + SinglyLinkedListNode greaterTail = greaterHead; while (head != null) { if (head.value < pivot.value) { @@ -164,14 +164,14 @@ private Node sortList(Node head) { lessTail.next = null; greaterTail.next = null; - Node sortedLess = sortList(lessHead.next); - Node sortedGreater = sortList(greaterHead.next); + SinglyLinkedListNode sortedLess = sortList(lessHead.next); + SinglyLinkedListNode sortedGreater = sortList(greaterHead.next); if (sortedLess == null) { pivot.next = sortedGreater; return pivot; } else { - Node current = sortedLess; + SinglyLinkedListNode current = sortedLess; while (current.next != null) { current = current.next; } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/README.md b/src/main/java/com/thealgorithms/datastructures/lists/README.md index 6aefa4c98e6d..5a19c3bfa990 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/README.md +++ b/src/main/java/com/thealgorithms/datastructures/lists/README.md @@ -30,3 +30,4 @@ The `next` variable points to the next node in the data structure and value stor 6. `MergeKSortedLinkedlist.java` : Merges K sorted linked list with mergesort (mergesort is also the most efficient sorting algorithm for linked list). 7. `RandomNode.java` : Selects a random node from given linked list and diplays it. 8. `SkipList.java` : Data Structure used for storing a sorted list of elements with help of a Linked list hierarchy that connects to subsequences of elements. +9. `TortoiseHareAlgo.java` : Finds the middle element of a linked list using the fast and slow pointer (Tortoise-Hare) algorithm. diff --git a/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java b/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java index c9a5c1df9870..9b9464d388b5 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/ReverseKGroup.java @@ -14,10 +14,10 @@ *

*

* The implementation contains: - * - {@code length(Node head)}: A method to calculate the length of the linked list. - * - {@code reverse(Node head, int count, int k)}: A helper method that reverses the nodes + * - {@code length(SinglyLinkedListNode head)}: A method to calculate the length of the linked list. + * - {@code reverse(SinglyLinkedListNode head, int count, int k)}: A helper method that reverses the nodes * in the linked list in groups of k. - * - {@code reverseKGroup(Node head, int k)}: The main method that initiates the reversal + * - {@code reverseKGroup(SinglyLinkedListNode head, int k)}: The main method that initiates the reversal * process by calling the reverse method. *

*

@@ -38,8 +38,8 @@ public class ReverseKGroup { * @param head The head node of the linked list. * @return The total number of nodes in the linked list. */ - public int length(Node head) { - Node curr = head; + public int length(SinglyLinkedListNode head) { + SinglyLinkedListNode curr = head; int count = 0; while (curr != null) { curr = curr.next; @@ -56,14 +56,14 @@ public int length(Node head) { * @param k The size of the group to reverse. * @return The new head of the reversed linked list segment. */ - public Node reverse(Node head, int count, int k) { + public SinglyLinkedListNode reverse(SinglyLinkedListNode head, int count, int k) { if (count < k) { return head; } - Node prev = null; + SinglyLinkedListNode prev = null; int count1 = 0; - Node curr = head; - Node next = null; + SinglyLinkedListNode curr = head; + SinglyLinkedListNode next = null; while (curr != null && count1 < k) { next = curr.next; curr.next = prev; @@ -85,7 +85,7 @@ public Node reverse(Node head, int count, int k) { * @param k The size of the group to reverse. * @return The head of the modified linked list after reversal. */ - public Node reverseKGroup(Node head, int k) { + public SinglyLinkedListNode reverseKGroup(SinglyLinkedListNode head, int k) { int count = length(head); return reverse(head, count, k); } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java b/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java index 7676cc343653..47ee5397097c 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedLists.java @@ -38,12 +38,12 @@ public class RotateSinglyLinkedLists { * @param k The number of positions to rotate the list to the right. * @return The head of the rotated linked list. */ - public Node rotateRight(Node head, int k) { + public SinglyLinkedListNode rotateRight(SinglyLinkedListNode head, int k) { if (head == null || head.next == null || k == 0) { return head; } - Node curr = head; + SinglyLinkedListNode curr = head; int len = 1; while (curr.next != null) { curr = curr.next; diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java b/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java index a40e9b2a1a66..4ac2de422595 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/SearchSinglyLinkedListRecursion.java @@ -30,7 +30,7 @@ public class SearchSinglyLinkedListRecursion extends SinglyLinkedList { * @param key the integer value to be searched for. * @return {@code true} if the value `key` is present in the list; otherwise, {@code false}. */ - private boolean searchRecursion(Node node, int key) { + private boolean searchRecursion(SinglyLinkedListNode node, int key) { return (node != null && (node.value == key || searchRecursion(node.next, key))); } diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java index eb6cdf48f58b..ff4af4437cc7 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedList.java @@ -12,7 +12,7 @@ public class SinglyLinkedList implements Iterable { /** * Head refer to the front of the list */ - private Node head; + private SinglyLinkedListNode head; /** * Size of SinglyLinkedList @@ -33,7 +33,7 @@ public SinglyLinkedList() { * @param head the head node of list * @param size the size of list */ - public SinglyLinkedList(Node head, int size) { + public SinglyLinkedList(SinglyLinkedListNode head, int size) { this.head = head; this.size = size; } @@ -44,8 +44,8 @@ public SinglyLinkedList(Node head, int size) { * */ public boolean detectLoop() { - Node currentNodeFast = head; - Node currentNodeSlow = head; + SinglyLinkedListNode currentNodeFast = head; + SinglyLinkedListNode currentNodeSlow = head; while (currentNodeFast != null && currentNodeFast.next != null) { currentNodeFast = currentNodeFast.next.next; currentNodeSlow = currentNodeSlow.next; @@ -61,12 +61,12 @@ public boolean detectLoop() { * If the length of the list is even then return item number length/2 * @return middle node of the list */ - public Node middle() { + public SinglyLinkedListNode middle() { if (head == null) { return null; } - Node firstCounter = head; - Node secondCounter = firstCounter.next; + SinglyLinkedListNode firstCounter = head; + SinglyLinkedListNode secondCounter = firstCounter.next; while (secondCounter != null && secondCounter.next != null) { firstCounter = firstCounter.next; secondCounter = secondCounter.next.next; @@ -82,15 +82,15 @@ public void swapNodes(int valueFirst, int valueSecond) { if (valueFirst == valueSecond) { return; } - Node previousA = null; - Node currentA = head; + SinglyLinkedListNode previousA = null; + SinglyLinkedListNode currentA = head; while (currentA != null && currentA.value != valueFirst) { previousA = currentA; currentA = currentA.next; } - Node previousB = null; - Node currentB = head; + SinglyLinkedListNode previousB = null; + SinglyLinkedListNode currentB = head; while (currentB != null && currentB.value != valueSecond) { previousB = currentB; currentB = currentB.next; @@ -117,7 +117,7 @@ public void swapNodes(int valueFirst, int valueSecond) { } // Swap next pointer - Node temp = currentA.next; + var temp = currentA.next; currentA.next = currentB.next; currentB.next = temp; } @@ -126,12 +126,12 @@ public void swapNodes(int valueFirst, int valueSecond) { * Reverse a singly linked list[Iterative] from a given node till the end * */ - public Node reverseListIter(Node node) { - Node prev = null; - Node curr = node; + public SinglyLinkedListNode reverseListIter(SinglyLinkedListNode node) { + SinglyLinkedListNode prev = null; + SinglyLinkedListNode curr = node; while (curr != null && curr.next != null) { - Node next = curr.next; + var next = curr.next; curr.next = prev; prev = curr; curr = next; @@ -149,13 +149,13 @@ public Node reverseListIter(Node node) { * Reverse a singly linked list[Recursive] from a given node till the end * */ - public Node reverseListRec(Node head) { + public SinglyLinkedListNode reverseListRec(SinglyLinkedListNode head) { if (head == null || head.next == null) { return head; } - Node prev = null; - Node h2 = reverseListRec(head.next); + SinglyLinkedListNode prev = null; + SinglyLinkedListNode h2 = reverseListRec(head.next); head.next.next = head; head.next = prev; @@ -167,7 +167,7 @@ public Node reverseListRec(Node head) { * Clear all nodes in the list */ public void clear() { - Node cur = head; + SinglyLinkedListNode cur = head; while (cur != null) { cur = cur.next; } @@ -198,7 +198,7 @@ public int size() { * * @return head of the list. */ - public Node getHead() { + public SinglyLinkedListNode getHead() { return head; } @@ -206,7 +206,7 @@ public Node getHead() { * Set head of the list. * */ - public void setHead(Node head) { + public void setHead(SinglyLinkedListNode head) { this.head = head; } @@ -249,10 +249,10 @@ public String toString() { } public void deleteDuplicates() { - Node pred = head; + SinglyLinkedListNode pred = head; // predecessor = the node // having sublist of its duplicates - Node newHead = head; + SinglyLinkedListNode newHead = head; while (newHead != null) { // if it's a beginning of duplicates sublist // skip all duplicates @@ -273,7 +273,7 @@ public void deleteDuplicates() { } public void print() { - Node temp = head; + SinglyLinkedListNode temp = head; while (temp != null && temp.next != null) { System.out.print(temp.value + "->"); temp = temp.next; @@ -310,7 +310,7 @@ public void insert(int data) { */ public void insertNth(int data, int position) { checkBounds(position, 0, size); - Node newNode = new Node(data); + SinglyLinkedListNode newNode = new SinglyLinkedListNode(data); if (head == null) { /* the list is empty */ head = newNode; @@ -325,7 +325,7 @@ public void insertNth(int data, int position) { return; } - Node cur = head; + SinglyLinkedListNode cur = head; for (int i = 0; i < position - 1; ++i) { cur = cur.next; } @@ -359,7 +359,7 @@ public void deleteNth(int position) { size--; return; } - Node cur = head; + SinglyLinkedListNode cur = head; for (int i = 0; i < position - 1; ++i) { cur = cur.next; } @@ -376,7 +376,7 @@ public void deleteNth(int position) { */ public int getNth(int index) { checkBounds(index, 0, size - 1); - Node cur = head; + SinglyLinkedListNode cur = head; for (int i = 0; i < index; ++i) { cur = cur.next; } @@ -440,7 +440,7 @@ public static void main(String[] arg) { } SinglyLinkedList instance = new SinglyLinkedList(); - Node head = new Node(0, new Node(2, new Node(3, new Node(3, new Node(4))))); + SinglyLinkedListNode head = new SinglyLinkedListNode(0, new SinglyLinkedListNode(2, new SinglyLinkedListNode(3, new SinglyLinkedListNode(3, new SinglyLinkedListNode(4))))); instance.setHead(head); instance.deleteDuplicates(); instance.print(); @@ -452,7 +452,7 @@ public Iterator iterator() { } private class SinglyLinkedListIterator implements Iterator { - private Node current; + private SinglyLinkedListNode current; SinglyLinkedListIterator() { current = head; @@ -474,43 +474,3 @@ public Integer next() { } } } - -/** - * This class is the nodes of the SinglyLinked List. They consist of a value and - * a pointer to the node after them. - */ -class Node { - - /** - * The value of the node - */ - int value; - - /** - * Point to the next node - */ - Node next; - - Node() { - } - - /** - * Constructor - * - * @param value Value to be put in the node - */ - Node(int value) { - this(value, null); - } - - /** - * Constructor - * - * @param value Value to be put in the node - * @param next Reference to the next node - */ - Node(int value, Node next) { - this.value = value; - this.next = next; - } -} diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java new file mode 100644 index 000000000000..d0a06369215a --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/lists/SinglyLinkedListNode.java @@ -0,0 +1,34 @@ +package com.thealgorithms.datastructures.lists; + +/** + * This class is the nodes of the SinglyLinked List. They consist of a value and + * a pointer to the node after them. + */ +class SinglyLinkedListNode { + + int value; + SinglyLinkedListNode next = null; + + SinglyLinkedListNode() { + } + + /** + * Constructor + * + * @param value Value to be put in the node + */ + SinglyLinkedListNode(int value) { + this(value, null); + } + + /** + * Constructor + * + * @param value Value to be put in the node + * @param next Reference to the next node + */ + SinglyLinkedListNode(int value, SinglyLinkedListNode next) { + this.value = value; + this.next = next; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/lists/SkipList.java b/src/main/java/com/thealgorithms/datastructures/lists/SkipList.java index 3309ab24917d..0b4fcd91483c 100644 --- a/src/main/java/com/thealgorithms/datastructures/lists/SkipList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/SkipList.java @@ -29,6 +29,7 @@ * @param type of elements * @see Wiki. Skip list */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class SkipList> { /** diff --git a/src/main/java/com/thealgorithms/datastructures/lists/TortoiseHareAlgo.java b/src/main/java/com/thealgorithms/datastructures/lists/TortoiseHareAlgo.java new file mode 100644 index 000000000000..9d803003c658 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/lists/TortoiseHareAlgo.java @@ -0,0 +1,63 @@ +package com.thealgorithms.datastructures.lists; + +public class TortoiseHareAlgo { + static final class Node { + Node next; + E value; + + private Node(E value, Node next) { + this.value = value; + this.next = next; + } + } + + private Node head = null; + + public TortoiseHareAlgo() { + head = null; + } + + public void append(E value) { + Node newNode = new Node<>(value, null); + if (head == null) { + head = newNode; + return; + } + Node current = head; + while (current.next != null) { + current = current.next; + } + current.next = newNode; + } + + public E getMiddle() { + if (head == null) { + return null; + } + + Node slow = head; + Node fast = head; + + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + } + + return slow.value; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("["); + Node current = head; + while (current != null) { + sb.append(current.value); + if (current.next != null) { + sb.append(", "); + } + current = current.next; + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java b/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java index 11e5e9b83892..981b3b32e0b2 100644 --- a/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java +++ b/src/main/java/com/thealgorithms/datastructures/queues/QueueByTwoStacks.java @@ -11,6 +11,7 @@ * * @param The type of elements held in this queue. */ +@SuppressWarnings("unchecked") public class QueueByTwoStacks { private final Stack enqueueStk; diff --git a/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java b/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java index e9de14b53302..d87f5f4ea86a 100644 --- a/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java +++ b/src/main/java/com/thealgorithms/datastructures/stacks/ReverseStack.java @@ -38,6 +38,9 @@ private ReverseStack() { * @param stack the stack to reverse; should not be null */ public static void reverseStack(Stack stack) { + if (stack == null) { + throw new IllegalArgumentException("Stack cannot be null"); + } if (stack.isEmpty()) { return; } diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursiveGeneric.java b/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursiveGeneric.java index 5c1334ffa0e8..0245372fe012 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursiveGeneric.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/BSTRecursiveGeneric.java @@ -13,6 +13,7 @@ *

* * @author [Madhur Panwar](git-Madhur Panwar) + * @author [Udaya Krishnan M](git-Udaya Krishnan M) {added prettyDisplay() method} */ public class BSTRecursiveGeneric> { @@ -28,6 +29,29 @@ public BSTRecursiveGeneric() { root = null; } + /** + * Displays the tree is a structed format + */ + public void prettyDisplay() { + prettyDisplay(root, 0); + } + + private void prettyDisplay(Node node, int level) { + if (node == null) { + return; + } + prettyDisplay(node.right, level + 1); + if (level != 0) { + for (int i = 0; i < level - 1; i++) { + System.out.print("|\t"); + } + System.out.println("|---->" + node.data); + } else { + System.out.println(node.data); + } + prettyDisplay(node.left, level + 1); + } + /** * main function for testing */ @@ -38,7 +62,12 @@ public static void main(String[] args) { integerTree.add(5); integerTree.add(10); - integerTree.add(9); + integerTree.add(-9); + integerTree.add(4); + integerTree.add(3); + integerTree.add(1); + System.out.println("Pretty Display of current tree is:"); + integerTree.prettyDisplay(); assert !integerTree.find(4) : "4 is not yet present in BST"; assert integerTree.find(10) @@ -54,16 +83,21 @@ public static void main(String[] args) { assert integerTree.find(70) : "70 was inserted but not found"; /* - Will print in following order - 5 10 20 70 + Will print in following order + 5 10 20 70 */ + System.out.println("Pretty Display of current tree is:"); + integerTree.prettyDisplay(); integerTree.inorder(); + System.out.println("Pretty Display of current tree is:"); + integerTree.prettyDisplay(); System.out.println(); System.out.println("Testing for string data..."); // String BSTRecursiveGeneric stringTree = new BSTRecursiveGeneric(); stringTree.add("banana"); + stringTree.add("apple"); stringTree.add("pineapple"); stringTree.add("date"); assert !stringTree.find("girl") @@ -80,11 +114,15 @@ public static void main(String[] args) { stringTree.add("hills"); assert stringTree.find("hills") : "hills was inserted but not found"; + System.out.println("Pretty Display of current tree is:"); + stringTree.prettyDisplay(); /* Will print in following order banana hills india pineapple */ stringTree.inorder(); + System.out.println("Pretty Display of current tree is:"); + stringTree.prettyDisplay(); } /** diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BTree.java b/src/main/java/com/thealgorithms/datastructures/trees/BTree.java new file mode 100644 index 000000000000..2c19253b45e7 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/BTree.java @@ -0,0 +1,323 @@ +package com.thealgorithms.datastructures.trees; + +import java.util.ArrayList; + +/** + * Implementation of a B-Tree, a self-balancing tree data structure that maintains sorted data + * and allows searches, sequential access, insertions, and deletions in logarithmic time. + * + * B-Trees are generalizations of binary search trees in that a node can have more than two children. + * They're widely used in databases and file systems. + * + * For more information: https://en.wikipedia.org/wiki/B-tree + */ + +public class BTree { + static class BTreeNode { + int[] keys; + int t; // Minimum degree (defines range for number of keys) + BTreeNode[] children; + int n; // Current number of keys + boolean leaf; + + BTreeNode(int t, boolean leaf) { + this.t = t; + this.leaf = leaf; + this.keys = new int[2 * t - 1]; + this.children = new BTreeNode[2 * t]; + this.n = 0; + } + + void traverse(ArrayList result) { + for (int i = 0; i < n; i++) { + if (!leaf) { + children[i].traverse(result); + } + result.add(keys[i]); + } + if (!leaf) { + children[n].traverse(result); + } + } + + BTreeNode search(int key) { + int i = 0; + while (i < n && key > keys[i]) { + i++; + } + if (i < n && keys[i] == key) { + return this; + } + if (leaf) { + return null; + } + return children[i].search(key); + } + + void insertNonFull(int key) { + int i = n - 1; + if (leaf) { + while (i >= 0 && keys[i] > key) { + keys[i + 1] = keys[i]; + i--; + } + keys[i + 1] = key; + n++; + } else { + while (i >= 0 && keys[i] > key) { + i--; + } + if (children[i + 1].n == 2 * t - 1) { + splitChild(i + 1, children[i + 1]); + if (keys[i + 1] < key) { + i++; + } + } + children[i + 1].insertNonFull(key); + } + } + + void splitChild(int i, BTreeNode y) { + BTreeNode z = new BTreeNode(y.t, y.leaf); + z.n = t - 1; + + System.arraycopy(y.keys, t, z.keys, 0, t - 1); + if (!y.leaf) { + System.arraycopy(y.children, t, z.children, 0, t); + } + y.n = t - 1; + + for (int j = n; j >= i + 1; j--) { + children[j + 1] = children[j]; + } + children[i + 1] = z; + + for (int j = n - 1; j >= i; j--) { + keys[j + 1] = keys[j]; + } + keys[i] = y.keys[t - 1]; + n++; + } + + void remove(int key) { + int idx = findKey(key); + + if (idx < n && keys[idx] == key) { + if (leaf) { + removeFromLeaf(idx); + } else { + removeFromNonLeaf(idx); + } + } else { + if (leaf) { + return; // Key not found + } + + boolean flag = idx == n; + if (children[idx].n < t) { + fill(idx); + } + + if (flag && idx > n) { + children[idx - 1].remove(key); + } else { + children[idx].remove(key); + } + } + } + + private int findKey(int key) { + int idx = 0; + while (idx < n && keys[idx] < key) { + ++idx; + } + return idx; + } + + private void removeFromLeaf(int idx) { + for (int i = idx + 1; i < n; ++i) { + keys[i - 1] = keys[i]; + } + n--; + } + + private void removeFromNonLeaf(int idx) { + int key = keys[idx]; + if (children[idx].n >= t) { + int pred = getPredecessor(idx); + keys[idx] = pred; + children[idx].remove(pred); + } else if (children[idx + 1].n >= t) { + int succ = getSuccessor(idx); + keys[idx] = succ; + children[idx + 1].remove(succ); + } else { + merge(idx); + children[idx].remove(key); + } + } + + private int getPredecessor(int idx) { + BTreeNode cur = children[idx]; + while (!cur.leaf) { + cur = cur.children[cur.n]; + } + return cur.keys[cur.n - 1]; + } + + private int getSuccessor(int idx) { + BTreeNode cur = children[idx + 1]; + while (!cur.leaf) { + cur = cur.children[0]; + } + return cur.keys[0]; + } + + private void fill(int idx) { + if (idx != 0 && children[idx - 1].n >= t) { + borrowFromPrev(idx); + } else if (idx != n && children[idx + 1].n >= t) { + borrowFromNext(idx); + } else { + if (idx != n) { + merge(idx); + } else { + merge(idx - 1); + } + } + } + + private void borrowFromPrev(int idx) { + BTreeNode child = children[idx]; + BTreeNode sibling = children[idx - 1]; + + for (int i = child.n - 1; i >= 0; --i) { + child.keys[i + 1] = child.keys[i]; + } + + if (!child.leaf) { + for (int i = child.n; i >= 0; --i) { + child.children[i + 1] = child.children[i]; + } + } + + child.keys[0] = keys[idx - 1]; + + if (!child.leaf) { + child.children[0] = sibling.children[sibling.n]; + } + + keys[idx - 1] = sibling.keys[sibling.n - 1]; + + child.n += 1; + sibling.n -= 1; + } + + private void borrowFromNext(int idx) { + BTreeNode child = children[idx]; + BTreeNode sibling = children[idx + 1]; + + child.keys[child.n] = keys[idx]; + + if (!child.leaf) { + child.children[child.n + 1] = sibling.children[0]; + } + + keys[idx] = sibling.keys[0]; + + for (int i = 1; i < sibling.n; ++i) { + sibling.keys[i - 1] = sibling.keys[i]; + } + + if (!sibling.leaf) { + for (int i = 1; i <= sibling.n; ++i) { + sibling.children[i - 1] = sibling.children[i]; + } + } + + child.n += 1; + sibling.n -= 1; + } + + private void merge(int idx) { + BTreeNode child = children[idx]; + BTreeNode sibling = children[idx + 1]; + + child.keys[t - 1] = keys[idx]; + + for (int i = 0; i < sibling.n; ++i) { + child.keys[i + t] = sibling.keys[i]; + } + + if (!child.leaf) { + for (int i = 0; i <= sibling.n; ++i) { + child.children[i + t] = sibling.children[i]; + } + } + + for (int i = idx + 1; i < n; ++i) { + keys[i - 1] = keys[i]; + } + + for (int i = idx + 2; i <= n; ++i) { + children[i - 1] = children[i]; + } + + child.n += sibling.n + 1; + n--; + } + } + + private BTreeNode root; + private final int t; + + public BTree(int t) { + this.root = null; + this.t = t; + } + + public void traverse(ArrayList result) { + if (root != null) { + root.traverse(result); + } + } + + public boolean search(int key) { + return root != null && root.search(key) != null; + } + + public void insert(int key) { + if (search(key)) { + return; + } + if (root == null) { + root = new BTreeNode(t, true); + root.keys[0] = key; + root.n = 1; + } else { + if (root.n == 2 * t - 1) { + BTreeNode s = new BTreeNode(t, false); + s.children[0] = root; + s.splitChild(0, root); + int i = 0; + if (s.keys[0] < key) { + i++; + } + s.children[i].insertNonFull(key); + root = s; + } else { + root.insertNonFull(key); + } + } + } + + public void delete(int key) { + if (root == null) { + return; + } + root.remove(key); + if (root.n == 0) { + root = root.leaf ? null : root.children[0]; + } + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistance.java b/src/main/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistance.java new file mode 100644 index 000000000000..9721d4ab0ad5 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistance.java @@ -0,0 +1,185 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation of the full Damerauโ€“Levenshtein distance algorithm. + * + * This algorithm calculates the minimum number of operations required + * to transform one string into another. Supported operations are: + * insertion, deletion, substitution, and transposition of adjacent characters. + * + * Unlike the restricted version (OSA), this implementation allows multiple + * edits on the same substring, computing the true edit distance. + * + * Time Complexity: O(n * m * max(n, m)) + * Space Complexity: O(n * m) + */ +public final class DamerauLevenshteinDistance { + + private DamerauLevenshteinDistance() { + // Utility class + } + + /** + * Computes the full Damerauโ€“Levenshtein distance between two strings. + * + * @param s1 the first string + * @param s2 the second string + * @return the minimum edit distance between the two strings + * @throws IllegalArgumentException if either input string is null + */ + public static int distance(String s1, String s2) { + validateInputs(s1, s2); + + int n = s1.length(); + int m = s2.length(); + + Map charLastPosition = buildCharacterMap(s1, s2); + int[][] dp = initializeTable(n, m); + + fillTable(s1, s2, dp, charLastPosition); + + return dp[n + 1][m + 1]; + } + + /** + * Validates that both input strings are not null. + * + * @param s1 the first string to validate + * @param s2 the second string to validate + * @throws IllegalArgumentException if either string is null + */ + private static void validateInputs(String s1, String s2) { + if (s1 == null || s2 == null) { + throw new IllegalArgumentException("Input strings must not be null."); + } + } + + /** + * Builds a character map containing all unique characters from both strings. + * Each character is initialized with a position value of 0. + * + * This map is used to track the last occurrence position of each character + * during the distance computation, which is essential for handling transpositions. + * + * @param s1 the first string + * @param s2 the second string + * @return a map containing all unique characters from both strings, initialized to 0 + */ + private static Map buildCharacterMap(String s1, String s2) { + Map charMap = new HashMap<>(); + for (char c : s1.toCharArray()) { + charMap.putIfAbsent(c, 0); + } + for (char c : s2.toCharArray()) { + charMap.putIfAbsent(c, 0); + } + return charMap; + } + + /** + * Initializes the dynamic programming table for the algorithm. + * + * The table has dimensions (n+2) x (m+2) where n and m are the lengths + * of the input strings. The extra rows and columns are used to handle + * the transposition operation correctly. + * + * The first row and column are initialized with the maximum possible distance, + * while the second row and column represent the base case of transforming + * from an empty string. + * + * @param n the length of the first string + * @param m the length of the second string + * @return an initialized DP table ready for computation + */ + private static int[][] initializeTable(int n, int m) { + int maxDist = n + m; + int[][] dp = new int[n + 2][m + 2]; + + dp[0][0] = maxDist; + + for (int i = 0; i <= n; i++) { + dp[i + 1][0] = maxDist; + dp[i + 1][1] = i; + } + + for (int j = 0; j <= m; j++) { + dp[0][j + 1] = maxDist; + dp[1][j + 1] = j; + } + + return dp; + } + + /** + * Fills the dynamic programming table by computing the minimum edit distance + * for each substring pair. + * + * This method implements the core algorithm logic, iterating through both strings + * and computing the minimum cost of transforming substrings. It considers all + * four operations: insertion, deletion, substitution, and transposition. + * + * The character position map is updated as we progress through the first string + * to enable efficient transposition cost calculation. + * + * @param s1 the first string + * @param s2 the second string + * @param dp the dynamic programming table to fill + * @param charLastPosition map tracking the last position of each character in s1 + */ + private static void fillTable(String s1, String s2, int[][] dp, Map charLastPosition) { + int n = s1.length(); + int m = s2.length(); + + for (int i = 1; i <= n; i++) { + int lastMatchCol = 0; + + for (int j = 1; j <= m; j++) { + char char1 = s1.charAt(i - 1); + char char2 = s2.charAt(j - 1); + + int lastMatchRow = charLastPosition.get(char2); + int cost = (char1 == char2) ? 0 : 1; + + if (char1 == char2) { + lastMatchCol = j; + } + + dp[i + 1][j + 1] = computeMinimumCost(dp, i, j, lastMatchRow, lastMatchCol, cost); + } + + charLastPosition.put(s1.charAt(i - 1), i); + } + } + + /** + * Computes the minimum cost among all possible operations at the current position. + * + * This method evaluates four possible operations: + * 1. Substitution: replace character at position i with character at position j + * 2. Insertion: insert character from s2 at position j + * 3. Deletion: delete character from s1 at position i + * 4. Transposition: swap characters that have been seen before + * + * The transposition cost accounts for the gap between the current position + * and the last position where matching characters were found. + * + * @param dp the dynamic programming table + * @param i the current position in the first string (1-indexed in the DP table) + * @param j the current position in the second string (1-indexed in the DP table) + * @param lastMatchRow the row index where the current character of s2 last appeared in s1 + * @param lastMatchCol the column index where the current character of s1 last matched in s2 + * @param cost the substitution cost (0 if characters match, 1 otherwise) + * @return the minimum cost among all operations + */ + private static int computeMinimumCost(int[][] dp, int i, int j, int lastMatchRow, int lastMatchCol, int cost) { + int substitution = dp[i][j] + cost; + int insertion = dp[i + 1][j] + 1; + int deletion = dp[i][j + 1] + 1; + int transposition = dp[lastMatchRow][lastMatchCol] + i - lastMatchRow - 1 + 1 + j - lastMatchCol - 1; + + return Math.min(Math.min(substitution, insertion), Math.min(deletion, transposition)); + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java new file mode 100644 index 000000000000..abc1e321ca8f --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java @@ -0,0 +1,53 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * The {@code KnapsackZeroOne} provides Recursive solution for the 0/1 Knapsack + * problem. Solves by exploring all combinations of items using recursion. No + * memoization or dynamic programming optimizations are applied. + * + * Time Complexity: O(2^n) โ€” explores all subsets. + * Space Complexity: O(n) โ€” due to recursive call stack. + * + * Problem Reference: https://en.wikipedia.org/wiki/Knapsack_problem + */ +public final class KnapsackZeroOne { + + private KnapsackZeroOne() { + // Prevent instantiation + } + + /** + * Solves the 0/1 Knapsack problem using recursion. + * + * @param values the array containing values of the items + * @param weights the array containing weights of the items + * @param capacity the total capacity of the knapsack + * @param n the number of items + * @return the maximum total value achievable within the given weight limit + * @throws IllegalArgumentException if input arrays are null, empty, or + * lengths mismatch + */ + public static int compute(final int[] values, final int[] weights, final int capacity, final int n) { + if (values == null || weights == null) { + throw new IllegalArgumentException("Input arrays cannot be null."); + } + if (values.length != weights.length) { + throw new IllegalArgumentException("Value and weight arrays must be of the same length."); + } + if (capacity < 0 || n < 0) { + throw new IllegalArgumentException("Invalid input: arrays must be non-empty and capacity/n " + + "non-negative."); + } + if (n == 0 || capacity == 0 || values.length == 0) { + return 0; + } + + if (weights[n - 1] <= capacity) { + final int include = values[n - 1] + compute(values, weights, capacity - weights[n - 1], n - 1); + final int exclude = compute(values, weights, capacity, n - 1); + return Math.max(include, exclude); + } else { + return compute(values, weights, capacity, n - 1); + } + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java new file mode 100644 index 000000000000..c560efc61c71 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java @@ -0,0 +1,69 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * Tabulation (Bottom-Up) Solution for 0-1 Knapsack Problem. + * This method uses dynamic programming to build up a solution iteratively, + * filling a 2-D array where each entry dp[i][w] represents the maximum value + * achievable with the first i items and a knapsack capacity of w. + * + * The tabulation approach is efficient because it avoids redundant calculations + * by solving all subproblems in advance and storing their results, ensuring + * each subproblem is solved only once. This is a key technique in dynamic programming, + * making it possible to solve problems that would otherwise be infeasible due to + * exponential time complexity in naive recursive solutions. + * + * Time Complexity: O(n * W), where n is the number of items and W is the knapsack capacity. + * Space Complexity: O(n * W) for the DP table. + * + * For more information, see: + * https://en.wikipedia.org/wiki/Knapsack_problem#Dynamic_programming + */ +public final class KnapsackZeroOneTabulation { + + private KnapsackZeroOneTabulation() { + // Prevent instantiation + } + + /** + * Solves the 0-1 Knapsack problem using the bottom-up tabulation technique. + * @param values the values of the items + * @param weights the weights of the items + * @param capacity the total capacity of the knapsack + * @param itemCount the number of items + * @return the maximum value that can be put in the knapsack + * @throws IllegalArgumentException if input arrays are null, of different lengths,or if capacity or itemCount is invalid + */ + public static int compute(final int[] values, final int[] weights, final int capacity, final int itemCount) { + if (values == null || weights == null) { + throw new IllegalArgumentException("Values and weights arrays must not be null."); + } + if (values.length != weights.length) { + throw new IllegalArgumentException("Values and weights arrays must be non-null and of same length."); + } + if (capacity < 0) { + throw new IllegalArgumentException("Capacity must not be negative."); + } + if (itemCount < 0 || itemCount > values.length) { + throw new IllegalArgumentException("Item count must be between 0 and the length of the values array."); + } + + final int[][] dp = new int[itemCount + 1][capacity + 1]; + + for (int i = 1; i <= itemCount; i++) { + final int currentValue = values[i - 1]; + final int currentWeight = weights[i - 1]; + + for (int w = 1; w <= capacity; w++) { + if (currentWeight <= w) { + final int includeItem = currentValue + dp[i - 1][w - currentWeight]; + final int excludeItem = dp[i - 1][w]; + dp[i][w] = Math.max(includeItem, excludeItem); + } else { + dp[i][w] = dp[i - 1][w]; + } + } + } + + return dp[itemCount][capacity]; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequence.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequence.java index b5ac62b4674b..ba1def551192 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequence.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestArithmeticSubsequence.java @@ -2,6 +2,7 @@ import java.util.HashMap; +@SuppressWarnings({"rawtypes", "unchecked"}) final class LongestArithmeticSubsequence { private LongestArithmeticSubsequence() { } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java new file mode 100644 index 000000000000..7bc0855e0566 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogN.java @@ -0,0 +1,75 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * Implementation of the Longest Increasing Subsequence (LIS) problem using + * an O(n log n) dynamic programming solution enhanced with binary search. + * + * @author Vusal Huseynov (https://github.com/huseynovvusal) + */ +public final class LongestIncreasingSubsequenceNLogN { + private LongestIncreasingSubsequenceNLogN() { + } + + /** + * Finds the index of the smallest element in the array that is greater than + * or equal to the target using binary search. The search is restricted to + * the first `size` elements of the array. + * + * @param arr The array to search in (assumed to be sorted up to `size`). + * @param size The number of valid elements in the array. + * @param target The target value to find the lower bound for. + * @return The index of the lower bound. + */ + private static int lowerBound(int[] arr, int target, int size) { + int l = 0; + int r = size; + + while (l < r) { + int mid = l + (r - l) / 2; + + if (target > arr[mid]) { + // Move right if target is greater than mid element + l = mid + 1; + } else { + // Move left if target is less than or equal to mid element + r = mid; + } + } + + // Return the index where the target can be inserted + return l; + } + + /** + * Calculates the length of the Longest Increasing Subsequence (LIS) in the given array. + * + * @param arr The input array of integers. + * @return The length of the LIS. + */ + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; // Return 0 for empty or null arrays + } + + // tails[i] - the smallest end element of an increasing subsequence of length i+1 + int[] tails = new int[arr.length]; + // size - the length of the longest increasing subsequence found so far + int size = 0; + + for (int x : arr) { + // Find the position to replace or extend the subsequence + int index = lowerBound(tails, x, size); + + // Update the tails array with the current element + tails[index] = x; + + // If the element extends the subsequence, increase the size + if (index == size) { + size++; + } + } + + // Return the length of the LIS + return size; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/MaximumProductSubarray.java b/src/main/java/com/thealgorithms/dynamicprogramming/MaximumProductSubarray.java new file mode 100644 index 000000000000..1c16f612e98f --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/MaximumProductSubarray.java @@ -0,0 +1,58 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * The MaximumProductSubarray class implements the algorithm to find the + * maximum product of a contiguous subarray within a given array of integers. + * + *

Given an array of integers (which may contain positive numbers, negative + * numbers, and zeros), this algorithm finds the contiguous subarray that has + * the largest product. The algorithm handles negative numbers efficiently by + * tracking both maximum and minimum products, since a negative number can turn + * a minimum product into a maximum product.

+ * + *

This implementation uses a dynamic programming approach that runs in O(n) + * time complexity and O(1) space complexity, making it highly efficient for + * large arrays.

+ */ +public final class MaximumProductSubarray { + + private MaximumProductSubarray() { + // Prevent instantiation + } + + /** + * Finds the maximum product of any contiguous subarray in the given array. + * + * @param nums an array of integers which may contain positive, negative, + * and zero values. + * @return the maximum product of a contiguous subarray. Returns 0 if the + * array is null or empty. + */ + public static int maxProduct(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + + long maxProduct = nums[0]; + long currentMax = nums[0]; + long currentMin = nums[0]; + + for (int i = 1; i < nums.length; i++) { + // Swap currentMax and currentMin if current number is negative + if (nums[i] < 0) { + long temp = currentMax; + currentMax = currentMin; + currentMin = temp; + } + + // Update currentMax and currentMin + currentMax = Math.max(nums[i], currentMax * nums[i]); + currentMin = Math.min(nums[i], currentMin * nums[i]); + + // Update global max product + maxProduct = Math.max(maxProduct, currentMax); + } + + return (int) maxProduct; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/NeedlemanWunsch.java b/src/main/java/com/thealgorithms/dynamicprogramming/NeedlemanWunsch.java new file mode 100644 index 000000000000..13b640cf0d04 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/NeedlemanWunsch.java @@ -0,0 +1,60 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * The Needlemanโ€“Wunsch algorithm performs global sequence alignment between two strings. + * It computes the optimal alignment score using dynamic programming, + * given a scoring scheme for matches, mismatches, and gaps. + * + * Time Complexity: O(n * m) + * Space Complexity: O(n * m) + */ +public final class NeedlemanWunsch { + + private NeedlemanWunsch() { + // Utility Class + } + + /** + * Computes the Needlemanโ€“Wunsch global alignment score between two strings. + * + * @param s1 the first string + * @param s2 the second string + * @param matchScore score for a character match + * @param mismatchPenalty penalty for a mismatch (should be negative) + * @param gapPenalty penalty for inserting a gap (should be negative) + * @return the optimal alignment score + */ + public static int align(String s1, String s2, int matchScore, int mismatchPenalty, int gapPenalty) { + if (s1 == null || s2 == null) { + throw new IllegalArgumentException("Input strings must not be null."); + } + + int n = s1.length(); + int m = s2.length(); + + int[][] dp = new int[n + 1][m + 1]; + + // Initialize gap penalties for first row and column + for (int i = 0; i <= n; i++) { + dp[i][0] = i * gapPenalty; + } + for (int j = 0; j <= m; j++) { + dp[0][j] = j * gapPenalty; + } + + // Fill the DP matrix + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + int matchOrMismatch = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? matchScore : mismatchPenalty; + + dp[i][j] = Math.max(Math.max(dp[i - 1][j - 1] + matchOrMismatch, // match/mismatch + dp[i - 1][j] + gapPenalty // deletion (gap in s2) + ), + dp[i][j - 1] + gapPenalty // insertion (gap in s1) + ); + } + } + + return dp[n][m]; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java index 49c4a0a3a008..8c72fa347f50 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java @@ -1,3 +1,5 @@ +package com.thealgorithms.dynamicprogramming; +import java.util.Arrays; /** * @author Md Asif Joardar * @@ -13,11 +15,6 @@ * * The time complexity of the solution is O(n ร— sum) and requires O(n ร— sum) space */ - -package com.thealgorithms.dynamicprogramming; - -import java.util.Arrays; - public final class PartitionProblem { private PartitionProblem() { } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/SmithWaterman.java b/src/main/java/com/thealgorithms/dynamicprogramming/SmithWaterman.java new file mode 100644 index 000000000000..c0d66f68b502 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/SmithWaterman.java @@ -0,0 +1,56 @@ +package com.thealgorithms.dynamicprogramming; + +/** + * Smithโ€“Waterman algorithm for local sequence alignment. + * Finds the highest scoring local alignment between substrings of two sequences. + * + * Time Complexity: O(n * m) + * Space Complexity: O(n * m) + */ +public final class SmithWaterman { + + private SmithWaterman() { + // Utility Class + } + + /** + * Computes the Smithโ€“Waterman local alignment score between two strings. + * + * @param s1 first string + * @param s2 second string + * @param matchScore score for a match + * @param mismatchPenalty penalty for mismatch (negative) + * @param gapPenalty penalty for insertion/deletion (negative) + * @return the maximum local alignment score + */ + public static int align(String s1, String s2, int matchScore, int mismatchPenalty, int gapPenalty) { + if (s1 == null || s2 == null) { + throw new IllegalArgumentException("Input strings must not be null."); + } + + int n = s1.length(); + int m = s2.length(); + int maxScore = 0; + + int[][] dp = new int[n + 1][m + 1]; + + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + int matchOrMismatch = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? matchScore : mismatchPenalty; + + dp[i][j] = Math.max(0, + Math.max(Math.max(dp[i - 1][j - 1] + matchOrMismatch, // match/mismatch + dp[i - 1][j] + gapPenalty // deletion + ), + dp[i][j - 1] + gapPenalty // insertion + )); + + if (dp[i][j] > maxScore) { + maxScore = dp[i][j]; + } + } + } + + return maxScore; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java index 946da46cb292..5c0167977ae4 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/SubsetSumSpaceOptimized.java @@ -1,35 +1,39 @@ package com.thealgorithms.dynamicprogramming; -/* -The Sum of Subset problem determines whether a subset of elements from a -given array sums up to a specific target value. -*/ + +/** + * Utility class for solving the Subset Sum problem using a space-optimized dynamic programming approach. + * + *

This algorithm determines whether any subset of a given array sums up to a specific target value.

+ * + *

Time Complexity: O(n * sum)

+ *

Space Complexity: O(sum)

+ */ public final class SubsetSumSpaceOptimized { private SubsetSumSpaceOptimized() { } + /** - * This method checks whether the subset of an array - * contains a given sum or not. This is an space - * optimized solution using 1D boolean array - * Time Complexity: O(n * sum), Space complexity: O(sum) + * Determines whether there exists a subset of the given array that adds up to the specified sum. + * This method uses a space-optimized dynamic programming approach with a 1D boolean array. * - * @param arr An array containing integers - * @param sum The target sum of the subset - * @return True or False + * @param nums The array of non-negative integers + * @param targetSum The desired subset sum + * @return {@code true} if such a subset exists, {@code false} otherwise */ - public static boolean isSubsetSum(int[] arr, int sum) { - int n = arr.length; - // Declare the boolean array with size sum + 1 - boolean[] dp = new boolean[sum + 1]; + public static boolean isSubsetSum(int[] nums, int targetSum) { + if (targetSum < 0) { + return false; // Subset sum can't be negative + } - // Initialize the first element as true - dp[0] = true; + boolean[] dp = new boolean[targetSum + 1]; + dp[0] = true; // Empty subset always sums to 0 - // Find the subset sum using 1D array - for (int i = 0; i < n; i++) { - for (int j = sum; j >= arr[i]; j--) { - dp[j] = dp[j] || dp[j - arr[i]]; + for (int number : nums) { + for (int j = targetSum; j >= number; j--) { + dp[j] = dp[j] || dp[j - number]; } } - return dp[sum]; + + return dp[targetSum]; } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java b/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java new file mode 100644 index 000000000000..9fd82ccaf078 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/TreeMatching.java @@ -0,0 +1,78 @@ +package com.thealgorithms.dynamicprogramming; + +import com.thealgorithms.datastructures.graphs.UndirectedAdjacencyListGraph; + +/** + * This class implements the algorithm for calculating the maximum weighted matching in a tree. + * The tree is represented as an undirected graph with weighted edges. + * + * Problem Description: + * Given an undirected tree G = (V, E) with edge weights ฮณ: E โ†’ N and a root r โˆˆ V, + * the goal is to find a maximum weight matching M โІ E such that no two edges in M + * share a common vertex. The sum of the weights of the edges in M, โˆ‘ eโˆˆM ฮณ(e), should be maximized. + * For more Information: Matching (graph theory) + * + * @author Deniz Altunkapan + */ +public class TreeMatching { + + private UndirectedAdjacencyListGraph graph; + private int[][] dp; + + /** + * Constructor that initializes the graph and the DP table. + * + * @param graph The graph that represents the tree and is used for the matching algorithm. + */ + public TreeMatching(UndirectedAdjacencyListGraph graph) { + this.graph = graph; + this.dp = new int[graph.size()][2]; + } + + /** + * Calculates the maximum weighted matching for the tree, starting from the given root node. + * + * @param root The index of the root node of the tree. + * @param parent The index of the parent node (used for recursion). + * @return The maximum weighted matching for the tree, starting from the root node. + * + */ + public int getMaxMatching(int root, int parent) { + if (root < 0 || root >= graph.size()) { + throw new IllegalArgumentException("Invalid root: " + root); + } + maxMatching(root, parent); + return Math.max(dp[root][0], dp[root][1]); + } + + /** + * Recursively computes the maximum weighted matching for a node, assuming that the node + * can either be included or excluded from the matching. + * + * @param node The index of the current node for which the matching is calculated. + * @param parent The index of the parent node (to avoid revisiting the parent node during recursion). + */ + private void maxMatching(int node, int parent) { + dp[node][0] = 0; + dp[node][1] = 0; + + int sumWithoutEdge = 0; + for (int adjNode : graph.getNeighbors(node)) { + if (adjNode == parent) { + continue; + } + maxMatching(adjNode, node); + sumWithoutEdge += Math.max(dp[adjNode][0], dp[adjNode][1]); + } + + dp[node][0] = sumWithoutEdge; + + for (int adjNode : graph.getNeighbors(node)) { + if (adjNode == parent) { + continue; + } + int weight = graph.getEdgeWeight(node, adjNode); + dp[node][1] = Math.max(dp[node][1], sumWithoutEdge - Math.max(dp[adjNode][0], dp[adjNode][1]) + dp[adjNode][0] + weight); + } + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java b/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java index 80b553f2744c..22ad8a7dd8e3 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/UniquePaths.java @@ -1,3 +1,7 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.Arrays; + /** * Author: Siddhant Swarup Mallick * Github: https://github.com/siddhant2002 @@ -12,11 +16,6 @@ * This program calculates the number of unique paths possible for a robot to reach the bottom-right corner * of an m x n grid using dynamic programming. */ - -package com.thealgorithms.dynamicprogramming; - -import java.util.Arrays; - public final class UniquePaths { private UniquePaths() { diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java b/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java index 8e8bf3cc6606..8658ea30af00 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java @@ -1,3 +1,5 @@ +package com.thealgorithms.dynamicprogramming; + /** * * Author: Janmesh Singh @@ -11,9 +13,6 @@ * Use DP to return True if the pattern matches the entire text and False otherwise * */ - -package com.thealgorithms.dynamicprogramming; - public final class WildcardMatching { private WildcardMatching() { } diff --git a/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java new file mode 100644 index 000000000000..a11acb87dd7a --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/BentleyOttmann.java @@ -0,0 +1,423 @@ +package com.thealgorithms.geometry; + +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Implementation of the Bentleyโ€“Ottmann algorithm for finding all intersection + * points among a set of line segments in O((n + k) log n) time. + * + *

Uses a sweep-line approach with an event queue and status structure to + * efficiently detect intersections in 2D plane geometry.

+ * + * @see + * Bentleyโ€“Ottmann algorithm + */ +public final class BentleyOttmann { + + private BentleyOttmann() { + } + + private static final double EPS = 1e-9; + private static double currentSweepX; + + /** + * Represents a line segment with two endpoints. + */ + public static class Segment { + final Point2D.Double p1; + final Point2D.Double p2; + final int id; // Unique identifier for each segment + + Segment(Point2D.Double p1, Point2D.Double p2) { + this.p1 = p1; + this.p2 = p2; + this.id = segmentCounter++; + } + + private static int segmentCounter = 0; + + /** + * Computes the y-coordinate of this segment at a given x value. + */ + double getY(double x) { + if (Math.abs(p2.x - p1.x) < EPS) { + // Vertical segment: return midpoint y + return (p1.y + p2.y) / 2.0; + } + double t = (x - p1.x) / (p2.x - p1.x); + return p1.y + t * (p2.y - p1.y); + } + + Point2D.Double leftPoint() { + return p1.x < p2.x ? p1 : p1.x > p2.x ? p2 : p1.y < p2.y ? p1 : p2; + } + + Point2D.Double rightPoint() { + return p1.x > p2.x ? p1 : p1.x < p2.x ? p2 : p1.y > p2.y ? p1 : p2; + } + + @Override + public String toString() { + return String.format("S%d[(%.2f, %.2f), (%.2f, %.2f)]", id, p1.x, p1.y, p2.x, p2.y); + } + } + + /** + * Event types for the sweep line algorithm. + */ + private enum EventType { START, END, INTERSECTION } + + /** + * Represents an event in the event queue. + */ + private static class Event implements Comparable { + final Point2D.Double point; + final EventType type; + final Set segments; // Segments involved in this event + + Event(Point2D.Double point, EventType type) { + this.point = point; + this.type = type; + this.segments = new HashSet<>(); + } + + void addSegment(Segment s) { + segments.add(s); + } + + @Override + public int compareTo(Event other) { + // Sort by x-coordinate, then by y-coordinate + int cmp = Double.compare(this.point.x, other.point.x); + if (cmp == 0) { + cmp = Double.compare(this.point.y, other.point.y); + } + if (cmp == 0) { + // Process END events before START events at same point + cmp = this.type.compareTo(other.type); + } + return cmp; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Event e)) { + return false; + } + return pointsEqual(this.point, e.point); + } + + @Override + public int hashCode() { + return Objects.hash(Math.round(point.x * 1e6), Math.round(point.y * 1e6)); + } + } + + /** + * Comparator for segments in the status structure (sweep line). + * Orders segments by their y-coordinate at the current sweep line position. + */ + private static final class StatusComparator implements Comparator { + @Override + public int compare(Segment s1, Segment s2) { + if (s1.id == s2.id) { + return 0; + } + + double y1 = s1.getY(currentSweepX); + double y2 = s2.getY(currentSweepX); + + int cmp = Double.compare(y1, y2); + if (Math.abs(y1 - y2) < EPS) { + // If y-coordinates are equal, use segment id for consistency + return Integer.compare(s1.id, s2.id); + } + return cmp; + } + } + + /** + * Finds all intersection points among a set of line segments. + * + *

An intersection point is reported when two or more segments cross or touch. + * For overlapping segments, only actual crossing/touching points are reported, + * not all points along the overlap.

+ * + * @param segments list of line segments represented as pairs of points + * @return a set of intersection points where segments meet or cross + * @throws IllegalArgumentException if the list is null or contains null points + */ + public static Set findIntersections(List segments) { + if (segments == null) { + throw new IllegalArgumentException("Segment list must not be null"); + } + + Segment.segmentCounter = 0; // Reset counter + Set intersections = new HashSet<>(); + PriorityQueue eventQueue = new PriorityQueue<>(); + TreeSet status = new TreeSet<>(new StatusComparator()); + Map eventMap = new HashMap<>(); + + // Initialize event queue with segment start and end points + for (Segment s : segments) { + Point2D.Double left = s.leftPoint(); + Point2D.Double right = s.rightPoint(); + + Event startEvent = getOrCreateEvent(eventMap, left, EventType.START); + startEvent.addSegment(s); + + Event endEvent = getOrCreateEvent(eventMap, right, EventType.END); + endEvent.addSegment(s); + } + + // Add all unique events to the queue + for (Event e : eventMap.values()) { + if (!e.segments.isEmpty()) { + eventQueue.add(e); + } + } + + // Process events + while (!eventQueue.isEmpty()) { + Event event = eventQueue.poll(); + currentSweepX = event.point.x; + + handleEvent(event, status, eventQueue, eventMap, intersections); + } + + return intersections; + } + + private static Event getOrCreateEvent(Map eventMap, Point2D.Double point, EventType type) { + // Find existing event at this point + for (Map.Entry entry : eventMap.entrySet()) { + if (pointsEqual(entry.getKey(), point)) { + return entry.getValue(); + } + } + // Create new event + Event event = new Event(point, type); + eventMap.put(point, event); + return event; + } + + private static void handleEvent(Event event, TreeSet status, PriorityQueue eventQueue, Map eventMap, Set intersections) { + Point2D.Double p = event.point; + Set segmentsAtPoint = new HashSet<>(event.segments); + + // Check segments in status structure (much smaller than allSegments) + for (Segment s : status) { + if (pointsEqual(s.p1, p) || pointsEqual(s.p2, p) || (onSegment(s, p) && !pointsEqual(s.p1, p) && !pointsEqual(s.p2, p))) { + segmentsAtPoint.add(s); + } + } + + // If 2 or more segments meet at this point, it's an intersection + if (segmentsAtPoint.size() >= 2) { + intersections.add(p); + } + + // Categorize segments + Set upperSegs = new HashSet<>(); // Segments starting at p + Set lowerSegs = new HashSet<>(); // Segments ending at p + Set containingSegs = new HashSet<>(); // Segments containing p in interior + + for (Segment s : segmentsAtPoint) { + if (pointsEqual(s.leftPoint(), p)) { + upperSegs.add(s); + } else if (pointsEqual(s.rightPoint(), p)) { + lowerSegs.add(s); + } else { + containingSegs.add(s); + } + } + + // Remove ending segments and segments containing p from status + status.removeAll(lowerSegs); + status.removeAll(containingSegs); + + // Update sweep line position slightly past the event + currentSweepX = p.x + EPS; + + // Add starting segments and re-add containing segments + status.addAll(upperSegs); + status.addAll(containingSegs); + + if (upperSegs.isEmpty() && containingSegs.isEmpty()) { + // Find neighbors and check for new intersections + Segment sl = getNeighbor(status, lowerSegs, true); + Segment sr = getNeighbor(status, lowerSegs, false); + if (sl != null && sr != null) { + findNewEvent(sl, sr, p, eventQueue, eventMap); + } + } else { + Set unionSegs = new HashSet<>(upperSegs); + unionSegs.addAll(containingSegs); + + Segment leftmost = getLeftmost(unionSegs, status); + Segment rightmost = getRightmost(unionSegs, status); + + if (leftmost != null) { + Segment sl = status.lower(leftmost); + if (sl != null) { + findNewEvent(sl, leftmost, p, eventQueue, eventMap); + } + } + + if (rightmost != null) { + Segment sr = status.higher(rightmost); + if (sr != null) { + findNewEvent(rightmost, sr, p, eventQueue, eventMap); + } + } + } + } + + private static Segment getNeighbor(NavigableSet status, Set removed, boolean lower) { + if (removed.isEmpty()) { + return null; + } + Segment ref = removed.iterator().next(); + return lower ? status.lower(ref) : status.higher(ref); + } + + private static Segment getLeftmost(Set segments, SortedSet status) { + Segment leftmost = null; + for (Segment s : segments) { + if (leftmost == null || Objects.requireNonNull(status.comparator()).compare(s, leftmost) < 0) { + leftmost = s; + } + } + return leftmost; + } + + private static Segment getRightmost(Set segments, SortedSet status) { + Segment rightmost = null; + for (Segment s : segments) { + if (status.comparator() != null && (rightmost == null || status.comparator().compare(s, rightmost) > 0)) { + rightmost = s; + } + } + return rightmost; + } + + private static void findNewEvent(Segment s1, Segment s2, Point2D.Double currentPoint, PriorityQueue eventQueue, Map eventMap) { + Point2D.Double intersection = getIntersection(s1, s2); + + if (intersection != null && intersection.x > currentPoint.x - EPS && !pointsEqual(intersection, currentPoint)) { + + // Check if event already exists + boolean exists = false; + for (Map.Entry entry : eventMap.entrySet()) { + if (pointsEqual(entry.getKey(), intersection)) { + exists = true; + Event existingEvent = entry.getValue(); + existingEvent.addSegment(s1); + existingEvent.addSegment(s2); + break; + } + } + + if (!exists) { + Event newEvent = new Event(intersection, EventType.INTERSECTION); + newEvent.addSegment(s1); + newEvent.addSegment(s2); + eventMap.put(intersection, newEvent); + eventQueue.add(newEvent); + } + } + } + + private static Point2D.Double getIntersection(Segment s1, Segment s2) { + double x1 = s1.p1.x; + double y1 = s1.p1.y; + double x2 = s1.p2.x; + double y2 = s1.p2.y; + double x3 = s2.p1.x; + double y3 = s2.p1.y; + double x4 = s2.p2.x; + double y4 = s2.p2.y; + + double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + + if (Math.abs(denom) < EPS) { + // Parallel or collinear + if (areCollinear(s1, s2)) { + // For collinear segments, check if they overlap + // Return any overlapping point + List overlapPoints = new ArrayList<>(); + + if (onSegment(s1, s2.p1)) { + overlapPoints.add(s2.p1); + } + if (onSegment(s1, s2.p2)) { + overlapPoints.add(s2.p2); + } + if (onSegment(s2, s1.p1)) { + overlapPoints.add(s1.p1); + } + if (onSegment(s2, s1.p2)) { + overlapPoints.add(s1.p2); + } + + // Remove duplicates and return the first point + if (!overlapPoints.isEmpty()) { + // Find the point that's not an endpoint of both segments + for (Point2D.Double pt : overlapPoints) { + boolean isS1Endpoint = pointsEqual(pt, s1.p1) || pointsEqual(pt, s1.p2); + boolean isS2Endpoint = pointsEqual(pt, s2.p1) || pointsEqual(pt, s2.p2); + + // If it's an endpoint of both, it's a touching point + if (isS1Endpoint && isS2Endpoint) { + return pt; + } + } + // Return the first overlap point + return overlapPoints.getFirst(); + } + } + return null; + } + + double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom; + double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom; + + if (t >= -EPS && t <= 1 + EPS && u >= -EPS && u <= 1 + EPS) { + double px = x1 + t * (x2 - x1); + double py = y1 + t * (y2 - y1); + return new Point2D.Double(px, py); + } + + return null; + } + + private static boolean areCollinear(Segment s1, Segment s2) { + double cross1 = crossProduct(s1.p1, s1.p2, s2.p1); + double cross2 = crossProduct(s1.p1, s1.p2, s2.p2); + return Math.abs(cross1) < EPS && Math.abs(cross2) < EPS; + } + + private static double crossProduct(Point2D.Double a, Point2D.Double b, Point2D.Double c) { + return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); + } + + private static boolean onSegment(Segment s, Point2D.Double p) { + return p.x >= Math.min(s.p1.x, s.p2.x) - EPS && p.x <= Math.max(s.p1.x, s.p2.x) + EPS && p.y >= Math.min(s.p1.y, s.p2.y) - EPS && p.y <= Math.max(s.p1.y, s.p2.y) + EPS && Math.abs(crossProduct(s.p1, s.p2, p)) < EPS; + } + + private static boolean pointsEqual(Point2D.Double p1, Point2D.Double p2) { + return Math.abs(p1.x - p2.x) < EPS && Math.abs(p1.y - p2.y) < EPS; + } +} diff --git a/src/main/java/com/thealgorithms/geometry/ConvexHull.java b/src/main/java/com/thealgorithms/geometry/ConvexHull.java index 17f400854c64..d44d20c68807 100644 --- a/src/main/java/com/thealgorithms/geometry/ConvexHull.java +++ b/src/main/java/com/thealgorithms/geometry/ConvexHull.java @@ -61,11 +61,24 @@ public static List convexHullBruteForce(List points) { return new ArrayList<>(convexSet); } + /** + * Computes the convex hull using a recursive divide-and-conquer approach. + * Returns points in counter-clockwise order starting from the bottom-most, left-most point. + * + * @param points the input points + * @return the convex hull points in counter-clockwise order + */ public static List convexHullRecursive(List points) { + if (points.size() < 3) { + List result = new ArrayList<>(points); + Collections.sort(result); + return result; + } + Collections.sort(points); Set convexSet = new HashSet<>(); - Point leftMostPoint = points.get(0); - Point rightMostPoint = points.get(points.size() - 1); + Point leftMostPoint = points.getFirst(); + Point rightMostPoint = points.getLast(); convexSet.add(leftMostPoint); convexSet.add(rightMostPoint); @@ -85,9 +98,8 @@ public static List convexHullRecursive(List points) { constructHull(upperHull, leftMostPoint, rightMostPoint, convexSet); constructHull(lowerHull, rightMostPoint, leftMostPoint, convexSet); - List result = new ArrayList<>(convexSet); - Collections.sort(result); - return result; + // Convert to list and sort in counter-clockwise order + return sortCounterClockwise(new ArrayList<>(convexSet)); } private static void constructHull(Collection points, Point left, Point right, Set convexSet) { @@ -114,4 +126,82 @@ private static void constructHull(Collection points, Point left, Point ri } } } + + /** + * Sorts convex hull points in counter-clockwise order starting from + * the bottom-most, left-most point. + * + * @param hullPoints the unsorted convex hull points + * @return the points sorted in counter-clockwise order + */ + private static List sortCounterClockwise(List hullPoints) { + if (hullPoints.size() <= 2) { + Collections.sort(hullPoints); + return hullPoints; + } + + // Find the bottom-most, left-most point (pivot) + Point pivot = hullPoints.getFirst(); + for (Point p : hullPoints) { + if (p.y() < pivot.y() || (p.y() == pivot.y() && p.x() < pivot.x())) { + pivot = p; + } + } + + // Sort other points by polar angle with respect to pivot + final Point finalPivot = pivot; + List sorted = new ArrayList<>(hullPoints); + sorted.remove(finalPivot); + + sorted.sort((p1, p2) -> { + int crossProduct = Point.orientation(finalPivot, p1, p2); + + if (crossProduct == 0) { + // Collinear points: sort by distance from pivot (closer first for convex hull) + long dist1 = distanceSquared(finalPivot, p1); + long dist2 = distanceSquared(finalPivot, p2); + return Long.compare(dist1, dist2); + } + + // Positive cross product means p2 is counter-clockwise from p1 + // We want counter-clockwise order, so if p2 is CCW from p1, p1 should come first + return -crossProduct; + }); + + // Build result with pivot first, filtering out intermediate collinear points + List result = new ArrayList<>(); + result.add(finalPivot); + + if (!sorted.isEmpty()) { + // This loop iterates through the points sorted by angle. + // For points that are collinear with the pivot, we only want the one that is farthest away. + // The sort places closer points first. + for (int i = 0; i < sorted.size() - 1; i++) { + // Check the orientation of the pivot, the current point, and the next point. + int orientation = Point.orientation(finalPivot, sorted.get(i), sorted.get(i + 1)); + + // If the orientation is not 0, it means the next point (i+1) is at a new angle. + // Therefore, the current point (i) must be the farthest point at its angle. We keep it. + if (orientation != 0) { + result.add(sorted.get(i)); + } + // If the orientation is 0, the points are collinear. We discard the current point (i) + // because it is closer to the pivot than the next point (i+1). + } + // Always add the very last point from the sorted list. It is either the only point + // at its angle, or it's the farthest among a set of collinear points. + result.add(sorted.getLast()); + } + + return result; + } + + /** + * Computes the squared distance between two points to avoid floating point operations. + */ + private static long distanceSquared(Point p1, Point p2) { + long dx = (long) p1.x() - p2.x(); + long dy = (long) p1.y() - p2.y(); + return dx * dx + dy * dy; + } } diff --git a/src/main/java/com/thealgorithms/geometry/DDALine.java b/src/main/java/com/thealgorithms/geometry/DDALine.java new file mode 100644 index 000000000000..cb24951f398a --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/DDALine.java @@ -0,0 +1,54 @@ +package com.thealgorithms.geometry; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +/** + * The {@code DDALine} class implements the Digital Differential Analyzer (DDA) + * line drawing algorithm. It computes points along a line between two given + * endpoints using floating-point arithmetic. + * + * The algorithm is straightforward but less efficient compared to + * Bresenhamโ€™s line algorithm, since it relies on floating-point operations. + * + * For more information, please visit {@link https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm)} + */ +public final class DDALine { + + private DDALine() { + // Prevent instantiation + } + + /** + * Finds the list of points forming a line between two endpoints using DDA. + * + * @param x0 the x-coordinate of the starting point + * @param y0 the y-coordinate of the starting point + * @param x1 the x-coordinate of the ending point + * @param y1 the y-coordinate of the ending point + * @return an unmodifiable {@code List} containing all points on the line + */ + public static List findLine(int x0, int y0, int x1, int y1) { + int dx = x1 - x0; + int dy = y1 - y0; + + int steps = Math.max(Math.abs(dx), Math.abs(dy)); // number of steps + + double xIncrement = dx / (double) steps; + double yIncrement = dy / (double) steps; + + double x = x0; + double y = y0; + + List line = new ArrayList<>(steps + 1); + + for (int i = 0; i <= steps; i++) { + line.add(new Point((int) Math.round(x), (int) Math.round(y))); + x += xIncrement; + y += yIncrement; + } + + return line; + } +} diff --git a/src/main/java/com/thealgorithms/geometry/Haversine.java b/src/main/java/com/thealgorithms/geometry/Haversine.java new file mode 100644 index 000000000000..fa1799e9f076 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/Haversine.java @@ -0,0 +1,49 @@ +package com.thealgorithms.geometry; +/** + * This Class implements the Haversine formula to calculate the distance between two points on a sphere (like Earth) from their latitudes and longitudes. + * + * The Haversine formula is used in navigation and mapping to find the great-circle distance, + * which is the shortest distance between two points along the surface of a sphere. It is often + * used to calculate the "as the crow flies" distance between two geographical locations. + * + * The formula is reliable for all distances, including small ones, and avoids issues with + * numerical instability that can affect other methods. + * + * @see "https://en.wikipedia.org/wiki/Haversine_formula" - Wikipedia + */ +public final class Haversine { + + // Average radius of Earth in kilometers + private static final double EARTH_RADIUS_KM = 6371.0; + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private Haversine() { + } + + /** + * Calculates the great-circle distance between two points on the earth + * (specified in decimal degrees). + * + * @param lat1 Latitude of the first point in decimal degrees. + * @param lon1 Longitude of the first point in decimal degrees. + * @param lat2 Latitude of the second point in decimal degrees. + * @param lon2 Longitude of the second point in decimal degrees. + * @return The distance between the two points in kilometers. + */ + public static double haversine(double lat1, double lon1, double lat2, double lon2) { + // Convert latitude and longitude from degrees to radians + double dLat = Math.toRadians(lat2 - lat1); + double dLon = Math.toRadians(lon2 - lon1); + + double lat1Rad = Math.toRadians(lat1); + double lat2Rad = Math.toRadians(lat2); + + // Apply the Haversine formula + double a = Math.pow(Math.sin(dLat / 2), 2) + Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1Rad) * Math.cos(lat2Rad); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return EARTH_RADIUS_KM * c; + } +} diff --git a/src/main/java/com/thealgorithms/geometry/WusLine.java b/src/main/java/com/thealgorithms/geometry/WusLine.java new file mode 100644 index 000000000000..3539daaf6e5a --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/WusLine.java @@ -0,0 +1,235 @@ +package com.thealgorithms.geometry; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +/** + * The {@code WusLine} class implements Xiaolin Wu's line drawing algorithm, + * which produces anti-aliased lines by varying pixel brightness + * according to the line's proximity to pixel centers. + * + * This implementation returns the pixel coordinates along with + * their associated intensity values (in range [0.0, 1.0]), allowing + * rendering systems to blend accordingly. + * + * The algorithm works by: + * - Computing a line's intersection with pixel boundaries + * - Assigning intensity values based on distance from pixel centers + * - Drawing pairs of pixels perpendicular to the line's direction + * + * Reference: Xiaolin Wu, "An Efficient Antialiasing Technique", + * Computer Graphics (SIGGRAPH '91 Proceedings). + * + */ +public final class WusLine { + + private WusLine() { + // Utility class; prevent instantiation. + } + + /** + * Represents a pixel and its intensity for anti-aliased rendering. + * + * The intensity value determines how bright the pixel should be drawn, + * with 1.0 being fully opaque and 0.0 being fully transparent. + */ + public static class Pixel { + /** The pixel's coordinate on the screen. */ + public final Point point; + + /** The pixel's intensity value, clamped to the range [0.0, 1.0]. */ + public final double intensity; + + /** + * Constructs a new Pixel with the given coordinates and intensity. + * + * @param x the x-coordinate of the pixel + * @param y the y-coordinate of the pixel + * @param intensity the brightness/opacity of the pixel, will be clamped to [0.0, 1.0] + */ + public Pixel(int x, int y, double intensity) { + this.point = new Point(x, y); + this.intensity = Math.clamp(intensity, 0.0, 1.0); + } + } + + /** + * Internal class to hold processed endpoint data. + */ + private static class EndpointData { + final int xPixel; + final int yPixel; + final double yEnd; + final double xGap; + + EndpointData(int xPixel, int yPixel, double yEnd, double xGap) { + this.xPixel = xPixel; + this.yPixel = yPixel; + this.yEnd = yEnd; + this.xGap = xGap; + } + } + + /** + * Draws an anti-aliased line using Wu's algorithm. + * + * The algorithm produces smooth lines by drawing pairs of pixels at each + * x-coordinate (or y-coordinate for steep lines), with intensities based on + * the line's distance from pixel centers. + * + * @param x0 the x-coordinate of the line's start point + * @param y0 the y-coordinate of the line's start point + * @param x1 the x-coordinate of the line's end point + * @param y1 the y-coordinate of the line's end point + * @return a list of {@link Pixel} objects representing the anti-aliased line, + * ordered from start to end + */ + public static List drawLine(int x0, int y0, int x1, int y1) { + List pixels = new ArrayList<>(); + + // Determine if the line is steep (more vertical than horizontal) + boolean steep = Math.abs(y1 - y0) > Math.abs(x1 - x0); + + if (steep) { + // For steep lines, swap x and y coordinates to iterate along y-axis + int temp = x0; + x0 = y0; + y0 = temp; + + temp = x1; + x1 = y1; + y1 = temp; + } + + if (x0 > x1) { + // Ensure we always draw from left to right + int temp = x0; + x0 = x1; + x1 = temp; + + temp = y0; + y0 = y1; + y1 = temp; + } + + // Calculate the line's slope + double deltaX = x1 - (double) x0; + double deltaY = y1 - (double) y0; + double gradient = (deltaX == 0) ? 1.0 : deltaY / deltaX; + + // Process the first endpoint + EndpointData firstEndpoint = processEndpoint(x0, y0, gradient, true); + addEndpointPixels(pixels, firstEndpoint, steep); + + // Process the second endpoint + EndpointData secondEndpoint = processEndpoint(x1, y1, gradient, false); + addEndpointPixels(pixels, secondEndpoint, steep); + + // Draw the main line between endpoints + drawMainLine(pixels, firstEndpoint, secondEndpoint, gradient, steep); + + return pixels; + } + + /** + * Processes a line endpoint to determine its pixel coordinates and intensities. + * + * @param x the x-coordinate of the endpoint + * @param y the y-coordinate of the endpoint + * @param gradient the slope of the line + * @param isStart true if this is the start endpoint, false if it's the end + * @return an {@link EndpointData} object containing processed endpoint information + */ + private static EndpointData processEndpoint(double x, double y, double gradient, boolean isStart) { + double xEnd = round(x); + double yEnd = y + gradient * (xEnd - x); + double xGap = isStart ? rfpart(x + 0.5) : fpart(x + 0.5); + + int xPixel = (int) xEnd; + int yPixel = (int) Math.floor(yEnd); + + return new EndpointData(xPixel, yPixel, yEnd, xGap); + } + + /** + * Adds the two endpoint pixels (one above, one below the line) to the pixel list. + * + * @param pixels the list to add pixels to + * @param endpoint the endpoint data containing coordinates and gaps + * @param steep true if the line is steep (coordinates should be swapped) + */ + private static void addEndpointPixels(List pixels, EndpointData endpoint, boolean steep) { + double fractionalY = fpart(endpoint.yEnd); + double complementFractionalY = rfpart(endpoint.yEnd); + + if (steep) { + pixels.add(new Pixel(endpoint.yPixel, endpoint.xPixel, complementFractionalY * endpoint.xGap)); + pixels.add(new Pixel(endpoint.yPixel + 1, endpoint.xPixel, fractionalY * endpoint.xGap)); + } else { + pixels.add(new Pixel(endpoint.xPixel, endpoint.yPixel, complementFractionalY * endpoint.xGap)); + pixels.add(new Pixel(endpoint.xPixel, endpoint.yPixel + 1, fractionalY * endpoint.xGap)); + } + } + + /** + * Draws the main portion of the line between the two endpoints. + * + * @param pixels the list to add pixels to + * @param firstEndpoint the processed start endpoint + * @param secondEndpoint the processed end endpoint + * @param gradient the slope of the line + * @param steep true if the line is steep (coordinates should be swapped) + */ + private static void drawMainLine(List pixels, EndpointData firstEndpoint, EndpointData secondEndpoint, double gradient, boolean steep) { + // Start y-intersection after the first endpoint + double intersectionY = firstEndpoint.yEnd + gradient; + + // Iterate through x-coordinates between the endpoints + for (int x = firstEndpoint.xPixel + 1; x < secondEndpoint.xPixel; x++) { + int yFloor = (int) Math.floor(intersectionY); + double fractionalPart = fpart(intersectionY); + double complementFractionalPart = rfpart(intersectionY); + + if (steep) { + pixels.add(new Pixel(yFloor, x, complementFractionalPart)); + pixels.add(new Pixel(yFloor + 1, x, fractionalPart)); + } else { + pixels.add(new Pixel(x, yFloor, complementFractionalPart)); + pixels.add(new Pixel(x, yFloor + 1, fractionalPart)); + } + + intersectionY += gradient; + } + } + + /** + * Returns the fractional part of a number. + * + * @param x the input number + * @return the fractional part (always in range [0.0, 1.0)) + */ + private static double fpart(double x) { + return x - Math.floor(x); + } + + /** + * Returns the reverse fractional part of a number (1 - fractional part). + * + * @param x the input number + * @return 1.0 minus the fractional part (always in range (0.0, 1.0]) + */ + private static double rfpart(double x) { + return 1.0 - fpart(x); + } + + /** + * Rounds a number to the nearest integer. + * + * @param x the input number + * @return the nearest integer value as a double + */ + private static double round(double x) { + return Math.floor(x + 0.5); + } +} diff --git a/src/main/java/com/thealgorithms/graph/BronKerbosch.java b/src/main/java/com/thealgorithms/graph/BronKerbosch.java new file mode 100644 index 000000000000..0510d9bcf494 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/BronKerbosch.java @@ -0,0 +1,114 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Implementation of the Bronโ€“Kerbosch algorithm with pivoting for enumerating all maximal cliques + * in an undirected graph. + * + *

The input graph is represented as an adjacency list where {@code adjacency.get(u)} returns the + * set of vertices adjacent to {@code u}. The algorithm runs in time proportional to the number of + * maximal cliques produced and is widely used for clique enumeration problems.

+ * + * @author Wikipedia: Bronโ€“Kerbosch algorithm + */ +public final class BronKerbosch { + + private BronKerbosch() { + } + + /** + * Finds all maximal cliques of the provided graph. + * + * @param adjacency adjacency list where {@code adjacency.size()} equals the number of vertices + * @return a list containing every maximal clique, each represented as a {@link Set} of vertices + * @throws IllegalArgumentException if the adjacency list is {@code null}, contains {@code null} + * entries, or references invalid vertices + */ + public static List> findMaximalCliques(List> adjacency) { + if (adjacency == null) { + throw new IllegalArgumentException("Adjacency list must not be null"); + } + + int n = adjacency.size(); + List> graph = new ArrayList<>(n); + for (int u = 0; u < n; u++) { + Set neighbors = adjacency.get(u); + if (neighbors == null) { + throw new IllegalArgumentException("Adjacency list must not contain null sets"); + } + Set copy = new HashSet<>(); + for (int v : neighbors) { + if (v < 0 || v >= n) { + throw new IllegalArgumentException("Neighbor index out of bounds: " + v); + } + if (v != u) { + copy.add(v); + } + } + graph.add(copy); + } + + Set r = new HashSet<>(); + Set p = new HashSet<>(); + Set x = new HashSet<>(); + for (int v = 0; v < n; v++) { + p.add(v); + } + + List> cliques = new ArrayList<>(); + bronKerboschPivot(r, p, x, graph, cliques); + return cliques; + } + + private static void bronKerboschPivot(Set r, Set p, Set x, List> graph, List> cliques) { + if (p.isEmpty() && x.isEmpty()) { + cliques.add(new HashSet<>(r)); + return; + } + + int pivot = choosePivot(p, x, graph); + Set candidates = new HashSet<>(p); + if (pivot != -1) { + candidates.removeAll(graph.get(pivot)); + } + + for (Integer v : candidates) { + r.add(v); + Set newP = intersection(p, graph.get(v)); + Set newX = intersection(x, graph.get(v)); + bronKerboschPivot(r, newP, newX, graph, cliques); + r.remove(v); + p.remove(v); + x.add(v); + } + } + + private static int choosePivot(Set p, Set x, List> graph) { + int pivot = -1; + int maxDegree = -1; + Set union = new HashSet<>(p); + union.addAll(x); + for (Integer v : union) { + int degree = graph.get(v).size(); + if (degree > maxDegree) { + maxDegree = degree; + pivot = v; + } + } + return pivot; + } + + private static Set intersection(Set base, Set neighbors) { + Set result = new HashSet<>(); + for (Integer v : base) { + if (neighbors.contains(v)) { + result.add(v); + } + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/graph/Dinic.java b/src/main/java/com/thealgorithms/graph/Dinic.java new file mode 100644 index 000000000000..c45bd62ddfbb --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/Dinic.java @@ -0,0 +1,119 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Queue; + +/** + * Dinic's algorithm for computing maximum flow in a directed graph. + * + *

Time complexity: O(E * V^2) in the worst case, but typically faster in practice + * and near O(E * sqrt(V)) for unit networks.

+ * + *

The graph is represented using a capacity matrix where capacity[u][v] is the + * capacity of the directed edge u -> v. Capacities must be non-negative. + * The algorithm builds level graphs using BFS and finds blocking flows using DFS + * with current-edge optimization.

+ * + *

This implementation mirrors the API and validation style of + * {@link EdmondsKarp#maxFlow(int[][], int, int)} for consistency.

+ * + * @see Wikipedia: Dinic's algorithm + */ +public final class Dinic { + private Dinic() { + } + + /** + * Computes the maximum flow from source to sink using Dinic's algorithm. + * + * @param capacity square capacity matrix (n x n); entries must be >= 0 + * @param source source vertex index in [0, n) + * @param sink sink vertex index in [0, n) + * @return the maximum flow value + * @throws IllegalArgumentException if the input matrix is null/non-square/has negatives or + * indices invalid + */ + public static int maxFlow(int[][] capacity, int source, int sink) { + if (capacity == null || capacity.length == 0) { + throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + } + final int n = capacity.length; + for (int i = 0; i < n; i++) { + if (capacity[i] == null || capacity[i].length != n) { + throw new IllegalArgumentException("Capacity matrix must be square"); + } + for (int j = 0; j < n; j++) { + if (capacity[i][j] < 0) { + throw new IllegalArgumentException("Capacities must be non-negative"); + } + } + } + if (source < 0 || sink < 0 || source >= n || sink >= n) { + throw new IllegalArgumentException("Source and sink must be valid vertex indices"); + } + if (source == sink) { + return 0; + } + + // residual capacities + int[][] residual = new int[n][n]; + for (int i = 0; i < n; i++) { + residual[i] = Arrays.copyOf(capacity[i], n); + } + + int[] level = new int[n]; + int flow = 0; + while (bfsBuildLevelGraph(residual, source, sink, level)) { + int[] next = new int[n]; // current-edge optimization + int pushed; + do { + pushed = dfsBlocking(residual, level, next, source, sink, Integer.MAX_VALUE); + flow += pushed; + } while (pushed > 0); + } + return flow; + } + + private static boolean bfsBuildLevelGraph(int[][] residual, int source, int sink, int[] level) { + Arrays.fill(level, -1); + level[source] = 0; + Queue q = new ArrayDeque<>(); + q.add(source); + while (!q.isEmpty()) { + int u = q.poll(); + for (int v = 0; v < residual.length; v++) { + if (residual[u][v] > 0 && level[v] == -1) { + level[v] = level[u] + 1; + if (v == sink) { + return true; + } + q.add(v); + } + } + } + return level[sink] != -1; + } + + private static int dfsBlocking(int[][] residual, int[] level, int[] next, int u, int sink, int f) { + if (u == sink) { + return f; + } + final int n = residual.length; + for (int v = next[u]; v < n; v++, next[u] = v) { + if (residual[u][v] <= 0) { + continue; + } + if (level[v] != level[u] + 1) { + continue; + } + int pushed = dfsBlocking(residual, level, next, v, sink, Math.min(f, residual[u][v])); + if (pushed > 0) { + residual[u][v] -= pushed; + residual[v][u] += pushed; + return pushed; + } + } + return 0; + } +} diff --git a/src/main/java/com/thealgorithms/graph/Edmonds.java b/src/main/java/com/thealgorithms/graph/Edmonds.java new file mode 100644 index 000000000000..4ddb8f9ff544 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/Edmonds.java @@ -0,0 +1,201 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * An implementation of Edmonds's algorithm (also known as the Chuโ€“Liu/Edmonds algorithm) + * for finding a Minimum Spanning Arborescence (MSA). + * + *

An MSA is a directed graph equivalent of a Minimum Spanning Tree. It is a tree rooted + * at a specific vertex 'r' that reaches all other vertices, such that the sum of the + * weights of its edges is minimized. + * + *

The algorithm works recursively: + *

    + *
  1. For each vertex other than the root, select the incoming edge with the minimum weight.
  2. + *
  3. If the selected edges form a spanning arborescence, it is the MSA.
  4. + *
  5. If cycles are formed, contract each cycle into a new "supernode".
  6. + *
  7. Modify the weights of edges entering the new supernode.
  8. + *
  9. Recursively call the algorithm on the contracted graph.
  10. + *
  11. The final cost is the sum of the initial edge selections and the result of the recursive call.
  12. + *
+ * + *

Time Complexity: O(E * V) where E is the number of edges and V is the number of vertices. + * + *

References: + *

+ */ +public final class Edmonds { + + private Edmonds() { + } + + /** + * Represents a directed weighted edge in the graph. + */ + public static class Edge { + final int from; + final int to; + final long weight; + + /** + * Constructs a directed edge. + * + * @param from source vertex + * @param to destination vertex + * @param weight edge weight + */ + public Edge(int from, int to, long weight) { + this.from = from; + this.to = to; + this.weight = weight; + } + } + + /** + * Computes the total weight of the Minimum Spanning Arborescence of a directed, + * weighted graph from a given root. + * + * @param numVertices the number of vertices, labeled {@code 0..numVertices-1} + * @param edges list of directed edges in the graph + * @param root the root vertex + * @return the total weight of the MSA. Returns -1 if not all vertices are reachable + * from the root or if a valid arborescence cannot be formed. + * @throws IllegalArgumentException if {@code numVertices <= 0} or {@code root} is out of range. + */ + public static long findMinimumSpanningArborescence(int numVertices, List edges, int root) { + if (root < 0 || root >= numVertices) { + throw new IllegalArgumentException("Invalid number of vertices or root"); + } + if (numVertices == 1) { + return 0; + } + + return findMSARecursive(numVertices, edges, root); + } + + /** + * Recursive helper method for finding MSA. + */ + private static long findMSARecursive(int n, List edges, int root) { + long[] minWeightEdge = new long[n]; + int[] predecessor = new int[n]; + Arrays.fill(minWeightEdge, Long.MAX_VALUE); + Arrays.fill(predecessor, -1); + + for (Edge edge : edges) { + if (edge.to != root && edge.weight < minWeightEdge[edge.to]) { + minWeightEdge[edge.to] = edge.weight; + predecessor[edge.to] = edge.from; + } + } + // Check if all non-root nodes are reachable + for (int i = 0; i < n; i++) { + if (i != root && minWeightEdge[i] == Long.MAX_VALUE) { + return -1; // No spanning arborescence exists + } + } + int[] cycleId = new int[n]; + Arrays.fill(cycleId, -1); + boolean[] visited = new boolean[n]; + int cycleCount = 0; + + for (int i = 0; i < n; i++) { + if (visited[i]) { + continue; + } + + List path = new ArrayList<>(); + int curr = i; + + // Follow predecessor chain + while (curr != -1 && !visited[curr]) { + visited[curr] = true; + path.add(curr); + curr = predecessor[curr]; + } + + // If we hit a visited node, check if it forms a cycle + if (curr != -1) { + boolean inCycle = false; + for (int node : path) { + if (node == curr) { + inCycle = true; + } + if (inCycle) { + cycleId[node] = cycleCount; + } + } + if (inCycle) { + cycleCount++; + } + } + } + if (cycleCount == 0) { + long totalWeight = 0; + for (int i = 0; i < n; i++) { + if (i != root) { + totalWeight += minWeightEdge[i]; + } + } + return totalWeight; + } + long cycleWeightSum = 0; + for (int i = 0; i < n; i++) { + if (cycleId[i] >= 0) { + cycleWeightSum += minWeightEdge[i]; + } + } + + // Map old nodes to new nodes (cycles become supernodes) + int[] newNodeMap = new int[n]; + int[] cycleToNewNode = new int[cycleCount]; + int newN = 0; + + // Assign new node IDs to cycles first + for (int i = 0; i < cycleCount; i++) { + cycleToNewNode[i] = newN++; + } + + // Assign new node IDs to non-cycle nodes + for (int i = 0; i < n; i++) { + if (cycleId[i] == -1) { + newNodeMap[i] = newN++; + } else { + newNodeMap[i] = cycleToNewNode[cycleId[i]]; + } + } + + int newRoot = newNodeMap[root]; + + // Build contracted graph + List newEdges = new ArrayList<>(); + for (Edge edge : edges) { + int uCycleId = cycleId[edge.from]; + int vCycleId = cycleId[edge.to]; + + // Skip edges internal to a cycle + if (uCycleId >= 0 && uCycleId == vCycleId) { + continue; + } + + int newU = newNodeMap[edge.from]; + int newV = newNodeMap[edge.to]; + + long newWeight = edge.weight; + // Adjust weight for edges entering a cycle + if (vCycleId >= 0) { + newWeight -= minWeightEdge[edge.to]; + } + + if (newU != newV) { + newEdges.add(new Edge(newU, newV, newWeight)); + } + } + return cycleWeightSum + findMSARecursive(newN, newEdges, newRoot); + } +} diff --git a/src/main/java/com/thealgorithms/graph/EdmondsKarp.java b/src/main/java/com/thealgorithms/graph/EdmondsKarp.java new file mode 100644 index 000000000000..59e7b09cb49c --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/EdmondsKarp.java @@ -0,0 +1,107 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Queue; + +/** + * Implementation of the Edmondsโ€“Karp algorithm for computing the maximum flow of a directed graph. + *

+ * The algorithm runs in O(V * E^2) time and is a specific implementation of the Fordโ€“Fulkerson + * method where the augmenting paths are found using breadth-first search (BFS) to ensure the + * shortest augmenting paths (in terms of the number of edges) are used. + *

+ * + *

The graph is represented with a capacity matrix where {@code capacity[u][v]} denotes the + * capacity of the edge from {@code u} to {@code v}. Negative capacities are not allowed.

+ * + * @author Wikipedia: EdmondsKarp algorithm + */ +public final class EdmondsKarp { + + private EdmondsKarp() { + } + + /** + * Computes the maximum flow from {@code source} to {@code sink} in the provided capacity matrix. + * + * @param capacity the capacity matrix representing the directed graph; must be square and non-null + * @param source the source vertex index + * @param sink the sink vertex index + * @return the value of the maximum flow between {@code source} and {@code sink} + * @throws IllegalArgumentException if the matrix is {@code null}, not square, contains negative + * capacities, or if {@code source} / {@code sink} indices are invalid + */ + public static int maxFlow(int[][] capacity, int source, int sink) { + if (capacity == null || capacity.length == 0) { + throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + } + + final int n = capacity.length; + for (int row = 0; row < n; row++) { + if (capacity[row] == null || capacity[row].length != n) { + throw new IllegalArgumentException("Capacity matrix must be square"); + } + for (int col = 0; col < n; col++) { + if (capacity[row][col] < 0) { + throw new IllegalArgumentException("Capacities must be non-negative"); + } + } + } + + if (source < 0 || source >= n || sink < 0 || sink >= n) { + throw new IllegalArgumentException("Source and sink must be valid vertex indices"); + } + if (source == sink) { + return 0; + } + + final int[][] residual = new int[n][n]; + for (int i = 0; i < n; i++) { + residual[i] = Arrays.copyOf(capacity[i], n); + } + + final int[] parent = new int[n]; + int maxFlow = 0; + + while (bfs(residual, source, sink, parent)) { + int pathFlow = Integer.MAX_VALUE; + for (int v = sink; v != source; v = parent[v]) { + int u = parent[v]; + pathFlow = Math.min(pathFlow, residual[u][v]); + } + + for (int v = sink; v != source; v = parent[v]) { + int u = parent[v]; + residual[u][v] -= pathFlow; + residual[v][u] += pathFlow; + } + + maxFlow += pathFlow; + } + + return maxFlow; + } + + private static boolean bfs(int[][] residual, int source, int sink, int[] parent) { + Arrays.fill(parent, -1); + parent[source] = source; + + Queue queue = new ArrayDeque<>(); + queue.add(source); + + while (!queue.isEmpty()) { + int u = queue.poll(); + for (int v = 0; v < residual.length; v++) { + if (residual[u][v] > 0 && parent[v] == -1) { + parent[v] = u; + if (v == sink) { + return true; + } + queue.add(v); + } + } + } + return false; + } +} diff --git a/src/main/java/com/thealgorithms/graph/HierholzerEulerianPath.java b/src/main/java/com/thealgorithms/graph/HierholzerEulerianPath.java new file mode 100644 index 000000000000..82da5c8163e5 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/HierholzerEulerianPath.java @@ -0,0 +1,303 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.List; + +/** + * Implementation of Hierholzer's Algorithm for finding an Eulerian Path or Circuit + * in a directed graph. + * + *

+ * An Eulerian Circuit is a path that starts and ends at the same vertex + * and visits every edge exactly once. + *

+ * + *

+ * An Eulerian Path visits every edge exactly once but may start and end + * at different vertices. + *

+ * + *

+ * Algorithm Summary:
+ * 1. Compute indegree and outdegree for all vertices.
+ * 2. Check if the graph satisfies Eulerian path or circuit conditions.
+ * 3. Verify that all vertices with non-zero degree are weakly connected (undirected connectivity).
+ * 4. Use Hierholzerโ€™s algorithm to build the path by exploring unused edges iteratively. + *

+ * + *

+ * Time Complexity: O(E + V).
+ * Space Complexity: O(V + E). + *

+ * + * @author Wikipedia: Hierholzer algorithm + */ +public class HierholzerEulerianPath { + + /** + * Simple directed graph represented by adjacency lists. + */ + public static class Graph { + private final List> adjacencyList; + + /** + * Constructs a graph with a given number of vertices. + * + * @param numNodes number of vertices + */ + public Graph(int numNodes) { + adjacencyList = new ArrayList<>(); + for (int i = 0; i < numNodes; i++) { + adjacencyList.add(new ArrayList<>()); + } + } + + /** + * Adds a directed edge from vertex {@code from} to vertex {@code to}. + * + * @param from source vertex + * @param to destination vertex + */ + public void addEdge(int from, int to) { + adjacencyList.get(from).add(to); + } + + /** + * Returns a list of outgoing edges from the given vertex. + * + * @param node vertex index + * @return list of destination vertices + */ + public List getEdges(int node) { + return adjacencyList.get(node); + } + + /** + * Returns the number of vertices in the graph. + * + * @return number of vertices + */ + public int getNumNodes() { + return adjacencyList.size(); + } + } + + private final Graph graph; + + /** + * Creates a Hierholzer solver for the given graph. + * + * @param graph directed graph + */ + public HierholzerEulerianPath(Graph graph) { + this.graph = graph; + } + + /** + * Finds an Eulerian Path or Circuit using Hierholzerโ€™s Algorithm. + * + * @return list of vertices representing the Eulerian Path/Circuit, + * or an empty list if none exists + */ + public List findEulerianPath() { + int n = graph.getNumNodes(); + + // empty graph -> no path + if (n == 0) { + return new ArrayList<>(); + } + + int[] inDegree = new int[n]; + int[] outDegree = new int[n]; + int edgeCount = computeDegrees(inDegree, outDegree); + + // no edges -> single vertex response requested by tests: [0] + if (edgeCount == 0) { + return Collections.singletonList(0); + } + + int startNode = determineStartNode(inDegree, outDegree); + if (startNode == -1) { + return new ArrayList<>(); + } + + if (!allNonZeroDegreeVerticesWeaklyConnected(startNode, n, outDegree, inDegree)) { + return new ArrayList<>(); + } + + List path = buildHierholzerPath(startNode, n); + if (path.size() != edgeCount + 1) { + return new ArrayList<>(); + } + + return rotateEulerianCircuitIfNeeded(path, outDegree, inDegree); + } + + private int computeDegrees(int[] inDegree, int[] outDegree) { + int edgeCount = 0; + for (int u = 0; u < graph.getNumNodes(); u++) { + for (int v : graph.getEdges(u)) { + outDegree[u]++; + inDegree[v]++; + edgeCount++; + } + } + return edgeCount; + } + + private int determineStartNode(int[] inDegree, int[] outDegree) { + int n = graph.getNumNodes(); + int startNode = -1; + int startCount = 0; + int endCount = 0; + + for (int i = 0; i < n; i++) { + int diff = outDegree[i] - inDegree[i]; + if (diff == 1) { + startNode = i; + startCount++; + } else if (diff == -1) { + endCount++; + } else if (Math.abs(diff) > 1) { + return -1; + } + } + + if (!((startCount == 1 && endCount == 1) || (startCount == 0 && endCount == 0))) { + return -1; + } + + if (startNode == -1) { + for (int i = 0; i < n; i++) { + if (outDegree[i] > 0) { + startNode = i; + break; + } + } + } + return startNode; + } + + private List buildHierholzerPath(int startNode, int n) { + List> tempAdj = new ArrayList<>(); + for (int i = 0; i < n; i++) { + tempAdj.add(new ArrayDeque<>(graph.getEdges(i))); + } + + Deque stack = new ArrayDeque<>(); + List path = new ArrayList<>(); + stack.push(startNode); + + while (!stack.isEmpty()) { + int u = stack.peek(); + if (!tempAdj.get(u).isEmpty()) { + stack.push(tempAdj.get(u).pollFirst()); + } else { + path.add(stack.pop()); + } + } + + Collections.reverse(path); + return path; + } + + private List rotateEulerianCircuitIfNeeded(List path, int[] outDegree, int[] inDegree) { + int startCount = 0; + int endCount = 0; + for (int i = 0; i < outDegree.length; i++) { + int diff = outDegree[i] - inDegree[i]; + if (diff == 1) { + startCount++; + } else if (diff == -1) { + endCount++; + } + } + + if (startCount == 0 && endCount == 0 && !path.isEmpty()) { + int preferredStart = -1; + for (int i = 0; i < outDegree.length; i++) { + if (outDegree[i] > 0) { + preferredStart = i; + break; + } + } + + if (preferredStart != -1 && path.get(0) != preferredStart) { + int idx = 0; + for (Integer node : path) { // replaced indexed loop + if (node == preferredStart) { + break; + } + idx++; + } + + if (idx > 0) { + List rotated = new ArrayList<>(); + int currentIndex = 0; + for (Integer node : path) { // replaced indexed loop + if (currentIndex >= idx) { + rotated.add(node); + } + currentIndex++; + } + currentIndex = 0; + for (Integer node : path) { // replaced indexed loop + if (currentIndex < idx) { + rotated.add(node); + } + currentIndex++; + } + path = rotated; + } + } + } + return path; + } + + /** + * Checks weak connectivity (undirected) among vertices that have non-zero degree. + * + * @param startNode node to start DFS from (must be a vertex with non-zero degree) + * @param n number of vertices + * @param outDegree out-degree array + * @param inDegree in-degree array + * @return true if all vertices having non-zero degree belong to a single weak component + */ + private boolean allNonZeroDegreeVerticesWeaklyConnected(int startNode, int n, int[] outDegree, int[] inDegree) { + boolean[] visited = new boolean[n]; + Deque stack = new ArrayDeque<>(); + stack.push(startNode); + visited[startNode] = true; + + while (!stack.isEmpty()) { + int u = stack.pop(); + for (int v : graph.getEdges(u)) { + if (!visited[v]) { + visited[v] = true; + stack.push(v); + } + } + for (int x = 0; x < n; x++) { + if (!visited[x]) { + for (int y : graph.getEdges(x)) { + if (y == u) { + visited[x] = true; + stack.push(x); + break; + } + } + } + } + } + + for (int i = 0; i < n; i++) { + if (outDegree[i] + inDegree[i] > 0 && !visited[i]) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/thealgorithms/graph/HopcroftKarp.java b/src/main/java/com/thealgorithms/graph/HopcroftKarp.java new file mode 100644 index 000000000000..76f7f3eaa3a7 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/HopcroftKarp.java @@ -0,0 +1,103 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.List; +import java.util.Queue; + +/** + * Hopcroftโ€“Karp algorithm for maximum bipartite matching. + * + * Left part: vertices [0,nLeft-1], Right part: [0,nRight-1]. + * Adjacency list: for each left vertex u, list right vertices v it connects to. + * + * Time complexity: O(E * sqrt(V)). + * + * @see + * Wikipedia: Hopcroftโ€“Karp algorithm + * @author ptzecher + */ +public class HopcroftKarp { + + private final int nLeft; + private final List> adj; + + private final int[] pairU; + private final int[] pairV; + private final int[] dist; + + public HopcroftKarp(int nLeft, int nRight, List> adj) { + this.nLeft = nLeft; + this.adj = adj; + + this.pairU = new int[nLeft]; + this.pairV = new int[nRight]; + this.dist = new int[nLeft]; + + Arrays.fill(pairU, -1); + Arrays.fill(pairV, -1); + } + + /** Returns the size of the maximum matching. */ + public int maxMatching() { + int matching = 0; + while (bfs()) { + for (int u = 0; u < nLeft; u++) { + if (pairU[u] == -1 && dfs(u)) { + matching++; + } + } + } + return matching; + } + + // BFS to build layers + private boolean bfs() { + Queue queue = new ArrayDeque<>(); + Arrays.fill(dist, -1); + + for (int u = 0; u < nLeft; u++) { + if (pairU[u] == -1) { + dist[u] = 0; + queue.add(u); + } + } + + boolean foundAugPath = false; + while (!queue.isEmpty()) { + int u = queue.poll(); + for (int v : adj.get(u)) { + int matchedLeft = pairV[v]; + if (matchedLeft == -1) { + foundAugPath = true; + } else if (dist[matchedLeft] == -1) { + dist[matchedLeft] = dist[u] + 1; + queue.add(matchedLeft); + } + } + } + return foundAugPath; + } + + // DFS to find augmenting paths within the BFS layering + private boolean dfs(int u) { + for (int v : adj.get(u)) { + int matchedLeft = pairV[v]; + if (matchedLeft == -1 || (dist[matchedLeft] == dist[u] + 1 && dfs(matchedLeft))) { + pairU[u] = v; + pairV[v] = u; + return true; + } + } + dist[u] = -1; + return false; + } + + public int[] getLeftMatches() { + return pairU.clone(); + } + + public int[] getRightMatches() { + return pairV.clone(); + } +} diff --git a/src/main/java/com/thealgorithms/graph/HungarianAlgorithm.java b/src/main/java/com/thealgorithms/graph/HungarianAlgorithm.java new file mode 100644 index 000000000000..84f5671c4f9d --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/HungarianAlgorithm.java @@ -0,0 +1,150 @@ +package com.thealgorithms.graph; + +import java.util.Arrays; + +/** + * Hungarian algorithm (a.k.a. Kuhnโ€“Munkres) for the Assignment Problem. + * + *

Given an n x m cost matrix (n tasks, m workers), finds a minimum-cost + * one-to-one assignment. If the matrix is rectangular, the algorithm pads to a + * square internally. Costs must be finite non-negative integers. + * + *

Time complexity: O(n^3) with n = max(rows, cols). + * + *

API returns the assignment as an array where {@code assignment[i]} is the + * column chosen for row i (or -1 if unassigned when rows != cols), and a total + * minimal cost. + * + * @see Wikipedia: Hungarian algorithm + */ +public final class HungarianAlgorithm { + + private HungarianAlgorithm() { + } + + /** Result holder for the Hungarian algorithm. */ + public static final class Result { + public final int[] assignment; // assignment[row] = col or -1 + public final int minCost; + + public Result(int[] assignment, int minCost) { + this.assignment = assignment; + this.minCost = minCost; + } + } + + /** + * Solves the assignment problem for a non-negative cost matrix. + * + * @param cost an r x c matrix of non-negative costs + * @return Result with row-to-column assignment and minimal total cost + * @throws IllegalArgumentException for null/empty or negative costs + */ + public static Result solve(int[][] cost) { + validate(cost); + int rows = cost.length; + int cols = cost[0].length; + int n = Math.max(rows, cols); + + // Build square matrix with padding 0 for missing cells + int[][] a = new int[n][n]; + for (int i = 0; i < n; i++) { + if (i < rows) { + for (int j = 0; j < n; j++) { + a[i][j] = (j < cols) ? cost[i][j] : 0; + } + } else { + Arrays.fill(a[i], 0); + } + } + + // Potentials and matching arrays + int[] u = new int[n + 1]; + int[] v = new int[n + 1]; + int[] p = new int[n + 1]; + int[] way = new int[n + 1]; + + for (int i = 1; i <= n; i++) { + p[0] = i; + int j0 = 0; + int[] minv = new int[n + 1]; + boolean[] used = new boolean[n + 1]; + Arrays.fill(minv, Integer.MAX_VALUE); + Arrays.fill(used, false); + do { + used[j0] = true; + int i0 = p[j0]; + int delta = Integer.MAX_VALUE; + int j1 = 0; + for (int j = 1; j <= n; j++) { + if (!used[j]) { + int cur = a[i0 - 1][j - 1] - u[i0] - v[j]; + if (cur < minv[j]) { + minv[j] = cur; + way[j] = j0; + } + if (minv[j] < delta) { + delta = minv[j]; + j1 = j; + } + } + } + for (int j = 0; j <= n; j++) { + if (used[j]) { + u[p[j]] += delta; + v[j] -= delta; + } else { + minv[j] -= delta; + } + } + j0 = j1; + } while (p[j0] != 0); + do { + int j1 = way[j0]; + p[j0] = p[j1]; + j0 = j1; + } while (j0 != 0); + } + + int[] matchColForRow = new int[n]; + Arrays.fill(matchColForRow, -1); + for (int j = 1; j <= n; j++) { + if (p[j] != 0) { + matchColForRow[p[j] - 1] = j - 1; + } + } + + // Build assignment for original rows only, ignore padded rows + int[] assignment = new int[rows]; + Arrays.fill(assignment, -1); + int total = 0; + for (int i = 0; i < rows; i++) { + int j = matchColForRow[i]; + if (j >= 0 && j < cols) { + assignment[i] = j; + total += cost[i][j]; + } + } + return new Result(assignment, total); + } + + private static void validate(int[][] cost) { + if (cost == null || cost.length == 0) { + throw new IllegalArgumentException("Cost matrix must not be null or empty"); + } + int c = cost[0].length; + if (c == 0) { + throw new IllegalArgumentException("Cost matrix must have at least 1 column"); + } + for (int i = 0; i < cost.length; i++) { + if (cost[i] == null || cost[i].length != c) { + throw new IllegalArgumentException("Cost matrix must be rectangular with equal row lengths"); + } + for (int j = 0; j < c; j++) { + if (cost[i][j] < 0) { + throw new IllegalArgumentException("Costs must be non-negative"); + } + } + } + } +} diff --git a/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java new file mode 100644 index 000000000000..2cf4ed23c44f --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java @@ -0,0 +1,163 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * DFS that visits a successor only when all its predecessors are already visited, + * emitting VISIT and SKIP events. + *

+ * This class includes a DFS variant that visits a successor only when all of its + * predecessors have already been visited + *

+ *

Related reading: + *

+ *

+ */ + +public final class PredecessorConstrainedDfs { + + private PredecessorConstrainedDfs() { + // utility class + } + + /** An event emitted by the traversal: either a VISIT with an order, or a SKIP with a note. */ + public record TraversalEvent(T node, + Integer order, // non-null for visit, null for skip + String note // non-null for skip, null for visit + ) { + public TraversalEvent { + Objects.requireNonNull(node); + // order and note can be null based on event type + } + + /** A visit event with an increasing order (0,1,2,...) */ + public static TraversalEvent visit(T node, int order) { + return new TraversalEvent<>(node, order, null); + } + + /** A skip event with an explanatory note (e.g., not all parents visited yet). */ + public static TraversalEvent skip(T node, String note) { + return new TraversalEvent<>(node, null, Objects.requireNonNull(note)); + } + + public boolean isVisit() { + return order != null; + } + + public boolean isSkip() { + return order == null; + } + + @Override + public String toString() { + return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")"; + } + } + + /** + * DFS (recursive) that records the order of first visit starting at {@code start}, + * but only recurses to a child when all its predecessors have been visited. + * If a child is encountered early (some parent unvisited), a SKIP event is recorded. + * + *

Equivalent idea to the Python pseudo in the user's description (with successors and predecessors), + * but implemented in Java and returning a sequence of {@link TraversalEvent}s.

+ * + * @param successors adjacency list: for each node, its outgoing neighbors + * @param start start node + * @return immutable list of traversal events (VISITs with monotonically increasing order and SKIPs with messages) + * @throws IllegalArgumentException if {@code successors} is null + */ + public static List> dfsRecursiveOrder(Map> successors, T start) { + if (successors == null) { + throw new IllegalArgumentException("successors must not be null"); + } + // derive predecessors once + Map> predecessors = derivePredecessors(successors); + return dfsRecursiveOrder(successors, predecessors, start); + } + + /** + * Same as {@link #dfsRecursiveOrder(Map, Object)} but with an explicit predecessors map. + */ + public static List> dfsRecursiveOrder(Map> successors, Map> predecessors, T start) { + + if (successors == null || predecessors == null) { + throw new IllegalArgumentException("successors and predecessors must not be null"); + } + if (start == null) { + return List.of(); + } + if (!successors.containsKey(start) && !appearsAnywhere(successors, start)) { + return List.of(); // start not present in graph + } + + List> events = new ArrayList<>(); + Set visited = new HashSet<>(); + int[] order = {0}; + dfs(start, successors, predecessors, visited, order, events); + return Collections.unmodifiableList(events); + } + + private static void dfs(T currentNode, Map> successors, Map> predecessors, Set visited, int[] order, List> result) { + + if (!visited.add(currentNode)) { + return; // already visited + } + result.add(TraversalEvent.visit(currentNode, order[0]++)); // record visit and increment + + for (T childNode : successors.getOrDefault(currentNode, List.of())) { + if (visited.contains(childNode)) { + continue; + } + if (allParentsVisited(childNode, visited, predecessors)) { + dfs(childNode, successors, predecessors, visited, order, result); + } else { + result.add(TraversalEvent.skip(childNode, "โ›” Skipping " + childNode + ": not all parents are visited yet.")); + // do not mark visited; it may be visited later from another parent + } + } + } + + private static boolean allParentsVisited(T node, Set visited, Map> predecessors) { + for (T parent : predecessors.getOrDefault(node, List.of())) { + if (!visited.contains(parent)) { + return false; + } + } + return true; + } + + private static boolean appearsAnywhere(Map> successors, T node) { + if (successors.containsKey(node)) { + return true; + } + for (List neighbours : successors.values()) { + if (neighbours != null && neighbours.contains(node)) { + return true; + } + } + return false; + } + + private static Map> derivePredecessors(Map> successors) { + Map> predecessors = new HashMap<>(); + // ensure keys exist for all nodes appearing anywhere + for (Map.Entry> entry : successors.entrySet()) { + predecessors.computeIfAbsent(entry.getKey(), key -> new ArrayList<>()); + for (T childNode : entry.getValue()) { + predecessors.computeIfAbsent(childNode, key -> new ArrayList<>()).add(entry.getKey()); + } + } + return predecessors; + } +} diff --git a/src/main/java/com/thealgorithms/graph/PushRelabel.java b/src/main/java/com/thealgorithms/graph/PushRelabel.java new file mode 100644 index 000000000000..1bfb5ceacce0 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/PushRelabel.java @@ -0,0 +1,162 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Queue; + +/** + * Pushโ€“Relabel (Relabel-to-Front variant simplified to array scanning) for maximum flow. + * + *

Input graph is a capacity matrix where {@code capacity[u][v]} is the capacity of the edge + * {@code u -> v}. Capacities must be non-negative. Vertices are indexed in {@code [0, n)}. + * + *

Time complexity: O(V^3) in the worst case for the array-based variant; typically fast in + * practice. This implementation uses a residual network over an adjacency-matrix representation. + * + *

The API mirrors {@link EdmondsKarp#maxFlow(int[][], int, int)} and {@link Dinic#maxFlow(int[][], int, int)}. + * + * @see Wikipedia: Pushโ€“Relabel maximum flow algorithm + */ +public final class PushRelabel { + + private PushRelabel() { + } + + /** + * Computes the maximum flow from {@code source} to {@code sink} using Pushโ€“Relabel. + * + * @param capacity square capacity matrix (n x n); entries must be >= 0 + * @param source source vertex index in [0, n) + * @param sink sink vertex index in [0, n) + * @return the maximum flow value + * @throws IllegalArgumentException if inputs are invalid + */ + public static int maxFlow(int[][] capacity, int source, int sink) { + validate(capacity, source, sink); + final int n = capacity.length; + if (source == sink) { + return 0; + } + + int[][] residual = new int[n][n]; + for (int i = 0; i < n; i++) { + residual[i] = Arrays.copyOf(capacity[i], n); + } + + int[] height = new int[n]; + int[] excess = new int[n]; + int[] nextNeighbor = new int[n]; + + // Preflow initialization + height[source] = n; + for (int v = 0; v < n; v++) { + int cap = residual[source][v]; + if (cap > 0) { + residual[source][v] -= cap; + residual[v][source] += cap; + excess[v] += cap; + excess[source] -= cap; + } + } + + // Active queue contains vertices (except source/sink) with positive excess + Queue active = new ArrayDeque<>(); + for (int v = 0; v < n; v++) { + if (v != source && v != sink && excess[v] > 0) { + active.add(v); + } + } + + State state = new State(residual, height, excess, nextNeighbor, source, sink, active); + + while (!active.isEmpty()) { + int u = active.poll(); + discharge(u, state); + if (excess[u] > 0) { + // still active after discharge; push to back + active.add(u); + } + } + + // Total flow equals excess at sink + return excess[sink]; + } + + private static void discharge(int u, State s) { + final int n = s.residual.length; + while (s.excess[u] > 0) { + if (s.nextNeighbor[u] >= n) { + relabel(u, s.residual, s.height); + s.nextNeighbor[u] = 0; + continue; + } + int v = s.nextNeighbor[u]; + if (s.residual[u][v] > 0 && s.height[u] == s.height[v] + 1) { + int delta = Math.min(s.excess[u], s.residual[u][v]); + s.residual[u][v] -= delta; + s.residual[v][u] += delta; + s.excess[u] -= delta; + int prevExcessV = s.excess[v]; + s.excess[v] += delta; + if (v != s.source && v != s.sink && prevExcessV == 0) { + s.active.add(v); + } + } else { + s.nextNeighbor[u]++; + } + } + } + + private static final class State { + final int[][] residual; + final int[] height; + final int[] excess; + final int[] nextNeighbor; + final int source; + final int sink; + final Queue active; + + State(int[][] residual, int[] height, int[] excess, int[] nextNeighbor, int source, int sink, Queue active) { + this.residual = residual; + this.height = height; + this.excess = excess; + this.nextNeighbor = nextNeighbor; + this.source = source; + this.sink = sink; + this.active = active; + } + } + + private static void relabel(int u, int[][] residual, int[] height) { + final int n = residual.length; + int minHeight = Integer.MAX_VALUE; + for (int v = 0; v < n; v++) { + if (residual[u][v] > 0) { + minHeight = Math.min(minHeight, height[v]); + } + } + if (minHeight < Integer.MAX_VALUE) { + height[u] = minHeight + 1; + } + } + + private static void validate(int[][] capacity, int source, int sink) { + if (capacity == null || capacity.length == 0) { + throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + } + int n = capacity.length; + for (int i = 0; i < n; i++) { + if (capacity[i] == null || capacity[i].length != n) { + throw new IllegalArgumentException("Capacity matrix must be square"); + } + for (int j = 0; j < n; j++) { + if (capacity[i][j] < 0) { + throw new IllegalArgumentException("Capacities must be non-negative"); + } + } + } + if (source < 0 || sink < 0 || source >= n || sink >= n) { + throw new IllegalArgumentException("Source and sink must be valid vertex indices"); + } + } +} diff --git a/src/main/java/com/thealgorithms/graph/TravelingSalesman.java b/src/main/java/com/thealgorithms/graph/TravelingSalesman.java new file mode 100644 index 000000000000..14bf89f57cf3 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/TravelingSalesman.java @@ -0,0 +1,155 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * This class provides solutions to the Traveling Salesman Problem (TSP) using both brute-force and dynamic programming approaches. + * For more information, see Wikipedia. + * @author Deniz Altunkapan + */ + +public final class TravelingSalesman { + + // Private constructor to prevent instantiation + private TravelingSalesman() { + } + + /** + * Solves the Traveling Salesman Problem (TSP) using brute-force approach. + * This method generates all possible permutations of cities, calculates the total distance for each route, and returns the shortest distance found. + * + * @param distanceMatrix A square matrix where element [i][j] represents the distance from city i to city j. + * @return The shortest possible route distance visiting all cities exactly once and returning to the starting city. + */ + public static int bruteForce(int[][] distanceMatrix) { + if (distanceMatrix.length <= 1) { + return 0; + } + + List cities = new ArrayList<>(); + for (int i = 1; i < distanceMatrix.length; i++) { + cities.add(i); + } + + List> permutations = generatePermutations(cities); + int minDistance = Integer.MAX_VALUE; + + for (List permutation : permutations) { + List route = new ArrayList<>(); + route.add(0); + route.addAll(permutation); + int currentDistance = calculateDistance(distanceMatrix, route); + if (currentDistance < minDistance) { + minDistance = currentDistance; + } + } + + return minDistance; + } + + /** + * Computes the total distance of a given route. + * + * @param distanceMatrix A square matrix where element [i][j] represents the + * distance from city i to city j. + * @param route A list representing the order in which the cities are visited. + * @return The total distance of the route, or Integer.MAX_VALUE if the route is invalid. + */ + public static int calculateDistance(int[][] distanceMatrix, List route) { + int distance = 0; + for (int i = 0; i < route.size() - 1; i++) { + int d = distanceMatrix[route.get(i)][route.get(i + 1)]; + if (d == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + distance += d; + } + int returnDist = distanceMatrix[route.get(route.size() - 1)][route.get(0)]; + return (returnDist == Integer.MAX_VALUE) ? Integer.MAX_VALUE : distance + returnDist; + } + + /** + * Generates all permutations of a given list of cities. + * + * @param cities A list of cities to permute. + * @return A list of all possible permutations. + */ + private static List> generatePermutations(List cities) { + List> permutations = new ArrayList<>(); + permute(cities, 0, permutations); + return permutations; + } + + /** + * Recursively generates permutations using backtracking. + * + * @param arr The list of cities. + * @param k The current index in the permutation process. + * @param output The list to store generated permutations. + */ + private static void permute(List arr, int k, List> output) { + if (k == arr.size()) { + output.add(new ArrayList<>(arr)); + return; + } + for (int i = k; i < arr.size(); i++) { + Collections.swap(arr, i, k); + permute(arr, k + 1, output); + Collections.swap(arr, i, k); + } + } + + /** + * Solves the Traveling Salesman Problem (TSP) using dynamic programming with the Held-Karp algorithm. + * + * @param distanceMatrix A square matrix where element [i][j] represents the distance from city i to city j. + * @return The shortest possible route distance visiting all cities exactly once and returning to the starting city. + * @throws IllegalArgumentException if the input matrix is not square. + */ + public static int dynamicProgramming(int[][] distanceMatrix) { + if (distanceMatrix.length == 0) { + return 0; + } + int n = distanceMatrix.length; + + for (int[] row : distanceMatrix) { + if (row.length != n) { + throw new IllegalArgumentException("Matrix must be square"); + } + } + + int[][] dp = new int[n][1 << n]; + for (int[] row : dp) { + Arrays.fill(row, Integer.MAX_VALUE); + } + dp[0][1] = 0; + + for (int mask = 1; mask < (1 << n); mask++) { + for (int u = 0; u < n; u++) { + if ((mask & (1 << u)) == 0 || dp[u][mask] == Integer.MAX_VALUE) { + continue; + } + for (int v = 0; v < n; v++) { + if ((mask & (1 << v)) != 0 || distanceMatrix[u][v] == Integer.MAX_VALUE) { + continue; + } + int newMask = mask | (1 << v); + dp[v][newMask] = Math.min(dp[v][newMask], dp[u][mask] + distanceMatrix[u][v]); + } + } + } + + int minDistance = Integer.MAX_VALUE; + int fullMask = (1 << n) - 1; + for (int i = 1; i < n; i++) { + if (dp[i][fullMask] != Integer.MAX_VALUE && distanceMatrix[i][0] != Integer.MAX_VALUE) { + minDistance = Math.min(minDistance, dp[i][fullMask] + distanceMatrix[i][0]); + } + } + + return minDistance == Integer.MAX_VALUE ? 0 : minDistance; + } +} diff --git a/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java new file mode 100644 index 000000000000..dfc8386de6ce --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/YensKShortestPaths.java @@ -0,0 +1,263 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Set; + +/** + * Yen's algorithm for finding K loopless shortest paths in a directed graph with non-negative edge weights. + * + *

Input is an adjacency matrix of edge weights. A value of -1 indicates no edge. + * All existing edge weights must be non-negative. Zero-weight edges are allowed.

+ * + *

References: + * - Wikipedia: Yen's algorithm (https://en.wikipedia.org/wiki/Yen%27s_algorithm) + * - Dijkstra's algorithm for the base shortest path computation.

+ */ +public final class YensKShortestPaths { + + private YensKShortestPaths() { + } + + private static final int NO_EDGE = -1; + private static final long INF_COST = Long.MAX_VALUE / 4; + + /** + * Compute up to k loopless shortest paths from src to dst using Yen's algorithm. + * + * @param weights adjacency matrix; weights[u][v] = -1 means no edge; otherwise non-negative edge weight + * @param src source vertex index + * @param dst destination vertex index + * @param k maximum number of paths to return (k >= 1) + * @return list of paths, each path is a list of vertex indices in order from src to dst + * @throws IllegalArgumentException on invalid inputs (null, non-square, negatives on existing edges, bad indices, k < 1) + */ + public static List> kShortestPaths(int[][] weights, int src, int dst, int k) { + validate(weights, src, dst, k); + final int n = weights.length; + // Make a defensive copy to avoid mutating caller's matrix + int[][] weightsCopy = new int[n][n]; + for (int i = 0; i < n; i++) { + weightsCopy[i] = Arrays.copyOf(weights[i], n); + } + + List shortestPaths = new ArrayList<>(); + PriorityQueue candidates = new PriorityQueue<>(); // min-heap by cost then lexicographic nodes + Set seen = new HashSet<>(); // deduplicate candidate paths by node sequence key + + Path first = dijkstra(weightsCopy, src, dst, new boolean[n]); + if (first == null) { + return List.of(); + } + shortestPaths.add(first); + + for (int kIdx = 1; kIdx < k; kIdx++) { + Path lastPath = shortestPaths.get(kIdx - 1); + List lastNodes = lastPath.nodes; + for (int i = 0; i < lastNodes.size() - 1; i++) { + int spurNode = lastNodes.get(i); + List rootPath = lastNodes.subList(0, i + 1); + + // Build modified graph: remove edges that would recreate same root + next edge as any A path + int[][] modifiedWeights = cloneMatrix(weightsCopy); + + for (Path p : shortestPaths) { + if (startsWith(p.nodes, rootPath) && p.nodes.size() > i + 1) { + int u = p.nodes.get(i); + int v = p.nodes.get(i + 1); + modifiedWeights[u][v] = NO_EDGE; // remove edge + } + } + // Prevent revisiting nodes in rootPath (loopless constraint), except spurNode itself + boolean[] blocked = new boolean[n]; + for (int j = 0; j < rootPath.size() - 1; j++) { + blocked[rootPath.get(j)] = true; + } + + Path spurPath = dijkstra(modifiedWeights, spurNode, dst, blocked); + if (spurPath != null) { + // concatenate rootPath (excluding spurNode at end) + spurPath + List totalNodes = new ArrayList<>(rootPath); + // spurPath.nodes starts with spurNode; avoid duplication + for (int idx = 1; idx < spurPath.nodes.size(); idx++) { + totalNodes.add(spurPath.nodes.get(idx)); + } + long rootCost = pathCost(weightsCopy, rootPath); + long totalCost = rootCost + spurPath.cost; // spurPath.cost covers from spurNode to dst + Path candidate = new Path(totalNodes, totalCost); + String key = candidate.key(); + if (seen.add(key)) { + candidates.add(candidate); + } + } + } + if (candidates.isEmpty()) { + break; + } + shortestPaths.add(candidates.poll()); + } + + // Map to list of node indices for output + List> result = new ArrayList<>(shortestPaths.size()); + for (Path p : shortestPaths) { + result.add(new ArrayList<>(p.nodes)); + } + return result; + } + + private static void validate(int[][] weights, int src, int dst, int k) { + if (weights == null || weights.length == 0) { + throw new IllegalArgumentException("Weights matrix must not be null or empty"); + } + int n = weights.length; + for (int i = 0; i < n; i++) { + if (weights[i] == null || weights[i].length != n) { + throw new IllegalArgumentException("Weights matrix must be square"); + } + for (int j = 0; j < n; j++) { + int val = weights[i][j]; + if (val < NO_EDGE) { + throw new IllegalArgumentException("Weights must be -1 (no edge) or >= 0"); + } + } + } + if (src < 0 || dst < 0 || src >= n || dst >= n) { + throw new IllegalArgumentException("Invalid src/dst indices"); + } + if (k < 1) { + throw new IllegalArgumentException("k must be >= 1"); + } + } + + private static boolean startsWith(List list, List prefix) { + if (prefix.size() > list.size()) { + return false; + } + for (int i = 0; i < prefix.size(); i++) { + if (!Objects.equals(list.get(i), prefix.get(i))) { + return false; + } + } + return true; + } + + private static int[][] cloneMatrix(int[][] a) { + int n = a.length; + int[][] b = new int[n][n]; + for (int i = 0; i < n; i++) { + b[i] = Arrays.copyOf(a[i], n); + } + return b; + } + + private static long pathCost(int[][] weights, List nodes) { + long cost = 0; + for (int i = 0; i + 1 < nodes.size(); i++) { + int u = nodes.get(i); + int v = nodes.get(i + 1); + int edgeCost = weights[u][v]; + if (edgeCost < 0) { + return INF_COST; // invalid + } + cost += edgeCost; + } + return cost; + } + + private static Path dijkstra(int[][] weights, int src, int dst, boolean[] blocked) { + int n = weights.length; + final long inf = INF_COST; + long[] dist = new long[n]; + int[] parent = new int[n]; + Arrays.fill(dist, inf); + Arrays.fill(parent, -1); + PriorityQueue queue = new PriorityQueue<>(); + if (blocked[src]) { + return null; + } + dist[src] = 0; + queue.add(new Node(src, 0)); + while (!queue.isEmpty()) { + Node current = queue.poll(); + if (current.dist != dist[current.u]) { + continue; + } + if (current.u == dst) { + break; + } + for (int v = 0; v < n; v++) { + int edgeWeight = weights[current.u][v]; + if (edgeWeight >= 0 && !blocked[v]) { + long newDist = current.dist + edgeWeight; + if (newDist < dist[v]) { + dist[v] = newDist; + parent[v] = current.u; + queue.add(new Node(v, newDist)); + } + } + } + } + if (dist[dst] >= inf) { + // If src==dst and not blocked, the path is trivial with cost 0 + if (src == dst) { + List nodes = new ArrayList<>(); + nodes.add(src); + return new Path(nodes, 0); + } + return null; + } + // Reconstruct path + List nodes = new ArrayList<>(); + int cur = dst; + while (cur != -1) { + nodes.add(0, cur); + cur = parent[cur]; + } + return new Path(nodes, dist[dst]); + } + + private static final class Node implements Comparable { + final int u; + final long dist; + Node(int u, long dist) { + this.u = u; + this.dist = dist; + } + public int compareTo(Node o) { + return Long.compare(this.dist, o.dist); + } + } + + private static final class Path implements Comparable { + final List nodes; + final long cost; + Path(List nodes, long cost) { + this.nodes = nodes; + this.cost = cost; + } + String key() { + return nodes.toString(); + } + @Override + public int compareTo(Path o) { + int costCmp = Long.compare(this.cost, o.cost); + if (costCmp != 0) { + return costCmp; + } + // tie-break lexicographically on nodes + int minLength = Math.min(this.nodes.size(), o.nodes.size()); + for (int i = 0; i < minLength; i++) { + int aNode = this.nodes.get(i); + int bNode = o.nodes.get(i); + if (aNode != bNode) { + return Integer.compare(aNode, bNode); + } + } + return Integer.compare(this.nodes.size(), o.nodes.size()); + } + } +} diff --git a/src/main/java/com/thealgorithms/graph/ZeroOneBfs.java b/src/main/java/com/thealgorithms/graph/ZeroOneBfs.java new file mode 100644 index 000000000000..53181a654215 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/ZeroOneBfs.java @@ -0,0 +1,77 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; + +/** + * 0-1 BFS for shortest paths on graphs with edges weighted 0 or 1. + * + *

Time Complexity: O(V + E). Space Complexity: O(V). + * + *

References: + *

    + *
  • https://cp-algorithms.com/graph/01_bfs.html
  • + *
+ */ +public final class ZeroOneBfs { + + private ZeroOneBfs() { + // Utility class; do not instantiate. + } + + /** + * Computes shortest distances from {@code src} in a graph whose edges have weight 0 or 1. + * + * @param n the number of vertices, labeled {@code 0..n-1} + * @param adj adjacency list; for each vertex u, {@code adj.get(u)} is a list of pairs + * {@code (v, w)} where {@code v} is a neighbor and {@code w} is 0 or 1 + * @param src the source vertex + * @return an array of distances; {@code Integer.MAX_VALUE} denotes unreachable + * @throws IllegalArgumentException if {@code n < 0}, {@code src} is out of range, + * or any edge has weight other than 0 or 1 + */ + public static int[] shortestPaths(int n, List> adj, int src) { + if (n < 0 || src < 0 || src >= n) { + throw new IllegalArgumentException("Invalid n or src"); + } + int[] dist = new int[n]; + Arrays.fill(dist, Integer.MAX_VALUE); + Deque dq = new ArrayDeque<>(); + + dist[src] = 0; + dq.addFirst(src); + + while (!dq.isEmpty()) { + int u = dq.pollFirst(); + List edges = adj.get(u); + if (edges == null) { + continue; + } + for (int[] e : edges) { + if (e == null || e.length < 2) { + continue; + } + int v = e[0]; + int w = e[1]; + if (v < 0 || v >= n) { + continue; // ignore bad edges + } + if (w != 0 && w != 1) { + throw new IllegalArgumentException("Edge weight must be 0 or 1"); + } + int nd = dist[u] + w; + if (nd < dist[v]) { + dist[v] = nd; + if (w == 0) { + dq.addFirst(v); + } else { + dq.addLast(v); + } + } + } + } + return dist; + } +} diff --git a/src/main/java/com/thealgorithms/maths/AliquotSum.java b/src/main/java/com/thealgorithms/maths/AliquotSum.java index 0dbc58bed605..996843b56826 100644 --- a/src/main/java/com/thealgorithms/maths/AliquotSum.java +++ b/src/main/java/com/thealgorithms/maths/AliquotSum.java @@ -56,7 +56,7 @@ public static int getAliquotSum(int n) { // if n is a perfect square then its root was added twice in above loop, so subtracting root // from sum if (root == (int) root) { - sum -= root; + sum -= (int) root; } return sum; } diff --git a/src/main/java/com/thealgorithms/maths/Ceil.java b/src/main/java/com/thealgorithms/maths/Ceil.java index aacb9d969950..28804eb1c569 100644 --- a/src/main/java/com/thealgorithms/maths/Ceil.java +++ b/src/main/java/com/thealgorithms/maths/Ceil.java @@ -1,23 +1,34 @@ package com.thealgorithms.maths; +/** + * Utility class to compute the ceiling of a given number. + */ public final class Ceil { + private Ceil() { } /** - * Returns the smallest (closest to negative infinity) + * Returns the smallest double value that is greater than or equal to the input. + * Equivalent to mathematical โŒˆxโŒ‰ (ceiling function). * - * @param number the number - * @return the smallest (closest to negative infinity) of given - * {@code number} + * @param number the number to ceil + * @return the smallest double greater than or equal to {@code number} */ public static double ceil(double number) { - if (number - (int) number == 0) { + if (Double.isNaN(number) || Double.isInfinite(number) || number == 0.0 || number < Integer.MIN_VALUE || number > Integer.MAX_VALUE) { return number; - } else if (number - (int) number > 0) { - return (int) (number + 1); + } + + if (number < 0.0 && number > -1.0) { + return -0.0; + } + + long intPart = (long) number; + if (number > 0 && number != intPart) { + return intPart + 1.0; } else { - return (int) number; + return intPart; } } } diff --git a/src/main/java/com/thealgorithms/maths/Convolution.java b/src/main/java/com/thealgorithms/maths/Convolution.java index 93e103f8c7cf..a86ecabce933 100644 --- a/src/main/java/com/thealgorithms/maths/Convolution.java +++ b/src/main/java/com/thealgorithms/maths/Convolution.java @@ -23,24 +23,21 @@ public static double[] convolution(double[] a, double[] b) { double[] convolved = new double[a.length + b.length - 1]; /* - The discrete convolution of two signals A and B is defined as: - - A.length - C[i] = ฮฃ (A[k]*B[i-k]) - k=0 - - It's obvious that: 0 <= k <= A.length , 0 <= i <= A.length + B.length - 2 and 0 <= i-k <= - B.length - 1 From the last inequality we get that: i - B.length + 1 <= k <= i and thus we get - the conditions below. + * Discrete convolution formula: + * C[i] = ฮฃ A[k] * B[i - k] + * where k ranges over valid indices so that both A[k] and B[i-k] are in bounds. */ + for (int i = 0; i < convolved.length; i++) { - convolved[i] = 0; - int k = Math.max(i - b.length + 1, 0); + double sum = 0; + int kStart = Math.max(0, i - b.length + 1); + int kEnd = Math.min(i, a.length - 1); - while (k < i + 1 && k < a.length) { - convolved[i] += a[k] * b[i - k]; - k++; + for (int k = kStart; k <= kEnd; k++) { + sum += a[k] * b[i - k]; } + + convolved[i] = sum; } return convolved; diff --git a/src/main/java/com/thealgorithms/maths/DudeneyNumber.java b/src/main/java/com/thealgorithms/maths/DudeneyNumber.java index acf1e55d49c8..37f28e188663 100644 --- a/src/main/java/com/thealgorithms/maths/DudeneyNumber.java +++ b/src/main/java/com/thealgorithms/maths/DudeneyNumber.java @@ -1,11 +1,11 @@ +package com.thealgorithms.maths; + /** * A number is said to be Dudeney if the sum of the digits, is the cube root of the entered number. * Example- Let the number be 512, its sum of digits is 5+1+2=8. The cube root of 512 is also 8. * Since, the sum of the digits is equal to the cube root of the entered number; * it is a Dudeney Number. */ -package com.thealgorithms.maths; - public final class DudeneyNumber { private DudeneyNumber() { } diff --git a/src/main/java/com/thealgorithms/maths/EulerPseudoprime.java b/src/main/java/com/thealgorithms/maths/EulerPseudoprime.java new file mode 100644 index 000000000000..9a03f4e21d17 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/EulerPseudoprime.java @@ -0,0 +1,108 @@ +package com.thealgorithms.maths; + +import java.math.BigInteger; +import java.util.Random; + +/** + * The {@code EulerPseudoprime} class implements the Euler primality test. + * + * It is based on Eulerโ€™s criterion: + * For an odd prime number {@code n} and any integer {@code a} coprime to {@code n}: + * a^((n-1)/2) โ‰ก (a/n) (mod n) + * where (a/n) is the Jacobi symbol. + * + * This algorithm is a stronger probabilistic test than Fermatโ€™s test. + * It may still incorrectly identify a composite as โ€œprobably primeโ€ (Euler pseudoprime), + * but such cases are rare. + */ +public final class EulerPseudoprime { + + private EulerPseudoprime() { + // Private constructor to prevent instantiation. + } + + private static final Random RANDOM = new Random(1); + + /** + * Performs the Euler primality test for a given number. + * + * @param n number to test (must be > 2 and odd) + * @param trials number of random bases to test + * @return {@code true} if {@code n} passes all Euler tests (probably prime), + * {@code false} if composite. + */ + public static boolean isProbablePrime(BigInteger n, int trials) { + if (n.compareTo(BigInteger.TWO) < 0) { + return false; + } + if (n.equals(BigInteger.TWO) || n.equals(BigInteger.valueOf(3))) { + return true; + } + if (n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) { + return false; + } + + for (int i = 0; i < trials; i++) { + BigInteger a = uniformRandom(BigInteger.TWO, n.subtract(BigInteger.TWO)); + BigInteger jacobi = BigInteger.valueOf(jacobiSymbol(a, n)); + if (jacobi.equals(BigInteger.ZERO)) { + return false; + } + + BigInteger exp = n.subtract(BigInteger.ONE).divide(BigInteger.TWO); + BigInteger modExp = a.modPow(exp, n); + + // Euler's criterion: a^((n-1)/2) โ‰ก (a/n) (mod n) + if (!modExp.equals(jacobi.mod(n))) { + return false; // definitely composite + } + } + return true; // probably prime + } + + /** + * Computes the Jacobi symbol (a/n). + * Assumes n is positive and odd. + */ + public static int jacobiSymbol(BigInteger a, BigInteger n) { + if (n.signum() <= 0 || n.mod(BigInteger.TWO).equals(BigInteger.ZERO)) { + throw new IllegalArgumentException("n must be positive and odd."); + } + + int result = 1; + a = a.mod(n); + + while (a.compareTo(BigInteger.ZERO) != 0) { + while (a.mod(BigInteger.TWO).equals(BigInteger.ZERO)) { + a = a.divide(BigInteger.TWO); + BigInteger nMod8 = n.mod(BigInteger.valueOf(8)); + if (nMod8.equals(BigInteger.valueOf(3)) || nMod8.equals(BigInteger.valueOf(5))) { + result = -result; + } + } + + BigInteger temp = a; + a = n; + n = temp; + + if (a.mod(BigInteger.valueOf(4)).equals(BigInteger.valueOf(3)) && n.mod(BigInteger.valueOf(4)).equals(BigInteger.valueOf(3))) { + result = -result; + } + + a = a.mod(n); + } + + return n.equals(BigInteger.ONE) ? result : 0; + } + + /** + * Generates a random BigInteger between {@code min} and {@code max}, inclusive. + */ + private static BigInteger uniformRandom(BigInteger min, BigInteger max) { + BigInteger result; + do { + result = new BigInteger(max.bitLength(), RANDOM); + } while (result.compareTo(min) < 0 || result.compareTo(max) > 0); + return result; + } +} diff --git a/src/main/java/com/thealgorithms/maths/GenericRoot.java b/src/main/java/com/thealgorithms/maths/GenericRoot.java index 07f4756f93f8..e13efe5a77e0 100644 --- a/src/main/java/com/thealgorithms/maths/GenericRoot.java +++ b/src/main/java/com/thealgorithms/maths/GenericRoot.java @@ -1,30 +1,48 @@ package com.thealgorithms.maths; -/* - * Algorithm explanation: - * https://technotip.com/6774/c-program-to-find-generic-root-of-a-number/#:~:text=Generic%20Root%3A%20of%20a%20number,get%20a%20single%2Ddigit%20output.&text=For%20Example%3A%20If%20user%20input,%2B%204%20%2B%205%20%3D%2015. +/** + * Calculates the generic root (repeated digital sum) of a non-negative integer. + *

+ * For example, the generic root of 12345 is calculated as: + * 1 + 2 + 3 + 4 + 5 = 15, + * then 1 + 5 = 6, so the generic root is 6. + *

+ * Reference: + * https://technotip.com/6774/c-program-to-find-generic-root-of-a-number/ */ public final class GenericRoot { + + private static final int BASE = 10; + private GenericRoot() { } - private static int base = 10; - + /** + * Computes the sum of the digits of a non-negative integer in base 10. + * + * @param n non-negative integer + * @return sum of digits of {@code n} + */ private static int sumOfDigits(final int n) { assert n >= 0; - if (n < base) { + if (n < BASE) { return n; } - return n % base + sumOfDigits(n / base); + return (n % BASE) + sumOfDigits(n / BASE); } + /** + * Computes the generic root (repeated digital sum) of an integer. + * For negative inputs, the absolute value is used. + * + * @param n integer input + * @return generic root of {@code n} + */ public static int genericRoot(final int n) { - if (n < 0) { - return genericRoot(-n); - } - if (n > base) { - return genericRoot(sumOfDigits(n)); + int number = Math.abs(n); + if (number < BASE) { + return number; } - return n; + return genericRoot(sumOfDigits(number)); } } diff --git a/src/main/java/com/thealgorithms/maths/GermainPrimeAndSafePrime.java b/src/main/java/com/thealgorithms/maths/GermainPrimeAndSafePrime.java new file mode 100644 index 000000000000..180bbdfe9ac3 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/GermainPrimeAndSafePrime.java @@ -0,0 +1,63 @@ +package com.thealgorithms.maths; + +import com.thealgorithms.maths.Prime.PrimeCheck; + +/** + * A utility class to check whether a number is a Germain prime or a Safe prime. + * + *

This class provides methods to: + *

    + *
  • Check if a number is a Germain prime
  • + *
  • Check if a number is a Safe prime
  • + *
+ * + *

Definitions: + *

    + *
  • A Germain prime is a prime number p such that 2p + 1 is also prime.
  • + *
  • A Safe prime is a prime number p such that (p - 1) / 2 is also prime.
  • + *
+ * + *

This class is final and cannot be instantiated. + * + * @see Wikipedia: Safe and Sophie Germain primes + */ +public final class GermainPrimeAndSafePrime { + + // Private constructor to prevent instantiation + private GermainPrimeAndSafePrime() { + } + + /** + * Checks if a number is a Germain prime. + * + *

A Germain prime is a prime number p such that 2p + 1 is also prime. + * + * @param number the number to check; must be a positive integer + * @return {@code true} if the number is a Germain prime, {@code false} otherwise + * @throws IllegalArgumentException if the input number is less than 1 + */ + public static boolean isGermainPrime(int number) { + if (number < 1) { + throw new IllegalArgumentException("Input value must be a positive integer. Input value: " + number); + } + // A number is a Germain prime if it is prime and 2 * number + 1 is also prime + return PrimeCheck.isPrime(number) && PrimeCheck.isPrime(2 * number + 1); + } + + /** + * Checks if a number is a Safe prime. + * + *

A Safe prime is a prime number p such that (p - 1) / 2 is also prime. + * + * @param number the number to check; must be a positive integer + * @return {@code true} if the number is a Safe prime, {@code false} otherwise + * @throws IllegalArgumentException if the input number is less than 1 + */ + public static boolean isSafePrime(int number) { + if (number < 1) { + throw new IllegalArgumentException("Input value must be a positive integer. Input value: " + number); + } + // A number is a Safe prime if it is prime, (number - 1) is even, and (number - 1) / 2 is prime + return ((number - 1) % 2 == 0) && PrimeCheck.isPrime(number) && PrimeCheck.isPrime((number - 1) / 2); + } +} diff --git a/src/main/java/com/thealgorithms/maths/HappyNumber.java b/src/main/java/com/thealgorithms/maths/HappyNumber.java new file mode 100644 index 000000000000..bfe746953b33 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/HappyNumber.java @@ -0,0 +1,57 @@ +package com.thealgorithms.maths; + +/** + * A Happy Number is defined as a number which eventually reaches 1 when replaced + * by the sum of the squares of each digit. + * If it falls into a cycle that does not include 1, then it is not a happy number. + + * Example: + * 19 โ†’ 1ยฒ + 9ยฒ = 82 + * 82 โ†’ 8ยฒ + 2ยฒ = 68 + * 68 โ†’ 6ยฒ + 8ยฒ = 100 + * 100 โ†’ 1ยฒ + 0ยฒ + 0ยฒ = 1 โ†’ Happy Number! + */ +public final class HappyNumber { + + private HappyNumber() { + } + + /** + * Checks whether the given number is a Happy Number. + * Uses Floydโ€™s Cycle Detection algorithm (tortoise and hare method) + * to detect loops efficiently. + * + * @param n The number to check + * @return true if n is a Happy Number, false otherwise + */ + public static boolean isHappy(int n) { + int slow = n; + int fast = n; + + do { + slow = sumOfSquares(slow); // move 1 step + fast = sumOfSquares(sumOfSquares(fast)); // move 2 steps + } while (slow != fast); + + return slow == 1; // If cycle ends in 1 โ†’ Happy number + } + + /** + * Calculates the sum of squares of the digits of a number. + * + * Example: + * num = 82 โ†’ 8ยฒ + 2ยฒ = 64 + 4 = 68 + * + * @param num The number to calculate sum of squares of digits + * @return The sum of squares of the digits + */ + private static int sumOfSquares(int num) { + int sum = 0; + while (num > 0) { + int digit = num % 10; + sum += digit * digit; + num /= 10; + } + return sum; + } +} diff --git a/src/main/java/com/thealgorithms/maths/HarshadNumber.java b/src/main/java/com/thealgorithms/maths/HarshadNumber.java index 5792e925a8aa..abe21cb045ae 100644 --- a/src/main/java/com/thealgorithms/maths/HarshadNumber.java +++ b/src/main/java/com/thealgorithms/maths/HarshadNumber.java @@ -1,49 +1,78 @@ package com.thealgorithms.maths; -// Wikipedia for Harshad Number : https://en.wikipedia.org/wiki/Harshad_number - +/** + * A Harshad number (or Niven number) in a given number base is an integer that + * is divisible by the sum of its digits. + * For example, 18 is a Harshad number because 18 is divisible by (1 + 8) = 9. + * The name "Harshad" comes from the Sanskrit words "harแนฃa" (joy) and "da" + * (give), meaning "joy-giver". + * + * @author Hardvan + * @see Harshad Number - + * Wikipedia + */ public final class HarshadNumber { private HarshadNumber() { } /** - * A function to check if a number is Harshad number or not + * Checks if a number is a Harshad number. + * A Harshad number is a positive integer that is divisible by the sum of its + * digits. * - * @param n The number to be checked - * @return {@code true} if {@code a} is Harshad number, otherwise + * @param n the number to be checked (must be positive) + * @return {@code true} if {@code n} is a Harshad number, otherwise * {@code false} + * @throws IllegalArgumentException if {@code n} is less than or equal to 0 */ public static boolean isHarshad(long n) { if (n <= 0) { - return false; + throw new IllegalArgumentException("Input must be a positive integer. Received: " + n); } - long t = n; + long temp = n; long sumOfDigits = 0; - while (t > 0) { - sumOfDigits += t % 10; - t /= 10; + while (temp > 0) { + sumOfDigits += temp % 10; + temp /= 10; } return n % sumOfDigits == 0; } /** - * A function to check if a number is Harshad number or not + * Checks if a number represented as a string is a Harshad number. + * A Harshad number is a positive integer that is divisible by the sum of its + * digits. * - * @param s The number in String to be checked - * @return {@code true} if {@code a} is Harshad number, otherwise + * @param s the string representation of the number to be checked + * @return {@code true} if the number is a Harshad number, otherwise * {@code false} + * @throws IllegalArgumentException if {@code s} is null, empty, or represents a + * non-positive integer + * @throws NumberFormatException if {@code s} cannot be parsed as a long */ public static boolean isHarshad(String s) { - final Long n = Long.valueOf(s); + if (s == null || s.isEmpty()) { + throw new IllegalArgumentException("Input string cannot be null or empty"); + } + + final long n; + try { + n = Long.parseLong(s); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Input string must be a valid integer: " + s, e); + } + if (n <= 0) { - return false; + throw new IllegalArgumentException("Input must be a positive integer. Received: " + n); } int sumOfDigits = 0; for (char ch : s.toCharArray()) { - sumOfDigits += ch - '0'; + if (Character.isDigit(ch)) { + sumOfDigits += ch - '0'; + } } return n % sumOfDigits == 0; diff --git a/src/main/java/com/thealgorithms/maths/HeronsFormula.java b/src/main/java/com/thealgorithms/maths/HeronsFormula.java index 5baee715d1ec..10438e2888b9 100644 --- a/src/main/java/com/thealgorithms/maths/HeronsFormula.java +++ b/src/main/java/com/thealgorithms/maths/HeronsFormula.java @@ -1,33 +1,76 @@ package com.thealgorithms.maths; /** - * Wikipedia for HeronsFormula => https://en.wikipedia.org/wiki/Heron%27s_formula - * Find the area of a triangle using only side lengths + * Heron's Formula implementation for calculating the area of a triangle given + * its three side lengths. + *

+ * Heron's Formula states that the area of a triangle whose sides have lengths + * a, b, and c is: + * Area = โˆš(s(s - a)(s - b)(s - c)) + * where s is the semi-perimeter of the triangle: s = (a + b + c) / 2 + *

+ * + * @see Heron's + * Formula - Wikipedia + * @author satyabarghav */ - public final class HeronsFormula { - /* - * A function to get the Area of a Triangle using Heron's Formula - * @param s1,s2,s3 => the three sides of the Triangle - * @return area using the formula (โˆš(s(s โ€“ s1)(s โ€“ s2)(s โ€“ s3))) - * here s is called semi-perimeter and it is the half of the perimeter (i.e; s = (s1+s2+s3)/2) - * @author satyabarghav + /** + * Private constructor to prevent instantiation of this utility class. */ private HeronsFormula() { } + /** + * Checks if all three side lengths are positive. + * + * @param a the length of the first side + * @param b the length of the second side + * @param c the length of the third side + * @return true if all sides are positive, false otherwise + */ private static boolean areAllSidesPositive(final double a, final double b, final double c) { return a > 0 && b > 0 && c > 0; } + /** + * Checks if the given side lengths satisfy the triangle inequality theorem. + *

+ * The triangle inequality theorem states that the sum of any two sides + * of a triangle must be greater than the third side. + *

+ * + * @param a the length of the first side + * @param b the length of the second side + * @param c the length of the third side + * @return true if the sides can form a valid triangle, false otherwise + */ private static boolean canFormTriangle(final double a, final double b, final double c) { return a + b > c && b + c > a && c + a > b; } + /** + * Calculates the area of a triangle using Heron's Formula. + *

+ * Given three side lengths a, b, and c, the area is computed as: + * Area = โˆš(s(s - a)(s - b)(s - c)) + * where s is the semi-perimeter: s = (a + b + c) / 2 + *

+ * + * @param a the length of the first side (must be positive) + * @param b the length of the second side (must be positive) + * @param c the length of the third side (must be positive) + * @return the area of the triangle + * @throws IllegalArgumentException if any side length is non-positive or if the + * sides cannot form a valid triangle + */ public static double herons(final double a, final double b, final double c) { - if (!areAllSidesPositive(a, b, c) || !canFormTriangle(a, b, c)) { - throw new IllegalArgumentException("Triangle can't be formed with the given side lengths"); + if (!areAllSidesPositive(a, b, c)) { + throw new IllegalArgumentException("All side lengths must be positive"); + } + if (!canFormTriangle(a, b, c)) { + throw new IllegalArgumentException("Triangle cannot be formed with the given side lengths (violates triangle inequality)"); } final double s = (a + b + c) / 2.0; return Math.sqrt((s) * (s - a) * (s - b) * (s - c)); diff --git a/src/main/java/com/thealgorithms/maths/KaprekarNumbers.java b/src/main/java/com/thealgorithms/maths/KaprekarNumbers.java index eb9750f9ce08..99842e2f4f5e 100644 --- a/src/main/java/com/thealgorithms/maths/KaprekarNumbers.java +++ b/src/main/java/com/thealgorithms/maths/KaprekarNumbers.java @@ -4,23 +4,51 @@ import java.util.ArrayList; import java.util.List; +/** + * Utility class for identifying and working with Kaprekar Numbers. + *

+ * A Kaprekar number is a positive integer with the following property: + * If you square it, then split the resulting number into two parts (right part + * has same number of + * digits as the original number, left part has the remaining digits), and + * finally add the two + * parts together, you get the original number. + *

+ * For example: + *

    + *
  • 9: 9ยฒ = 81 โ†’ 8 + 1 = 9 (Kaprekar number)
  • + *
  • 45: 45ยฒ = 2025 โ†’ 20 + 25 = 45 (Kaprekar number)
  • + *
  • 297: 297ยฒ = 88209 โ†’ 88 + 209 = 297 (Kaprekar number)
  • + *
+ *

+ * Note: The right part can have leading zeros, but must not be all zeros. + * + * @see Kaprekar Number + * - Wikipedia + * @author TheAlgorithms (https://github.com/TheAlgorithms) + */ public final class KaprekarNumbers { private KaprekarNumbers() { } - /* This program demonstrates if a given number is Kaprekar Number or not. - Kaprekar Number: A Kaprekar number is an n-digit number which its square can be split into - two parts where the right part has n digits and sum of these parts is equal to the original - number. */ - - // Provides a list of kaprekarNumber in a range - public static List kaprekarNumberInRange(long start, long end) throws Exception { - long n = end - start; - if (n < 0) { - throw new Exception("Invalid range"); + /** + * Finds all Kaprekar numbers within a given range (inclusive). + * + * @param start the starting number of the range (inclusive) + * @param end the ending number of the range (inclusive) + * @return a list of all Kaprekar numbers in the specified range + * @throws IllegalArgumentException if start is greater than end or if start is + * negative + */ + public static List kaprekarNumberInRange(long start, long end) { + if (start > end) { + throw new IllegalArgumentException("Start must be less than or equal to end. Given start: " + start + ", end: " + end); + } + if (start < 0) { + throw new IllegalArgumentException("Start must be non-negative. Given start: " + start); } - ArrayList list = new ArrayList<>(); + ArrayList list = new ArrayList<>(); for (long i = start; i <= end; i++) { if (isKaprekarNumber(i)) { list.add(i); @@ -30,24 +58,60 @@ public static List kaprekarNumberInRange(long start, long end) throws Exce return list; } - // Checks whether a given number is Kaprekar Number or not + /** + * Checks whether a given number is a Kaprekar number. + *

+ * The algorithm works as follows: + *

    + *
  1. Square the number
  2. + *
  3. Split the squared number into two parts: left and right
  4. + *
  5. The right part has the same number of digits as the original number
  6. + *
  7. Add the left and right parts
  8. + *
  9. If the sum equals the original number, it's a Kaprekar number
  10. + *
+ *

+ * Special handling is required for numbers whose squares contain zeros. + * + * @param num the number to check + * @return true if the number is a Kaprekar number, false otherwise + * @throws IllegalArgumentException if num is negative + */ public static boolean isKaprekarNumber(long num) { + if (num < 0) { + throw new IllegalArgumentException("Number must be non-negative. Given: " + num); + } + + if (num == 0 || num == 1) { + return true; + } + String number = Long.toString(num); BigInteger originalNumber = BigInteger.valueOf(num); BigInteger numberSquared = originalNumber.multiply(originalNumber); - if (number.length() == numberSquared.toString().length()) { - return number.equals(numberSquared.toString()); - } else { - BigInteger leftDigits1 = BigInteger.ZERO; - BigInteger leftDigits2; - if (numberSquared.toString().contains("0")) { - leftDigits1 = new BigInteger(numberSquared.toString().substring(0, numberSquared.toString().indexOf("0"))); - } - leftDigits2 = new BigInteger(numberSquared.toString().substring(0, (numberSquared.toString().length() - number.length()))); - BigInteger rightDigits = new BigInteger(numberSquared.toString().substring(numberSquared.toString().length() - number.length())); - String x = leftDigits1.add(rightDigits).toString(); - String y = leftDigits2.add(rightDigits).toString(); - return (number.equals(x)) || (number.equals(y)); + String squaredStr = numberSquared.toString(); + + // Special case: if the squared number has the same length as the original + if (number.length() == squaredStr.length()) { + return number.equals(squaredStr); + } + + // Calculate the split position + int splitPos = squaredStr.length() - number.length(); + + // Split the squared number into left and right parts + String leftPart = squaredStr.substring(0, splitPos); + String rightPart = squaredStr.substring(splitPos); + + // Parse the parts as BigInteger (handles empty left part as zero) + BigInteger leftNum = leftPart.isEmpty() ? BigInteger.ZERO : new BigInteger(leftPart); + BigInteger rightNum = new BigInteger(rightPart); + + // Check if right part is all zeros (invalid for Kaprekar numbers except 1) + if (rightNum.equals(BigInteger.ZERO)) { + return false; } + + // Check if the sum equals the original number + return leftNum.add(rightNum).equals(originalNumber); } } diff --git a/src/main/java/com/thealgorithms/maths/KeithNumber.java b/src/main/java/com/thealgorithms/maths/KeithNumber.java index 1756cfbae91b..eb7f800d378f 100644 --- a/src/main/java/com/thealgorithms/maths/KeithNumber.java +++ b/src/main/java/com/thealgorithms/maths/KeithNumber.java @@ -4,57 +4,98 @@ import java.util.Collections; import java.util.Scanner; -final class KeithNumber { +/** + * A Keith number is an n-digit positive integer where the sequence formed by + * starting with its digits and repeatedly adding the previous n terms, + * eventually reaches the number itself. + * + *

+ * For example: + *

    + *
  • 14 is a Keith number: 1, 4, 5, 9, 14
  • + *
  • 19 is a Keith number: 1, 9, 10, 19
  • + *
  • 28 is a Keith number: 2, 8, 10, 18, 28
  • + *
  • 197 is a Keith number: 1, 9, 7, 17, 33, 57, 107, 197
  • + *
+ * + * @see Keith Number - + * Wikipedia + * @see Keith Number - + * MathWorld + */ +public final class KeithNumber { private KeithNumber() { } - // user-defined function that checks if the given number is Keith or not - static boolean isKeith(int x) { - // List stores all the digits of the X + /** + * Checks if a given number is a Keith number. + * + *

+ * The algorithm works as follows: + *

    + *
  1. Extract all digits of the number and store them in a list
  2. + *
  3. Generate subsequent terms by summing the last n digits
  4. + *
  5. Continue until a term equals or exceeds the original number
  6. + *
  7. If a term equals the number, it is a Keith number
  8. + *
+ * + * @param number the number to check (must be positive) + * @return {@code true} if the number is a Keith number, {@code false} otherwise + * @throws IllegalArgumentException if the number is not positive + */ + public static boolean isKeith(int number) { + if (number <= 0) { + throw new IllegalArgumentException("Number must be positive"); + } + + // Extract digits and store them in the list ArrayList terms = new ArrayList<>(); - // n denotes the number of digits - int temp = x; - int n = 0; - // executes until the condition becomes false + int temp = number; + int digitCount = 0; + while (temp > 0) { - // determines the last digit of the number and add it to the List terms.add(temp % 10); - // removes the last digit temp = temp / 10; - // increments the number of digits (n) by 1 - n++; + digitCount++; } - // reverse the List + + // Reverse the list to get digits in correct order Collections.reverse(terms); + + // Generate subsequent terms in the sequence int nextTerm = 0; - int i = n; - // finds next term for the series - // loop executes until the condition returns true - while (nextTerm < x) { + int currentIndex = digitCount; + + while (nextTerm < number) { nextTerm = 0; - // next term is the sum of previous n terms (it depends on number of digits the number - // has) - for (int j = 1; j <= n; j++) { - nextTerm = nextTerm + terms.get(i - j); + // Sum the last 'digitCount' terms + for (int j = 1; j <= digitCount; j++) { + nextTerm = nextTerm + terms.get(currentIndex - j); } terms.add(nextTerm); - i++; + currentIndex++; } - // when the control comes out of the while loop, there will be two conditions: - // either nextTerm will be equal to x or greater than x - // if equal, the given number is Keith, else not - return (nextTerm == x); + + // Check if the generated term equals the original number + return (nextTerm == number); } - // driver code + /** + * Main method for demonstrating Keith number detection. + * Reads a number from standard input and checks if it is a Keith number. + * + * @param args command line arguments (not used) + */ public static void main(String[] args) { - Scanner in = new Scanner(System.in); - int n = in.nextInt(); - if (isKeith(n)) { - System.out.println("Yes, the given number is a Keith number."); + Scanner scanner = new Scanner(System.in); + System.out.print("Enter a positive integer: "); + int number = scanner.nextInt(); + + if (isKeith(number)) { + System.out.println("Yes, " + number + " is a Keith number."); } else { - System.out.println("No, the given number is not a Keith number."); + System.out.println("No, " + number + " is not a Keith number."); } - in.close(); + scanner.close(); } } diff --git a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java index 03f18aca786f..a00ef7e3b15d 100644 --- a/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java +++ b/src/main/java/com/thealgorithms/maths/KrishnamurthyNumber.java @@ -3,10 +3,25 @@ /** * Utility class for checking if a number is a Krishnamurthy number. * - * A Krishnamurthy number (also known as a Strong number) is a number whose sum of the factorials of its digits is equal to the number itself. + *

+ * A Krishnamurthy number (also known as a Strong number or Factorion) is a + * number + * whose sum of the factorials of its digits is equal to the number itself. + *

* - * For example, 145 is a Krishnamurthy number because 1! + 4! + 5! = 1 + 24 + 120 = 145. + *

+ * For example, 145 is a Krishnamurthy number because 1! + 4! + 5! = 1 + 24 + + * 120 = 145. + *

+ * + *

+ * The only Krishnamurthy numbers in base 10 are: 1, 2, 145, and 40585. + *

+ * + *

* Example usage: + *

+ * *
  * boolean isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(145);
  * System.out.println(isKrishnamurthy); // Output: true
@@ -14,40 +29,43 @@
  * isKrishnamurthy = KrishnamurthyNumber.isKrishnamurthy(123);
  * System.out.println(isKrishnamurthy); // Output: false
  * 
+ * + * @see Factorion + * (Wikipedia) */ public final class KrishnamurthyNumber { + // Pre-computed factorials for digits 0-9 to improve performance + private static final int[] FACTORIALS = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; + private KrishnamurthyNumber() { } /** * Checks if a number is a Krishnamurthy number. * - * @param n The number to check + *

+ * A number is a Krishnamurthy number if the sum of the factorials of its digits + * equals the number itself. + *

+ * + * @param n the number to check * @return true if the number is a Krishnamurthy number, false otherwise */ public static boolean isKrishnamurthy(int n) { - int tmp = n; - int s = 0; - if (n <= 0) { return false; - } else { - while (n != 0) { - // initialising the variable fact that will store the factorials of the digits - int fact = 1; - // computing factorial of each digit - for (int i = 1; i <= n % 10; i++) { - fact = fact * i; - } - // computing the sum of the factorials - s = s + fact; - // discarding the digit for which factorial has been calculated - n = n / 10; - } + } - // evaluating if sum of the factorials of the digits equals the number itself - return tmp == s; + int original = n; + int sum = 0; + + while (n != 0) { + int digit = n % 10; + sum = sum + FACTORIALS[digit]; + n = n / 10; } + + return sum == original; } } diff --git a/src/main/java/com/thealgorithms/maths/LeonardoNumber.java b/src/main/java/com/thealgorithms/maths/LeonardoNumber.java index bbeec052777f..9ac3691a6285 100644 --- a/src/main/java/com/thealgorithms/maths/LeonardoNumber.java +++ b/src/main/java/com/thealgorithms/maths/LeonardoNumber.java @@ -1,25 +1,79 @@ package com.thealgorithms.maths; /** - * https://en.wikipedia.org/wiki/Leonardo_number + * Utility class for calculating Leonardo Numbers. + *

+ * Leonardo numbers are a sequence of numbers defined by the recurrence: + * L(n) = L(n-1) + L(n-2) + 1, with L(0) = 1 and L(1) = 1 + *

+ * The sequence begins: 1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, ... + *

+ * This class provides both a recursive implementation and an optimized + * iterative + * implementation for calculating Leonardo numbers. + * + * @see Leonardo Number + * - Wikipedia + * @see OEIS A001595 */ public final class LeonardoNumber { private LeonardoNumber() { } /** - * Calculate nth Leonardo Number (1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, ...) + * Calculates the nth Leonardo Number using recursion. + *

+ * Time Complexity: O(2^n) - exponential due to repeated calculations + * Space Complexity: O(n) - due to recursion stack + *

+ * Note: This method is not recommended for large values of n due to exponential + * time complexity. + * Consider using {@link #leonardoNumberIterative(int)} for better performance. * - * @param n the index of Leonardo Number to calculate - * @return nth number of Leonardo sequences + * @param n the index of the Leonardo Number to calculate (must be non-negative) + * @return the nth Leonardo Number + * @throws IllegalArgumentException if n is negative */ public static int leonardoNumber(int n) { if (n < 0) { - throw new ArithmeticException(); + throw new IllegalArgumentException("Input must be non-negative. Received: " + n); } if (n == 0 || n == 1) { return 1; } - return (leonardoNumber(n - 1) + leonardoNumber(n - 2) + 1); + return leonardoNumber(n - 1) + leonardoNumber(n - 2) + 1; + } + + /** + * Calculates the nth Leonardo Number using an iterative approach. + *

+ * This method provides better performance than the recursive version for large + * values of n. + *

+ * Time Complexity: O(n) + * Space Complexity: O(1) + * + * @param n the index of the Leonardo Number to calculate (must be non-negative) + * @return the nth Leonardo Number + * @throws IllegalArgumentException if n is negative + */ + public static int leonardoNumberIterative(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative. Received: " + n); + } + if (n == 0 || n == 1) { + return 1; + } + + int previous = 1; + int current = 1; + + for (int i = 2; i <= n; i++) { + int next = current + previous + 1; + previous = current; + current = next; + } + + return current; } } diff --git a/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java b/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java index a50cfb218283..a95e7053e210 100644 --- a/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java +++ b/src/main/java/com/thealgorithms/maths/LinearDiophantineEquationsSolver.java @@ -2,20 +2,77 @@ import java.util.Objects; +/** + * A solver for linear Diophantine equations of the form ax + by = c. + *

+ * A linear Diophantine equation is an equation in which only integer solutions + * are allowed. + * This solver uses the Extended Euclidean Algorithm to find integer solutions + * (x, y) + * for equations of the form ax + by = c, where a, b, and c are integers. + *

+ *

+ * The equation has solutions if and only if gcd(a, b) divides c. + * If solutions exist, this solver finds one particular solution. + *

+ * + * @see Diophantine + * Equation + * @see Extended + * Euclidean Algorithm + */ public final class LinearDiophantineEquationsSolver { private LinearDiophantineEquationsSolver() { } + /** + * Demonstrates the solver with a sample equation: 3x + 4y = 7. + * + * @param args command line arguments (not used) + */ public static void main(String[] args) { // 3x + 4y = 7 final var toSolve = new Equation(3, 4, 7); System.out.println(findAnySolution(toSolve)); } + /** + * Finds any integer solution to the linear Diophantine equation ax + by = c. + *

+ * The method returns one of three types of solutions: + *

    + *
  • A specific solution (x, y) if solutions exist
  • + *
  • {@link Solution#NO_SOLUTION} if no integer solutions exist
  • + *
  • {@link Solution#INFINITE_SOLUTIONS} if the equation is 0x + 0y = 0
  • + *
+ *

+ * + * @param equation the linear Diophantine equation to solve + * @return a Solution object containing the result + * @throws NullPointerException if equation is null + */ public static Solution findAnySolution(final Equation equation) { if (equation.a() == 0 && equation.b() == 0 && equation.c() == 0) { return Solution.INFINITE_SOLUTIONS; } + if (equation.a() == 0 && equation.b() == 0) { + return Solution.NO_SOLUTION; + } + if (equation.a() == 0) { + if (equation.c() % equation.b() == 0) { + return new Solution(0, equation.c() / equation.b()); + } else { + return Solution.NO_SOLUTION; + } + } + if (equation.b() == 0) { + if (equation.c() % equation.a() == 0) { + return new Solution(equation.c() / equation.a(), 0); + } else { + return Solution.NO_SOLUTION; + } + } final var stub = new GcdSolutionWrapper(0, new Solution(0, 0)); final var gcdSolution = gcd(equation.a(), equation.b(), stub); if (equation.c() % gcdSolution.getGcd() != 0) { @@ -29,43 +86,100 @@ public static Solution findAnySolution(final Equation equation) { return toReturn; } + /** + * Computes the GCD of two integers using the Extended Euclidean Algorithm. + *

+ * This method also finds coefficients x and y such that ax + by = gcd(a, b). + * The coefficients are stored in the 'previous' wrapper object. + *

+ * + * @param a the first integer + * @param b the second integer + * @param previous a wrapper to store the solution coefficients + * @return a GcdSolutionWrapper containing the GCD and coefficients + */ private static GcdSolutionWrapper gcd(final int a, final int b, final GcdSolutionWrapper previous) { if (b == 0) { return new GcdSolutionWrapper(a, new Solution(1, 0)); } // stub wrapper becomes the `previous` of the next recursive call final var stubWrapper = new GcdSolutionWrapper(0, new Solution(0, 0)); - final var next = /* recursive call */ gcd(b, a % b, stubWrapper); + final var next = gcd(b, a % b, stubWrapper); previous.getSolution().setX(next.getSolution().getY()); previous.getSolution().setY(next.getSolution().getX() - (a / b) * (next.getSolution().getY())); previous.setGcd(next.getGcd()); return new GcdSolutionWrapper(next.getGcd(), previous.getSolution()); } + /** + * Represents a solution (x, y) to a linear Diophantine equation. + *

+ * Special instances: + *

    + *
  • {@link #NO_SOLUTION} - indicates no integer solutions exist
  • + *
  • {@link #INFINITE_SOLUTIONS} - indicates infinitely many solutions + * exist
  • + *
+ *

+ */ public static final class Solution { + /** + * Singleton instance representing the case where no solution exists. + */ public static final Solution NO_SOLUTION = new Solution(Integer.MAX_VALUE, Integer.MAX_VALUE); + + /** + * Singleton instance representing the case where infinite solutions exist. + */ public static final Solution INFINITE_SOLUTIONS = new Solution(Integer.MIN_VALUE, Integer.MIN_VALUE); + private int x; private int y; + /** + * Constructs a solution with the given x and y values. + * + * @param x the x coordinate of the solution + * @param y the y coordinate of the solution + */ public Solution(int x, int y) { this.x = x; this.y = y; } + /** + * Gets the x value of this solution. + * + * @return the x value + */ public int getX() { return x; } + /** + * Gets the y value of this solution. + * + * @return the y value + */ public int getY() { return y; } + /** + * Sets the x value of this solution. + * + * @param x the new x value + */ public void setX(int x) { this.x = x; } + /** + * Sets the y value of this solution. + * + * @param y the new y value + */ public void setY(int y) { this.y = y; } @@ -95,14 +209,35 @@ public String toString() { } } + /** + * Represents a linear Diophantine equation of the form ax + by = c. + * + * @param a the coefficient of x + * @param b the coefficient of y + * @param c the constant term + */ public record Equation(int a, int b, int c) { } + /** + * A wrapper class that holds both the GCD and the solution coefficients + * from the Extended Euclidean Algorithm. + *

+ * This class is used internally to pass results between recursive calls + * of the GCD computation. + *

+ */ public static final class GcdSolutionWrapper { private int gcd; private Solution solution; + /** + * Constructs a GcdSolutionWrapper with the given GCD and solution. + * + * @param gcd the greatest common divisor + * @param solution the solution coefficients + */ public GcdSolutionWrapper(int gcd, Solution solution) { this.gcd = gcd; this.solution = solution; @@ -120,18 +255,38 @@ public boolean equals(Object obj) { return (this.gcd == that.gcd && Objects.equals(this.solution, that.solution)); } + /** + * Gets the GCD value. + * + * @return the GCD + */ public int getGcd() { return gcd; } + /** + * Sets the GCD value. + * + * @param gcd the new GCD value + */ public void setGcd(int gcd) { this.gcd = gcd; } + /** + * Gets the solution coefficients. + * + * @return the solution + */ public Solution getSolution() { return solution; } + /** + * Sets the solution coefficients. + * + * @param solution the new solution + */ public void setSolution(Solution solution) { this.solution = solution; } diff --git a/src/main/java/com/thealgorithms/maths/LucasSeries.java b/src/main/java/com/thealgorithms/maths/LucasSeries.java index e277c511f317..90a35f0d6259 100644 --- a/src/main/java/com/thealgorithms/maths/LucasSeries.java +++ b/src/main/java/com/thealgorithms/maths/LucasSeries.java @@ -1,38 +1,69 @@ package com.thealgorithms.maths; /** - * https://en.wikipedia.org/wiki/Lucas_number + * Utility class for calculating Lucas numbers. + * The Lucas sequence is similar to the Fibonacci sequence but starts with 2 and + * 1. + * The sequence follows: L(n) = L(n-1) + L(n-2) + * Starting values: L(1) = 2, L(2) = 1 + * Sequence: 2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 123, ... + * + * @see Lucas Number + * @author TheAlgorithms Contributors */ public final class LucasSeries { private LucasSeries() { } /** - * Calculate nth number of Lucas Series(2, 1, 3, 4, 7, 11, 18, 29, 47, 76, - * 123, ....) using recursion + * Calculate the nth Lucas number using recursion. + * Time Complexity: O(2^n) - exponential due to recursive calls + * Space Complexity: O(n) - recursion depth * - * @param n nth - * @return nth number of Lucas Series + * @param n the position in the Lucas sequence (1-indexed, must be positive) + * @return the nth Lucas number + * @throws IllegalArgumentException if n is less than 1 */ public static int lucasSeries(int n) { - return n == 1 ? 2 : n == 2 ? 1 : lucasSeries(n - 1) + lucasSeries(n - 2); + if (n < 1) { + throw new IllegalArgumentException("Input must be a positive integer. Provided: " + n); + } + if (n == 1) { + return 2; + } + if (n == 2) { + return 1; + } + return lucasSeries(n - 1) + lucasSeries(n - 2); } /** - * Calculate nth number of Lucas Series(2, 1, 3, 4, 7, 11, 18, 29, 47, 76, - * 123, ....) using iteration + * Calculate the nth Lucas number using iteration. + * Time Complexity: O(n) - single loop through n iterations + * Space Complexity: O(1) - constant space usage * - * @param n nth - * @return nth number of lucas series + * @param n the position in the Lucas sequence (1-indexed, must be positive) + * @return the nth Lucas number + * @throws IllegalArgumentException if n is less than 1 */ public static int lucasSeriesIteration(int n) { + if (n < 1) { + throw new IllegalArgumentException("Input must be a positive integer. Provided: " + n); + } + if (n == 1) { + return 2; + } + if (n == 2) { + return 1; + } + int previous = 2; int current = 1; - for (int i = 1; i < n; i++) { + for (int i = 2; i < n; i++) { int next = previous + current; previous = current; current = next; } - return previous; + return current; } } diff --git a/src/main/java/com/thealgorithms/maths/MathBuilder.java b/src/main/java/com/thealgorithms/maths/MathBuilder.java new file mode 100644 index 000000000000..1cf3d8b7fc9a --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/MathBuilder.java @@ -0,0 +1,503 @@ +package com.thealgorithms.maths; + +import java.text.DecimalFormat; +import java.util.Random; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Author: Sadiul Hakim : https://github.com/sadiul-hakim + * Profession: Backend Engineer + * Date: Oct 20, 2024 + */ +public final class MathBuilder { + private final double result; + + private MathBuilder(Builder builder) { + this.result = builder.number; + } + + // Returns final result + public double get() { + return result; + } + + // Return result in long + public long toLong() { + try { + if (Double.isNaN(result)) { + throw new IllegalArgumentException("Cannot convert NaN to long!"); + } + if (result == Double.POSITIVE_INFINITY) { + return Long.MAX_VALUE; + } + if (result == Double.NEGATIVE_INFINITY) { + return Long.MIN_VALUE; + } + if (result > Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + if (result < Long.MIN_VALUE) { + return Long.MIN_VALUE; + } + return Math.round(result); + } catch (Exception ex) { + return 0; + } + } + + public static class Builder { + private double number; + private double sideNumber; + private boolean inParenthesis; + private double memory = 0; + + public Builder() { + number = 0; + } + + public Builder(double num) { + number = num; + } + + public Builder add(double num) { + if (inParenthesis) { + sideNumber += num; + } else { + number += num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder addIf(double num, BiFunction condition) { + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber += num; + } else { + number += num; + } + return this; + } + + public Builder minus(double num) { + if (inParenthesis) { + sideNumber -= num; + } else { + number -= num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder minusIf(double num, BiFunction condition) { + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber -= num; + } else { + number -= num; + } + return this; + } + + // Generates a random number and sets to NUMBER + public Builder rand(long seed) { + if (number != 0) { + throw new RuntimeException("Number must be zero for random assignment!"); + } + Random random = new Random(); + number = random.nextDouble(seed); + return this; + } + + // Takes PI value and sets to NUMBER + public Builder pi() { + if (number != 0) { + throw new RuntimeException("Number must be zero for PI assignment!"); + } + number = Math.PI; + return this; + } + + // Takes E value and sets to NUMBER + public Builder e() { + if (number != 0) { + throw new RuntimeException("Number must be zero for E assignment!"); + } + number = Math.E; + return this; + } + + public Builder randomInRange(double min, double max) { + if (number != 0) { + throw new RuntimeException("Number must be zero for random assignment!"); + } + Random random = new Random(); + number = min + (max - min) * random.nextDouble(); + return this; + } + + public Builder toDegrees() { + if (inParenthesis) { + sideNumber = Math.toDegrees(sideNumber); + } else { + number = Math.toDegrees(number); + } + return this; + } + + public Builder max(double num) { + if (inParenthesis) { + sideNumber = Math.max(sideNumber, num); + } else { + number = Math.max(number, num); + } + return this; + } + + public Builder min(double num) { + if (inParenthesis) { + sideNumber = Math.min(sideNumber, num); + } else { + number = Math.min(number, num); + } + return this; + } + + public Builder multiply(double num) { + if (inParenthesis) { + sideNumber *= num; + } else { + number *= num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder multiplyIf(double num, BiFunction condition) { + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber *= num; + } else { + number *= num; + } + return this; + } + + public Builder divide(double num) { + if (num == 0) { + return this; + } + if (inParenthesis) { + sideNumber /= num; + } else { + number /= num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder divideIf(double num, BiFunction condition) { + if (num == 0) { + return this; + } + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber /= num; + } else { + number /= num; + } + return this; + } + + public Builder mod(double num) { + if (inParenthesis) { + sideNumber %= num; + } else { + number %= num; + } + return this; + } + + // Takes a number and a condition, only does the operation if condition is true. + public Builder modIf(double num, BiFunction condition) { + if (!condition.apply(number, num)) { + return this; + } + if (inParenthesis) { + sideNumber %= num; + } else { + number %= num; + } + return this; + } + + public Builder pow(double num) { + if (inParenthesis) { + sideNumber = Math.pow(sideNumber, num); + } else { + number = Math.pow(number, num); + } + return this; + } + + public Builder sqrt() { + if (inParenthesis) { + sideNumber = Math.sqrt(sideNumber); + } else { + number = Math.sqrt(number); + } + return this; + } + + public Builder round() { + if (inParenthesis) { + sideNumber = Math.round(sideNumber); + } else { + number = Math.round(number); + } + return this; + } + + public Builder floor() { + if (inParenthesis) { + sideNumber = Math.floor(sideNumber); + } else { + number = Math.floor(number); + } + return this; + } + + public Builder ceil() { + if (inParenthesis) { + sideNumber = Math.ceil(sideNumber); + } else { + number = Math.ceil(number); + } + return this; + } + + public Builder abs() { + if (inParenthesis) { + sideNumber = Math.abs(sideNumber); + } else { + number = Math.abs(number); + } + return this; + } + + public Builder cbrt() { + if (inParenthesis) { + sideNumber = Math.cbrt(sideNumber); + } else { + number = Math.cbrt(number); + } + return this; + } + + public Builder log() { + if (inParenthesis) { + sideNumber = Math.log(sideNumber); + } else { + number = Math.log(number); + } + return this; + } + + public Builder log10() { + if (inParenthesis) { + sideNumber = Math.log10(sideNumber); + } else { + number = Math.log10(number); + } + return this; + } + + public Builder sin() { + if (inParenthesis) { + sideNumber = Math.sin(sideNumber); + } else { + number = Math.sin(number); + } + return this; + } + + public Builder cos() { + if (inParenthesis) { + sideNumber = Math.cos(sideNumber); + } else { + number = Math.cos(number); + } + return this; + } + + public Builder tan() { + if (inParenthesis) { + sideNumber = Math.tan(sideNumber); + } else { + number = Math.tan(number); + } + return this; + } + + public Builder sinh() { + if (inParenthesis) { + sideNumber = Math.sinh(sideNumber); + } else { + number = Math.sinh(number); + } + return this; + } + + public Builder cosh() { + if (inParenthesis) { + sideNumber = Math.cosh(sideNumber); + } else { + number = Math.cosh(number); + } + return this; + } + + public Builder tanh() { + if (inParenthesis) { + sideNumber = Math.tanh(sideNumber); + } else { + number = Math.tanh(number); + } + return this; + } + + public Builder exp() { + if (inParenthesis) { + sideNumber = Math.exp(sideNumber); + } else { + number = Math.exp(number); + } + return this; + } + + public Builder toRadians() { + if (inParenthesis) { + sideNumber = Math.toRadians(sideNumber); + } else { + number = Math.toRadians(number); + } + return this; + } + + // Remembers the NUMBER + public Builder remember() { + memory = number; + return this; + } + + // Recalls the NUMBER + public Builder recall(boolean cleanMemory) { + number = memory; + if (cleanMemory) { + memory = 0; + } + return this; + } + + // Recalls the NUMBER on condition + public Builder recallIf(Function condition, boolean cleanMemory) { + if (!condition.apply(number)) { + return this; + } + number = memory; + if (cleanMemory) { + memory = 0; + } + return this; + } + + // Replaces NUMBER with given number + public Builder set(double num) { + if (number != 0) { + throw new RuntimeException("Number must be zero to set!"); + } + number = num; + return this; + } + + // Replaces NUMBER with given number on condition + public Builder setIf(double num, BiFunction condition) { + if (number != 0) { + throw new RuntimeException("Number must be zero to set!"); + } + if (condition.apply(number, num)) { + number = num; + } + return this; + } + + // Prints current NUMBER + public Builder print() { + System.out.println("MathBuilder Result :: " + number); + return this; + } + + public Builder openParenthesis(double num) { + sideNumber = num; + inParenthesis = true; + return this; + } + + public Builder closeParenthesisAndPlus() { + number += sideNumber; + inParenthesis = false; + sideNumber = 0; + return this; + } + + public Builder closeParenthesisAndMinus() { + number -= sideNumber; + inParenthesis = false; + sideNumber = 0; + return this; + } + + public Builder closeParenthesisAndMultiply() { + number *= sideNumber; + inParenthesis = false; + sideNumber = 0; + return this; + } + + public Builder closeParenthesisAndDivide() { + number /= sideNumber; + inParenthesis = false; + sideNumber = 0; + return this; + } + + public Builder format(String format) { + DecimalFormat formater = new DecimalFormat(format); + String num = formater.format(number); + number = Double.parseDouble(num); + return this; + } + + public Builder format(int decimalPlace) { + String pattern = "." + + "#".repeat(decimalPlace); + DecimalFormat formater = new DecimalFormat(pattern); + String num = formater.format(number); + number = Double.parseDouble(num); + return this; + } + + public MathBuilder build() { + return new MathBuilder(this); + } + } +} diff --git a/src/main/java/com/thealgorithms/maths/Means.java b/src/main/java/com/thealgorithms/maths/Means.java index dccc820b172e..5445a3caebc7 100644 --- a/src/main/java/com/thealgorithms/maths/Means.java +++ b/src/main/java/com/thealgorithms/maths/Means.java @@ -4,9 +4,27 @@ import org.apache.commons.collections4.IterableUtils; /** - * https://en.wikipedia.org/wiki/Mean + * Utility class for computing various types of statistical means. *

- * by: Punit Patel + * This class provides static methods to calculate different types of means + * (averages) + * from a collection of numbers. All methods accept any {@link Iterable} + * collection of + * {@link Double} values and return the computed mean as a {@link Double}. + *

+ * + *

+ * Supported means: + *

    + *
  • Arithmetic Mean: The sum of all values divided by the count
  • + *
  • Geometric Mean: The nth root of the product of n values
  • + *
  • Harmonic Mean: The reciprocal of the arithmetic mean of + * reciprocals
  • + *
+ *

+ * + * @see Mean (Wikipedia) + * @author Punit Patel */ public final class Means { @@ -14,41 +32,90 @@ private Means() { } /** - * @brief computes the [Arithmetic Mean](https://en.wikipedia.org/wiki/Arithmetic_mean) of the input - * @param numbers the input numbers - * @throws IllegalArgumentException empty input + * Computes the arithmetic mean (average) of the given numbers. + *

+ * The arithmetic mean is calculated as: (xโ‚ + xโ‚‚ + ... + xโ‚™) / n + *

+ *

+ * Example: For numbers [2, 4, 6], the arithmetic mean is (2+4+6)/3 = 4.0 + *

+ * + * @param numbers the input numbers (must not be empty) * @return the arithmetic mean of the input numbers + * @throws IllegalArgumentException if the input is empty + * @see Arithmetic + * Mean */ public static Double arithmetic(final Iterable numbers) { checkIfNotEmpty(numbers); - return StreamSupport.stream(numbers.spliterator(), false).reduce((x, y) -> x + y).get() / IterableUtils.size(numbers); + double sum = StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + y); + int size = IterableUtils.size(numbers); + return sum / size; } /** - * @brief computes the [Geometric Mean](https://en.wikipedia.org/wiki/Geometric_mean) of the input - * @param numbers the input numbers - * @throws IllegalArgumentException empty input + * Computes the geometric mean of the given numbers. + *

+ * The geometric mean is calculated as: โฟโˆš(xโ‚ ร— xโ‚‚ ร— ... ร— xโ‚™) + *

+ *

+ * Example: For numbers [2, 8], the geometric mean is โˆš(2ร—8) = โˆš16 = 4.0 + *

+ *

+ * Note: This method may produce unexpected results for negative numbers, + * as it computes the real-valued nth root which may not exist for negative + * products. + *

+ * + * @param numbers the input numbers (must not be empty) * @return the geometric mean of the input numbers + * @throws IllegalArgumentException if the input is empty + * @see Geometric + * Mean */ public static Double geometric(final Iterable numbers) { checkIfNotEmpty(numbers); - return Math.pow(StreamSupport.stream(numbers.spliterator(), false).reduce((x, y) -> x * y).get(), 1d / IterableUtils.size(numbers)); + double product = StreamSupport.stream(numbers.spliterator(), false).reduce(1d, (x, y) -> x * y); + int size = IterableUtils.size(numbers); + return Math.pow(product, 1.0 / size); } /** - * @brief computes the [Harmonic Mean](https://en.wikipedia.org/wiki/Harmonic_mean) of the input - * @param numbers the input numbers - * @throws IllegalArgumentException empty input + * Computes the harmonic mean of the given numbers. + *

+ * The harmonic mean is calculated as: n / (1/xโ‚ + 1/xโ‚‚ + ... + 1/xโ‚™) + *

+ *

+ * Example: For numbers [1, 2, 4], the harmonic mean is 3/(1/1 + 1/2 + 1/4) = + * 3/1.75 โ‰ˆ 1.714 + *

+ *

+ * Note: This method will produce unexpected results if any input number is + * zero, + * as it involves computing reciprocals. + *

+ * + * @param numbers the input numbers (must not be empty) * @return the harmonic mean of the input numbers + * @throws IllegalArgumentException if the input is empty + * @see Harmonic Mean */ public static Double harmonic(final Iterable numbers) { checkIfNotEmpty(numbers); - return IterableUtils.size(numbers) / StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + 1d / y); + double sumOfReciprocals = StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + 1d / y); + int size = IterableUtils.size(numbers); + return size / sumOfReciprocals; } + /** + * Validates that the input iterable is not empty. + * + * @param numbers the input numbers to validate + * @throws IllegalArgumentException if the input is empty + */ private static void checkIfNotEmpty(final Iterable numbers) { if (!numbers.iterator().hasNext()) { - throw new IllegalArgumentException("Emtpy list given for Mean computation."); + throw new IllegalArgumentException("Empty list given for Mean computation."); } } } diff --git a/src/main/java/com/thealgorithms/maths/Median.java b/src/main/java/com/thealgorithms/maths/Median.java index e4daec8fc11a..5e1a4bc3a8d9 100644 --- a/src/main/java/com/thealgorithms/maths/Median.java +++ b/src/main/java/com/thealgorithms/maths/Median.java @@ -3,20 +3,47 @@ import java.util.Arrays; /** - * Wikipedia: https://en.wikipedia.org/wiki/Median + * Utility class for calculating the median of an array of integers. + * The median is the middle value in a sorted list of numbers. If the list has + * an even number + * of elements, the median is the average of the two middle values. + * + *

+ * Time Complexity: O(n log n) due to sorting + *

+ * Space Complexity: O(1) if sorting is done in-place + * + * @see Median (Wikipedia) + * @see Mean, + * Median, and Mode Review */ public final class Median { private Median() { } /** - * Calculate average median - * @param values sorted numbers to find median of - * @return median of given {@code values} + * Calculates the median of an array of integers. + * The array is sorted internally, so the original order is not preserved. + * For arrays with an odd number of elements, returns the middle element. + * For arrays with an even number of elements, returns the average of the two + * middle elements. + * + * @param values the array of integers to find the median of (can be unsorted) + * @return the median value as a double + * @throws IllegalArgumentException if the input array is empty or null */ public static double median(int[] values) { + if (values == null || values.length == 0) { + throw new IllegalArgumentException("Values array cannot be empty or null"); + } + Arrays.sort(values); int length = values.length; - return length % 2 == 0 ? (values[length / 2] + values[length / 2 - 1]) / 2.0 : values[length / 2]; + if (length % 2 == 0) { + return (values[length / 2] + values[length / 2 - 1]) / 2.0; + } else { + return values[length / 2]; + } } } diff --git a/src/main/java/com/thealgorithms/maths/Mode.java b/src/main/java/com/thealgorithms/maths/Mode.java index f0b747cf02ec..657f8ece2a50 100644 --- a/src/main/java/com/thealgorithms/maths/Mode.java +++ b/src/main/java/com/thealgorithms/maths/Mode.java @@ -3,46 +3,49 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; +import java.util.Map; -/* - * Find the mode of an array of numbers - * - * The mode of an array of numbers is the most frequently occurring number in the array, - * or the most frequently occurring numbers if there are multiple numbers with the same frequency +/** + * Utility class to calculate the mode(s) of an array of integers. + *

+ * The mode of an array is the integer value(s) that occur most frequently. + * If multiple values have the same highest frequency, all such values are returned. + *

*/ public final class Mode { private Mode() { } - /* - * Find the mode of an array of integers + /** + * Computes the mode(s) of the specified array of integers. + *

+ * If the input array is empty, this method returns {@code null}. + * If multiple numbers share the highest frequency, all are returned in the result array. + *

* - * @param numbers array of integers - * @return mode of the array + * @param numbers an array of integers to analyze + * @return an array containing the mode(s) of the input array, or {@code null} if the input is empty */ public static int[] mode(final int[] numbers) { if (numbers.length == 0) { return null; } - HashMap count = new HashMap<>(); + Map count = new HashMap<>(); for (int num : numbers) { - if (count.containsKey(num)) { - count.put(num, count.get(num) + 1); - } else { - count.put(num, 1); - } + count.put(num, count.getOrDefault(num, 0) + 1); } int max = Collections.max(count.values()); - ArrayList modes = new ArrayList<>(); + List modes = new ArrayList<>(); for (final var entry : count.entrySet()) { if (entry.getValue() == max) { modes.add(entry.getKey()); } } - return modes.stream().mapToInt(n -> n).toArray(); + return modes.stream().mapToInt(Integer::intValue).toArray(); } } diff --git a/src/main/java/com/thealgorithms/maths/NumberPersistence.java b/src/main/java/com/thealgorithms/maths/NumberPersistence.java new file mode 100644 index 000000000000..9cc6667dca02 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/NumberPersistence.java @@ -0,0 +1,79 @@ +package com.thealgorithms.maths; + +/** + * A utility class for calculating the persistence of a number. + * + *

This class provides methods to calculate: + *

    + *
  • Multiplicative persistence: The number of steps required to reduce a number to a single digit by multiplying its digits.
  • + *
  • Additive persistence: The number of steps required to reduce a number to a single digit by summing its digits.
  • + *
+ * + *

This class is final and cannot be instantiated. + * + * @see Wikipedia: Persistence of a number + */ +public final class NumberPersistence { + + // Private constructor to prevent instantiation + private NumberPersistence() { + } + + /** + * Calculates the multiplicative persistence of a given number. + * + *

Multiplicative persistence is the number of steps required to reduce a number to a single digit + * by multiplying its digits repeatedly. + * + * @param num the number to calculate persistence for; must be non-negative + * @return the multiplicative persistence of the number + * @throws IllegalArgumentException if the input number is negative + */ + public static int multiplicativePersistence(int num) { + if (num < 0) { + throw new IllegalArgumentException("multiplicativePersistence() does not accept negative values"); + } + + int steps = 0; + while (num >= 10) { + int product = 1; + int temp = num; + while (temp > 0) { + product *= temp % 10; + temp /= 10; + } + num = product; + steps++; + } + return steps; + } + + /** + * Calculates the additive persistence of a given number. + * + *

Additive persistence is the number of steps required to reduce a number to a single digit + * by summing its digits repeatedly. + * + * @param num the number to calculate persistence for; must be non-negative + * @return the additive persistence of the number + * @throws IllegalArgumentException if the input number is negative + */ + public static int additivePersistence(int num) { + if (num < 0) { + throw new IllegalArgumentException("additivePersistence() does not accept negative values"); + } + + int steps = 0; + while (num >= 10) { + int sum = 0; + int temp = num; + while (temp > 0) { + sum += temp % 10; + temp /= 10; + } + num = sum; + steps++; + } + return steps; + } +} diff --git a/src/main/java/com/thealgorithms/maths/PerfectNumber.java b/src/main/java/com/thealgorithms/maths/PerfectNumber.java index 2a935b067094..f299d08e5d27 100644 --- a/src/main/java/com/thealgorithms/maths/PerfectNumber.java +++ b/src/main/java/com/thealgorithms/maths/PerfectNumber.java @@ -63,7 +63,7 @@ public static boolean isPerfectNumber2(int n) { // if n is a perfect square then its root was added twice in above loop, so subtracting root // from sum if (root == (int) root) { - sum -= root; + sum -= (int) root; } return sum == n; diff --git a/src/main/java/com/thealgorithms/maths/PiApproximation.java b/src/main/java/com/thealgorithms/maths/PiApproximation.java new file mode 100644 index 000000000000..1be945d7ef24 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/PiApproximation.java @@ -0,0 +1,74 @@ +package com.thealgorithms.maths; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Implementation to calculate an estimate of the number ฯ€ (Pi). + * + * We take a random point P with coordinates (x, y) such that 0 โ‰ค x โ‰ค 1 and 0 โ‰ค y โ‰ค 1. + * If xยฒ + yยฒ โ‰ค 1, then the point is inside the quarter disk of radius 1, + * else the point is outside. We know that the probability of the point being + * inside the quarter disk is equal to ฯ€/4. + * + * + * @author [Yash Rajput](https://github.com/the-yash-rajput) + */ +public final class PiApproximation { + + private PiApproximation() { + throw new AssertionError("No instances."); + } + + /** + * Structure representing a point with coordinates (x, y) + * where 0 โ‰ค x โ‰ค 1 and 0 โ‰ค y โ‰ค 1. + */ + static class Point { + double x; + double y; + + Point(double x, double y) { + this.x = x; + this.y = y; + } + } + + /** + * This function uses the points in a given list (drawn at random) + * to return an approximation of the number ฯ€. + * + * @param pts List of points where each point contains x and y coordinates + * @return An estimate of the number ฯ€ + */ + public static double approximatePi(List pts) { + double count = 0; // Points in circle + + for (Point p : pts) { + if ((p.x * p.x) + (p.y * p.y) <= 1) { + count++; + } + } + + return 4.0 * count / pts.size(); + } + + /** + * Generates random points for testing the Pi approximation. + * + * @param numPoints Number of random points to generate + * @return List of random points + */ + public static List generateRandomPoints(int numPoints) { + List points = new ArrayList<>(); + Random rand = new Random(); + + for (int i = 0; i < numPoints; i++) { + double x = rand.nextDouble(); // Random value between 0 and 1 + double y = rand.nextDouble(); // Random value between 0 and 1 + points.add(new Point(x, y)); + } + + return points; + } +} diff --git a/src/main/java/com/thealgorithms/maths/PythagoreanTriple.java b/src/main/java/com/thealgorithms/maths/PythagoreanTriple.java index f535e9e6929b..2780b113d904 100644 --- a/src/main/java/com/thealgorithms/maths/PythagoreanTriple.java +++ b/src/main/java/com/thealgorithms/maths/PythagoreanTriple.java @@ -1,36 +1,43 @@ package com.thealgorithms.maths; /** - * https://en.wikipedia.org/wiki/Pythagorean_triple + * Utility class to check if three integers form a Pythagorean triple. + * A Pythagorean triple consists of three positive integers a, b, and c, + * such that aยฒ + bยฒ = cยฒ. + * + * Common examples: + * - (3, 4, 5) + * - (5, 12, 13) + * + * Reference: https://en.wikipedia.org/wiki/Pythagorean_triple */ public final class PythagoreanTriple { - private PythagoreanTriple() { - } - public static void main(String[] args) { - assert isPythagTriple(3, 4, 5); - assert isPythagTriple(5, 12, 13); - assert isPythagTriple(6, 8, 10); - assert !isPythagTriple(10, 20, 30); - assert !isPythagTriple(6, 8, 100); - assert !isPythagTriple(-1, -1, 1); + private PythagoreanTriple() { } /** - * Check if a,b,c are a Pythagorean Triple + * Checks whether three integers form a Pythagorean triple. + * The order of parameters does not matter. * - * @param a x/y component length of a right triangle - * @param b y/x component length of a right triangle - * @param c hypotenuse length of a right triangle - * @return boolean true if a, b, c satisfy the Pythagorean theorem, - * otherwise - * false + * @param a one side length + * @param b another side length + * @param c another side length + * @return {@code true} if (a, b, c) can form a Pythagorean triple, otherwise {@code false} */ public static boolean isPythagTriple(int a, int b, int c) { if (a <= 0 || b <= 0 || c <= 0) { return false; - } else { - return (a * a) + (b * b) == (c * c); } + + // Sort the sides so the largest is treated as hypotenuse + int[] sides = {a, b, c}; + java.util.Arrays.sort(sides); + + int x = sides[0]; + int y = sides[1]; + int hypotenuse = sides[2]; + + return x * x + y * y == hypotenuse * hypotenuse; } } diff --git a/src/main/java/com/thealgorithms/maths/SieveOfAtkin.java b/src/main/java/com/thealgorithms/maths/SieveOfAtkin.java new file mode 100644 index 000000000000..ee59d0784ec4 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SieveOfAtkin.java @@ -0,0 +1,143 @@ +package com.thealgorithms.maths; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of the Sieve of Atkin, an optimized algorithm to generate + * all prime numbers up to a given limit. + * + * The Sieve of Atkin uses quadratic forms and modular arithmetic to identify + * prime candidates, then eliminates multiples of squares. It is more efficient + * than the Sieve of Eratosthenes for large limits. + */ +public final class SieveOfAtkin { + + private SieveOfAtkin() { + // Utlity class; prevent instantiation + } + + /** + * Generates a list of all prime numbers up to the specified limit + * using the Sieve of Atkin algorithm. + * + * @param limit the upper bound up to which primes are generated; must be zero or positive + * @return a list of prime numbers up to the limit; empty if the limit is less than 2 + */ + public static List generatePrimes(int limit) { + if (limit < 1) { + return List.of(); + } + + boolean[] sieve = new boolean[limit + 1]; + int sqrtLimit = (int) Math.sqrt(limit); + + markQuadraticResidues(limit, sqrtLimit, sieve); + eliminateMultiplesOfSquares(limit, sqrtLimit, sieve); + + List primes = new ArrayList<>(); + if (limit >= 2) { + primes.add(2); + } + if (limit >= 3) { + primes.add(3); + } + + for (int i = 5; i <= limit; i++) { + if (sieve[i]) { + primes.add(i); + } + } + + return primes; + } + + /** + * Marks numbers in the sieve as prime candidates based on quadratic residues. + * + * This method iterates over all x and y up to sqrt(limit) and applies + * the three quadratic forms used in the Sieve of Atkin. Numbers satisfying + * the modulo conditions are toggled in the sieve array. + * + * @param limit the upper bound for primes + * @param sqrtLimit square root of the limit + * @param sieve boolean array representing potential primes + */ + private static void markQuadraticResidues(int limit, int sqrtLimit, boolean[] sieve) { + for (int x = 1; x <= sqrtLimit; x++) { + for (int y = 1; y <= sqrtLimit; y++) { + applyQuadraticForm(4 * x * x + y * y, limit, sieve, 1, 5); + applyQuadraticForm(3 * x * x + y * y, limit, sieve, 7); + applyQuadraticForm(3 * x * x - y * y, limit, sieve, 11, x > y); + } + } + } + + /** + * Toggles the sieve entry for a number if it satisfies one modulo condition. + * + * @param n the number to check + * @param limit upper bound of primes + * @param sieve boolean array representing potential primes + * @param modulo the modulo condition number to check + */ + private static void applyQuadraticForm(int n, int limit, boolean[] sieve, int modulo) { + if (n <= limit && n % 12 == modulo) { + sieve[n] ^= true; + } + } + + /** + * Toggles the sieve entry for a number if it satisfies either of two modulo conditions. + * + * @param n the number to check + * @param limit upper bound of primes + * @param sieve boolean array representing potential primes + * @param modulo1 first modulo condition number to check + * @param modulo2 second modulo condition number to check + */ + private static void applyQuadraticForm(int n, int limit, boolean[] sieve, int modulo1, int modulo2) { + if (n <= limit && (n % 12 == modulo1 || n % 12 == modulo2)) { + sieve[n] ^= true; + } + } + + /** + * Toggles the sieve entry for a number if it satisfies the modulo condition and an additional boolean condition. + * + * This version is used for the quadratic form 3*x*x - y*y, which requires x > y. + * + * @param n the number to check + * @param limit upper bound of primes + * @param sieve boolean array representing potential primes + * @param modulo the modulo condition number to check + * @param condition an additional boolean condition that must be true + */ + private static void applyQuadraticForm(int n, int limit, boolean[] sieve, int modulo, boolean condition) { + if (condition && n <= limit && n % 12 == modulo) { + sieve[n] ^= true; + } + } + + /** + * Eliminates numbers that are multiples of squares from the sieve. + * + * All numbers that are multiples of i*i (where i is marked as prime) are + * marked non-prime to finalize the sieve. This ensures only actual primes remain. + * + * @param limit the upper bound for primes + * @param sqrtLimit square root of the limit + * @param sieve boolean array representing potential primes + */ + private static void eliminateMultiplesOfSquares(int limit, int sqrtLimit, boolean[] sieve) { + for (int i = 5; i <= sqrtLimit; i++) { + if (!sieve[i]) { + continue; + } + int square = i * i; + for (int j = square; j <= limit; j += square) { + sieve[j] = false; + } + } + } +} diff --git a/src/main/java/com/thealgorithms/maths/SumOfSquares.java b/src/main/java/com/thealgorithms/maths/SumOfSquares.java new file mode 100644 index 000000000000..c050d5a75f7b --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SumOfSquares.java @@ -0,0 +1,53 @@ +package com.thealgorithms.maths; + +/** + * Implementation of Lagrange's Four Square Theorem + * Find minimum number of perfect squares that sum to given number + * + * @see Lagrange's Four Square Theorem + * @author BEASTSHRIRAM + */ +public final class SumOfSquares { + + private SumOfSquares() { + // Utility class + } + + /** + * Find minimum number of perfect squares that sum to n + * + * @param n the target number + * @return minimum number of squares needed + */ + public static int minSquares(int n) { + if (isPerfectSquare(n)) { + return 1; + } + + for (int i = 1; i * i <= n; i++) { + int remaining = n - i * i; + if (isPerfectSquare(remaining)) { + return 2; + } + } + + // Legendre's three-square theorem + int temp = n; + while (temp % 4 == 0) { + temp /= 4; + } + if (temp % 8 == 7) { + return 4; + } + + return 3; + } + + private static boolean isPerfectSquare(int n) { + if (n < 0) { + return false; + } + int root = (int) Math.sqrt(n); + return root * root == n; + } +} diff --git a/src/main/java/com/thealgorithms/maths/ZellersCongruence.java b/src/main/java/com/thealgorithms/maths/ZellersCongruence.java new file mode 100644 index 000000000000..95ed061ac17f --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/ZellersCongruence.java @@ -0,0 +1,107 @@ +package com.thealgorithms.maths; + +import java.time.DateTimeException; +import java.time.LocalDate; +import java.util.Objects; + +/** + * A utility class for calculating the day of the week for a given date using Zeller's Congruence. + * + *

Zeller's Congruence is an algorithm devised by Christian Zeller in the 19th century to calculate + * the day of the week for any Julian or Gregorian calendar date. The input date must be in the format + * "MM-DD-YYYY" or "MM/DD/YYYY". + * + *

This class is final and cannot be instantiated. + * + * @see Wikipedia: Zeller's Congruence + */ +public final class ZellersCongruence { + + private static final String[] DAYS = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + + // Private constructor to prevent instantiation + private ZellersCongruence() { + } + + /** + * Calculates the day of the week for a given date using Zeller's Congruence. + * + *

The algorithm works for both Julian and Gregorian calendar dates. The input date must be + * in the format "MM-DD-YYYY" or "MM/DD/YYYY". + * + * @param input the date in the format "MM-DD-YYYY" or "MM/DD/YYYY" + * @return a string indicating the day of the week for the given date + * @throws IllegalArgumentException if the input format is invalid, the date is invalid, + * or the year is out of range + */ + public static String calculateDay(String input) { + if (input == null || input.length() != 10) { + throw new IllegalArgumentException("Input date must be 10 characters long in the format MM-DD-YYYY or MM/DD/YYYY."); + } + + int month = parsePart(input.substring(0, 2), 1, 12, "Month must be between 1 and 12."); + char sep1 = input.charAt(2); + validateSeparator(sep1); + + int day = parsePart(input.substring(3, 5), 1, 31, "Day must be between 1 and 31."); + char sep2 = input.charAt(5); + validateSeparator(sep2); + + int year = parsePart(input.substring(6, 10), 46, 8499, "Year must be between 46 and 8499."); + + try { + Objects.requireNonNull(LocalDate.of(year, month, day)); + } catch (DateTimeException e) { + throw new IllegalArgumentException("Invalid date.", e); + } + if (month <= 2) { + year -= 1; + month += 12; + } + + int century = year / 100; + int yearOfCentury = year % 100; + int t = (int) (2.6 * month - 5.39); + int u = century / 4; + int v = yearOfCentury / 4; + int f = (int) Math.round((day + yearOfCentury + t + u + v - 2 * century) % 7.0); + + int correctedDay = (f + 7) % 7; + + return "The date " + input + " falls on a " + DAYS[correctedDay] + "."; + } + + /** + * Parses a part of the date string and validates its range. + * + * @param part the substring to parse + * @param min the minimum valid value + * @param max the maximum valid value + * @param error the error message to throw if validation fails + * @return the parsed integer value + * @throws IllegalArgumentException if the part is not a valid number or is out of range + */ + private static int parsePart(String part, int min, int max, String error) { + try { + int value = Integer.parseInt(part); + if (value < min || value > max) { + throw new IllegalArgumentException(error); + } + return value; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid numeric part: " + part, e); + } + } + + /** + * Validates the separator character in the date string. + * + * @param sep the separator character + * @throws IllegalArgumentException if the separator is not '-' or '/' + */ + private static void validateSeparator(char sep) { + if (sep != '-' && sep != '/') { + throw new IllegalArgumentException("Date separator must be '-' or '/'."); + } + } +} diff --git a/src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java b/src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java new file mode 100644 index 000000000000..6467a438577b --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/MatrixMultiplication.java @@ -0,0 +1,69 @@ +package com.thealgorithms.matrix; + +/** + * This class provides a method to perform matrix multiplication. + * + *

Matrix multiplication takes two 2D arrays (matrices) as input and + * produces their product, following the mathematical definition of + * matrix multiplication. + * + *

For more details: + * https://www.geeksforgeeks.org/java/java-program-to-multiply-two-matrices-of-any-size/ + * https://en.wikipedia.org/wiki/Matrix_multiplication + * + *

Time Complexity: O(n^3) โ€“ where n is the dimension of the matrices + * (assuming square matrices for simplicity). + * + *

Space Complexity: O(n^2) โ€“ for storing the result matrix. + * + * + * @author Nishitha Wihala Pitigala + * + */ + +public final class MatrixMultiplication { + private MatrixMultiplication() { + } + + /** + * Multiplies two matrices. + * + * @param matrixA the first matrix rowsA x colsA + * @param matrixB the second matrix rowsB x colsB + * @return the product of the two matrices rowsA x colsB + * @throws IllegalArgumentException if the matrices cannot be multiplied + */ + public static double[][] multiply(double[][] matrixA, double[][] matrixB) { + // Check the input matrices are not null + if (matrixA == null || matrixB == null) { + throw new IllegalArgumentException("Input matrices cannot be null"); + } + + // Check for empty matrices + if (matrixA.length == 0 || matrixB.length == 0 || matrixA[0].length == 0 || matrixB[0].length == 0) { + throw new IllegalArgumentException("Input matrices must not be empty"); + } + + // Validate the matrix dimensions + if (matrixA[0].length != matrixB.length) { + throw new IllegalArgumentException("Matrices cannot be multiplied: incompatible dimensions."); + } + + int rowsA = matrixA.length; + int colsA = matrixA[0].length; + int colsB = matrixB[0].length; + + // Initialize the result matrix with zeros + double[][] result = new double[rowsA][colsB]; + + // Perform matrix multiplication + for (int i = 0; i < rowsA; i++) { + for (int j = 0; j < colsB; j++) { + for (int k = 0; k < colsA; k++) { + result[i][j] += matrixA[i][k] * matrixB[k][j]; + } + } + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java b/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java index c710c60a2d2a..1ec977af07c6 100644 --- a/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java +++ b/src/main/java/com/thealgorithms/matrix/MedianOfMatrix.java @@ -14,19 +14,19 @@ private MedianOfMatrix() { } public static int median(Iterable> matrix) { - // Flatten the matrix into a 1D list - List linear = new ArrayList<>(); + List flattened = new ArrayList<>(); + for (List row : matrix) { - linear.addAll(row); + if (row != null) { + flattened.addAll(row); + } } - // Sort the 1D list - Collections.sort(linear); - - // Calculate the middle index - int mid = (0 + linear.size() - 1) / 2; + if (flattened.isEmpty()) { + throw new IllegalArgumentException("Matrix must contain at least one element."); + } - // Return the median - return linear.get(mid); + Collections.sort(flattened); + return flattened.get((flattened.size() - 1) / 2); } } diff --git a/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java b/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java index 2e735222b7a6..4ae5970a9574 100644 --- a/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java +++ b/src/main/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrder.java @@ -3,52 +3,67 @@ import java.util.ArrayList; import java.util.List; +/** + * Utility class to print a matrix in spiral order. + *

+ * Given a 2D array (matrix), this class provides a method to return the + * elements + * of the matrix in spiral order, starting from the top-left corner and moving + * clockwise. + *

+ * + * @author Sadiul Hakim (https://github.com/sadiul-hakim) + */ public class PrintAMatrixInSpiralOrder { + /** - * Search a key in row and column wise sorted matrix + * Returns the elements of the given matrix in spiral order. + * + * @param matrix the 2D array to traverse in spiral order + * @param row the number of rows in the matrix + * @param col the number of columns in the matrix + * @return a list containing the elements of the matrix in spiral order * - * @param matrix matrix to be searched - * @param row number of rows matrix has - * @param col number of columns matrix has - * @author Sadiul Hakim : https://github.com/sadiul-hakim + *

+ * Example: + * + *

+     * int[][] matrix = {
+     *   {1, 2, 3},
+     *   {4, 5, 6},
+     *   {7, 8, 9}
+     * };
+     * print(matrix, 3, 3) returns [1, 2, 3, 6, 9, 8, 7, 4, 5]
+     *         
+ *

*/ - public List print(int[][] matrix, int row, int col) { - // r traverses matrix row wise from first int r = 0; // c traverses matrix column wise from first int c = 0; int i; - List result = new ArrayList<>(); - while (r < row && c < col) { // print first row of matrix for (i = c; i < col; i++) { result.add(matrix[r][i]); } - // increase r by one because first row printed r++; - // print last column for (i = r; i < row; i++) { result.add(matrix[i][col - 1]); } - // decrease col by one because last column has been printed col--; - // print rows from last except printed elements if (r < row) { for (i = col - 1; i >= c; i--) { result.add(matrix[row - 1][i]); } - row--; } - // print columns from first except printed elements if (c < col) { for (i = row - 1; i >= r; i--) { diff --git a/src/main/java/com/thealgorithms/matrix/SolveSystem.java b/src/main/java/com/thealgorithms/matrix/SolveSystem.java new file mode 100644 index 000000000000..9e683bc4dc5c --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/SolveSystem.java @@ -0,0 +1,71 @@ +package com.thealgorithms.matrix; + +/** + * This class implements an algorithm for solving a system of equations of the form Ax=b using gaussian elimination and back substitution. + * + * @link Gaussian Elimination Wiki + * @see InverseOfMatrix finds the full of inverse of a matrice, but is not required to solve a system. + */ +public final class SolveSystem { + private SolveSystem() { + } + + /** + * Problem: Given a matrix A and vector b, solve the linear system Ax = b for the vector x.\ + *

+ * This OVERWRITES the input matrix to save on memory + * + * @param matrix - a square matrix of doubles + * @param constants - an array of constant + * @return solutions + */ + public static double[] solveSystem(double[][] matrix, double[] constants) { + final double tol = 0.00000001; // tolerance for round off + for (int k = 0; k < matrix.length - 1; k++) { + // find the largest value in column (to avoid zero pivots) + double maxVal = Math.abs(matrix[k][k]); + int maxIdx = k; + for (int j = k + 1; j < matrix.length; j++) { + if (Math.abs(matrix[j][k]) > maxVal) { + maxVal = matrix[j][k]; + maxIdx = j; + } + } + if (Math.abs(maxVal) < tol) { + // hope the matrix works out + continue; + } + // swap rows + double[] temp = matrix[k]; + matrix[k] = matrix[maxIdx]; + matrix[maxIdx] = temp; + double tempConst = constants[k]; + constants[k] = constants[maxIdx]; + constants[maxIdx] = tempConst; + for (int i = k + 1; i < matrix.length; i++) { + // compute multipliers and save them in the column + matrix[i][k] /= matrix[k][k]; + for (int j = k + 1; j < matrix.length; j++) { + matrix[i][j] -= matrix[i][k] * matrix[k][j]; + } + constants[i] -= matrix[i][k] * constants[k]; + } + } + // back substitution + double[] x = new double[constants.length]; + System.arraycopy(constants, 0, x, 0, constants.length); + for (int i = matrix.length - 1; i >= 0; i--) { + double sum = 0; + for (int j = i + 1; j < matrix.length; j++) { + sum += matrix[i][j] * x[j]; + } + x[i] = constants[i] - sum; + if (Math.abs(matrix[i][i]) > tol) { + x[i] /= matrix[i][i]; + } else { + throw new IllegalArgumentException("Matrix was found to be singular"); + } + } + return x; + } +} diff --git a/src/main/java/com/thealgorithms/misc/MapReduce.java b/src/main/java/com/thealgorithms/misc/MapReduce.java index c076957344f9..d98b2ee2dd03 100644 --- a/src/main/java/com/thealgorithms/misc/MapReduce.java +++ b/src/main/java/com/thealgorithms/misc/MapReduce.java @@ -7,35 +7,34 @@ import java.util.function.Function; import java.util.stream.Collectors; -/* -* MapReduce is a programming model for processing and generating large data sets with a parallel, -distributed algorithm on a cluster. -* It has two main steps: the Map step, where the data is divided into smaller chunks and processed in parallel, -and the Reduce step, where the results from the Map step are combined to produce the final output. -* Wikipedia link : https://en.wikipedia.org/wiki/MapReduce -*/ - +/** + * MapReduce is a programming model for processing and generating large data sets + * using a parallel, distributed algorithm on a cluster. + * It consists of two main phases: + * - Map: the input data is split into smaller chunks and processed in parallel. + * - Reduce: the results from the Map phase are aggregated to produce the final output. + * + * See also: https://en.wikipedia.org/wiki/MapReduce + */ public final class MapReduce { + private MapReduce() { } - /* - *Counting all the words frequency within a sentence. - */ - public static String mapreduce(String sentence) { - List wordList = Arrays.stream(sentence.split(" ")).toList(); - // Map step - Map wordCounts = wordList.stream().collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting())); - - // Reduce step - StringBuilder result = new StringBuilder(); - wordCounts.forEach((word, count) -> result.append(word).append(": ").append(count).append(",")); + /** + * Counts the frequency of each word in a given sentence using a simple MapReduce-style approach. + * + * @param sentence the input sentence + * @return a string representing word frequencies in the format "word: count,word: count,..." + */ + public static String countWordFrequencies(String sentence) { + // Map phase: split the sentence into words + List words = Arrays.asList(sentence.trim().split("\\s+")); - // Removing the last ',' if it exists - if (!result.isEmpty()) { - result.setLength(result.length() - 1); - } + // Group and count occurrences of each word, maintain insertion order + Map wordCounts = words.stream().collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting())); - return result.toString(); + // Reduce phase: format the result + return wordCounts.entrySet().stream().map(entry -> entry.getKey() + ": " + entry.getValue()).collect(Collectors.joining(",")); } } diff --git a/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java b/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java index 62013ee31183..95f86f63f720 100644 --- a/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java +++ b/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java @@ -4,50 +4,74 @@ import java.util.PriorityQueue; /** - * @author shrutisheoran + * A generic abstract class to compute the median of a dynamically growing stream of numbers. + * + * @param the number type, must extend Number and be Comparable + * + * Usage: + * Extend this class and implement {@code calculateAverage(T a, T b)} to define how averaging is done. */ public abstract class MedianOfRunningArray> { - private PriorityQueue maxHeap; - private PriorityQueue minHeap; + private final PriorityQueue maxHeap; // Lower half (max-heap) + private final PriorityQueue minHeap; // Upper half (min-heap) - // Constructor public MedianOfRunningArray() { - this.maxHeap = new PriorityQueue<>(Collections.reverseOrder()); // Max Heap - this.minHeap = new PriorityQueue<>(); // Min Heap + this.maxHeap = new PriorityQueue<>(Collections.reverseOrder()); + this.minHeap = new PriorityQueue<>(); } - /* - Inserting lower half of array to max Heap - and upper half to min heap + /** + * Inserts a new number into the data structure. + * + * @param element the number to insert */ - public void insert(final T e) { - if (!minHeap.isEmpty() && e.compareTo(minHeap.peek()) < 0) { - maxHeap.offer(e); - if (maxHeap.size() > minHeap.size() + 1) { - minHeap.offer(maxHeap.poll()); - } + public final void insert(final T element) { + if (!minHeap.isEmpty() && element.compareTo(minHeap.peek()) < 0) { + maxHeap.offer(element); + balanceHeapsIfNeeded(); } else { - minHeap.offer(e); - if (minHeap.size() > maxHeap.size() + 1) { - maxHeap.offer(minHeap.poll()); - } + minHeap.offer(element); + balanceHeapsIfNeeded(); } } - /* - Returns median at any given point + /** + * Returns the median of the current elements. + * + * @return the median value + * @throws IllegalArgumentException if no elements have been inserted */ - public T median() { + public final T getMedian() { if (maxHeap.isEmpty() && minHeap.isEmpty()) { - throw new IllegalArgumentException("Enter at least 1 element, Median of empty list is not defined!"); - } else if (maxHeap.size() == minHeap.size()) { - T maxHeapTop = maxHeap.peek(); - T minHeapTop = minHeap.peek(); - return calculateAverage(maxHeapTop, minHeapTop); + throw new IllegalArgumentException("Median is undefined for an empty data set."); } - return maxHeap.size() > minHeap.size() ? maxHeap.peek() : minHeap.peek(); + + if (maxHeap.size() == minHeap.size()) { + return calculateAverage(maxHeap.peek(), minHeap.peek()); + } + + return (maxHeap.size() > minHeap.size()) ? maxHeap.peek() : minHeap.peek(); } - public abstract T calculateAverage(T a, T b); + /** + * Calculates the average between two values. + * Concrete subclasses must define how averaging works (e.g., for Integer, Double, etc.). + * + * @param a first number + * @param b second number + * @return the average of a and b + */ + protected abstract T calculateAverage(T a, T b); + + /** + * Balances the two heaps so that their sizes differ by at most 1. + */ + private void balanceHeapsIfNeeded() { + if (maxHeap.size() > minHeap.size() + 1) { + minHeap.offer(maxHeap.poll()); + } else if (minHeap.size() > maxHeap.size() + 1) { + maxHeap.offer(minHeap.poll()); + } + } } diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java index 51dc099ba32e..c81476eaec32 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java @@ -10,6 +10,7 @@ * See more: * https://www.geeksforgeeks.org/function-to-check-if-a-singly-linked-list-is-palindrome/ */ +@SuppressWarnings("rawtypes") public final class PalindromeSinglyLinkedList { private PalindromeSinglyLinkedList() { } diff --git a/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java b/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java index 6d3caa1814b6..0ae73dd73f09 100644 --- a/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java +++ b/src/main/java/com/thealgorithms/misc/RangeInSortedArray.java @@ -1,25 +1,46 @@ package com.thealgorithms.misc; +/** + * Utility class for operations to find the range of occurrences of a key + * in a sorted (non-decreasing) array, and to count elements less than or equal to a given key. + */ public final class RangeInSortedArray { + private RangeInSortedArray() { } - // Get the 1st and last occurrence index of a number 'key' in a non-decreasing array 'nums' - // Gives [-1, -1] in case element doesn't exist in array + /** + * Finds the first and last occurrence indices of the key in a sorted array. + * + * @param nums sorted array of integers (non-decreasing order) + * @param key the target value to search for + * @return int array of size two where + * - index 0 is the first occurrence of key, + * - index 1 is the last occurrence of key, + * or [-1, -1] if the key does not exist in the array. + */ public static int[] sortedRange(int[] nums, int key) { int[] range = new int[] {-1, -1}; - alteredBinSearchIter(nums, key, 0, nums.length - 1, range, true); - alteredBinSearchIter(nums, key, 0, nums.length - 1, range, false); + alteredBinSearchIter(nums, key, 0, nums.length - 1, range, true); // find left boundary + alteredBinSearchIter(nums, key, 0, nums.length - 1, range, false); // find right boundary return range; } - // Recursive altered binary search which searches for leftmost as well as rightmost occurrence - // of 'key' + /** + * Recursive altered binary search to find either the leftmost or rightmost occurrence of a key. + * + * @param nums the sorted array + * @param key the target to find + * @param left current left bound in search + * @param right current right bound in search + * @param range array to update with boundaries: range[0] for leftmost, range[1] for rightmost + * @param goLeft if true, searches for leftmost occurrence; if false, for rightmost occurrence + */ public static void alteredBinSearch(int[] nums, int key, int left, int right, int[] range, boolean goLeft) { if (left > right) { return; } - int mid = (left + right) >>> 1; + int mid = left + ((right - left) >>> 1); if (nums[mid] > key) { alteredBinSearch(nums, key, left, mid - 1, range, goLeft); } else if (nums[mid] < key) { @@ -41,11 +62,19 @@ public static void alteredBinSearch(int[] nums, int key, int left, int right, in } } - // Iterative altered binary search which searches for leftmost as well as rightmost occurrence - // of 'key' + /** + * Iterative altered binary search to find either the leftmost or rightmost occurrence of a key. + * + * @param nums the sorted array + * @param key the target to find + * @param left initial left bound + * @param right initial right bound + * @param range array to update with boundaries: range[0] for leftmost, range[1] for rightmost + * @param goLeft if true, searches for leftmost occurrence; if false, for rightmost occurrence + */ public static void alteredBinSearchIter(int[] nums, int key, int left, int right, int[] range, boolean goLeft) { while (left <= right) { - final int mid = (left + right) >>> 1; + int mid = left + ((right - left) >>> 1); if (nums[mid] > key) { right = mid - 1; } else if (nums[mid] < key) { @@ -55,33 +84,48 @@ public static void alteredBinSearchIter(int[] nums, int key, int left, int right if (mid == 0 || nums[mid - 1] != key) { range[0] = mid; return; - } else { - right = mid - 1; } + right = mid - 1; } else { if (mid == nums.length - 1 || nums[mid + 1] != key) { range[1] = mid; return; - } else { - left = mid + 1; } + left = mid + 1; } } } } + /** + * Counts the number of elements strictly less than the given key. + * + * @param nums sorted array + * @param key the key to compare + * @return the count of elements less than the key + */ public static int getCountLessThan(int[] nums, int key) { return getLessThan(nums, key, 0, nums.length - 1); } + /** + * Helper method using binary search to count elements less than or equal to the key. + * + * @param nums sorted array + * @param key the key to compare + * @param left current left bound + * @param right current right bound + * @return count of elements less than or equal to the key + */ public static int getLessThan(int[] nums, int key, int left, int right) { int count = 0; while (left <= right) { - final int mid = (left + right) >>> 1; + int mid = left + ((right - left) >>> 1); if (nums[mid] > key) { right = mid - 1; - } else if (nums[mid] <= key) { - count = mid + 1; // At least mid+1 elements exist which are <= key + } else { + // nums[mid] <= key + count = mid + 1; // all elements from 0 to mid inclusive are <= key left = mid + 1; } } diff --git a/src/main/java/com/thealgorithms/misc/ShuffleArray.java b/src/main/java/com/thealgorithms/misc/ShuffleArray.java index 65d38adfc37a..e07c8df771d3 100644 --- a/src/main/java/com/thealgorithms/misc/ShuffleArray.java +++ b/src/main/java/com/thealgorithms/misc/ShuffleArray.java @@ -17,19 +17,37 @@ * @author Rashi Dashore (https://github.com/rashi07dashore) */ public final class ShuffleArray { - // Prevent instantiation + private ShuffleArray() { } /** - * This method shuffles an array using the Fisher-Yates algorithm. + * Shuffles the provided array in-place using the Fisherโ€“Yates algorithm. * - * @param arr is the input array to be shuffled + * @param arr the array to shuffle; must not be {@code null} + * @throws IllegalArgumentException if the input array is {@code null} */ public static void shuffle(int[] arr) { + if (arr == null) { + throw new IllegalArgumentException("Input array must not be null"); + } + Random random = new Random(); for (int i = arr.length - 1; i > 0; i--) { int j = random.nextInt(i + 1); + swap(arr, i, j); + } + } + + /** + * Swaps two elements in an array. + * + * @param arr the array + * @param i index of first element + * @param j index of second element + */ + private static void swap(int[] arr, int i, int j) { + if (i != j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; diff --git a/src/main/java/com/thealgorithms/misc/Sparsity.java b/src/main/java/com/thealgorithms/misc/Sparsity.java index 08e50a121da4..4a919e0e55c6 100644 --- a/src/main/java/com/thealgorithms/misc/Sparsity.java +++ b/src/main/java/com/thealgorithms/misc/Sparsity.java @@ -1,40 +1,46 @@ package com.thealgorithms.misc; -/* - *A matrix is sparse if many of its coefficients are zero (In general if 2/3rd of matrix elements - *are 0, it is considered as sparse). The interest in sparsity arises because its exploitation can - *lead to enormous computational savings and because many large matrix problems that occur in - *practice are sparse. +/** + * Utility class for calculating the sparsity of a matrix. + * A matrix is considered sparse if a large proportion of its elements are zero. + * Typically, if more than 2/3 of the elements are zero, the matrix is considered sparse. * - * @author Ojasva Jain + * Sparsity is defined as: + * sparsity = (number of zero elements) / (total number of elements) + * + * This can lead to significant computational optimizations. */ +public final class Sparsity { -final class Sparsity { private Sparsity() { } - /* - * @param mat the input matrix - * @return Sparsity of matrix - * - * where sparsity = number of zeroes/total elements in matrix + /** + * Calculates the sparsity of a given 2D matrix. * + * @param matrix the input matrix + * @return the sparsity value between 0 and 1 + * @throws IllegalArgumentException if the matrix is null, empty, or contains empty rows */ - static double sparsity(double[][] mat) { - if (mat == null || mat.length == 0) { + public static double sparsity(double[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { throw new IllegalArgumentException("Matrix cannot be null or empty"); } - int zero = 0; - // Traversing the matrix to count number of zeroes - for (int i = 0; i < mat.length; i++) { - for (int j = 0; j < mat[i].length; j++) { - if (mat[i][j] == 0) { - zero++; + int zeroCount = 0; + int totalElements = 0; + + // Count the number of zero elements and total elements + for (double[] row : matrix) { + for (double value : row) { + if (value == 0.0) { + zeroCount++; } + totalElements++; } } - // return sparsity - return ((double) zero / (mat.length * mat[0].length)); + + // Return sparsity as a double + return (double) zeroCount / totalElements; } } diff --git a/src/main/java/com/thealgorithms/others/CRCAlgorithm.java b/src/main/java/com/thealgorithms/others/CRCAlgorithm.java index 284a290a5af8..00ddc86be820 100644 --- a/src/main/java/com/thealgorithms/others/CRCAlgorithm.java +++ b/src/main/java/com/thealgorithms/others/CRCAlgorithm.java @@ -7,6 +7,7 @@ /** * @author dimgrichr */ +@SuppressWarnings("unchecked") public class CRCAlgorithm { private int correctMess; diff --git a/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java b/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java deleted file mode 100644 index 0ae1e451bc6a..000000000000 --- a/src/main/java/com/thealgorithms/others/HappyNumbersSeq.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.thealgorithms.others; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Scanner; -import java.util.Set; - -public final class HappyNumbersSeq { - private HappyNumbersSeq() { - } - - private static final Set CYCLE_NUMS = new HashSet<>(Arrays.asList(4, 16, 20, 37, 58, 145)); - - public static void main(String[] args) { - Scanner in = new Scanner(System.in); - System.out.print("Enter number: "); - int n = in.nextInt(); - while (n != 1 && !isSad(n)) { - System.out.print(n + " "); - n = sumSquares(n); - } - String res = n == 1 ? "1 Happy number" : "Sad number"; - System.out.println(res); - in.close(); - } - - private static int sumSquares(int n) { - int s = 0; - for (; n > 0; n /= 10) { - int r = n % 10; - s += r * r; - } - return s; - } - - private static boolean isSad(int n) { - return CYCLE_NUMS.contains(n); - } -} diff --git a/src/main/java/com/thealgorithms/others/Huffman.java b/src/main/java/com/thealgorithms/others/Huffman.java index 4fdee5d5e70e..22e75da502b5 100644 --- a/src/main/java/com/thealgorithms/others/Huffman.java +++ b/src/main/java/com/thealgorithms/others/Huffman.java @@ -1,125 +1,211 @@ package com.thealgorithms.others; import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; import java.util.PriorityQueue; -import java.util.Scanner; -// node class is the basic structure -// of each node present in the Huffman - tree. +/** + * Node class representing a node in the Huffman tree. + * Each node contains a character, its frequency, and references to left and + * right children. + */ class HuffmanNode { - int data; char c; - HuffmanNode left; HuffmanNode right; -} -// comparator class helps to compare the node -// on the basis of one of its attribute. -// Here we will be compared -// on the basis of data values of the nodes. -class MyComparator implements Comparator { + /** + * Constructor for HuffmanNode. + * + * @param c the character stored in this node + * @param data the frequency of the character + */ + HuffmanNode(char c, int data) { + this.c = c; + this.data = data; + this.left = null; + this.right = null; + } + + /** + * Default constructor for HuffmanNode. + */ + HuffmanNode() { + this.left = null; + this.right = null; + } +} +/** + * Comparator class for comparing HuffmanNode objects based on their frequency + * data. + * Used to maintain min-heap property in the priority queue. + */ +class HuffmanComparator implements Comparator { + @Override public int compare(HuffmanNode x, HuffmanNode y) { - return x.data - y.data; + return Integer.compare(x.data, y.data); } } +/** + * Implementation of Huffman Coding algorithm for data compression. + * Huffman Coding is a greedy algorithm that assigns variable-length codes to + * characters + * based on their frequency of occurrence. Characters with higher frequency get + * shorter codes. + * + *

+ * Time Complexity: O(n log n) where n is the number of unique characters + * Space Complexity: O(n) + * + * @see Huffman + * Coding + */ public final class Huffman { private Huffman() { } - // recursive function to print the - // huffman-code through the tree traversal. - // Here s is the huffman - code generated. - public static void printCode(HuffmanNode root, String s) { - // base case; if the left and right are null - // then its a leaf node and we print - // the code s generated by traversing the tree. - if (root.left == null && root.right == null && Character.isLetter(root.c)) { - // c is the character in the node - System.out.println(root.c + ":" + s); - - return; + /** + * Builds a Huffman tree from the given character array and their frequencies. + * + * @param charArray array of characters + * @param charFreq array of frequencies corresponding to the characters + * @return root node of the Huffman tree + * @throws IllegalArgumentException if arrays are null, empty, or have different + * lengths + */ + public static HuffmanNode buildHuffmanTree(char[] charArray, int[] charFreq) { + if (charArray == null || charFreq == null) { + throw new IllegalArgumentException("Character array and frequency array cannot be null"); + } + if (charArray.length == 0 || charFreq.length == 0) { + throw new IllegalArgumentException("Character array and frequency array cannot be empty"); + } + if (charArray.length != charFreq.length) { + throw new IllegalArgumentException("Character array and frequency array must have the same length"); } - // if we go to left then add "0" to the code. - // if we go to the right add"1" to the code. - // recursive calls for left and - // right sub-tree of the generated tree. - printCode(root.left, s + "0"); - printCode(root.right, s + "1"); - } + int n = charArray.length; + PriorityQueue priorityQueue = new PriorityQueue<>(n, new HuffmanComparator()); - // main function - public static void main(String[] args) { - Scanner s = new Scanner(System.in); + // Create leaf nodes and add to priority queue + for (int i = 0; i < n; i++) { + if (charFreq[i] < 0) { + throw new IllegalArgumentException("Frequencies must be non-negative"); + } + HuffmanNode node = new HuffmanNode(charArray[i], charFreq[i]); + priorityQueue.add(node); + } - // number of characters. - int n = 6; - char[] charArray = {'a', 'b', 'c', 'd', 'e', 'f'}; - int[] charfreq = {5, 9, 12, 13, 16, 45}; + // Build the Huffman tree + while (priorityQueue.size() > 1) { + HuffmanNode left = priorityQueue.poll(); + HuffmanNode right = priorityQueue.poll(); - // creating a priority queue q. - // makes a min-priority queue(min-heap). - PriorityQueue q = new PriorityQueue(n, new MyComparator()); + HuffmanNode parent = new HuffmanNode(); + parent.data = left.data + right.data; + parent.c = '-'; + parent.left = left; + parent.right = right; - for (int i = 0; i < n; i++) { - // creating a Huffman node object - // and add it to the priority queue. - HuffmanNode hn = new HuffmanNode(); + priorityQueue.add(parent); + } - hn.c = charArray[i]; - hn.data = charfreq[i]; + return priorityQueue.poll(); + } - hn.left = null; - hn.right = null; + /** + * Generates Huffman codes for all characters in the tree. + * + * @param root root node of the Huffman tree + * @return map of characters to their Huffman codes + */ + public static Map generateCodes(HuffmanNode root) { + Map huffmanCodes = new HashMap<>(); + if (root != null) { + generateCodesHelper(root, "", huffmanCodes); + } + return huffmanCodes; + } - // add functions adds - // the huffman node to the queue. - q.add(hn); + /** + * Helper method to recursively generate Huffman codes by traversing the tree. + * + * @param node current node in the tree + * @param code current code being built + * @param huffmanCodes map to store character-to-code mappings + */ + private static void generateCodesHelper(HuffmanNode node, String code, Map huffmanCodes) { + if (node == null) { + return; } - // create a root node - HuffmanNode root = null; + // If it's a leaf node, store the code + if (node.left == null && node.right == null && Character.isLetter(node.c)) { + huffmanCodes.put(node.c, code.isEmpty() ? "0" : code); + return; + } - // Here we will extract the two minimum value - // from the heap each time until - // its size reduces to 1, extract until - // all the nodes are extracted. - while (q.size() > 1) { - // first min extract. - HuffmanNode x = q.peek(); - q.poll(); + // Traverse left with '0' and right with '1' + if (node.left != null) { + generateCodesHelper(node.left, code + "0", huffmanCodes); + } + if (node.right != null) { + generateCodesHelper(node.right, code + "1", huffmanCodes); + } + } - // second min extarct. - HuffmanNode y = q.peek(); - q.poll(); + /** + * Prints Huffman codes for all characters in the tree. + * This method is kept for backward compatibility and demonstration purposes. + * + * @param root root node of the Huffman tree + * @param code current code being built (initially empty string) + */ + public static void printCode(HuffmanNode root, String code) { + if (root == null) { + return; + } - // new node f which is equal - HuffmanNode f = new HuffmanNode(); + // If it's a leaf node, print the code + if (root.left == null && root.right == null && Character.isLetter(root.c)) { + System.out.println(root.c + ":" + code); + return; + } - // to the sum of the frequency of the two nodes - // assigning values to the f node. - f.data = x.data + y.data; - f.c = '-'; + // Traverse left with '0' and right with '1' + if (root.left != null) { + printCode(root.left, code + "0"); + } + if (root.right != null) { + printCode(root.right, code + "1"); + } + } - // first extracted node as left child. - f.left = x; + /** + * Demonstrates the Huffman coding algorithm with sample data. + * + * @param args command line arguments (not used) + */ + public static void main(String[] args) { + // Sample characters and their frequencies + char[] charArray = {'a', 'b', 'c', 'd', 'e', 'f'}; + int[] charFreq = {5, 9, 12, 13, 16, 45}; - // second extracted node as the right child. - f.right = y; + System.out.println("Characters: a, b, c, d, e, f"); + System.out.println("Frequencies: 5, 9, 12, 13, 16, 45"); + System.out.println("\nHuffman Codes:"); - // marking the f node as the root node. - root = f; + // Build Huffman tree + HuffmanNode root = buildHuffmanTree(charArray, charFreq); - // add this node to the priority-queue. - q.add(f); + // Generate and print Huffman codes + Map codes = generateCodes(root); + for (Map.Entry entry : codes.entrySet()) { + System.out.println(entry.getKey() + ": " + entry.getValue()); } - - // print the codes by traversing the tree - printCode(root, ""); - s.close(); } } diff --git a/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java b/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java index 15093549871b..06a2539ee8b7 100644 --- a/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java +++ b/src/main/java/com/thealgorithms/others/InsertDeleteInArray.java @@ -1,51 +1,172 @@ package com.thealgorithms.others; +import java.util.Arrays; import java.util.Scanner; +/** + * Utility class for performing insert and delete operations on arrays. + *

+ * This class demonstrates how to insert an element at a specific position and + * delete an element from a specific position in an integer array. Since arrays + * in Java have fixed size, insertion creates a new array with increased size, + * and deletion shifts elements to fill the gap. + *

+ * + *

+ * Time Complexity: + *

+ *
    + *
  • Insert: O(n) - requires copying elements to new array
  • + *
  • Delete: O(n) - requires shifting elements
  • + *
+ * + *

+ * Space Complexity: + *

+ *
    + *
  • Insert: O(n) - new array of size n+1
  • + *
  • Delete: O(1) - in-place modification (excluding result array)
  • + *
+ * + * @author TheAlgorithms community + * @see Array + * Data Structure + */ public final class InsertDeleteInArray { private InsertDeleteInArray() { } + /** + * Inserts an element at the specified position in the array. + *

+ * Creates a new array with size = original array size + 1. + * Elements at positions <= insertPos retain their positions, + * while elements at positions > insertPos are shifted right by one position. + *

+ * + * @param array the original array + * @param element the element to be inserted + * @param position the index at which the element should be inserted (0-based) + * @return a new array with the element inserted at the specified position + * @throws IllegalArgumentException if position is negative or greater than + * array length + * @throws IllegalArgumentException if array is null + */ + public static int[] insertElement(int[] array, int element, int position) { + if (array == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + if (position < 0 || position > array.length) { + throw new IllegalArgumentException("Position must be between 0 and " + array.length); + } + + int[] newArray = new int[array.length + 1]; + + // Copy elements before insertion position + System.arraycopy(array, 0, newArray, 0, position); + + // Insert the new element + newArray[position] = element; + + // Copy remaining elements after insertion position + System.arraycopy(array, position, newArray, position + 1, array.length - position); + + return newArray; + } + + /** + * Deletes an element at the specified position from the array. + *

+ * Creates a new array with size = original array size - 1. + * Elements after the deletion position are shifted left by one position. + *

+ * + * @param array the original array + * @param position the index of the element to be deleted (0-based) + * @return a new array with the element at the specified position removed + * @throws IllegalArgumentException if position is negative or greater than or + * equal to array length + * @throws IllegalArgumentException if array is null or empty + */ + public static int[] deleteElement(int[] array, int position) { + if (array == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + if (array.length == 0) { + throw new IllegalArgumentException("Array is empty"); + } + if (position < 0 || position >= array.length) { + throw new IllegalArgumentException("Position must be between 0 and " + (array.length - 1)); + } + + int[] newArray = new int[array.length - 1]; + + // Copy elements before deletion position + System.arraycopy(array, 0, newArray, 0, position); + + // Copy elements after deletion position + System.arraycopy(array, position + 1, newArray, position, array.length - position - 1); + + return newArray; + } + + /** + * Main method demonstrating insert and delete operations on an array. + *

+ * This method interactively: + *

    + *
  1. Takes array size and elements as input
  2. + *
  3. Inserts a new element at a specified position
  4. + *
  5. Deletes an element from a specified position
  6. + *
  7. Displays the array after each operation
  8. + *
+ *

+ * + * @param args command line arguments (not used) + */ public static void main(String[] args) { - try (Scanner s = new Scanner(System.in)) { - System.out.println("Enter the size of the array"); - int size = s.nextInt(); - int[] a = new int[size]; - int i; - - // To enter the initial elements - for (i = 0; i < size; i++) { - System.out.println("Enter the element"); - a[i] = s.nextInt(); - } + try (Scanner scanner = new Scanner(System.in)) { + // Input: array size and elements + System.out.println("Enter the size of the array:"); + int size = scanner.nextInt(); - // To insert a new element(we are creating a new array) - System.out.println("Enter the index at which the element should be inserted"); - int insertPos = s.nextInt(); - System.out.println("Enter the element to be inserted"); - int ins = s.nextInt(); - int size2 = size + 1; - int[] b = new int[size2]; - for (i = 0; i < size2; i++) { - if (i <= insertPos) { - b[i] = a[i]; - } else { - b[i] = a[i - 1]; - } + if (size <= 0) { + System.out.println("Array size must be positive"); + return; } - b[insertPos] = ins; - for (i = 0; i < size2; i++) { - System.out.println(b[i]); + + int[] array = new int[size]; + + System.out.println("Enter " + size + " elements:"); + for (int i = 0; i < size; i++) { + array[i] = scanner.nextInt(); } - // To delete an element given the index - System.out.println("Enter the index at which element is to be deleted"); - int delPos = s.nextInt(); - for (i = delPos; i < size2 - 1; i++) { - b[i] = b[i + 1]; + System.out.println("Original array: " + Arrays.toString(array)); + + // Insert operation + System.out.println("\nEnter the index at which the element should be inserted (0-" + size + "):"); + int insertPos = scanner.nextInt(); + System.out.println("Enter the element to be inserted:"); + int elementToInsert = scanner.nextInt(); + + try { + array = insertElement(array, elementToInsert, insertPos); + System.out.println("Array after insertion: " + Arrays.toString(array)); + } catch (IllegalArgumentException e) { + System.out.println("Error during insertion: " + e.getMessage()); + return; } - for (i = 0; i < size2 - 1; i++) { - System.out.println(b[i]); + + // Delete operation + System.out.println("\nEnter the index at which element is to be deleted (0-" + (array.length - 1) + "):"); + int deletePos = scanner.nextInt(); + + try { + array = deleteElement(array, deletePos); + System.out.println("Array after deletion: " + Arrays.toString(array)); + } catch (IllegalArgumentException e) { + System.out.println("Error during deletion: " + e.getMessage()); } } } diff --git a/src/main/java/com/thealgorithms/others/IterativeFloodFill.java b/src/main/java/com/thealgorithms/others/IterativeFloodFill.java new file mode 100644 index 000000000000..3ef90369bd52 --- /dev/null +++ b/src/main/java/com/thealgorithms/others/IterativeFloodFill.java @@ -0,0 +1,102 @@ +package com.thealgorithms.others; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * Implementation of the Flood Fill algorithm using an iterative BFS (Breadth-First Search) approach. + * + *

The Flood Fill algorithm is used to fill connected areas in an image with a new color, starting from a specified point. + * This implementation uses an iterative BFS approach with a queue + * instead of recursion to avoid stack overflow issues with large images.

+ * + *

Implementation Features:

+ *
    + *
  • Supports 8-connected filling (horizontal, vertical, and diagonal directions)
  • + *
  • Uses BFS traversal through {@link java.util.Queue}
  • + *
  • Includes nested {@code Point} class to represent pixel coordinates
  • + *
  • Iterative approach avoids stack overflow for large images
  • + *
+ * + *

Time Complexity: O(M ร— N) where M and N are the dimensions of the image

+ *

Space Complexity: O(M ร— N) in the worst case the queue stores every pixel

+ * + * @see Flood Fill Algorithm - GeeksforGeeks + * @see Flood Fill Algorithm - Wikipedia + */ +public final class IterativeFloodFill { + private IterativeFloodFill() { + } + + /** + * Iteratively fill the 2D image with new color + * + * @param image The image to be filled + * @param x The x co-ordinate at which color is to be filled + * @param y The y co-ordinate at which color is to be filled + * @param newColor The new color which to be filled in the image + * @param oldColor The old color which is to be replaced in the image + * @see FloodFill BFS + */ + public static void floodFill(final int[][] image, final int x, final int y, final int newColor, final int oldColor) { + if (image.length == 0 || image[0].length == 0 || newColor == oldColor || shouldSkipPixel(image, x, y, oldColor)) { + return; + } + + Queue queue = new LinkedList<>(); + queue.add(new Point(x, y)); + + int[] dx = {0, 0, -1, 1, 1, -1, 1, -1}; + int[] dy = {-1, 1, 0, 0, -1, 1, 1, -1}; + + while (!queue.isEmpty()) { + Point currPoint = queue.poll(); + + if (shouldSkipPixel(image, currPoint.x, currPoint.y, oldColor)) { + continue; + } + + image[currPoint.x][currPoint.y] = newColor; + + for (int i = 0; i < 8; i++) { + int curX = currPoint.x + dx[i]; + int curY = currPoint.y + dy[i]; + + if (!shouldSkipPixel(image, curX, curY, oldColor)) { + queue.add(new Point(curX, curY)); + } + } + } + } + + /** + * Represents a point in 2D space with integer coordinates. + */ + private static class Point { + final int x; + final int y; + + Point(final int x, final int y) { + this.x = x; + this.y = y; + } + } + + /** + * Checks if a pixel should be skipped during flood fill operation. + * + * @param image The image to get boundaries + * @param x The x co-ordinate of pixel to check + * @param y The y co-ordinate of pixel to check + * @param oldColor The old color which is to be replaced in the image + * @return {@code true} if pixel should be skipped, else {@code false} + */ + private static boolean shouldSkipPixel(final int[][] image, final int x, final int y, final int oldColor) { + + if (x < 0 || x >= image.length || y < 0 || y >= image[0].length || image[x][y] != oldColor) { + return true; + } + + return false; + } +} diff --git a/src/main/java/com/thealgorithms/others/Krishnamurthy.java b/src/main/java/com/thealgorithms/others/Krishnamurthy.java deleted file mode 100644 index 8e5ba7c6f1c7..000000000000 --- a/src/main/java/com/thealgorithms/others/Krishnamurthy.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.thealgorithms.others; - -import java.util.Scanner; - -final class Krishnamurthy { - private Krishnamurthy() { - } - - static int fact(int n) { - int i; - int p = 1; - for (i = n; i >= 1; i--) { - p = p * i; - } - return p; - } - - public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - int a; - int b; - int s = 0; - System.out.print("Enter the number : "); - a = sc.nextInt(); - int n = a; - while (a > 0) { - b = a % 10; - s = s + fact(b); - a = a / 10; - } - if (s == n) { - System.out.print(n + " is a krishnamurthy number"); - } else { - System.out.print(n + " is not a krishnamurthy number"); - } - sc.close(); - } -} diff --git a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java b/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java index 76d1ed4aba1d..a3ca8d6f6db8 100644 --- a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java +++ b/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java @@ -4,8 +4,26 @@ import java.util.List; /** - * @brief Class for finding the lowest base in which a given integer is a palindrome. - cf. https://oeis.org/A016026 + * Utility class for finding the lowest base in which a given integer is a + * palindrome. + *

+ * A number is a palindrome in a given base if its representation in that base + * reads the same + * forwards and backwards. For example, 15 in base 2 is 1111, which is + * palindromic. + * This class provides methods to check palindromic properties and find the + * smallest base + * where a number becomes palindromic. + *

+ *

+ * Example: The number 15 in base 2 is represented as [1,1,1,1], which is + * palindromic. + * The number 10 in base 3 is represented as [1,0,1], which is also palindromic. + *

+ * + * @see
OEIS A016026 - Smallest base in which + * n is palindromic + * @author TheAlgorithms Contributors */ public final class LowestBasePalindrome { private LowestBasePalindrome() { @@ -37,12 +55,18 @@ private static void checkNumber(int number) { /** * Computes the digits of a given number in a specified base. + *

+ * The digits are returned in reverse order (least significant digit first). + * For example, the number 13 in base 2 produces [1,0,1,1] representing 1101 in + * binary. + *

* - * @param number the number to be converted - * @param base the base to be used for the conversion - * @return a list of digits representing the number in the given base, with the most - * significant digit at the end of the list - * @throws IllegalArgumentException if the number is negative or the base is less than 2 + * @param number the number to be converted (must be non-negative) + * @param base the base to be used for the conversion (must be greater than 1) + * @return a list of digits representing the number in the given base, with the + * least significant digit at the beginning of the list + * @throws IllegalArgumentException if the number is negative or the base is + * less than 2 */ public static List computeDigitsInBase(int number, int base) { checkNumber(number); @@ -58,6 +82,10 @@ public static List computeDigitsInBase(int number, int base) { /** * Checks if a list of integers is palindromic. + *

+ * A list is palindromic if it reads the same forwards and backwards. + * For example, [1,2,1] is palindromic, but [1,2,3] is not. + *

* * @param list the list of integers to be checked * @return {@code true} if the list is a palindrome, {@code false} otherwise @@ -73,12 +101,29 @@ public static boolean isPalindromic(List list) { } /** - * Checks if the representation of a given number in a specified base is palindromic. + * Checks if the representation of a given number in a specified base is + * palindromic. + *

+ * This method first validates the input, then applies optimization: if the + * number + * ends with 0 in the given base (i.e., divisible by the base), it cannot be + * palindromic + * as palindromes cannot start with 0. + *

+ *

+ * Examples: + * - 101 in base 10 is palindromic (101) + * - 15 in base 2 is palindromic (1111) + * - 10 in base 3 is palindromic (101) + *

* - * @param number the number to be checked - * @param base the base in which the number will be represented - * @return {@code true} if the number is palindromic in the specified base, {@code false} otherwise - * @throws IllegalArgumentException if the number is negative or the base is less than 2 + * @param number the number to be checked (must be non-negative) + * @param base the base in which the number will be represented (must be + * greater than 1) + * @return {@code true} if the number is palindromic in the specified base, + * {@code false} otherwise + * @throws IllegalArgumentException if the number is negative or the base is + * less than 2 */ public static boolean isPalindromicInBase(int number, int base) { checkNumber(number); @@ -89,7 +134,8 @@ public static boolean isPalindromicInBase(int number, int base) { } if (number % base == 0) { - // If the last digit of the number in the given base is 0, it can't be palindromic + // If the last digit of the number in the given base is 0, it can't be + // palindromic return false; } @@ -97,10 +143,29 @@ public static boolean isPalindromicInBase(int number, int base) { } /** - * Finds the smallest base in which the representation of a given number is palindromic. + * Finds the smallest base in which the representation of a given number is + * palindromic. + *

+ * This method iteratively checks bases starting from 2 until it finds one where + * the number is palindromic. For any number n โ‰ฅ 2, the number is always + * palindromic + * in base n-1 (represented as [1, 1]), so this algorithm is guaranteed to + * terminate. + *

+ *

+ * Time Complexity: O(n * log(n)) in the worst case, where we check each base + * and + * convert the number to that base. + *

+ *

+ * Examples: + * - lowestBasePalindrome(15) returns 2 (15 in base 2 is 1111) + * - lowestBasePalindrome(10) returns 3 (10 in base 3 is 101) + * - lowestBasePalindrome(11) returns 10 (11 in base 10 is 11) + *

* - * @param number the number to be checked - * @return the smallest base in which the number is a palindrome + * @param number the number to be checked (must be non-negative) + * @return the smallest base in which the number is a palindrome (base โ‰ฅ 2) * @throws IllegalArgumentException if the number is negative */ public static int lowestBasePalindrome(int number) { diff --git a/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java b/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java index c05f1af4e327..dec813dd3213 100644 --- a/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java +++ b/src/main/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthK.java @@ -1,14 +1,26 @@ package com.thealgorithms.others; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.Map; /** - * References: https://en.wikipedia.org/wiki/Streaming_algorithm + * Algorithm to find the maximum sum of a subarray of size K with all distinct + * elements. * - * This model involves computing the maximum sum of subarrays of a fixed size \( K \) from a stream of integers. - * As the stream progresses, elements from the end of the window are removed, and new elements from the stream are added. + * This implementation uses a sliding window approach with a hash map to + * efficiently + * track element frequencies within the current window. The algorithm maintains + * a window + * of size K and slides it across the array, ensuring all elements in the window + * are distinct. * + * Time Complexity: O(n) where n is the length of the input array + * Space Complexity: O(k) for storing elements in the hash map + * + * @see Streaming + * Algorithm + * @see Sliding + * Window * @author Swarga-codes (https://github.com/Swarga-codes) */ public final class MaximumSumOfDistinctSubarraysWithLengthK { @@ -16,54 +28,62 @@ private MaximumSumOfDistinctSubarraysWithLengthK() { } /** - * Finds the maximum sum of a subarray of size K consisting of distinct elements. + * Finds the maximum sum of a subarray of size K consisting of distinct + * elements. * - * @param k The size of the subarray. - * @param nums The array from which subarrays will be considered. + * The algorithm uses a sliding window technique with a frequency map to track + * the count of each element in the current window. A window is valid only if + * all K elements are distinct (frequency map size equals K). * - * @return The maximum sum of any distinct-element subarray of size K. If no such subarray exists, returns 0. + * @param k The size of the subarray. Must be non-negative. + * @param nums The array from which subarrays will be considered. + * @return The maximum sum of any distinct-element subarray of size K. + * Returns 0 if no such subarray exists or if k is 0 or negative. + * @throws IllegalArgumentException if k is negative */ public static long maximumSubarraySum(int k, int... nums) { - if (nums.length < k) { + if (k <= 0 || nums == null || nums.length < k) { return 0; } - long masSum = 0; // Variable to store the maximum sum of distinct subarrays - long currentSum = 0; // Variable to store the sum of the current subarray - Set currentSet = new HashSet<>(); // Set to track distinct elements in the current subarray - // Initialize the first window + long maxSum = 0; + long currentSum = 0; + Map frequencyMap = new HashMap<>(); + + // Initialize the first window of size k for (int i = 0; i < k; i++) { currentSum += nums[i]; - currentSet.add(nums[i]); + frequencyMap.put(nums[i], frequencyMap.getOrDefault(nums[i], 0) + 1); } - // If the first window contains distinct elements, update maxSum - if (currentSet.size() == k) { - masSum = currentSum; + + // Check if the first window has all distinct elements + if (frequencyMap.size() == k) { + maxSum = currentSum; } + // Slide the window across the array - for (int i = 1; i < nums.length - k + 1; i++) { - // Update the sum by removing the element that is sliding out and adding the new element - currentSum = currentSum - nums[i - 1]; - currentSum = currentSum + nums[i + k - 1]; - int j = i; - boolean flag = false; // flag value which says that the subarray contains distinct elements - while (j < i + k && currentSet.size() < k) { - if (nums[i - 1] == nums[j]) { - flag = true; - break; - } else { - j++; - } - } - if (!flag) { - currentSet.remove(nums[i - 1]); + for (int i = k; i < nums.length; i++) { + // Remove the leftmost element from the window + int leftElement = nums[i - k]; + currentSum -= leftElement; + int leftFrequency = frequencyMap.get(leftElement); + if (leftFrequency == 1) { + frequencyMap.remove(leftElement); + } else { + frequencyMap.put(leftElement, leftFrequency - 1); } - currentSet.add(nums[i + k - 1]); - // If the current window has distinct elements, compare and possibly update maxSum - if (currentSet.size() == k && masSum < currentSum) { - masSum = currentSum; + + // Add the new rightmost element to the window + int rightElement = nums[i]; + currentSum += rightElement; + frequencyMap.put(rightElement, frequencyMap.getOrDefault(rightElement, 0) + 1); + + // If all elements in the window are distinct, update maxSum if needed + if (frequencyMap.size() == k && currentSum > maxSum) { + maxSum = currentSum; } } - return masSum; // the final maximum sum + + return maxSum; } } diff --git a/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java b/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java index cd2cd02ab908..28dc980034f3 100644 --- a/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java +++ b/src/main/java/com/thealgorithms/others/MiniMaxAlgorithm.java @@ -4,54 +4,99 @@ import java.util.Random; /** - * MiniMax is an algorithm used int artificial intelligence and game theory for - * minimizing the possible loss for the worst case scenario. + * MiniMax is an algorithm used in artificial intelligence and game theory for + * minimizing the possible loss for the worst case scenario. It is commonly used + * in two-player turn-based games such as Tic-Tac-Toe, Chess, and Checkers. * - * See more (https://en.wikipedia.org/wiki/Minimax, - * https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-1-introduction/). + *

+ * The algorithm simulates all possible moves in a game tree and chooses the + * move that minimizes the maximum possible loss. The algorithm assumes both + * players play optimally. + * + *

+ * Time Complexity: O(b^d) where b is the branching factor and d is the depth + *

+ * Space Complexity: O(d) for the recursive call stack + * + *

+ * See more: + *

* * @author aitofi (https://github.com/aitorfi) */ -public class MiniMaxAlgorithm { +public final class MiniMaxAlgorithm { + + private static final Random RANDOM = new Random(); /** * Game tree represented as an int array containing scores. Each array - * element is a leaf node. + * element is a leaf node. The array length must be a power of 2. */ private int[] scores; + + /** + * The height of the game tree, calculated as log2(scores.length). + */ private int height; /** - * Initializes the scores with 8 random leaf nodes + * Initializes the MiniMaxAlgorithm with 8 random leaf nodes (2^3 = 8). + * Each score is a random integer between 1 and 99 inclusive. */ public MiniMaxAlgorithm() { - scores = getRandomScores(3, 99); - height = log2(scores.length); + this(getRandomScores(3, 99)); } + /** + * Initializes the MiniMaxAlgorithm with the provided scores. + * + * @param scores An array of scores representing leaf nodes. The length must be + * a power of 2. + * @throws IllegalArgumentException if the scores array length is not a power of + * 2 + */ + public MiniMaxAlgorithm(int[] scores) { + if (!isPowerOfTwo(scores.length)) { + throw new IllegalArgumentException("The number of scores must be a power of 2."); + } + this.scores = Arrays.copyOf(scores, scores.length); + this.height = log2(scores.length); + } + + /** + * Demonstrates the MiniMax algorithm with a random game tree. + * + * @param args Command line arguments (not used) + */ public static void main(String[] args) { - MiniMaxAlgorithm miniMaxAlgorith = new MiniMaxAlgorithm(); + MiniMaxAlgorithm miniMaxAlgorithm = new MiniMaxAlgorithm(); boolean isMaximizer = true; // Specifies the player that goes first. - boolean verbose = true; // True to show each players choices. int bestScore; - bestScore = miniMaxAlgorith.miniMax(0, isMaximizer, 0, verbose); + bestScore = miniMaxAlgorithm.miniMax(0, isMaximizer, 0, true); - if (verbose) { - System.out.println(); - } - - System.out.println(Arrays.toString(miniMaxAlgorith.getScores())); + System.out.println(); + System.out.println(Arrays.toString(miniMaxAlgorithm.getScores())); System.out.println("The best score for " + (isMaximizer ? "Maximizer" : "Minimizer") + " is " + bestScore); } /** * Returns the optimal score assuming that both players play their best. * - * @param depth Indicates how deep we are into the game tree. - * @param isMaximizer True if it is maximizers turn; otherwise false. - * @param index Index of the leaf node that is being evaluated. - * @param verbose True to show each players choices. + *

+ * This method recursively evaluates the game tree using the minimax algorithm. + * At each level, the maximizer tries to maximize the score while the minimizer + * tries to minimize it. + * + * @param depth The current depth in the game tree (0 at root). + * @param isMaximizer True if it is the maximizer's turn; false for minimizer. + * @param index Index of the current node in the game tree. + * @param verbose True to print each player's choice during evaluation. * @return The optimal score for the player that made the first move. */ public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) { @@ -75,7 +120,7 @@ public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) { } // Leaf nodes can be sequentially inspected by - // recurssively multiplying (0 * 2) and ((0 * 2) + 1): + // recursively multiplying (0 * 2) and ((0 * 2) + 1): // (0 x 2) = 0; ((0 x 2) + 1) = 1 // (1 x 2) = 2; ((1 x 2) + 1) = 3 // (2 x 2) = 4; ((2 x 2) + 1) = 5 ... @@ -87,41 +132,73 @@ public int miniMax(int depth, boolean isMaximizer, int index, boolean verbose) { } /** - * Returns an array of random numbers which lenght is a power of 2. + * Returns an array of random numbers whose length is a power of 2. * - * @param size The power of 2 that will determine the lenght of the array. - * @param maxScore The maximum possible score. - * @return An array of random numbers. + * @param size The power of 2 that will determine the length of the array + * (array length = 2^size). + * @param maxScore The maximum possible score (scores will be between 1 and + * maxScore inclusive). + * @return An array of random numbers with length 2^size. */ public static int[] getRandomScores(int size, int maxScore) { int[] randomScores = new int[(int) Math.pow(2, size)]; - Random rand = new Random(); for (int i = 0; i < randomScores.length; i++) { - randomScores[i] = rand.nextInt(maxScore) + 1; + randomScores[i] = RANDOM.nextInt(maxScore) + 1; } return randomScores; } - // A utility function to find Log n in base 2 + /** + * Calculates the logarithm base 2 of a number. + * + * @param n The number to calculate log2 for (must be a power of 2). + * @return The log2 of n. + */ private int log2(int n) { return (n == 1) ? 0 : log2(n / 2) + 1; } + /** + * Checks if a number is a power of 2. + * + * @param n The number to check. + * @return True if n is a power of 2, false otherwise. + */ + private boolean isPowerOfTwo(int n) { + return n > 0 && (n & (n - 1)) == 0; + } + + /** + * Sets the scores array for the game tree. + * + * @param scores The array of scores. Length must be a power of 2. + * @throws IllegalArgumentException if the scores array length is not a power of + * 2 + */ public void setScores(int[] scores) { - if (scores.length % 1 == 0) { - this.scores = scores; - height = log2(this.scores.length); - } else { - System.out.println("The number of scores must be a power of 2."); + if (!isPowerOfTwo(scores.length)) { + throw new IllegalArgumentException("The number of scores must be a power of 2."); } + this.scores = Arrays.copyOf(scores, scores.length); + height = log2(this.scores.length); } + /** + * Returns a copy of the scores array. + * + * @return A copy of the scores array. + */ public int[] getScores() { - return scores; + return Arrays.copyOf(scores, scores.length); } + /** + * Returns the height of the game tree. + * + * @return The height of the game tree (log2 of the number of leaf nodes). + */ public int getHeight() { return height; } diff --git a/src/main/java/com/thealgorithms/others/MosAlgorithm.java b/src/main/java/com/thealgorithms/others/MosAlgorithm.java new file mode 100644 index 000000000000..2d2778339a7a --- /dev/null +++ b/src/main/java/com/thealgorithms/others/MosAlgorithm.java @@ -0,0 +1,260 @@ +package com.thealgorithms.others; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Mo's Algorithm (Square Root Decomposition) for offline range queries + * + * Mo's Algorithm is used to answer range queries efficiently when: + * 1. Queries can be processed offline (all queries known beforehand) + * 2. We can efficiently add/remove elements from current range + * 3. The problem has optimal substructure for range operations + * + * Time Complexity: O((N + Q) * sqrt(N)) where N = array size, Q = number of queries + * Space Complexity: O(N + Q) + * + * @see Mo's Algorithm + * @author BEASTSHRIRAM + */ +public final class MosAlgorithm { + + /** + * Query structure to store range queries + */ + public static class Query { + public final int left; + public final int right; + public final int index; // Original index of query + public int result; // Result of the query + + public Query(int left, int right, int index) { + this.left = left; + this.right = right; + this.index = index; + this.result = 0; + } + } + + private MosAlgorithm() { + // Utility class + } + + /** + * Solves range sum queries using Mo's Algorithm + * + * @param arr the input array + * @param queries array of queries to process + * @return array of results corresponding to each query + */ + public static int[] solveRangeSumQueries(int[] arr, Query[] queries) { + if (arr == null || queries == null || arr.length == 0) { + return new int[0]; + } + + int n = arr.length; + int blockSize = (int) Math.sqrt(n); + + // Sort queries using Mo's ordering + Arrays.sort(queries, new MoComparator(blockSize)); + + // Initialize variables for current range + int currentLeft = 0; + int currentRight = -1; + int currentSum = 0; + + // Process each query + for (Query query : queries) { + // Expand or shrink the current range to match query range + + // Expand right boundary + while (currentRight < query.right) { + currentRight++; + currentSum += arr[currentRight]; + } + + // Shrink right boundary + while (currentRight > query.right) { + currentSum -= arr[currentRight]; + currentRight--; + } + + // Expand left boundary + while (currentLeft < query.left) { + currentSum -= arr[currentLeft]; + currentLeft++; + } + + // Shrink left boundary + while (currentLeft > query.left) { + currentLeft--; + currentSum += arr[currentLeft]; + } + + // Store the result + query.result = currentSum; + } + + // Extract results in original query order + int[] results = new int[queries.length]; + for (Query query : queries) { + results[query.index] = query.result; + } + + return results; + } + + /** + * Solves range frequency queries using Mo's Algorithm + * Example: Count occurrences of a specific value in range [L, R] + * + * @param arr the input array + * @param queries array of queries to process + * @param targetValue the value to count in each range + * @return array of results corresponding to each query + */ + public static int[] solveRangeFrequencyQueries(int[] arr, Query[] queries, int targetValue) { + if (arr == null || queries == null || arr.length == 0) { + return new int[0]; + } + + int n = arr.length; + int blockSize = (int) Math.sqrt(n); + + // Sort queries using Mo's ordering + Arrays.sort(queries, new MoComparator(blockSize)); + + // Initialize variables for current range + int currentLeft = 0; + int currentRight = -1; + int currentCount = 0; + + // Process each query + for (Query query : queries) { + // Expand right boundary + while (currentRight < query.right) { + currentRight++; + if (arr[currentRight] == targetValue) { + currentCount++; + } + } + + // Shrink right boundary + while (currentRight > query.right) { + if (arr[currentRight] == targetValue) { + currentCount--; + } + currentRight--; + } + + // Expand left boundary + while (currentLeft < query.left) { + if (arr[currentLeft] == targetValue) { + currentCount--; + } + currentLeft++; + } + + // Shrink left boundary + while (currentLeft > query.left) { + currentLeft--; + if (arr[currentLeft] == targetValue) { + currentCount++; + } + } + + // Store the result + query.result = currentCount; + } + + // Extract results in original query order + int[] results = new int[queries.length]; + for (Query query : queries) { + results[query.index] = query.result; + } + + return results; + } + + /** + * Comparator for Mo's Algorithm query ordering + * Queries are sorted by block of left endpoint, then by right endpoint + */ + private static class MoComparator implements Comparator { + private final int blockSize; + + MoComparator(int blockSize) { + this.blockSize = blockSize; + } + + @Override + public int compare(Query a, Query b) { + int blockA = a.left / blockSize; + int blockB = b.left / blockSize; + + if (blockA != blockB) { + return Integer.compare(blockA, blockB); + } + + // For odd blocks, sort right in ascending order + // For even blocks, sort right in descending order (optimization) + if ((blockA & 1) == 1) { + return Integer.compare(a.right, b.right); + } else { + return Integer.compare(b.right, a.right); + } + } + } + + /** + * Demo method showing usage of Mo's Algorithm + * + * @param args command line arguments + */ + public static void main(String[] args) { + // Example: Range sum queries + int[] arr = {1, 3, 5, 2, 7, 6, 3, 1, 4, 8}; + + Query[] queries = { + new Query(0, 2, 0), // Sum of elements from index 0 to 2: 1+3+5 = 9 + new Query(1, 4, 1), // Sum of elements from index 1 to 4: 3+5+2+7 = 17 + new Query(2, 6, 2), // Sum of elements from index 2 to 6: 5+2+7+6+3 = 23 + new Query(3, 8, 3) // Sum of elements from index 3 to 8: 2+7+6+3+1+4 = 23 + }; + + System.out.println("Array: " + Arrays.toString(arr)); + System.out.println("Range Sum Queries:"); + + // Store original queries for display + Query[] originalQueries = new Query[queries.length]; + for (int i = 0; i < queries.length; i++) { + originalQueries[i] = new Query(queries[i].left, queries[i].right, queries[i].index); + } + + int[] results = solveRangeSumQueries(arr, queries); + + for (int i = 0; i < originalQueries.length; i++) { + System.out.printf("Query %d: Sum of range [%d, %d] = %d%n", i, originalQueries[i].left, originalQueries[i].right, results[i]); + } + + // Example: Range frequency queries + System.out.println("\nRange Frequency Queries (count of value 3):"); + Query[] freqQueries = { + new Query(0, 5, 0), // Count of 3 in range [0, 5]: 1 occurrence + new Query(2, 8, 1), // Count of 3 in range [2, 8]: 2 occurrences + new Query(6, 9, 2) // Count of 3 in range [6, 9]: 1 occurrence + }; + + // Store original frequency queries for display + Query[] originalFreqQueries = new Query[freqQueries.length]; + for (int i = 0; i < freqQueries.length; i++) { + originalFreqQueries[i] = new Query(freqQueries[i].left, freqQueries[i].right, freqQueries[i].index); + } + + int[] freqResults = solveRangeFrequencyQueries(arr, freqQueries, 3); + + for (int i = 0; i < originalFreqQueries.length; i++) { + System.out.printf("Query %d: Count of 3 in range [%d, %d] = %d%n", i, originalFreqQueries[i].left, originalFreqQueries[i].right, freqResults[i]); + } + } +} diff --git a/src/main/java/com/thealgorithms/others/PageRank.java b/src/main/java/com/thealgorithms/others/PageRank.java index c7be7a9882bc..2899b80bcee8 100644 --- a/src/main/java/com/thealgorithms/others/PageRank.java +++ b/src/main/java/com/thealgorithms/others/PageRank.java @@ -2,94 +2,306 @@ import java.util.Scanner; -class PageRank { +/** + * PageRank Algorithm Implementation + * + *

+ * The PageRank algorithm is used by Google Search to rank web pages in their + * search engine + * results. It was named after Larry Page, one of the founders of Google. + * PageRank is a way of + * measuring the importance of website pages. + * + *

+ * Algorithm: 1. Initialize PageRank values for all pages to 1/N (where N is the + * total number + * of pages) 2. For each iteration: - For each page, calculate the new PageRank + * by summing the + * contributions from all incoming links - Apply the damping factor: PR(page) = + * (1-d) + d * + * sum(PR(incoming_page) / outgoing_links(incoming_page)) 3. Repeat until + * convergence + * + * @see PageRank Algorithm + */ +public final class PageRank { + private static final int MAX_NODES = 10; + private static final double DEFAULT_DAMPING_FACTOR = 0.85; + private static final int DEFAULT_ITERATIONS = 2; + + private int[][] adjacencyMatrix; + private double[] pageRankValues; + private int nodeCount; + + /** + * Constructor to initialize PageRank with specified number of nodes + * + * @param numberOfNodes the number of nodes/pages in the graph + * @throws IllegalArgumentException if numberOfNodes is less than 1 or greater + * than MAX_NODES + */ + public PageRank(int numberOfNodes) { + if (numberOfNodes < 1 || numberOfNodes > MAX_NODES) { + throw new IllegalArgumentException("Number of nodes must be between 1 and " + MAX_NODES); + } + this.nodeCount = numberOfNodes; + this.adjacencyMatrix = new int[MAX_NODES][MAX_NODES]; + this.pageRankValues = new double[MAX_NODES]; + } + + /** + * Default constructor for interactive mode + */ + public PageRank() { + this.adjacencyMatrix = new int[MAX_NODES][MAX_NODES]; + this.pageRankValues = new double[MAX_NODES]; + } + + /** + * Main method for interactive PageRank calculation + * + * @param args command line arguments (not used) + */ public static void main(String[] args) { - int nodes; - int i; - int j; - Scanner in = new Scanner(System.in); - System.out.print("Enter the Number of WebPages: "); - nodes = in.nextInt(); - PageRank p = new PageRank(); - System.out.println("Enter the Adjacency Matrix with 1->PATH & 0->NO PATH Between two WebPages: "); - for (i = 1; i <= nodes; i++) { - for (j = 1; j <= nodes; j++) { - p.path[i][j] = in.nextInt(); - if (j == i) { - p.path[i][j] = 0; + try (Scanner scanner = new Scanner(System.in)) { + System.out.print("Enter the Number of WebPages: "); + int nodes = scanner.nextInt(); + + PageRank pageRank = new PageRank(nodes); + System.out.println("Enter the Adjacency Matrix with 1->PATH & 0->NO PATH Between two WebPages: "); + + for (int i = 1; i <= nodes; i++) { + for (int j = 1; j <= nodes; j++) { + int value = scanner.nextInt(); + pageRank.setEdge(i, j, value); } } + + pageRank.calculatePageRank(nodes, DEFAULT_DAMPING_FACTOR, DEFAULT_ITERATIONS, true); } - p.calc(nodes); } - public int[][] path = new int[10][10]; - public double[] pagerank = new double[10]; + /** + * Sets an edge in the adjacency matrix + * + * @param from source node (1-indexed) + * @param to destination node (1-indexed) + * @param value 1 if edge exists, 0 otherwise + */ + public void setEdge(int from, int to, int value) { + if (from == to) { + adjacencyMatrix[from][to] = 0; // No self-loops + } else { + adjacencyMatrix[from][to] = value; + } + } - public void calc(double totalNodes) { - double initialPageRank; - double outgoingLinks = 0; - double dampingFactor = 0.85; - double[] tempPageRank = new double[10]; - int externalNodeNumber; - int internalNodeNumber; - int k = 1; // For Traversing - int iterationStep = 1; - initialPageRank = 1 / totalNodes; - System.out.printf(" Total Number of Nodes :" + totalNodes + "\t Initial PageRank of All Nodes :" + initialPageRank + "\n"); + /** + * Sets the adjacency matrix for the graph + * + * @param matrix the adjacency matrix (1-indexed) + */ + public void setAdjacencyMatrix(int[][] matrix) { + for (int i = 1; i <= nodeCount; i++) { + for (int j = 1; j <= nodeCount; j++) { + setEdge(i, j, matrix[i][j]); + } + } + } - // 0th ITERATION _ OR _ INITIALIZATION PHASE // - for (k = 1; k <= totalNodes; k++) { - this.pagerank[k] = initialPageRank; + /** + * Gets the PageRank value for a specific node + * + * @param node the node index (1-indexed) + * @return the PageRank value + */ + public double getPageRank(int node) { + if (node < 1 || node > nodeCount) { + throw new IllegalArgumentException("Node index out of bounds"); } - System.out.print("\n Initial PageRank Values , 0th Step \n"); + return pageRankValues[node]; + } + + /** + * Gets all PageRank values + * + * @return array of PageRank values (1-indexed) + */ + public double[] getAllPageRanks() { + return pageRankValues.clone(); + } + + /** + * Calculates PageRank using the default damping factor and iterations + * + * @param totalNodes the total number of nodes + * @return array of PageRank values + */ + public double[] calculatePageRank(int totalNodes) { + return calculatePageRank(totalNodes, DEFAULT_DAMPING_FACTOR, DEFAULT_ITERATIONS, false); + } + + /** + * Calculates PageRank with custom parameters + * + * @param totalNodes the total number of nodes + * @param dampingFactor the damping factor (typically 0.85) + * @param iterations number of iterations to perform + * @param verbose whether to print detailed output + * @return array of PageRank values + */ + public double[] calculatePageRank(int totalNodes, double dampingFactor, int iterations, boolean verbose) { + validateInputParameters(totalNodes, dampingFactor, iterations); - for (k = 1; k <= totalNodes; k++) { - System.out.printf(" Page Rank of " + k + " is :\t" + this.pagerank[k] + "\n"); + this.nodeCount = totalNodes; + double initialPageRank = 1.0 / totalNodes; + + if (verbose) { + System.out.printf("Total Number of Nodes: %d\tInitial PageRank of All Nodes: %.6f%n", totalNodes, initialPageRank); } - while (iterationStep <= 2) { // Iterations - // Store the PageRank for All Nodes in Temporary Array - for (k = 1; k <= totalNodes; k++) { - tempPageRank[k] = this.pagerank[k]; - this.pagerank[k] = 0; - } + initializePageRanks(totalNodes, initialPageRank, verbose); + performIterations(totalNodes, dampingFactor, iterations, verbose); - for (internalNodeNumber = 1; internalNodeNumber <= totalNodes; internalNodeNumber++) { - for (externalNodeNumber = 1; externalNodeNumber <= totalNodes; externalNodeNumber++) { - if (this.path[externalNodeNumber][internalNodeNumber] == 1) { - k = 1; - outgoingLinks = 0; // Count the Number of Outgoing Links for each externalNodeNumber - while (k <= totalNodes) { - if (this.path[externalNodeNumber][k] == 1) { - outgoingLinks = outgoingLinks + 1; // Counter for Outgoing Links - } - k = k + 1; - } - // Calculate PageRank - this.pagerank[internalNodeNumber] += tempPageRank[externalNodeNumber] * (1 / outgoingLinks); - } - } - System.out.printf("\n After " + iterationStep + "th Step \n"); + if (verbose) { + System.out.println("\nFinal PageRank:"); + printPageRanks(totalNodes); + } - for (k = 1; k <= totalNodes; k++) { - System.out.printf(" Page Rank of " + k + " is :\t" + this.pagerank[k] + "\n"); - } + return pageRankValues.clone(); + } + + /** + * Validates input parameters for PageRank calculation + * + * @param totalNodes the total number of nodes + * @param dampingFactor the damping factor + * @param iterations number of iterations + * @throws IllegalArgumentException if parameters are invalid + */ + private void validateInputParameters(int totalNodes, double dampingFactor, int iterations) { + if (totalNodes < 1 || totalNodes > MAX_NODES) { + throw new IllegalArgumentException("Total nodes must be between 1 and " + MAX_NODES); + } + if (dampingFactor < 0 || dampingFactor > 1) { + throw new IllegalArgumentException("Damping factor must be between 0 and 1"); + } + if (iterations < 1) { + throw new IllegalArgumentException("Iterations must be at least 1"); + } + } + + /** + * Initializes PageRank values for all nodes + * + * @param totalNodes the total number of nodes + * @param initialPageRank the initial PageRank value + * @param verbose whether to print output + */ + private void initializePageRanks(int totalNodes, double initialPageRank, boolean verbose) { + for (int i = 1; i <= totalNodes; i++) { + pageRankValues[i] = initialPageRank; + } + + if (verbose) { + System.out.println("\nInitial PageRank Values, 0th Step"); + printPageRanks(totalNodes); + } + } - iterationStep = iterationStep + 1; + /** + * Performs the iterative PageRank calculation + * + * @param totalNodes the total number of nodes + * @param dampingFactor the damping factor + * @param iterations number of iterations + * @param verbose whether to print output + */ + private void performIterations(int totalNodes, double dampingFactor, int iterations, boolean verbose) { + for (int iteration = 1; iteration <= iterations; iteration++) { + double[] tempPageRank = storeCurrentPageRanks(totalNodes); + calculateNewPageRanks(totalNodes, tempPageRank); + applyDampingFactor(totalNodes, dampingFactor); + + if (verbose) { + System.out.printf("%nAfter %d iteration(s)%n", iteration); + printPageRanks(totalNodes); } + } + } - // Add the Damping Factor to PageRank - for (k = 1; k <= totalNodes; k++) { - this.pagerank[k] = (1 - dampingFactor) + dampingFactor * this.pagerank[k]; + /** + * Stores current PageRank values in a temporary array + * + * @param totalNodes the total number of nodes + * @return temporary array with current PageRank values + */ + private double[] storeCurrentPageRanks(int totalNodes) { + double[] tempPageRank = new double[MAX_NODES]; + for (int i = 1; i <= totalNodes; i++) { + tempPageRank[i] = pageRankValues[i]; + pageRankValues[i] = 0; + } + return tempPageRank; + } + + /** + * Calculates new PageRank values based on incoming links + * + * @param totalNodes the total number of nodes + * @param tempPageRank temporary array with previous PageRank values + */ + private void calculateNewPageRanks(int totalNodes, double[] tempPageRank) { + for (int targetNode = 1; targetNode <= totalNodes; targetNode++) { + for (int sourceNode = 1; sourceNode <= totalNodes; sourceNode++) { + if (adjacencyMatrix[sourceNode][targetNode] == 1) { + int outgoingLinks = countOutgoingLinks(sourceNode, totalNodes); + if (outgoingLinks > 0) { + pageRankValues[targetNode] += tempPageRank[sourceNode] / outgoingLinks; + } + } } + } + } - // Display PageRank - System.out.print("\n Final Page Rank : \n"); - for (k = 1; k <= totalNodes; k++) { - System.out.printf(" Page Rank of " + k + " is :\t" + this.pagerank[k] + "\n"); + /** + * Applies the damping factor to all PageRank values + * + * @param totalNodes the total number of nodes + * @param dampingFactor the damping factor + */ + private void applyDampingFactor(int totalNodes, double dampingFactor) { + for (int i = 1; i <= totalNodes; i++) { + pageRankValues[i] = (1 - dampingFactor) + dampingFactor * pageRankValues[i]; + } + } + + /** + * Counts the number of outgoing links from a node + * + * @param node the source node (1-indexed) + * @param totalNodes total number of nodes + * @return the count of outgoing links + */ + private int countOutgoingLinks(int node, int totalNodes) { + int count = 0; + for (int i = 1; i <= totalNodes; i++) { + if (adjacencyMatrix[node][i] == 1) { + count++; } } + return count; + } + + /** + * Prints the PageRank values for all nodes + * + * @param totalNodes the total number of nodes + */ + private void printPageRanks(int totalNodes) { + for (int i = 1; i <= totalNodes; i++) { + System.out.printf("PageRank of %d: %.6f%n", i, pageRankValues[i]); + } } } diff --git a/src/main/java/com/thealgorithms/others/PerlinNoise.java b/src/main/java/com/thealgorithms/others/PerlinNoise.java index e6551ed6b9ee..d97e3395ff18 100644 --- a/src/main/java/com/thealgorithms/others/PerlinNoise.java +++ b/src/main/java/com/thealgorithms/others/PerlinNoise.java @@ -4,99 +4,156 @@ import java.util.Scanner; /** - * For detailed info and implementation see: Perlin-Noise + * Utility for generating 2D value-noise blended across octaves (commonly known + * as Perlin-like noise). + * + *

+ * The implementation follows the classic approach of: + *

    + *
  1. Generate a base grid of random values in [0, 1).
  2. + *
  3. For each octave k, compute a layer by bilinear interpolation of the base + * grid + * at period 2^k.
  4. + *
  5. Blend all layers from coarse to fine using a geometric series of + * amplitudes + * controlled by {@code persistence}, then normalize to [0, 1].
  6. + *
+ * + *

+ * For background see: + * Perlin Noise. + * + *

+ * Constraints and notes: + *

    + *
  • {@code width} and {@code height} should be positive.
  • + *
  • {@code octaveCount} must be at least 1 (0 would lead to a division by + * zero).
  • + *
  • {@code persistence} should be in (0, 1], typical values around + * 0.5โ€“0.8.
  • + *
  • Given the same seed and parameters, results are deterministic.
  • + *
*/ + public final class PerlinNoise { private PerlinNoise() { } /** - * @param width width of noise array - * @param height height of noise array - * @param octaveCount numbers of layers used for blending noise - * @param persistence value of impact each layer get while blending - * @param seed used for randomizer - * @return float array containing calculated "Perlin-Noise" values + * Generate a 2D array of blended noise values normalized to [0, 1]. + * + * @param width width of the noise array (columns) + * @param height height of the noise array (rows) + * @param octaveCount number of octaves (layers) to blend; must be >= 1 + * @param persistence per-octave amplitude multiplier in (0, 1] + * @param seed seed for the random base grid + * @return a {@code width x height} array containing blended noise values in [0, + * 1] */ static float[][] generatePerlinNoise(int width, int height, int octaveCount, float persistence, long seed) { - final float[][] base = new float[width][height]; - final float[][] perlinNoise = new float[width][height]; - final float[][][] noiseLayers = new float[octaveCount][][]; + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("width and height must be > 0"); + } + if (octaveCount < 1) { + throw new IllegalArgumentException("octaveCount must be >= 1"); + } + if (!(persistence > 0f && persistence <= 1f)) { // using > to exclude 0 and NaN + throw new IllegalArgumentException("persistence must be in (0, 1]"); + } + final float[][] base = createBaseGrid(width, height, seed); + final float[][][] layers = createLayers(base, width, height, octaveCount); + return blendAndNormalize(layers, width, height, persistence); + } + + /** Create the base random lattice values in [0,1). */ + static float[][] createBaseGrid(int width, int height, long seed) { + final float[][] base = new float[width][height]; Random random = new Random(seed); - // fill base array with random values as base for noise for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { base[x][y] = random.nextFloat(); } } + return base; + } - // calculate octaves with different roughness + /** Pre-compute each octave layer at increasing frequency. */ + static float[][][] createLayers(float[][] base, int width, int height, int octaveCount) { + final float[][][] noiseLayers = new float[octaveCount][][]; for (int octave = 0; octave < octaveCount; octave++) { noiseLayers[octave] = generatePerlinNoiseLayer(base, width, height, octave); } + return noiseLayers; + } + /** Blend layers using geometric amplitudes and normalize to [0,1]. */ + static float[][] blendAndNormalize(float[][][] layers, int width, int height, float persistence) { + final int octaveCount = layers.length; + final float[][] out = new float[width][height]; float amplitude = 1f; float totalAmplitude = 0f; - // calculate perlin noise by blending each layer together with specific persistence for (int octave = octaveCount - 1; octave >= 0; octave--) { amplitude *= persistence; totalAmplitude += amplitude; - + final float[][] layer = layers[octave]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - // adding each value of the noise layer to the noise - // by increasing amplitude the rougher noises will have more impact - perlinNoise[x][y] += noiseLayers[octave][x][y] * amplitude; + out[x][y] += layer[x][y] * amplitude; } } } - // normalize values so that they stay between 0..1 + if (totalAmplitude <= 0f || Float.isInfinite(totalAmplitude) || Float.isNaN(totalAmplitude)) { + throw new IllegalStateException("Invalid totalAmplitude computed during normalization"); + } + + final float invTotal = 1f / totalAmplitude; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - perlinNoise[x][y] /= totalAmplitude; + out[x][y] *= invTotal; } } - - return perlinNoise; + return out; } /** - * @param base base random float array - * @param width width of noise array + * Generate a single octave layer by bilinear interpolation of a base grid at a + * given octave (period = 2^octave). + * + * @param base base random float array of size {@code width x height} + * @param width width of noise array * @param height height of noise array - * @param octave current layer - * @return float array containing calculated "Perlin-Noise-Layer" values + * @param octave current octave (0 for period 1, 1 for period 2, ...) + * @return float array containing the octave's interpolated values */ static float[][] generatePerlinNoiseLayer(float[][] base, int width, int height, int octave) { float[][] perlinNoiseLayer = new float[width][height]; - // calculate period (wavelength) for different shapes + // Calculate period (wavelength) for different shapes. int period = 1 << octave; // 2^k float frequency = 1f / period; // 1/2^k for (int x = 0; x < width; x++) { - // calculates the horizontal sampling indices + // Calculate the horizontal sampling indices. int x0 = (x / period) * period; int x1 = (x0 + period) % width; - float horizintalBlend = (x - x0) * frequency; + float horizontalBlend = (x - x0) * frequency; for (int y = 0; y < height; y++) { - // calculates the vertical sampling indices + // Calculate the vertical sampling indices. int y0 = (y / period) * period; int y1 = (y0 + period) % height; float verticalBlend = (y - y0) * frequency; - // blend top corners - float top = interpolate(base[x0][y0], base[x1][y0], horizintalBlend); + // Blend top corners. + float top = interpolate(base[x0][y0], base[x1][y0], horizontalBlend); - // blend bottom corners - float bottom = interpolate(base[x0][y1], base[x1][y1], horizintalBlend); + // Blend bottom corners. + float bottom = interpolate(base[x0][y1], base[x1][y1], horizontalBlend); - // blend top and bottom interpolation to get the final blend value for this cell + // Blend top and bottom interpolation to get the final value for this cell. perlinNoiseLayer[x][y] = interpolate(top, bottom, verticalBlend); } } @@ -105,16 +162,21 @@ static float[][] generatePerlinNoiseLayer(float[][] base, int width, int height, } /** - * @param a value of point a - * @param b value of point b - * @param alpha determine which value has more impact (closer to 0 -> a, - * closer to 1 -> b) - * @return interpolated value + * Linear interpolation between two values. + * + * @param a value at alpha = 0 + * @param b value at alpha = 1 + * @param alpha interpolation factor in [0, 1] + * @return interpolated value {@code (1 - alpha) * a + alpha * b} */ static float interpolate(float a, float b, float alpha) { return a * (1 - alpha) + alpha * b; } + /** + * Small demo that prints a text representation of the noise using a provided + * character set. + */ public static void main(String[] args) { Scanner in = new Scanner(System.in); @@ -148,7 +210,7 @@ public static void main(String[] args) { final char[] chars = charset.toCharArray(); final int length = chars.length; final float step = 1f / length; - // output based on charset + // Output based on charset thresholds. for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { float value = step; diff --git a/src/main/java/com/thealgorithms/others/ReverseStackUsingRecursion.java b/src/main/java/com/thealgorithms/others/ReverseStackUsingRecursion.java deleted file mode 100644 index de36673512a0..000000000000 --- a/src/main/java/com/thealgorithms/others/ReverseStackUsingRecursion.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.thealgorithms.others; - -import java.util.Stack; - -/** - * Class that provides methods to reverse a stack using recursion. - */ -public final class ReverseStackUsingRecursion { - private ReverseStackUsingRecursion() { - } - - /** - * Reverses the elements of the given stack using recursion. - * - * @param stack the stack to be reversed - * @throws IllegalArgumentException if the stack is null - */ - public static void reverse(Stack stack) { - if (stack == null) { - throw new IllegalArgumentException("Stack cannot be null"); - } - if (!stack.isEmpty()) { - int topElement = stack.pop(); - reverse(stack); - insertAtBottom(stack, topElement); - } - } - - /** - * Inserts an element at the bottom of the given stack. - * - * @param stack the stack where the element will be inserted - * @param element the element to be inserted at the bottom - */ - private static void insertAtBottom(Stack stack, int element) { - if (stack.isEmpty()) { - stack.push(element); - } else { - int topElement = stack.pop(); - insertAtBottom(stack, element); - stack.push(topElement); - } - } -} diff --git a/src/main/java/com/thealgorithms/others/TwoPointers.java b/src/main/java/com/thealgorithms/others/TwoPointers.java index c551408c38b9..c87e26269386 100644 --- a/src/main/java/com/thealgorithms/others/TwoPointers.java +++ b/src/main/java/com/thealgorithms/others/TwoPointers.java @@ -7,30 +7,37 @@ *

* Link: https://www.geeksforgeeks.org/two-pointers-technique/ */ -final class TwoPointers { +public final class TwoPointers { + private TwoPointers() { } /** - * Given a sorted array arr (sorted in ascending order), find if there exists - * any pair of elements such that their sum is equal to the key. + * Checks whether there exists a pair of elements in a sorted array whose sum equals the specified key. * - * @param arr the array containing elements (must be sorted in ascending order) - * @param key the number to search - * @return {@code true} if there exists a pair of elements, {@code false} otherwise. + * @param arr a sorted array of integers in ascending order (must not be null) + * @param key the target sum to find + * @return {@code true} if there exists at least one pair whose sum equals {@code key}, {@code false} otherwise + * @throws IllegalArgumentException if {@code arr} is {@code null} */ public static boolean isPairedSum(int[] arr, int key) { - int i = 0; // index of the first element - int j = arr.length - 1; // index of the last element + if (arr == null) { + throw new IllegalArgumentException("Input array must not be null."); + } + + int left = 0; + int right = arr.length - 1; + + while (left < right) { + int sum = arr[left] + arr[right]; - while (i < j) { - int sum = arr[i] + arr[j]; if (sum == key) { return true; - } else if (sum < key) { - i++; + } + if (sum < key) { + left++; } else { - j--; + right--; } } return false; diff --git a/src/main/java/com/thealgorithms/physics/DampedOscillator.java b/src/main/java/com/thealgorithms/physics/DampedOscillator.java new file mode 100644 index 000000000000..84028b628e77 --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/DampedOscillator.java @@ -0,0 +1,109 @@ +package com.thealgorithms.physics; + +/** + * Models a damped harmonic oscillator, capturing the behavior of a mass-spring-damper system. + * + *

The system is defined by the second-order differential equation: + * x'' + 2 * gamma * x' + omegaโ‚€ยฒ * x = 0 + * where: + *

    + *
  • omegaโ‚€ is the natural (undamped) angular frequency in radians per second.
  • + *
  • gamma is the damping coefficient in inverse seconds.
  • + *
+ * + *

This implementation provides: + *

    + *
  • An analytical solution for the underdamped case (ฮณ < ฯ‰โ‚€).
  • + *
  • A numerical integrator based on the explicit Euler method for simulation purposes.
  • + *
+ * + *

Usage Example: + *

{@code
+ * DampedOscillator oscillator = new DampedOscillator(10.0, 0.5);
+ * double displacement = oscillator.displacementAnalytical(1.0, 0.0, 0.1);
+ * double[] nextState = oscillator.stepEuler(new double[]{1.0, 0.0}, 0.001);
+ * }
+ * + * @author [Yash Rajput](https://github.com/the-yash-rajput) + */ +public final class DampedOscillator { + + /** Natural (undamped) angular frequency (rad/s). */ + private final double omega0; + + /** Damping coefficient (sโปยน). */ + private final double gamma; + + private DampedOscillator() { + throw new AssertionError("No instances."); + } + + /** + * Constructs a damped oscillator model. + * + * @param omega0 the natural frequency (rad/s), must be positive + * @param gamma the damping coefficient (sโปยน), must be non-negative + * @throws IllegalArgumentException if parameters are invalid + */ + public DampedOscillator(double omega0, double gamma) { + if (omega0 <= 0) { + throw new IllegalArgumentException("Natural frequency must be positive."); + } + if (gamma < 0) { + throw new IllegalArgumentException("Damping coefficient must be non-negative."); + } + this.omega0 = omega0; + this.gamma = gamma; + } + + /** + * Computes the analytical displacement of an underdamped oscillator. + * Formula: x(t) = A * exp(-ฮณt) * cos(ฯ‰_d t + ฯ†) + * + * @param amplitude the initial amplitude A + * @param phase the initial phase ฯ† (radians) + * @param time the time t (seconds) + * @return the displacement x(t) + */ + public double displacementAnalytical(double amplitude, double phase, double time) { + double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma)); + return amplitude * Math.exp(-gamma * time) * Math.cos(omegaD * time + phase); + } + + /** + * Performs a single integration step using the explicit Euler method. + * State vector format: [x, v], where v = dx/dt. + * + * @param state the current state [x, v] + * @param dt the time step (seconds) + * @return the next state [x_next, v_next] + * @throws IllegalArgumentException if the state array is invalid or dt is non-positive + */ + public double[] stepEuler(double[] state, double dt) { + if (state == null || state.length != 2) { + throw new IllegalArgumentException("State must be a non-null array of length 2."); + } + if (dt <= 0) { + throw new IllegalArgumentException("Time step must be positive."); + } + + double x = state[0]; + double v = state[1]; + double acceleration = -2.0 * gamma * v - omega0 * omega0 * x; + + double xNext = x + dt * v; + double vNext = v + dt * acceleration; + + return new double[] {xNext, vNext}; + } + + /** @return the natural (undamped) angular frequency (rad/s). */ + public double getOmega0() { + return omega0; + } + + /** @return the damping coefficient (sโปยน). */ + public double getGamma() { + return gamma; + } +} diff --git a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java new file mode 100644 index 000000000000..399c3f1e041f --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java @@ -0,0 +1,73 @@ +package com.thealgorithms.physics; + +/** + * 2D Elastic collision between two circular bodies + * Based on principles of conservation of momentum and kinetic energy. + * + * @author [Yash Rajput](https://github.com/the-yash-rajput) + */ +public final class ElasticCollision2D { + + private ElasticCollision2D() { + throw new AssertionError("No instances. Utility class"); + } + + public static class Body { + public double x; + public double y; + public double vx; + public double vy; + public double mass; + public double radius; + + public Body(double x, double y, double vx, double vy, double mass, double radius) { + this.x = x; + this.y = y; + this.vx = vx; + this.vy = vy; + this.mass = mass; + this.radius = radius; + } + } + + /** + * Resolve instantaneous elastic collision between two circular bodies. + * + * @param a first body + * @param b second body + */ + public static void resolveCollision(Body a, Body b) { + double dx = b.x - a.x; + double dy = b.y - a.y; + double dist = Math.hypot(dx, dy); + + if (dist == 0) { + return; // overlapping + } + + double nx = dx / dist; + double ny = dy / dist; + + // relative velocity along normal + double rv = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny; + + if (rv > 0) { + return; // moving apart + } + + // impulse with masses + double m1 = a.mass; + double m2 = b.mass; + + double j = -(1 + 1.0) * rv / (1.0 / m1 + 1.0 / m2); + + // impulse vector + double impulseX = j * nx; + double impulseY = j * ny; + + a.vx -= impulseX / m1; + a.vy -= impulseY / m1; + b.vx += impulseX / m2; + b.vy += impulseY / m2; + } +} diff --git a/src/main/java/com/thealgorithms/physics/Gravitation.java b/src/main/java/com/thealgorithms/physics/Gravitation.java new file mode 100644 index 000000000000..292fdc195f85 --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/Gravitation.java @@ -0,0 +1,66 @@ +package com.thealgorithms.physics; + +/** + * Implements Newton's Law of Universal Gravitation. + * Provides simple static methods to calculate gravitational force and circular orbit velocity. + * + * @author [Priyanshu Kumar Singh](https://github.com/Priyanshu1303d) + * @see Wikipedia + */ +public final class Gravitation { + + /** Gravitational constant in m^3 kg^-1 s^-2 */ + public static final double GRAVITATIONAL_CONSTANT = 6.67430e-11; + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private Gravitation() { + } + + /** + * Calculates the gravitational force vector exerted by one body on another. + * + * @param m1 Mass of the first body (kg). + * @param x1 X-position of the first body (m). + * @param y1 Y-position of the first body (m). + * @param m2 Mass of the second body (kg). + * @param x2 X-position of the second body (m). + * @param y2 Y-position of the second body (m). + * @return A double array `[fx, fy]` representing the force vector on the second body. + */ + public static double[] calculateGravitationalForce(double m1, double x1, double y1, double m2, double x2, double y2) { + double dx = x1 - x2; + double dy = y1 - y2; + double distanceSq = dx * dx + dy * dy; + + // If bodies are at the same position, force is zero to avoid division by zero. + if (distanceSq == 0) { + return new double[] {0, 0}; + } + + double distance = Math.sqrt(distanceSq); + double forceMagnitude = GRAVITATIONAL_CONSTANT * m1 * m2 / distanceSq; + + // Calculate the components of the force vector + double fx = forceMagnitude * (dx / distance); + double fy = forceMagnitude * (dy / distance); + + return new double[] {fx, fy}; + } + + /** + * Calculates the speed required for a stable circular orbit. + * + * @param centralMass The mass of the central body (kg). + * @param radius The radius of the orbit (m). + * @return The orbital speed (m/s). + * @throws IllegalArgumentException if mass or radius are not positive. + */ + public static double calculateCircularOrbitVelocity(double centralMass, double radius) { + if (centralMass <= 0 || radius <= 0) { + throw new IllegalArgumentException("Mass and radius must be positive."); + } + return Math.sqrt(GRAVITATIONAL_CONSTANT * centralMass / radius); + } +} diff --git a/src/main/java/com/thealgorithms/physics/GroundToGroundProjectileMotion.java b/src/main/java/com/thealgorithms/physics/GroundToGroundProjectileMotion.java new file mode 100644 index 000000000000..a8d7ac63a186 --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/GroundToGroundProjectileMotion.java @@ -0,0 +1,90 @@ +package com.thealgorithms.physics; + +/** + * Ground to ground projectile motion calculator + * + * Ground to ground projectile motion is when a projectile's trajectory + * starts at the ground, reaches the apex, then falls back on the ground. + * + * @author [Yash Rajput](https://github.com/the-yash-rajput) + */ +public final class GroundToGroundProjectileMotion { + + private GroundToGroundProjectileMotion() { + throw new AssertionError("No instances."); + } + + /** Standard gravity constant (m/s^2) */ + private static final double GRAVITY = 9.80665; + + /** + * Convert degrees to radians + * + * @param degrees Angle in degrees + * @return Angle in radians + */ + private static double degreesToRadians(double degrees) { + return degrees * (Math.PI / 180.0); + } + + /** + * Calculate the time of flight + * + * @param initialVelocity The starting velocity of the projectile (m/s) + * @param angle The angle that the projectile is launched at in degrees + * @return The time that the projectile is in the air for (seconds) + */ + public static double timeOfFlight(double initialVelocity, double angle) { + return timeOfFlight(initialVelocity, angle, GRAVITY); + } + + /** + * Calculate the time of flight with custom gravity + * + * @param initialVelocity The starting velocity of the projectile (m/s) + * @param angle The angle that the projectile is launched at in degrees + * @param gravity The value used for the gravity constant (m/s^2) + * @return The time that the projectile is in the air for (seconds) + */ + public static double timeOfFlight(double initialVelocity, double angle, double gravity) { + double viy = initialVelocity * Math.sin(degreesToRadians(angle)); + return 2.0 * viy / gravity; + } + + /** + * Calculate the horizontal distance that the projectile travels + * + * @param initialVelocity The starting velocity of the projectile (m/s) + * @param angle The angle that the projectile is launched at in degrees + * @param time The time that the projectile is in the air (seconds) + * @return Horizontal distance that the projectile travels (meters) + */ + public static double horizontalRange(double initialVelocity, double angle, double time) { + double vix = initialVelocity * Math.cos(degreesToRadians(angle)); + return vix * time; + } + + /** + * Calculate the max height of the projectile + * + * @param initialVelocity The starting velocity of the projectile (m/s) + * @param angle The angle that the projectile is launched at in degrees + * @return The max height that the projectile reaches (meters) + */ + public static double maxHeight(double initialVelocity, double angle) { + return maxHeight(initialVelocity, angle, GRAVITY); + } + + /** + * Calculate the max height of the projectile with custom gravity + * + * @param initialVelocity The starting velocity of the projectile (m/s) + * @param angle The angle that the projectile is launched at in degrees + * @param gravity The value used for the gravity constant (m/s^2) + * @return The max height that the projectile reaches (meters) + */ + public static double maxHeight(double initialVelocity, double angle, double gravity) { + double viy = initialVelocity * Math.sin(degreesToRadians(angle)); + return Math.pow(viy, 2) / (2.0 * gravity); + } +} diff --git a/src/main/java/com/thealgorithms/physics/ProjectileMotion.java b/src/main/java/com/thealgorithms/physics/ProjectileMotion.java new file mode 100644 index 000000000000..cfc79547922c --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/ProjectileMotion.java @@ -0,0 +1,96 @@ +package com.thealgorithms.physics; + +/** + * + * This implementation calculates the flight path of a projectile launched from any INITIAL HEIGHT. + * It is a more flexible version of the ground-to-ground model. + * + * @see Wikipedia - Projectile Motion + * @author [Priyanshu Kumar Singh](https://github.com/Priyanshu1303d) + */ +public final class ProjectileMotion { + + private ProjectileMotion() { + } + + /** Standard Earth gravity constant*/ + private static final double GRAVITY = 9.80665; + + /** + * A simple container for the results of a projectile motion calculation. + */ + public static final class Result { + private final double timeOfFlight; + private final double horizontalRange; + private final double maxHeight; + + public Result(double timeOfFlight, double horizontalRange, double maxHeight) { + this.timeOfFlight = timeOfFlight; + this.horizontalRange = horizontalRange; + this.maxHeight = maxHeight; + } + + /** @return The total time the projectile is in the air (seconds). */ + public double getTimeOfFlight() { + return timeOfFlight; + } + + /** @return The total horizontal distance traveled (meters). */ + public double getHorizontalRange() { + return horizontalRange; + } + + /** @return The maximum vertical height from the ground (meters). */ + public double getMaxHeight() { + return maxHeight; + } + } + + /** + * Calculates projectile trajectory using standard Earth gravity. + * + * @param initialVelocity Initial speed of the projectile (m/s). + * @param launchAngleDegrees Launch angle from the horizontal (degrees). + * @param initialHeight Starting height of the projectile (m). + * @return A {@link Result} object with the trajectory data. + */ + public static Result calculateTrajectory(double initialVelocity, double launchAngleDegrees, double initialHeight) { + return calculateTrajectory(initialVelocity, launchAngleDegrees, initialHeight, GRAVITY); + } + + /** + * Calculates projectile trajectory with a custom gravity value. + * + * @param initialVelocity Initial speed (m/s). Must be non-negative. + * @param launchAngleDegrees Launch angle (degrees). + * @param initialHeight Starting height (m). Must be non-negative. + * @param gravity Acceleration due to gravity (m/s^2). Must be positive. + * @return A {@link Result} object with the trajectory data. + */ + public static Result calculateTrajectory(double initialVelocity, double launchAngleDegrees, double initialHeight, double gravity) { + if (initialVelocity < 0 || initialHeight < 0 || gravity <= 0) { + throw new IllegalArgumentException("Velocity, height, and gravity must be non-negative, and gravity must be positive."); + } + + double launchAngleRadians = Math.toRadians(launchAngleDegrees); + double initialVerticalVelocity = initialVelocity * Math.sin(launchAngleRadians); // Initial vertical velocity + double initialHorizontalVelocity = initialVelocity * Math.cos(launchAngleRadians); // Initial horizontal velocity + + // Correctly calculate total time of flight using the quadratic formula for vertical motion. + // y(t) = y0 + initialVerticalVelocity*t - 0.5*g*t^2. We solve for t when y(t) = 0. + double totalTimeOfFlight = (initialVerticalVelocity + Math.sqrt(initialVerticalVelocity * initialVerticalVelocity + 2 * gravity * initialHeight)) / gravity; + + // Calculate max height. If launched downwards, max height is the initial height. + double maxHeight; + if (initialVerticalVelocity > 0) { + double heightGained = initialVerticalVelocity * initialVerticalVelocity / (2 * gravity); + maxHeight = initialHeight + heightGained; + } else { + maxHeight = initialHeight; + } + + double horizontalRange = initialHorizontalVelocity * totalTimeOfFlight; + + return new Result(totalTimeOfFlight, horizontalRange, maxHeight); + } +} diff --git a/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java b/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java new file mode 100644 index 000000000000..6de69c103b5a --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java @@ -0,0 +1,126 @@ +package com.thealgorithms.physics; + +/** + * Simulates a simple pendulum using the Runge-Kutta 4th order method. + * The pendulum is modeled with the nonlinear differential equation. + * + * @author [Yash Rajput](https://github.com/the-yash-rajput) + */ +public final class SimplePendulumRK4 { + + private SimplePendulumRK4() { + throw new AssertionError("No instances."); + } + + private final double length; // meters + private final double g; // acceleration due to gravity (m/s^2) + + /** + * Constructs a simple pendulum simulator. + * + * @param length the length of the pendulum in meters + * @param g the acceleration due to gravity in m/s^2 + */ + public SimplePendulumRK4(double length, double g) { + if (length <= 0) { + throw new IllegalArgumentException("Length must be positive"); + } + if (g <= 0) { + throw new IllegalArgumentException("Gravity must be positive"); + } + this.length = length; + this.g = g; + } + + /** + * Computes the derivatives of the state vector. + * State: [theta, omega] where theta is angle and omega is angular velocity. + * + * @param state the current state [theta, omega] + * @return the derivatives [dtheta/dt, domega/dt] + */ + private double[] derivatives(double[] state) { + double theta = state[0]; + double omega = state[1]; + double dtheta = omega; + double domega = -(g / length) * Math.sin(theta); + return new double[] {dtheta, domega}; + } + + /** + * Performs one time step using the RK4 method. + * + * @param state the current state [theta, omega] + * @param dt the time step size + * @return the new state after time dt + */ + public double[] stepRK4(double[] state, double dt) { + if (state == null || state.length != 2) { + throw new IllegalArgumentException("State must be array of length 2"); + } + if (dt <= 0) { + throw new IllegalArgumentException("Time step must be positive"); + } + + double[] k1 = derivatives(state); + double[] s2 = new double[] {state[0] + 0.5 * dt * k1[0], state[1] + 0.5 * dt * k1[1]}; + + double[] k2 = derivatives(s2); + double[] s3 = new double[] {state[0] + 0.5 * dt * k2[0], state[1] + 0.5 * dt * k2[1]}; + + double[] k3 = derivatives(s3); + double[] s4 = new double[] {state[0] + dt * k3[0], state[1] + dt * k3[1]}; + + double[] k4 = derivatives(s4); + + double thetaNext = state[0] + dt / 6.0 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]); + double omegaNext = state[1] + dt / 6.0 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]); + + return new double[] {thetaNext, omegaNext}; + } + + /** + * Simulates the pendulum for a given duration. + * + * @param initialState the initial state [theta, omega] + * @param dt the time step size + * @param steps the number of steps to simulate + * @return array of states at each step + */ + public double[][] simulate(double[] initialState, double dt, int steps) { + double[][] trajectory = new double[steps + 1][2]; + trajectory[0] = initialState.clone(); + + double[] currentState = initialState.clone(); + for (int i = 1; i <= steps; i++) { + currentState = stepRK4(currentState, dt); + trajectory[i] = currentState.clone(); + } + + return trajectory; + } + + /** + * Calculates the total energy of the pendulum. + * E = (1/2) * m * L^2 * omega^2 + m * g * L * (1 - cos(theta)) + * We use m = 1 for simplicity. + * + * @param state the current state [theta, omega] + * @return the total energy + */ + public double calculateEnergy(double[] state) { + double theta = state[0]; + double omega = state[1]; + double kineticEnergy = 0.5 * length * length * omega * omega; + double potentialEnergy = g * length * (1 - Math.cos(theta)); + return kineticEnergy + potentialEnergy; + } + + public double getLength() { + return length; + } + + public double getGravity() { + return g; + } +} diff --git a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java new file mode 100644 index 000000000000..14f1f97450a0 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java @@ -0,0 +1,195 @@ +package com.thealgorithms.randomized; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * Implementation of Karger's Minimum Cut algorithm. + * + *

Karger's algorithm is a randomized algorithm to compute the minimum cut of a connected graph. + * A minimum cut is the smallest set of edges that, if removed, would split the graph into two + * disconnected components. + * + *

The algorithm works by repeatedly contracting random edges in the graph until only two + * nodes remain. The edges between these two nodes represent a cut. By running the algorithm + * multiple times and keeping track of the smallest cut found, the probability of finding the + * true minimum cut increases. + * + *

Key steps of the algorithm: + *

    + *
  1. Randomly select an edge and contract it, merging the two nodes into one.
  2. + *
  3. Repeat the contraction process until only two nodes remain.
  4. + *
  5. Count the edges between the two remaining nodes to determine the cut size.
  6. + *
  7. Repeat the process multiple times to improve the likelihood of finding the true minimum cut.
  8. + *
+ *

+ * See more: Karger's algorithm + * + * @author MuhammadEzzatHBK + */ +public final class KargerMinCut { + + /** + * Output of the Karger algorithm. + * + * @param first The first set of nodes in the cut. + * @param second The second set of nodes in the cut. + * @param minCut The size of the minimum cut. + */ + public record KargerOutput(Set first, Set second, int minCut) { + } + + private KargerMinCut() { + } + + public static KargerOutput findMinCut(Collection nodeSet, List edges) { + return findMinCut(nodeSet, edges, 100); + } + + /** + * Finds the minimum cut of a graph using Karger's algorithm. + * + * @param nodeSet: Input graph nodes + * @param edges: Input graph edges + * @param iterations: Iterations to run the algorithms for, more iterations = more accuracy + * @return A KargerOutput object containing the two sets of nodes and the size of the minimum cut. + */ + public static KargerOutput findMinCut(Collection nodeSet, List edges, int iterations) { + Graph graph = new Graph(nodeSet, edges); + KargerOutput minCut = new KargerOutput(new HashSet<>(), new HashSet<>(), Integer.MAX_VALUE); + KargerOutput output; + + // Run the algorithm multiple times to increase the probability of finding + for (int i = 0; i < iterations; i++) { + Graph clone = graph.copy(); + output = clone.findMinCut(); + if (output.minCut < minCut.minCut) { + minCut = output; + } + } + return minCut; + } + + private static class DisjointSetUnion { + private final int[] parent; + int setCount; + + DisjointSetUnion(int size) { + parent = new int[size]; + for (int i = 0; i < size; i++) { + parent[i] = i; + } + setCount = size; + } + + int find(int i) { + // If it's not its own parent, then it's not the root of its set + if (parent[i] != i) { + // Recursively find the root of its parent + // and update i's parent to point directly to the root (path compression) + parent[i] = find(parent[i]); + } + + // Return the root (representative) of the set + return parent[i]; + } + + void union(int u, int v) { + // Find the root of each node + int rootU = find(u); + int rootV = find(v); + + // If they belong to different sets, merge them + if (rootU != rootV) { + // Make rootV point to rootU โ€” merge the two sets + parent[rootV] = rootU; + + // Reduce the count of disjoint sets by 1 + setCount--; + } + } + + boolean inSameSet(int u, int v) { + return find(u) == find(v); + } + + /* + This is a verbosity method, it's not a part of the core algorithm, + But it helps us provide more useful output. + */ + Set getAnySet() { + int aRoot = find(0); // Get one of the two roots + + Set set = new HashSet<>(); + for (int i = 0; i < parent.length; i++) { + if (find(i) == aRoot) { + set.add(i); + } + } + + return set; + } + } + + private static class Graph { + private final List nodes; + private final List edges; + + Graph(Collection nodeSet, List edges) { + this.nodes = new ArrayList<>(nodeSet); + this.edges = new ArrayList<>(); + for (int[] e : edges) { + this.edges.add(new int[] {e[0], e[1]}); + } + } + + Graph copy() { + return new Graph(this.nodes, this.edges); + } + + KargerOutput findMinCut() { + DisjointSetUnion dsu = new DisjointSetUnion(nodes.size()); + List workingEdges = new ArrayList<>(edges); + + Random rand = new Random(); + + while (dsu.setCount > 2) { + int[] e = workingEdges.get(rand.nextInt(workingEdges.size())); + if (!dsu.inSameSet(e[0], e[1])) { + dsu.union(e[0], e[1]); + } + } + + int cutEdges = 0; + for (int[] e : edges) { + if (!dsu.inSameSet(e[0], e[1])) { + cutEdges++; + } + } + + return collectResult(dsu, cutEdges); + } + + /* + This is a verbosity method, it's not a part of the core algorithm, + But it helps us provide more useful output. + */ + private KargerOutput collectResult(DisjointSetUnion dsu, int cutEdges) { + Set firstIndices = dsu.getAnySet(); + Set firstSet = new HashSet<>(); + Set secondSet = new HashSet<>(); + for (int i = 0; i < nodes.size(); i++) { + if (firstIndices.contains(i)) { + firstSet.add(nodes.get(i)); + } else { + secondSet.add(nodes.get(i)); + } + } + return new KargerOutput(firstSet, secondSet, cutEdges); + } + } +} diff --git a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java new file mode 100644 index 000000000000..05d7abbbcd6c --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java @@ -0,0 +1,82 @@ +package com.thealgorithms.randomized; + +import java.util.Random; +import java.util.function.Function; + +/** + * A demonstration of the Monte Carlo integration algorithm in Java. + * + *

This class estimates the value of definite integrals using randomized sampling, + * also known as the Monte Carlo method. It is particularly effective for: + *

    + *
  • Functions that are difficult or impossible to integrate analytically
  • + *
  • High-dimensional integrals where traditional methods are inefficient
  • + *
  • Simulation and probabilistic analysis tasks
  • + *
+ * + *

The core idea is to sample random points uniformly from the integration domain, + * evaluate the function at those points, and compute the scaled average to estimate the integral. + * + *

For a one-dimensional integral over [a, b], the approximation is the function range (b-a), + * multiplied by the function average result for a random sample. + * See more: Monte Carlo Integration + * + * @author: MuhammadEzzatHBK + */ + +public final class MonteCarloIntegration { + + private MonteCarloIntegration() { + } + + /** + * Approximates the definite integral of a given function over a specified + * interval using the Monte Carlo method with a fixed random seed for + * reproducibility. + * + * @param fx the function to integrate + * @param a the lower bound of the interval + * @param b the upper bound of the interval + * @param n the number of random samples to use + * @param seed the seed for the random number generator + * @return the approximate value of the integral + */ + public static double approximate(Function fx, double a, double b, int n, long seed) { + return doApproximate(fx, a, b, n, new Random(seed)); + } + + /** + * Approximates the definite integral of a given function over a specified + * interval using the Monte Carlo method with a random seed based on the + * current system time for more randomness. + * + * @param fx the function to integrate + * @param a the lower bound of the interval + * @param b the upper bound of the interval + * @param n the number of random samples to use + * @return the approximate value of the integral + */ + public static double approximate(Function fx, double a, double b, int n) { + return doApproximate(fx, a, b, n, new Random(System.currentTimeMillis())); + } + + private static double doApproximate(Function fx, double a, double b, int n, Random generator) { + if (!validate(fx, a, b, n)) { + throw new IllegalArgumentException("Invalid input parameters"); + } + double totalArea = 0.0; + double interval = b - a; + for (int i = 0; i < n; i++) { + double x = a + generator.nextDouble() * interval; + totalArea += fx.apply(x); + } + return interval * totalArea / n; + } + + private static boolean validate(Function fx, double a, double b, int n) { + boolean isFunctionValid = fx != null; + boolean isIntervalValid = a < b; + boolean isSampleSizeValid = n > 0; + return isFunctionValid && isIntervalValid && isSampleSizeValid; + } +} diff --git a/src/main/java/com/thealgorithms/randomized/RandomizedClosestPair.java b/src/main/java/com/thealgorithms/randomized/RandomizedClosestPair.java new file mode 100644 index 000000000000..616f7fb7d7cf --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/RandomizedClosestPair.java @@ -0,0 +1,113 @@ +package com.thealgorithms.randomized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + +/** + * Randomized Closest Pair of Points Algorithm + * + * Use Case: + * - Efficiently finds the closest pair of points in a 2D plane. + * - Applicable in computational geometry, clustering, and graphics. + * + * Time Complexity: + * - Expected: O(n log n) using randomized divide and conquer + * + * @see Closest Pair of Points - Wikipedia + */ +public final class RandomizedClosestPair { + + // Prevent instantiation of utility class + private RandomizedClosestPair() { + throw new UnsupportedOperationException("Utility class"); + } + + public static class Point { + public final double x; + public final double y; + + public Point(double x, double y) { + this.x = x; + this.y = y; + } + } + + public static double findClosestPairDistance(Point[] points) { + List shuffled = new ArrayList<>(Arrays.asList(points)); + Collections.shuffle(shuffled, new Random()); + + Point[] px = shuffled.toArray(new Point[0]); + Arrays.sort(px, Comparator.comparingDouble(p -> p.x)); + + Point[] py = px.clone(); + Arrays.sort(py, Comparator.comparingDouble(p -> p.y)); + + return closestPair(px, py); + } + + private static double closestPair(Point[] px, Point[] py) { + int n = px.length; + if (n <= 3) { + return bruteForce(px); + } + + int mid = n / 2; + Point midPoint = px[mid]; + + Point[] qx = Arrays.copyOfRange(px, 0, mid); + Point[] rx = Arrays.copyOfRange(px, mid, n); + + List qy = new ArrayList<>(); + List ry = new ArrayList<>(); + for (Point p : py) { + if (p.x <= midPoint.x) { + qy.add(p); + } else { + ry.add(p); + } + } + + double d1 = closestPair(qx, qy.toArray(new Point[0])); + double d2 = closestPair(rx, ry.toArray(new Point[0])); + + double d = Math.min(d1, d2); + + List strip = new ArrayList<>(); + for (Point p : py) { + if (Math.abs(p.x - midPoint.x) < d) { + strip.add(p); + } + } + + return Math.min(d, stripClosest(strip, d)); + } + + private static double bruteForce(Point[] points) { + double min = Double.POSITIVE_INFINITY; + for (int i = 0; i < points.length; i++) { + for (int j = i + 1; j < points.length; j++) { + min = Math.min(min, distance(points[i], points[j])); + } + } + return min; + } + + private static double stripClosest(List strip, double d) { + double min = d; + int n = strip.size(); + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n && (strip.get(j).y - strip.get(i).y) < min; j++) { + min = Math.min(min, distance(strip.get(i), strip.get(j))); + } + } + return min; + } + + private static double distance(Point p1, Point p2) { + return Math.hypot(p1.x - p2.x, p1.y - p2.y); + } +} diff --git a/src/main/java/com/thealgorithms/randomized/RandomizedMatrixMultiplicationVerification.java b/src/main/java/com/thealgorithms/randomized/RandomizedMatrixMultiplicationVerification.java new file mode 100644 index 000000000000..b5ac7076bfd6 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/RandomizedMatrixMultiplicationVerification.java @@ -0,0 +1,64 @@ +package com.thealgorithms.randomized; + +import java.util.Random; + +public final class RandomizedMatrixMultiplicationVerification { + + private RandomizedMatrixMultiplicationVerification() { + // Prevent instantiation of utility class + } + + /** + * Verifies whether A ร— B == C using Freivalds' algorithm. + * @param A Left matrix + * @param B Right matrix + * @param C Product matrix to verify + * @param iterations Number of randomized checks + * @return true if likely Aร—B == C; false if definitely not + */ + public static boolean verify(int[][] a, int[][] b, int[][] c, int iterations) { + int n = a.length; + Random random = new Random(); + + for (int iter = 0; iter < iterations; iter++) { + // Step 1: Generate random 0/1 vector + int[] r = new int[n]; + for (int i = 0; i < n; i++) { + r[i] = random.nextInt(2); + } + + // Step 2: Compute br = b ร— r + int[] br = new int[n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + br[i] += b[i][j] * r[j]; + } + } + + // Step 3: Compute a(br) + int[] abr = new int[n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + abr[i] += a[i][j] * br[j]; + } + } + + // Step 4: Compute cr = c ร— r + int[] cr = new int[n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + cr[i] += c[i][j] * r[j]; + } + } + + // Step 5: Compare abr and cr + for (int i = 0; i < n; i++) { + if (abr[i] != cr[i]) { + return false; + } + } + } + + return true; + } +} diff --git a/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java b/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java new file mode 100644 index 000000000000..e9af223a0622 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/RandomizedQuickSort.java @@ -0,0 +1,68 @@ +package com.thealgorithms.randomized; + +/** + * This class implements the Randomized QuickSort algorithm. + * It selects a pivot randomly to improve performance on sorted or nearly sorted data. + * @author Vibhu Khera + */ +public final class RandomizedQuickSort { + + private RandomizedQuickSort() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Sorts the array using the randomized quicksort algorithm. + * + * @param arr the array to sort + * @param low the starting index of the array + * @param high the ending index of the array + */ + public static void randomizedQuickSort(int[] arr, int low, int high) { + if (low < high) { + int pivotIndex = partition(arr, low, high); + randomizedQuickSort(arr, low, pivotIndex - 1); + randomizedQuickSort(arr, pivotIndex + 1, high); + } + } + + /** + * Partitions the array around a pivot chosen randomly. + * + * @param arr the array to partition + * @param low the starting index + * @param high the ending index + * @return the index of the pivot after partitioning + */ + private static int partition(int[] arr, int low, int high) { + int pivotIndex = low + (int) (Math.random() * (high - low + 1)); + int pivotValue = arr[pivotIndex]; + swap(arr, pivotIndex, high); // Move pivot to end + int storeIndex = low; + for (int i = low; i < high; i++) { + if (arr[i] < pivotValue) { + swap(arr, storeIndex, i); + storeIndex++; + } + } + swap(arr, storeIndex, high); // Move pivot to its final place + return storeIndex; + } + + /** + * Swaps two elements in the array, only if the indices are different. + * + * @param arr the array in which elements are to be swapped + * @param i the first index + * @param j the second index + */ + private static void swap(int[] arr, int i, int j) { + // Skip if indices are the same + if (i == j) { + return; + } + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + } +} diff --git a/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java b/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java new file mode 100644 index 000000000000..05e70f635055 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/ReservoirSampling.java @@ -0,0 +1,55 @@ +package com.thealgorithms.randomized; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Reservoir Sampling Algorithm + * + * Use Case: + * - Efficient for selecting k random items from a stream of unknown size + * - Used in streaming systems, big data, and memory-limited environments + * + * Time Complexity: O(n) + * Space Complexity: O(k) + * + * @author Michael Alexander Montoya (@cureprotocols) + * @see Reservoir Sampling - Wikipedia + */ +public final class ReservoirSampling { + + // Prevent instantiation of utility class + private ReservoirSampling() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Selects k random elements from a stream using reservoir sampling. + * + * @param stream The input stream as an array of integers. + * @param sampleSize The number of elements to sample. + * @return A list containing k randomly selected elements. + */ + public static List sample(int[] stream, int sampleSize) { + if (sampleSize > stream.length) { + throw new IllegalArgumentException("Sample size cannot exceed stream size."); + } + + List reservoir = new ArrayList<>(sampleSize); + Random rand = new Random(); + + for (int i = 0; i < stream.length; i++) { + if (i < sampleSize) { + reservoir.add(stream[i]); + } else { + int j = rand.nextInt(i + 1); + if (j < sampleSize) { + reservoir.set(j, stream[i]); + } + } + } + + return reservoir; + } +} diff --git a/src/main/java/com/thealgorithms/recursion/DiceThrower.java b/src/main/java/com/thealgorithms/recursion/DiceThrower.java new file mode 100644 index 000000000000..f46d82213aaa --- /dev/null +++ b/src/main/java/com/thealgorithms/recursion/DiceThrower.java @@ -0,0 +1,113 @@ +package com.thealgorithms.recursion; + +import java.util.ArrayList; +import java.util.List; + +/** + * DiceThrower - Generates all possible dice roll combinations that sum to a target + * + * This algorithm uses recursive backtracking to find all combinations of dice rolls + * (faces 1-6) that sum to a given target value. + * + * Example: If target = 4, possible combinations include: + * - "1111" (1+1+1+1 = 4) + * - "13" (1+3 = 4) + * - "22" (2+2 = 4) + * - "4" (4 = 4) + * + * @author BEASTSHRIRAM + * @see Backtracking Algorithm + */ +public final class DiceThrower { + + private DiceThrower() { + // Utility class + } + + /** + * Returns all possible dice roll combinations that sum to the target + * + * @param target the target sum to achieve with dice rolls + * @return list of all possible combinations as strings + */ + public static List getDiceCombinations(int target) { + if (target < 0) { + throw new IllegalArgumentException("Target must be non-negative"); + } + return generateCombinations("", target); + } + + /** + * Prints all possible dice roll combinations that sum to the target + * + * @param target the target sum to achieve with dice rolls + */ + public static void printDiceCombinations(int target) { + if (target < 0) { + throw new IllegalArgumentException("Target must be non-negative"); + } + printCombinations("", target); + } + + /** + * Recursive helper method to generate all combinations + * + * @param current the current combination being built + * @param remaining the remaining sum needed + * @return list of all combinations from this state + */ + private static List generateCombinations(String current, int remaining) { + List combinations = new ArrayList<>(); + + // Base case: if remaining sum is 0, we found a valid combination + if (remaining == 0) { + combinations.add(current); + return combinations; + } + + // Try all possible dice faces (1-6), but not more than remaining sum + for (int face = 1; face <= 6 && face <= remaining; face++) { + List subCombinations = generateCombinations(current + face, remaining - face); + combinations.addAll(subCombinations); + } + + return combinations; + } + + /** + * Recursive helper method to print all combinations + * + * @param current the current combination being built + * @param remaining the remaining sum needed + */ + private static void printCombinations(String current, int remaining) { + // Base case: if remaining sum is 0, we found a valid combination + if (remaining == 0) { + System.out.println(current); + return; + } + + // Try all possible dice faces (1-6), but not more than remaining sum + for (int face = 1; face <= 6 && face <= remaining; face++) { + printCombinations(current + face, remaining - face); + } + } + + /** + * Demo method to show usage + * + * @param args command line arguments + */ + public static void main(String[] args) { + int target = 4; + + System.out.println("All dice combinations that sum to " + target + ":"); + List combinations = getDiceCombinations(target); + + for (String combination : combinations) { + System.out.println(combination); + } + + System.out.println("\nTotal combinations: " + combinations.size()); + } +} diff --git a/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java b/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java index 5a3ff2e88040..0114a55e5b75 100644 --- a/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java +++ b/src/main/java/com/thealgorithms/recursion/GenerateSubsets.java @@ -1,36 +1,52 @@ package com.thealgorithms.recursion; -// program to find power set of a string - import java.util.ArrayList; import java.util.List; +/** + * Utility class to generate all subsets (power set) of a given string using recursion. + * + *

For example, the string "ab" will produce: ["ab", "a", "b", ""] + */ public final class GenerateSubsets { private GenerateSubsets() { - throw new UnsupportedOperationException("Utility class"); } + /** + * Generates all subsets (power set) of the given string using recursion. + * + * @param str the input string to generate subsets for + * @return a list of all subsets of the input string + */ public static List subsetRecursion(String str) { - return doRecursion("", str); + return generateSubsets("", str); } - private static List doRecursion(String p, String up) { - if (up.isEmpty()) { - List list = new ArrayList<>(); - list.add(p); - return list; + /** + * Recursive helper method to generate subsets by including or excluding characters. + * + * @param current the current prefix being built + * @param remaining the remaining string to process + * @return list of subsets formed from current and remaining + */ + private static List generateSubsets(String current, String remaining) { + if (remaining.isEmpty()) { + List result = new ArrayList<>(); + result.add(current); + return result; } - // Taking the character - char ch = up.charAt(0); - // Adding the character in the recursion - List left = doRecursion(p + ch, up.substring(1)); - // Not adding the character in the recursion - List right = doRecursion(p, up.substring(1)); + char ch = remaining.charAt(0); + String next = remaining.substring(1); + + // Include the character + List withChar = generateSubsets(current + ch, next); - left.addAll(right); + // Exclude the character + List withoutChar = generateSubsets(current, next); - return left; + withChar.addAll(withoutChar); + return withChar; } } diff --git a/src/main/java/com/thealgorithms/recursion/SylvesterSequence.java b/src/main/java/com/thealgorithms/recursion/SylvesterSequence.java new file mode 100644 index 000000000000..2b39f66c7936 --- /dev/null +++ b/src/main/java/com/thealgorithms/recursion/SylvesterSequence.java @@ -0,0 +1,50 @@ +package com.thealgorithms.recursion; + +import java.math.BigInteger; + +/** + * A utility class for calculating numbers in Sylvester's sequence. + * + *

Sylvester's sequence is a sequence of integers where each term is calculated + * using the formula: + *

+ * a(n) = a(n-1) * (a(n-1) - 1) + 1
+ * 
+ * with the first term being 2. + * + *

This class is final and cannot be instantiated. + * + * @see Wikipedia: Sylvester sequence + */ +public final class SylvesterSequence { + + // Private constructor to prevent instantiation + private SylvesterSequence() { + } + + /** + * Calculates the nth number in Sylvester's sequence. + * + *

The sequence is defined recursively, with the first term being 2: + *

+     * a(1) = 2
+     * a(n) = a(n-1) * (a(n-1) - 1) + 1 for n > 1
+     * 
+ * + * @param n the position in the sequence (must be greater than 0) + * @return the nth number in Sylvester's sequence + * @throws IllegalArgumentException if n is less than or equal to 0 + */ + public static BigInteger sylvester(int n) { + if (n <= 0) { + throw new IllegalArgumentException("sylvester() does not accept negative numbers or zero."); + } + if (n == 1) { + return BigInteger.valueOf(2); + } else { + BigInteger prev = sylvester(n - 1); + // Sylvester sequence formula: a(n) = a(n-1) * (a(n-1) - 1) + 1 + return prev.multiply(prev.subtract(BigInteger.ONE)).add(BigInteger.ONE); + } + } +} diff --git a/src/main/java/com/thealgorithms/scheduling/RRScheduling.java b/src/main/java/com/thealgorithms/scheduling/RRScheduling.java index 110c97416a42..05efe1d59141 100644 --- a/src/main/java/com/thealgorithms/scheduling/RRScheduling.java +++ b/src/main/java/com/thealgorithms/scheduling/RRScheduling.java @@ -1,7 +1,3 @@ -/** - * @author Md Asif Joardar - */ - package com.thealgorithms.scheduling; import com.thealgorithms.devutils.entities.ProcessDetails; @@ -11,6 +7,7 @@ import java.util.Queue; /** + * @author Md Asif Joardar * The Round-robin scheduling algorithm is a kind of preemptive First come, First Serve CPU * Scheduling algorithm. This can be understood here - * https://www.scaler.com/topics/round-robin-scheduling-in-os/ diff --git a/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java b/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java index cbbc65a3afc5..e3f4a8d03d07 100644 --- a/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java +++ b/src/main/java/com/thealgorithms/scheduling/SJFScheduling.java @@ -2,78 +2,62 @@ import com.thealgorithms.devutils.entities.ProcessDetails; import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; import java.util.List; /** - * Implementation of Shortest Job First Algorithm: The algorithm allows the waiting process with the - * minimal burst time to be executed first. see more here: - * https://www.guru99.com/shortest-job-first-sjf-scheduling.html + * Shortest Job First (SJF) Scheduling Algorithm: + * Executes processes with the shortest burst time first among the ones that have arrived. */ - public class SJFScheduling { - protected ArrayList processes; - protected ArrayList schedule; - - private static void sortProcessesByArrivalTime(List processes) { - for (int i = 0; i < processes.size(); i++) { - for (int j = i + 1; j < processes.size() - 1; j++) { - if (processes.get(j).getArrivalTime() > processes.get(j + 1).getArrivalTime()) { - final var temp = processes.get(j); - processes.set(j, processes.get(j + 1)); - processes.set(j + 1, temp); - } - } - } - } + private final List processes; + private final List schedule; - /** - * a simple constructor - * @param processes a list of processes the user wants to schedule - * it also sorts the processes based on the time of their arrival - */ - SJFScheduling(final ArrayList processes) { - this.processes = processes; - schedule = new ArrayList<>(); + public SJFScheduling(final List processes) { + this.processes = new ArrayList<>(processes); + this.schedule = new ArrayList<>(); sortProcessesByArrivalTime(this.processes); } - protected void sortByArrivalTime() { - sortProcessesByArrivalTime(processes); + + private static void sortProcessesByArrivalTime(List processes) { + processes.sort(Comparator.comparingInt(ProcessDetails::getArrivalTime)); } /** - * this functions returns the order of the executions + * Executes the SJF scheduling algorithm and builds the execution order. */ - public void scheduleProcesses() { - ArrayList ready = new ArrayList<>(); - + List ready = new ArrayList<>(); int size = processes.size(); - int runtime; int time = 0; int executed = 0; - int j; - int k = 0; - ProcessDetails running; - if (size == 0) { - return; + Iterator processIterator = processes.iterator(); + + // This will track the next process to be checked for arrival time + ProcessDetails nextProcess = null; + if (processIterator.hasNext()) { + nextProcess = processIterator.next(); } while (executed < size) { - while (k < size && processes.get(k).getArrivalTime() <= time) // here we find the processes that have arrived. - { - ready.add(processes.get(k)); - k++; + // Load all processes that have arrived by current time + while (nextProcess != null && nextProcess.getArrivalTime() <= time) { + ready.add(nextProcess); + if (processIterator.hasNext()) { + nextProcess = processIterator.next(); + } else { + nextProcess = null; + } } - running = findShortestJob(ready); + ProcessDetails running = findShortestJob(ready); if (running == null) { time++; } else { - runtime = running.getBurstTime(); - for (j = 0; j < runtime; j++) { - time++; - } + time += running.getBurstTime(); schedule.add(running.getProcessId()); ready.remove(running); executed++; @@ -82,30 +66,23 @@ public void scheduleProcesses() { } /** - * this function evaluates the shortest job of all the ready processes (based on a process - * burst time) + * Finds the process with the shortest job of all the ready processes (based on a process * @param readyProcesses an array list of ready processes * @return returns the process' with the shortest burst time OR NULL if there are no ready * processes */ - private ProcessDetails findShortestJob(List readyProcesses) { - if (readyProcesses.isEmpty()) { - return null; - } - int i; - int size = readyProcesses.size(); - int minBurstTime = readyProcesses.get(0).getBurstTime(); - int temp; - int positionOfShortestJob = 0; + private ProcessDetails findShortestJob(Collection readyProcesses) { + return readyProcesses.stream().min(Comparator.comparingInt(ProcessDetails::getBurstTime)).orElse(null); + } - for (i = 1; i < size; i++) { - temp = readyProcesses.get(i).getBurstTime(); - if (minBurstTime > temp) { - minBurstTime = temp; - positionOfShortestJob = i; - } - } + /** + * Returns the computed schedule after calling scheduleProcesses(). + */ + public List getSchedule() { + return schedule; + } - return readyProcesses.get(positionOfShortestJob); + public List getProcesses() { + return List.copyOf(processes); } } diff --git a/src/main/java/com/thealgorithms/searches/BoyerMoore.java b/src/main/java/com/thealgorithms/searches/BoyerMoore.java new file mode 100644 index 000000000000..6998021503cc --- /dev/null +++ b/src/main/java/com/thealgorithms/searches/BoyerMoore.java @@ -0,0 +1,58 @@ +package com.thealgorithms.searches; + +/** + * Boyer-Moore string search algorithm. + * Efficient algorithm for substring search. + * https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm + */ +public class BoyerMoore { + + private final int radix; // Radix (number of possible characters) + private final int[] right; // Bad character rule table + private final String pattern; + + public BoyerMoore(String pat) { + this.pattern = pat; + this.radix = 256; + this.right = new int[radix]; + + for (int c = 0; c < radix; c++) { + right[c] = -1; + } + + for (int j = 0; j < pat.length(); j++) { + right[pat.charAt(j)] = j; + } + } + + public int search(String text) { + if (pattern.isEmpty()) { + return 0; + } + + int m = pattern.length(); + int n = text.length(); + + int skip; + for (int i = 0; i <= n - m; i += skip) { + skip = 0; + for (int j = m - 1; j >= 0; j--) { + char txtChar = text.charAt(i + j); + char patChar = pattern.charAt(j); + if (patChar != txtChar) { + skip = Math.max(1, j - right[txtChar]); + break; + } + } + if (skip == 0) { + return i; // Match found + } + } + + return -1; // No match + } + + public static int staticSearch(String text, String pattern) { + return new BoyerMoore(pattern).search(text); + } +} diff --git a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java index 2124938bc258..78dac0f0a712 100644 --- a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java +++ b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java @@ -15,6 +15,7 @@ * Note: This algorithm requires that the input array be sorted. *

*/ +@SuppressWarnings({"rawtypes", "unchecked"}) public class FibonacciSearch implements SearchAlgorithm { /** diff --git a/src/main/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearch.java b/src/main/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearch.java index 3b4b0b08377f..b40c7554d84b 100644 --- a/src/main/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearch.java @@ -29,7 +29,7 @@ public > int[] find(T[][] matrix, T key) { public static > int[] search(T[][] matrix, T target) { int rowPointer = 0; // The pointer at 0th row - int colPointer = matrix.length - 1; // The pointer at end column + int colPointer = matrix[0].length - 1; // The pointer at end column while (rowPointer < matrix.length && colPointer >= 0) { int comp = target.compareTo(matrix[rowPointer][colPointer]); diff --git a/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java b/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java index 91fda373dca7..b53c7e5256ca 100644 --- a/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java +++ b/src/main/java/com/thealgorithms/searches/SearchInARowAndColWiseSortedMatrix.java @@ -15,7 +15,6 @@ public int[] search(int[][] matrix, int value) { // This variable iterates over columns int j = n - 1; int[] result = {-1, -1}; - while (i < n && j >= 0) { if (matrix[i][j] == value) { result[0] = i; diff --git a/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java b/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java new file mode 100644 index 000000000000..1a5903a5d134 --- /dev/null +++ b/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java @@ -0,0 +1,99 @@ +package com.thealgorithms.searches; + +import com.thealgorithms.devutils.searches.SearchAlgorithm; + +/** + * Sentinel Linear Search is a variation of linear search that eliminates the + * need to check the array bounds in each iteration by placing the search key + * at the end of the array as a sentinel value. + * + *

+ * The algorithm works by: + * 1. Storing the last element of the array + * 2. Placing the search key at the last position (sentinel) + * 3. Searching from the beginning without bound checking + * 4. If found before the last position, return the index + * 5. If found at the last position, check if it was originally there + * + *

+ * Time Complexity: + * - Best case: O(1) - when the element is at the first position + * - Average case: O(n) - when the element is in the middle + * - Worst case: O(n) - when the element is not present + * + *

+ * Space Complexity: O(1) - only uses constant extra space + * + *

+ * Advantages over regular linear search: + * - Reduces the number of comparisons by eliminating bound checking + * - Slightly more efficient in practice due to fewer conditional checks + * + * @author TheAlgorithms Contributors + * @see LinearSearch + * @see SearchAlgorithm + */ +public class SentinelLinearSearch implements SearchAlgorithm { + /** + * Performs sentinel linear search on the given array. + * + * @param array the array to search in + * @param key the element to search for + * @param the type of elements in the array, must be Comparable + * @return the index of the first occurrence of the key, or -1 if not found + * @throws IllegalArgumentException if the array is null + */ + @Override + public > int find(T[] array, T key) { + if (array == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + + if (array.length == 0) { + return -1; + } + + if (key == null) { + return findNull(array); + } + + // Store the last element + T lastElement = array[array.length - 1]; + + // Place the sentinel (search key) at the end + array[array.length - 1] = key; + + int i = 0; + // Search without bound checking since sentinel guarantees we'll find the key + while (array[i].compareTo(key) != 0) { + i++; + } + + // Restore the original last element + array[array.length - 1] = lastElement; + + // Check if we found the key before the sentinel position + // or if the original last element was the key we were looking for + if (i < array.length - 1 || (lastElement != null && lastElement.compareTo(key) == 0)) { + return i; + } + + return -1; // Key not found + } + + /** + * Helper method to find null values in the array. + * + * @param array the array to search in + * @param the type of elements in the array + * @return the index of the first null element, or -1 if not found + */ + private > int findNull(T[] array) { + for (int i = 0; i < array.length; i++) { + if (array[i] == null) { + return i; + } + } + return -1; + } +} diff --git a/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java b/src/main/java/com/thealgorithms/slidingwindow/MaximumSlidingWindow.java similarity index 97% rename from src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java rename to src/main/java/com/thealgorithms/slidingwindow/MaximumSlidingWindow.java index d0b2c2a0e56d..f52ba28fd8b5 100644 --- a/src/main/java/com/thealgorithms/others/MaximumSlidingWindow.java +++ b/src/main/java/com/thealgorithms/slidingwindow/MaximumSlidingWindow.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.slidingwindow; import java.util.ArrayDeque; import java.util.Deque; diff --git a/src/main/java/com/thealgorithms/slidingwindow/MinimumWindowSubstring.java b/src/main/java/com/thealgorithms/slidingwindow/MinimumWindowSubstring.java new file mode 100644 index 000000000000..c1a5ac067ab5 --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/MinimumWindowSubstring.java @@ -0,0 +1,69 @@ +package com.thealgorithms.slidingwindow; + +import java.util.HashMap; + +/** + * Finds the minimum window substring in 's' that contains all characters of 't'. + * + * Worst-case performance O(n) + * Best-case performance O(n) + * Average performance O(n) + * Worst-case space complexity O(1) + * + * @author https://github.com/Chiefpatwal + */ +public final class MinimumWindowSubstring { + // Prevent instantiation + private MinimumWindowSubstring() { + } + + /** + * Finds the minimum window substring of 's' containing all characters of 't'. + * + * @param s The input string to search within. + * @param t The string with required characters. + * @return The minimum window substring, or empty string if not found. + */ + public static String minWindow(String s, String t) { + if (s.length() < t.length()) { + return ""; + } + + HashMap tFreq = new HashMap<>(); + for (char c : t.toCharArray()) { + tFreq.put(c, tFreq.getOrDefault(c, 0) + 1); + } + + HashMap windowFreq = new HashMap<>(); + int left = 0; + int right = 0; + int minLen = Integer.MAX_VALUE; + int count = 0; + String result = ""; + + while (right < s.length()) { + char c = s.charAt(right); + windowFreq.put(c, windowFreq.getOrDefault(c, 0) + 1); + + if (tFreq.containsKey(c) && windowFreq.get(c).intValue() <= tFreq.get(c).intValue()) { + count++; + } + + while (count == t.length()) { + if (right - left + 1 < minLen) { + minLen = right - left + 1; + result = s.substring(left, right + 1); + } + + char leftChar = s.charAt(left); + windowFreq.put(leftChar, windowFreq.get(leftChar) - 1); + if (tFreq.containsKey(leftChar) && windowFreq.get(leftChar) < tFreq.get(leftChar)) { + count--; + } + left++; + } + right++; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/slidingwindow/ShortestCoprimeSegment.java b/src/main/java/com/thealgorithms/slidingwindow/ShortestCoprimeSegment.java new file mode 100644 index 000000000000..b99f7ca7d62f --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/ShortestCoprimeSegment.java @@ -0,0 +1,135 @@ +package com.thealgorithms.slidingwindow; + +import java.util.Arrays; +import java.util.LinkedList; + +/** + * The Sliding Window technique together with 2-stack technique is used to find coprime segment of minimal size in an array. + * Segment a[i],...,a[i+l] is coprime if gcd(a[i], a[i+1], ..., a[i+l]) = 1 + *

+ * Run-time complexity: O(n log n) + * What is special about this 2-stack technique is that it enables us to remove element a[i] and find gcd(a[i+1],...,a[i+l]) in amortized O(1) time. + * For 'remove' worst-case would be O(n) operation, but this happens rarely. + * Main observation is that each element gets processed a constant amount of times, hence complexity will be: + * O(n log n), where log n comes from complexity of gcd. + *

+ * More generally, the 2-stack technique enables us to 'remove' an element fast if it is known how to 'add' an element fast to the set. + * In our case 'adding' is calculating d' = gcd(a[i],...,a[i+l+1]), when d = gcd(a[i],...a[i]) with d' = gcd(d, a[i+l+1]). + * and removing is find gcd(a[i+1],...,a[i+l]). We don't calculate it explicitly, but it is pushed in the stack which we can pop in O(1). + *

+ * One can change methods 'legalSegment' and function 'f' in DoubleStack to adapt this code to other sliding-window type problems. + * I recommend this article for more explanations: "CF Article">Article 1 or USACO Article + *

+ * Another method to solve this problem is through segment trees. Then query operation would have O(log n), not O(1) time, but runtime complexity would still be O(n log n) + * + * @author DomTr (Github) + */ +public final class ShortestCoprimeSegment { + // Prevent instantiation + private ShortestCoprimeSegment() { + } + + /** + * @param arr is the input array + * @return shortest segment in the array which has gcd equal to 1. If no such segment exists or array is empty, returns empty array + */ + public static long[] shortestCoprimeSegment(long[] arr) { + if (arr == null || arr.length == 0) { + return new long[] {}; + } + DoubleStack front = new DoubleStack(); + DoubleStack back = new DoubleStack(); + int n = arr.length; + int l = 0; + int shortestLength = n + 1; + int beginsAt = -1; // beginning index of the shortest coprime segment + for (int i = 0; i < n; i++) { + back.push(arr[i]); + while (legalSegment(front, back)) { + remove(front, back); + if (shortestLength > i - l + 1) { + beginsAt = l; + shortestLength = i - l + 1; + } + l++; + } + } + if (shortestLength > n) { + shortestLength = -1; + } + if (shortestLength == -1) { + return new long[] {}; + } + return Arrays.copyOfRange(arr, beginsAt, beginsAt + shortestLength); + } + + private static boolean legalSegment(DoubleStack front, DoubleStack back) { + return gcd(front.top(), back.top()) == 1; + } + + private static long gcd(long a, long b) { + if (a < b) { + return gcd(b, a); + } else if (b == 0) { + return a; + } else { + return gcd(a % b, b); + } + } + + /** + * This solves the problem of removing elements quickly. + * Even though the worst case of 'remove' method is O(n), it is a very pessimistic view. + * We will need to empty out 'back', only when 'from' is empty. + * Consider element x when it is added to stack 'back'. + * After some time 'front' becomes empty and x goes to 'front'. Notice that in the for-loop we proceed further and x will never come back to any stacks 'back' or 'front'. + * In other words, every element gets processed by a constant number of operations. + * So 'remove' amortized runtime is actually O(n). + */ + private static void remove(DoubleStack front, DoubleStack back) { + if (front.isEmpty()) { + while (!back.isEmpty()) { + front.push(back.pop()); + } + } + front.pop(); + } + + /** + * DoubleStack serves as a collection of two stacks. One is a normal stack called 'stack', the other 'values' stores gcd-s up until some index. + */ + private static class DoubleStack { + LinkedList stack; + LinkedList values; + + DoubleStack() { + values = new LinkedList<>(); + stack = new LinkedList<>(); + values.add(0L); // Initialise with 0 which is neutral element in terms of gcd, i.e. gcd(a,0) = a + } + + long f(long a, long b) { // Can be replaced with other function + return gcd(a, b); + } + + public void push(long x) { + stack.addLast(x); + values.addLast(f(values.getLast(), x)); + } + + public long top() { + return values.getLast(); + } + + public long pop() { + long res = stack.getLast(); + stack.removeLast(); + values.removeLast(); + return res; + } + + public boolean isEmpty() { + return stack.isEmpty(); + } + } +} diff --git a/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java b/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java index 2c71bae8b557..09ea349875ac 100644 --- a/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java +++ b/src/main/java/com/thealgorithms/sorts/AdaptiveMergeSort.java @@ -30,7 +30,7 @@ private > void merge(T[] array, T[] aux, int low, int mi array[k] = aux[j++]; } else if (j > high) { array[k] = aux[i++]; - } else if (aux[j].compareTo(aux[i]) < 0) { + } else if (SortUtils.less(aux[j], aux[i])) { array[k] = aux[j++]; } else { array[k] = aux[i++]; diff --git a/src/main/java/com/thealgorithms/sorts/BinaryInsertionSort.java b/src/main/java/com/thealgorithms/sorts/BinaryInsertionSort.java index b6f5d92e7928..b8086bd0ebca 100644 --- a/src/main/java/com/thealgorithms/sorts/BinaryInsertionSort.java +++ b/src/main/java/com/thealgorithms/sorts/BinaryInsertionSort.java @@ -22,7 +22,7 @@ public > T[] sort(T[] array) { while (low <= high) { final int mid = (low + high) >>> 1; - if (temp.compareTo(array[mid]) < 0) { + if (SortUtils.less(temp, array[mid])) { high = mid - 1; } else { low = mid + 1; diff --git a/src/main/java/com/thealgorithms/sorts/BitonicSort.java b/src/main/java/com/thealgorithms/sorts/BitonicSort.java index 90d204818729..1c1a3ac45540 100644 --- a/src/main/java/com/thealgorithms/sorts/BitonicSort.java +++ b/src/main/java/com/thealgorithms/sorts/BitonicSort.java @@ -64,7 +64,7 @@ private > void bitonicMerge(T[] array, int low, int cnt, if (cnt > 1) { final int k = cnt / 2; - final BiPredicate areSorted = (direction == Direction.ASCENDING) ? (a, b) -> a.compareTo(b) < 0 : (a, b) -> a.compareTo(b) > 0; + final BiPredicate areSorted = (direction == Direction.ASCENDING) ? (a, b) -> SortUtils.less(a, b) : (a, b) -> SortUtils.greater(a, b); for (int i = low; i < low + k; i++) { if (!areSorted.test(array[i], array[i + k])) { SortUtils.swap(array, i, i + k); diff --git a/src/main/java/com/thealgorithms/sorts/BucketSort.java b/src/main/java/com/thealgorithms/sorts/BucketSort.java index 62c5e929593b..8882a1cb4bbd 100644 --- a/src/main/java/com/thealgorithms/sorts/BucketSort.java +++ b/src/main/java/com/thealgorithms/sorts/BucketSort.java @@ -111,7 +111,7 @@ private > int hash(final T element, final T min, final T private > T findMin(T[] array) { T min = array[0]; for (T element : array) { - if (element.compareTo(min) < 0) { + if (SortUtils.less(element, min)) { min = element; } } @@ -121,7 +121,7 @@ private > T findMin(T[] array) { private > T findMax(T[] array) { T max = array[0]; for (T element : array) { - if (element.compareTo(max) > 0) { + if (SortUtils.greater(element, max)) { max = element; } } diff --git a/src/main/java/com/thealgorithms/sorts/CircleSort.java b/src/main/java/com/thealgorithms/sorts/CircleSort.java index b9b41be16701..2863a40a2075 100644 --- a/src/main/java/com/thealgorithms/sorts/CircleSort.java +++ b/src/main/java/com/thealgorithms/sorts/CircleSort.java @@ -36,7 +36,7 @@ private > boolean doSort(final T[] array, final int left int high = right; while (low < high) { - if (array[low].compareTo(array[high]) > 0) { + if (SortUtils.greater(array[low], array[high])) { SortUtils.swap(array, low, high); swapped = true; } @@ -44,7 +44,7 @@ private > boolean doSort(final T[] array, final int left high--; } - if (low == high && array[low].compareTo(array[high + 1]) > 0) { + if (low == high && SortUtils.greater(array[low], array[high + 1])) { SortUtils.swap(array, low, high + 1); swapped = true; } diff --git a/src/main/java/com/thealgorithms/sorts/DutchNationalFlagSort.java b/src/main/java/com/thealgorithms/sorts/DutchNationalFlagSort.java index abfcb452b29a..f7e12da06568 100644 --- a/src/main/java/com/thealgorithms/sorts/DutchNationalFlagSort.java +++ b/src/main/java/com/thealgorithms/sorts/DutchNationalFlagSort.java @@ -26,11 +26,11 @@ private > T[] dutchNationalFlagSort(final T[] array, fin int k = array.length - 1; while (j <= k) { - if (0 > array[j].compareTo(intendedMiddle)) { + if (SortUtils.less(array[j], intendedMiddle)) { SortUtils.swap(array, i, j); j++; i++; - } else if (0 < array[j].compareTo(intendedMiddle)) { + } else if (SortUtils.greater(array[j], intendedMiddle)) { SortUtils.swap(array, j, k); k--; } else { diff --git a/src/main/java/com/thealgorithms/sorts/ExchangeSort.java b/src/main/java/com/thealgorithms/sorts/ExchangeSort.java index 67e94b889671..a58caaf1031a 100644 --- a/src/main/java/com/thealgorithms/sorts/ExchangeSort.java +++ b/src/main/java/com/thealgorithms/sorts/ExchangeSort.java @@ -31,7 +31,7 @@ class ExchangeSort implements SortAlgorithm { public > T[] sort(T[] array) { for (int i = 0; i < array.length - 1; i++) { for (int j = i + 1; j < array.length; j++) { - if (array[i].compareTo(array[j]) > 0) { + if (SortUtils.greater(array[i], array[j])) { SortUtils.swap(array, i, j); } } diff --git a/src/main/java/com/thealgorithms/sorts/IntrospectiveSort.java b/src/main/java/com/thealgorithms/sorts/IntrospectiveSort.java index 12ef197b931b..6f846c7b9a8f 100644 --- a/src/main/java/com/thealgorithms/sorts/IntrospectiveSort.java +++ b/src/main/java/com/thealgorithms/sorts/IntrospectiveSort.java @@ -63,7 +63,7 @@ private static > int partition(T[] array, final int low, final T pivot = array[high]; int i = low - 1; for (int j = low; j < high; j++) { - if (array[j].compareTo(pivot) <= 0) { + if (SortUtils.greaterOrEqual(pivot, array[j])) { i++; SortUtils.swap(array, i, j); } @@ -84,7 +84,7 @@ private static > void insertionSort(T[] array, final int for (int i = low + 1; i <= high; i++) { final T key = array[i]; int j = i - 1; - while (j >= low && array[j].compareTo(key) > 0) { + while (j >= low && SortUtils.greater(array[j], key)) { array[j + 1] = array[j]; j--; } @@ -125,10 +125,10 @@ private static > void heapify(T[] array, final int i, fi final int right = 2 * i + 2; int largest = i; - if (left < n && array[low + left].compareTo(array[low + largest]) > 0) { + if (left < n && SortUtils.greater(array[low + left], array[low + largest])) { largest = left; } - if (right < n && array[low + right].compareTo(array[low + largest]) > 0) { + if (right < n && SortUtils.greater(array[low + right], array[low + largest])) { largest = right; } if (largest != i) { diff --git a/src/main/java/com/thealgorithms/sorts/MergeSort.java b/src/main/java/com/thealgorithms/sorts/MergeSort.java index 9949783ca21b..86a184f67b26 100644 --- a/src/main/java/com/thealgorithms/sorts/MergeSort.java +++ b/src/main/java/com/thealgorithms/sorts/MergeSort.java @@ -7,6 +7,7 @@ * * @see SortAlgorithm */ +@SuppressWarnings("rawtypes") class MergeSort implements SortAlgorithm { private Comparable[] aux; diff --git a/src/main/java/com/thealgorithms/sorts/OddEvenSort.java b/src/main/java/com/thealgorithms/sorts/OddEvenSort.java index ac94982c1474..b854842e9645 100644 --- a/src/main/java/com/thealgorithms/sorts/OddEvenSort.java +++ b/src/main/java/com/thealgorithms/sorts/OddEvenSort.java @@ -30,7 +30,7 @@ public > T[] sort(T[] array) { private > boolean performOddSort(T[] array) { boolean sorted = true; for (int i = 1; i < array.length - 1; i += 2) { - if (array[i].compareTo(array[i + 1]) > 0) { + if (SortUtils.greater(array[i], array[i + 1])) { SortUtils.swap(array, i, i + 1); sorted = false; } @@ -41,7 +41,7 @@ private > boolean performOddSort(T[] array) { private > boolean performEvenSort(T[] array) { boolean sorted = true; for (int i = 0; i < array.length - 1; i += 2) { - if (array[i].compareTo(array[i + 1]) > 0) { + if (SortUtils.greater(array[i], array[i + 1])) { SortUtils.swap(array, i, i + 1); sorted = false; } diff --git a/src/main/java/com/thealgorithms/sorts/PriorityQueueSort.java b/src/main/java/com/thealgorithms/sorts/PriorityQueueSort.java new file mode 100644 index 000000000000..16f13050e8b3 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/PriorityQueueSort.java @@ -0,0 +1,49 @@ +package com.thealgorithms.sorts; + +import java.util.PriorityQueue; + +/** + * Sorts an array using Java's PriorityQueue (Min-Heap). + * + *

Example: Input: [7, 2, 9, 4, 1] Output: [1, 2, 4, 7, 9] + * + *

Time Complexity: + * - Inserting n elements into the PriorityQueue โ†’ O(n log n) + * - Polling n elements โ†’ O(n log n) + * - Total: O(n log n) + * + *

Space Complexity: O(n) for the PriorityQueue + * + * @see + * Heap / PriorityQueue + */ +public final class PriorityQueueSort { + + // Private constructor to prevent instantiation (utility class) + private PriorityQueueSort() { + } + + /** + * Sorts the given array in ascending order using a PriorityQueue. + * + * @param arr the array to be sorted + * @return the sorted array (in-place) + */ + public static int[] sort(int[] arr) { + if (arr == null || arr.length == 0) { + return arr; + } + + PriorityQueue pq = new PriorityQueue<>(); + for (int num : arr) { + pq.offer(num); + } + + int i = 0; + while (!pq.isEmpty()) { + arr[i++] = pq.poll(); + } + + return arr; + } +} diff --git a/src/main/java/com/thealgorithms/sorts/SelectionSort.java b/src/main/java/com/thealgorithms/sorts/SelectionSort.java index dbb2b88ffcef..db7732d7e218 100644 --- a/src/main/java/com/thealgorithms/sorts/SelectionSort.java +++ b/src/main/java/com/thealgorithms/sorts/SelectionSort.java @@ -21,7 +21,7 @@ public > T[] sort(T[] array) { private static > int findIndexOfMin(T[] array, final int startIndex) { int minIndex = startIndex; for (int i = startIndex + 1; i < array.length; i++) { - if (array[i].compareTo(array[minIndex]) < 0) { + if (SortUtils.less(array[i], array[minIndex])) { minIndex = i; } } diff --git a/src/main/java/com/thealgorithms/sorts/SelectionSortRecursive.java b/src/main/java/com/thealgorithms/sorts/SelectionSortRecursive.java index 9d24542de592..f220c2d8f994 100644 --- a/src/main/java/com/thealgorithms/sorts/SelectionSortRecursive.java +++ b/src/main/java/com/thealgorithms/sorts/SelectionSortRecursive.java @@ -56,6 +56,6 @@ private static > int findMinIndex(T[] array, final int s final int minIndexInRest = findMinIndex(array, start + 1); // Return the index of the smaller element between array[start] and the minimum element in the rest of the array - return array[start].compareTo(array[minIndexInRest]) < 0 ? start : minIndexInRest; + return SortUtils.less(array[start], array[minIndexInRest]) ? start : minIndexInRest; } } diff --git a/src/main/java/com/thealgorithms/sorts/SimpleSort.java b/src/main/java/com/thealgorithms/sorts/SimpleSort.java deleted file mode 100644 index a03223cb01a1..000000000000 --- a/src/main/java/com/thealgorithms/sorts/SimpleSort.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.thealgorithms.sorts; - -public class SimpleSort implements SortAlgorithm { - @Override - public > T[] sort(T[] array) { - for (int i = 0; i < array.length; i++) { - for (int j = i + 1; j < array.length; j++) { - if (SortUtils.less(array[j], array[i])) { - SortUtils.swap(array, i, j); - } - } - } - return array; - } -} diff --git a/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java b/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java index 7a3ded37bf3f..72b046d12861 100644 --- a/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java +++ b/src/main/java/com/thealgorithms/sorts/SortAlgorithm.java @@ -8,6 +8,7 @@ * * @author Podshivalov Nikita (https://github.com/nikitap492) */ +@SuppressWarnings("rawtypes") public interface SortAlgorithm { /** * Main method arrays sorting algorithms diff --git a/src/main/java/com/thealgorithms/sorts/SpreadSort.java b/src/main/java/com/thealgorithms/sorts/SpreadSort.java index f1fd24f4735d..1401f3d454a8 100644 --- a/src/main/java/com/thealgorithms/sorts/SpreadSort.java +++ b/src/main/java/com/thealgorithms/sorts/SpreadSort.java @@ -6,6 +6,7 @@ * It distributes elements into buckets and recursively sorts these buckets. * This implementation is generic and can sort any array of elements that extend Comparable. */ +@SuppressWarnings("rawtypes") public class SpreadSort implements SortAlgorithm { private static final int MAX_INSERTION_SORT_THRESHOLD = 1000; private static final int MAX_INITIAL_BUCKET_CAPACITY = 1000; diff --git a/src/main/java/com/thealgorithms/sorts/StalinSort.java b/src/main/java/com/thealgorithms/sorts/StalinSort.java index 5aaf530fd94c..fe07a6f6d986 100644 --- a/src/main/java/com/thealgorithms/sorts/StalinSort.java +++ b/src/main/java/com/thealgorithms/sorts/StalinSort.java @@ -8,7 +8,7 @@ public > T[] sort(T[] array) { } int currentIndex = 0; for (int i = 1; i < array.length; i++) { - if (array[i].compareTo(array[currentIndex]) >= 0) { + if (SortUtils.greaterOrEqual(array[i], array[currentIndex])) { currentIndex++; array[currentIndex] = array[i]; } diff --git a/src/main/java/com/thealgorithms/sorts/TimSort.java b/src/main/java/com/thealgorithms/sorts/TimSort.java index 2d5bca2ef6f3..13cd7fed35c7 100644 --- a/src/main/java/com/thealgorithms/sorts/TimSort.java +++ b/src/main/java/com/thealgorithms/sorts/TimSort.java @@ -7,6 +7,7 @@ *

* For more details @see TimSort Algorithm */ +@SuppressWarnings({"rawtypes", "unchecked"}) class TimSort implements SortAlgorithm { private static final int SUB_ARRAY_SIZE = 32; private Comparable[] aux; diff --git a/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java b/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java index ff6402c92695..2852454fd096 100644 --- a/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java +++ b/src/main/java/com/thealgorithms/stacks/DecimalToAnyUsingStack.java @@ -2,17 +2,29 @@ import java.util.Stack; +/** + * Utility class for converting a non-negative decimal (base-10) integer + * to its representation in another radix (base) between 2 and 16, inclusive. + * + *

This class uses a stack-based approach to reverse the digits obtained from + * successive divisions by the target radix. + * + *

This class cannot be instantiated.

+ */ public final class DecimalToAnyUsingStack { + private DecimalToAnyUsingStack() { } + private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + /** * Convert a decimal number to another radix. * * @param number the number to be converted * @param radix the radix * @return the number represented in the new radix as a String - * @throws IllegalArgumentException if number is negative or radix is not between 2 and 16 inclusive + * @throws IllegalArgumentException if number is negative or radix is not between 2 and 16 inclusive */ public static String convert(int number, int radix) { if (number < 0) { @@ -26,18 +38,17 @@ public static String convert(int number, int radix) { return "0"; } - char[] tables = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - Stack bits = new Stack<>(); + Stack digitStack = new Stack<>(); while (number > 0) { - bits.push(tables[number % radix]); - number = number / radix; + digitStack.push(DIGITS[number % radix]); + number /= radix; } - StringBuilder result = new StringBuilder(); - while (!bits.isEmpty()) { - result.append(bits.pop()); + StringBuilder result = new StringBuilder(digitStack.size()); + while (!digitStack.isEmpty()) { + result.append(digitStack.pop()); } + return result.toString(); } } diff --git a/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java b/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java index 33611bd73dba..77ca3e70849f 100644 --- a/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java +++ b/src/main/java/com/thealgorithms/stacks/InfixToPostfix.java @@ -4,54 +4,96 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Utility class for converting an infix arithmetic expression + * into its equivalent postfix (Reverse Polish Notation) form. + *

+ * This class provides a static method to perform the conversion, + * validating balanced brackets before processing. + *

+ */ public final class InfixToPostfix { + private InfixToPostfix() { } - public static String infix2PostFix(String infixExpression) throws Exception { + /** + * Converts a given infix expression string to a postfix expression string. + *

+ * The method first checks if the brackets in the input expression are balanced + * by calling {@code BalancedBrackets.isBalanced} on the filtered brackets. + * If the brackets are not balanced, it throws an IllegalArgumentException. + *

+ *

+ * Supported operators are: {@code +, -, *, /, ^} + * and operands can be letters or digits. + *

+ * + * @param infixExpression the arithmetic expression in infix notation + * @return the equivalent postfix notation expression + * @throws IllegalArgumentException if the brackets in the expression are unbalanced + */ + public static String infix2PostFix(String infixExpression) { if (!BalancedBrackets.isBalanced(filterBrackets(infixExpression))) { - throw new Exception("invalid expression"); + throw new IllegalArgumentException("Invalid expression: unbalanced brackets."); } + StringBuilder output = new StringBuilder(); - Stack stack = new Stack<>(); - for (char element : infixExpression.toCharArray()) { - if (Character.isLetterOrDigit(element)) { - output.append(element); - } else if (element == '(') { - stack.push(element); - } else if (element == ')') { - while (!stack.isEmpty() && stack.peek() != '(') { - output.append(stack.pop()); + Stack operatorStack = new Stack<>(); + + for (char token : infixExpression.toCharArray()) { + if (Character.isLetterOrDigit(token)) { + // Append operands (letters or digits) directly to output + output.append(token); + } else if (token == '(') { + // Push '(' to stack + operatorStack.push(token); + } else if (token == ')') { + // Pop and append until '(' is found + while (!operatorStack.isEmpty() && operatorStack.peek() != '(') { + output.append(operatorStack.pop()); } - stack.pop(); + operatorStack.pop(); // Remove '(' from stack } else { - while (!stack.isEmpty() && precedence(element) <= precedence(stack.peek())) { - output.append(stack.pop()); + // Pop operators with higher or equal precedence and append them + while (!operatorStack.isEmpty() && precedence(token) <= precedence(operatorStack.peek())) { + output.append(operatorStack.pop()); } - stack.push(element); + operatorStack.push(token); } } - while (!stack.isEmpty()) { - output.append(stack.pop()); + + // Pop any remaining operators + while (!operatorStack.isEmpty()) { + output.append(operatorStack.pop()); } + return output.toString(); } + /** + * Returns the precedence level of the given operator. + * + * @param operator the operator character (e.g., '+', '-', '*', '/', '^') + * @return the precedence value: higher means higher precedence, + * or -1 if the character is not a recognized operator + */ private static int precedence(char operator) { - switch (operator) { - case '+': - case '-': - return 0; - case '*': - case '/': - return 1; - case '^': - return 2; - default: - return -1; - } + return switch (operator) { + case '+', '-' -> 0; + case '*', '/' -> 1; + case '^' -> 2; + default -> -1; + }; } + /** + * Extracts only the bracket characters from the input string. + * Supports parentheses (), curly braces {}, square brackets [], and angle brackets <>. + * + * @param input the original expression string + * @return a string containing only bracket characters from the input + */ private static String filterBrackets(String input) { Pattern pattern = Pattern.compile("[^(){}\\[\\]<>]"); Matcher matcher = pattern.matcher(input); diff --git a/src/main/java/com/thealgorithms/stacks/InfixToPrefix.java b/src/main/java/com/thealgorithms/stacks/InfixToPrefix.java index 3d90d14e0d1e..e9ee5e208df6 100644 --- a/src/main/java/com/thealgorithms/stacks/InfixToPrefix.java +++ b/src/main/java/com/thealgorithms/stacks/InfixToPrefix.java @@ -4,85 +4,109 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Utility class for converting an infix arithmetic expression + * into its equivalent prefix notation expression. + *

+ * This class provides a static method to perform the conversion, + * validating balanced brackets before processing. + *

+ */ public final class InfixToPrefix { + private InfixToPrefix() { } /** - * Convert an infix expression to a prefix expression using stack. + * Converts a given infix expression string to a prefix expression string. + *

+ * The method validates that the input expression has balanced brackets using + * {@code BalancedBrackets.isBalanced} on the filtered bracket characters. + * It throws an {@code IllegalArgumentException} if the brackets are unbalanced, + * and a {@code NullPointerException} if the input is null. + *

+ *

+ * Supported operators: {@code +, -, *, /, ^} and operands can be letters or digits. + *

* - * @param infixExpression the infix expression to convert - * @return the prefix expression - * @throws IllegalArgumentException if the infix expression has unbalanced brackets - * @throws NullPointerException if the infix expression is null + * @param infixExpression the arithmetic expression in infix notation + * @return the equivalent prefix notation expression + * @throws IllegalArgumentException if brackets are unbalanced + * @throws NullPointerException if the input expression is null */ - public static String infix2Prefix(String infixExpression) throws IllegalArgumentException { + public static String infix2Prefix(String infixExpression) { if (infixExpression == null) { throw new NullPointerException("Input expression cannot be null."); } + infixExpression = infixExpression.trim(); if (infixExpression.isEmpty()) { return ""; } + if (!BalancedBrackets.isBalanced(filterBrackets(infixExpression))) { throw new IllegalArgumentException("Invalid expression: unbalanced brackets."); } StringBuilder output = new StringBuilder(); - Stack stack = new Stack<>(); - // Reverse the infix expression for prefix conversion + Stack operatorStack = new Stack<>(); + + // Reverse the infix expression to facilitate prefix conversion String reversedInfix = new StringBuilder(infixExpression).reverse().toString(); - for (char element : reversedInfix.toCharArray()) { - if (Character.isLetterOrDigit(element)) { - output.append(element); - } else if (element == ')') { - stack.push(element); - } else if (element == '(') { - while (!stack.isEmpty() && stack.peek() != ')') { - output.append(stack.pop()); + + for (char token : reversedInfix.toCharArray()) { + if (Character.isLetterOrDigit(token)) { + // Append operands directly to output + output.append(token); + } else if (token == ')') { + // Push ')' onto stack (since expression is reversed, '(' and ')' roles swapped) + operatorStack.push(token); + } else if (token == '(') { + // Pop operators until ')' is found + while (!operatorStack.isEmpty() && operatorStack.peek() != ')') { + output.append(operatorStack.pop()); } - stack.pop(); + operatorStack.pop(); // Remove the ')' } else { - while (!stack.isEmpty() && precedence(element) < precedence(stack.peek())) { - output.append(stack.pop()); + // Pop operators with higher precedence before pushing current operator + while (!operatorStack.isEmpty() && precedence(token) < precedence(operatorStack.peek())) { + output.append(operatorStack.pop()); } - stack.push(element); + operatorStack.push(token); } } - while (!stack.isEmpty()) { - output.append(stack.pop()); + + // Append any remaining operators in stack + while (!operatorStack.isEmpty()) { + output.append(operatorStack.pop()); } - // Reverse the result to get the prefix expression + // Reverse the output to obtain the final prefix expression return output.reverse().toString(); } /** - * Determines the precedence of an operator. + * Returns the precedence level of the given operator. * - * @param operator the operator whose precedence is to be determined - * @return the precedence of the operator + * @param operator the operator character (e.g., '+', '-', '*', '/', '^') + * @return the precedence value: higher means higher precedence, + * or -1 if the character is not a recognized operator */ private static int precedence(char operator) { - switch (operator) { - case '+': - case '-': - return 0; - case '*': - case '/': - return 1; - case '^': - return 2; - default: - return -1; - } + return switch (operator) { + case '+', '-' -> 0; + case '*', '/' -> 1; + case '^' -> 2; + default -> -1; + }; } /** - * Filters out all characters from the input string except brackets. + * Extracts only the bracket characters from the input string. + * Supports parentheses (), curly braces {}, square brackets [], and angle brackets <>. * - * @param input the input string to filter - * @return a string containing only brackets from the input string + * @param input the original expression string + * @return a string containing only bracket characters from the input */ private static String filterBrackets(String input) { Pattern pattern = Pattern.compile("[^(){}\\[\\]<>]"); diff --git a/src/main/java/com/thealgorithms/stacks/LargestRectangle.java b/src/main/java/com/thealgorithms/stacks/LargestRectangle.java index 006e03632e63..eb222c8c488e 100644 --- a/src/main/java/com/thealgorithms/stacks/LargestRectangle.java +++ b/src/main/java/com/thealgorithms/stacks/LargestRectangle.java @@ -3,36 +3,50 @@ import java.util.Stack; /** + * Utility class to calculate the largest rectangle area in a histogram. + * Each bar's width is assumed to be 1 unit. * - * @author mohd rameez github.com/rameez471 + *

This implementation uses a monotonic stack to efficiently calculate + * the area of the largest rectangle that can be formed from the histogram bars.

+ * + *

Example usage: + *

{@code
+ *     int[] heights = {2, 1, 5, 6, 2, 3};
+ *     String area = LargestRectangle.largestRectangleHistogram(heights);
+ *     // area is "10"
+ * }
*/ - public final class LargestRectangle { + private LargestRectangle() { } + /** + * Calculates the largest rectangle area in the given histogram. + * + * @param heights an array of non-negative integers representing bar heights + * @return the largest rectangle area as a {@link String} + */ public static String largestRectangleHistogram(int[] heights) { - int n = heights.length; int maxArea = 0; - Stack st = new Stack<>(); - for (int i = 0; i < n; i++) { + Stack stack = new Stack<>(); + + for (int i = 0; i < heights.length; i++) { int start = i; - while (!st.isEmpty() && st.peek()[1] > heights[i]) { - int[] tmp = st.pop(); - maxArea = Math.max(maxArea, tmp[1] * (i - tmp[0])); - start = tmp[0]; + while (!stack.isEmpty() && stack.peek()[1] > heights[i]) { + int[] popped = stack.pop(); + maxArea = Math.max(maxArea, popped[1] * (i - popped[0])); + start = popped[0]; } - st.push(new int[] {start, heights[i]}); + stack.push(new int[] {start, heights[i]}); } - while (!st.isEmpty()) { - int[] tmp = st.pop(); - maxArea = Math.max(maxArea, tmp[1] * (n - tmp[0])); + + int totalLength = heights.length; + while (!stack.isEmpty()) { + int[] remaining = stack.pop(); + maxArea = Math.max(maxArea, remaining[1] * (totalLength - remaining[0])); } - return Integer.toString(maxArea); - } - public static void main(String[] args) { - assert largestRectangleHistogram(new int[] {2, 1, 5, 6, 2, 3}).equals("10"); - assert largestRectangleHistogram(new int[] {2, 4}).equals("4"); + return Integer.toString(maxArea); } } diff --git a/src/main/java/com/thealgorithms/strings/Alphabetical.java b/src/main/java/com/thealgorithms/strings/Alphabetical.java index de07dde2d510..ef2974eb427d 100644 --- a/src/main/java/com/thealgorithms/strings/Alphabetical.java +++ b/src/main/java/com/thealgorithms/strings/Alphabetical.java @@ -1,36 +1,32 @@ package com.thealgorithms.strings; /** + * Utility class for checking if a string's characters are in alphabetical order. + *

* Alphabetical order is a system whereby character strings are placed in order * based on the position of the characters in the conventional ordering of an - * alphabet. Wikipedia: https://en.wikipedia.org/wiki/Alphabetical_order + * alphabet. + *

+ * Reference: Wikipedia: Alphabetical Order */ -final class Alphabetical { +public final class Alphabetical { private Alphabetical() { } - public static void main(String[] args) { - assert !isAlphabetical("123abc"); - assert isAlphabetical("aBC"); - assert isAlphabetical("abc"); - assert !isAlphabetical("xyzabc"); - assert isAlphabetical("abcxyz"); - } - /** - * Check if a string is alphabetical order or not + * Checks whether the characters in the given string are in alphabetical order. + * Non-letter characters will cause the check to fail. * - * @param s a string - * @return {@code true} if given string is alphabetical order, otherwise - * {@code false} + * @param s the input string + * @return {@code true} if all characters are in alphabetical order (case-insensitive), otherwise {@code false} */ public static boolean isAlphabetical(String s) { s = s.toLowerCase(); for (int i = 0; i < s.length() - 1; ++i) { - if (!Character.isLetter(s.charAt(i)) || !(s.charAt(i) <= s.charAt(i + 1))) { + if (!Character.isLetter(s.charAt(i)) || s.charAt(i) > s.charAt(i + 1)) { return false; } } - return true; + return !s.isEmpty() && Character.isLetter(s.charAt(s.length() - 1)); } } diff --git a/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java new file mode 100644 index 000000000000..cf736dbd8cab --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java @@ -0,0 +1,48 @@ +package com.thealgorithms.strings; + +/** + * This class provides a method to arrange two strings by alternating their characters. + * If one string is longer, the remaining characters of the longer string are appended at the end. + *

+ * Example: + * Input: "abc", "12345" + * Output: "a1b2c345" + *

+ * Input: "abcd", "12" + * Output: "a1b2cd" + * + * @author Milad Sadeghi + */ +public final class AlternativeStringArrange { + + // Private constructor to prevent instantiation + private AlternativeStringArrange() { + } + + /** + * Arranges two strings by alternating their characters. + * + * @param firstString the first input string + * @param secondString the second input string + * @return a new string with characters from both strings arranged alternately + */ + public static String arrange(String firstString, String secondString) { + StringBuilder result = new StringBuilder(); + int length1 = firstString.length(); + int length2 = secondString.length(); + int minLength = Math.min(length1, length2); + + for (int i = 0; i < minLength; i++) { + result.append(firstString.charAt(i)); + result.append(secondString.charAt(i)); + } + + if (length1 > length2) { + result.append(firstString.substring(minLength)); + } else if (length2 > length1) { + result.append(secondString.substring(minLength)); + } + + return result.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/strings/Anagrams.java b/src/main/java/com/thealgorithms/strings/Anagrams.java index 4b24979e2689..5b97af0758f2 100644 --- a/src/main/java/com/thealgorithms/strings/Anagrams.java +++ b/src/main/java/com/thealgorithms/strings/Anagrams.java @@ -23,7 +23,9 @@ private Anagrams() { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach1(String s, String t) { + public static boolean areAnagramsBySorting(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); if (s.length() != t.length()) { return false; } @@ -43,17 +45,18 @@ public static boolean approach1(String s, String t) { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach2(String s, String t) { - if (s.length() != t.length()) { - return false; + public static boolean areAnagramsByCountingChars(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); + int[] dict = new int[128]; + for (char ch : s.toCharArray()) { + dict[ch]++; } - int[] charCount = new int[26]; - for (int i = 0; i < s.length(); i++) { - charCount[s.charAt(i) - 'a']++; - charCount[t.charAt(i) - 'a']--; + for (char ch : t.toCharArray()) { + dict[ch]--; } - for (int count : charCount) { - if (count != 0) { + for (int e : dict) { + if (e != 0) { return false; } } @@ -70,7 +73,9 @@ public static boolean approach2(String s, String t) { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach3(String s, String t) { + public static boolean areAnagramsByCountingCharsSingleArray(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); if (s.length() != t.length()) { return false; } @@ -96,7 +101,9 @@ public static boolean approach3(String s, String t) { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach4(String s, String t) { + public static boolean areAnagramsUsingHashMap(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); if (s.length() != t.length()) { return false; } @@ -123,7 +130,9 @@ public static boolean approach4(String s, String t) { * @param t the second string * @return true if the strings are anagrams, false otherwise */ - public static boolean approach5(String s, String t) { + public static boolean areAnagramsBySingleFreqArray(String s, String t) { + s = s.toLowerCase().replaceAll("[^a-z]", ""); + t = t.toLowerCase().replaceAll("[^a-z]", ""); if (s.length() != t.length()) { return false; } diff --git a/src/main/java/com/thealgorithms/strings/CheckAnagrams.java b/src/main/java/com/thealgorithms/strings/CheckAnagrams.java deleted file mode 100644 index 7bf7cd9a7c66..000000000000 --- a/src/main/java/com/thealgorithms/strings/CheckAnagrams.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.thealgorithms.strings; - -import java.util.HashMap; -import java.util.Map; - -/** - * Two strings are anagrams if they are made of the same letters arranged - * differently (ignoring the case). - */ -public final class CheckAnagrams { - private CheckAnagrams() { - } - /** - * Check if two strings are anagrams or not - * - * @param s1 the first string - * @param s2 the second string - * @return {@code true} if two string are anagrams, otherwise {@code false} - */ - public static boolean isAnagrams(String s1, String s2) { - int l1 = s1.length(); - int l2 = s2.length(); - s1 = s1.toLowerCase(); - s2 = s2.toLowerCase(); - Map charAppearances = new HashMap<>(); - - for (int i = 0; i < l1; i++) { - char c = s1.charAt(i); - int numOfAppearances = charAppearances.getOrDefault(c, 0); - charAppearances.put(c, numOfAppearances + 1); - } - - for (int i = 0; i < l2; i++) { - char c = s2.charAt(i); - if (!charAppearances.containsKey(c)) { - return false; - } - charAppearances.put(c, charAppearances.get(c) - 1); - } - - for (int cnt : charAppearances.values()) { - if (cnt != 0) { - return false; - } - } - return true; - } - - /** - * If given strings contain Unicode symbols. - * The first 128 ASCII codes are identical to Unicode. - * This algorithm is case-sensitive. - * - * @param s1 the first string - * @param s2 the second string - * @return true if two string are anagrams, otherwise false - */ - public static boolean isAnagramsUnicode(String s1, String s2) { - int[] dict = new int[128]; - for (char ch : s1.toCharArray()) { - dict[ch]++; - } - for (char ch : s2.toCharArray()) { - dict[ch]--; - } - for (int e : dict) { - if (e != 0) { - return false; - } - } - return true; - } - - /** - * If given strings contain only lowercase English letters. - *

- * The main "trick": - * To map each character from the first string 's1' we need to subtract an integer value of 'a' character - * as 'dict' array starts with 'a' character. - * - * @param s1 the first string - * @param s2 the second string - * @return true if two string are anagrams, otherwise false - */ - public static boolean isAnagramsOptimised(String s1, String s2) { - // 26 - English alphabet length - int[] dict = new int[26]; - for (char ch : s1.toCharArray()) { - checkLetter(ch); - dict[ch - 'a']++; - } - for (char ch : s2.toCharArray()) { - checkLetter(ch); - dict[ch - 'a']--; - } - for (int e : dict) { - if (e != 0) { - return false; - } - } - return true; - } - - private static void checkLetter(char ch) { - int index = ch - 'a'; - if (index < 0 || index >= 26) { - throw new IllegalArgumentException("Strings must contain only lowercase English letters!"); - } - } -} diff --git a/src/main/java/com/thealgorithms/strings/Isogram.java b/src/main/java/com/thealgorithms/strings/Isogram.java new file mode 100644 index 000000000000..b37f085b075d --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/Isogram.java @@ -0,0 +1,92 @@ +package com.thealgorithms.strings; + +import java.util.HashSet; +import java.util.Set; + +/** + * An isogram (also called heterogram or nonpattern word) is a word in which no + * letter of the word occurs more than once. Each character appears exactly + * once. + * + * For example, the word "uncopyrightable" is the longest common English isogram + * with 15 unique letters. Other examples include "dermatoglyphics" (15 + * letters), + * "background" (10 letters), "python" (6 letters), and "keyboard" (8 letters). + * But words like "hello" and "programming" are not isograms because some + * letters + * appear multiple times ('l' appears twice in "hello", while 'r', 'm', 'g' + * repeat + * in "programming"). + * + * Isograms are particularly valuable in creating substitution ciphers and are + * studied in recreational linguistics. A perfect pangram, which uses all 26 + * letters + * of the alphabet exactly once, is a special type of isogram. + * + * Reference from https://en.wikipedia.org/wiki/Heterogram_(literature)#Isograms + */ +public final class Isogram { + /** + * Private constructor to prevent instantiation of utility class. + */ + private Isogram() { + } + + /** + * Checks if a string is an isogram using boolean array approach. + * + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * @param str the input string + * @return true if the string is an isogram, false otherwise + * @throws IllegalArgumentException if the string contains non-alphabetic + * characters + */ + public static boolean isAlphabeticIsogram(String str) { + if (str == null || str.isEmpty()) { + return true; + } + + str = str.toLowerCase(); + + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + if (ch < 'a' || ch > 'z') { + throw new IllegalArgumentException("Input contains non-alphabetic character: '" + ch + "'"); + } + } + + boolean[] seenChars = new boolean[26]; + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + int index = ch - 'a'; + if (seenChars[index]) { + return false; + } + seenChars[index] = true; + } + return true; + } + + /** + * Checks if a string is an isogram using length comparison approach. + * Time Complexity: O(n) + * Space Complexity: O(k) where k is the number of unique characters + * + * @param str the input string + * @return true if the string is an isogram, false otherwise + */ + public static boolean isFullIsogram(String str) { + if (str == null || str.isEmpty()) { + return true; + } + str = str.toLowerCase(); + + Set uniqueChars = new HashSet<>(); + for (char ch : str.toCharArray()) { + uniqueChars.add(ch); + } + return uniqueChars.size() == str.length(); + } +} diff --git a/src/main/java/com/thealgorithms/strings/Isomorphic.java b/src/main/java/com/thealgorithms/strings/Isomorphic.java index ccd686170715..7ee00e62e16b 100644 --- a/src/main/java/com/thealgorithms/strings/Isomorphic.java +++ b/src/main/java/com/thealgorithms/strings/Isomorphic.java @@ -5,35 +5,54 @@ import java.util.Map; import java.util.Set; +/** + * Utility class to check if two strings are isomorphic. + * + *

+ * Two strings {@code s} and {@code t} are isomorphic if the characters in {@code s} + * can be replaced to get {@code t}, while preserving the order of characters. + * Each character must map to exactly one character, and no two characters can map to the same character. + *

+ * + * @see Isomorphic Strings + */ public final class Isomorphic { + private Isomorphic() { } - public static boolean checkStrings(String s, String t) { + /** + * Checks if two strings are isomorphic. + * + * @param s the first input string + * @param t the second input string + * @return {@code true} if {@code s} and {@code t} are isomorphic; {@code false} otherwise + */ + public static boolean areIsomorphic(String s, String t) { if (s.length() != t.length()) { return false; } - // To mark the characters of string using MAP - // character of first string as KEY and another as VALUE - // now check occurence by keeping the track with SET data structure - Map characterMap = new HashMap<>(); - Set trackUniqueCharacter = new HashSet<>(); + Map map = new HashMap<>(); + Set usedCharacters = new HashSet<>(); for (int i = 0; i < s.length(); i++) { - if (characterMap.containsKey(s.charAt(i))) { - if (t.charAt(i) != characterMap.get(s.charAt(i))) { + char sourceChar = s.charAt(i); + char targetChar = t.charAt(i); + + if (map.containsKey(sourceChar)) { + if (map.get(sourceChar) != targetChar) { return false; } } else { - if (trackUniqueCharacter.contains(t.charAt(i))) { + if (usedCharacters.contains(targetChar)) { return false; } - - characterMap.put(s.charAt(i), t.charAt(i)); + map.put(sourceChar, targetChar); + usedCharacters.add(targetChar); } - trackUniqueCharacter.add(t.charAt(i)); } + return true; } } diff --git a/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java b/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java index 0fabdaa2658b..3348b9cf860c 100644 --- a/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java +++ b/src/main/java/com/thealgorithms/strings/LongestCommonPrefix.java @@ -2,21 +2,41 @@ import java.util.Arrays; +/** + * Utility class for string operations. + *

+ * This class provides a method to find the longest common prefix (LCP) + * among an array of strings. + *

+ * + * @see Longest Common Prefix - Wikipedia + */ public final class LongestCommonPrefix { - public String longestCommonPrefix(String[] strs) { + + private LongestCommonPrefix() { + } + + /** + * Finds the longest common prefix among a list of strings using lexicographical sorting. + * The prefix is common to the first and last elements after sorting the array. + * + * @param strs array of input strings + * @return the longest common prefix, or empty string if none exists + */ + public static String longestCommonPrefix(String[] strs) { if (strs == null || strs.length == 0) { return ""; } Arrays.sort(strs); - String shortest = strs[0]; - String longest = strs[strs.length - 1]; + String first = strs[0]; + String last = strs[strs.length - 1]; int index = 0; - while (index < shortest.length() && index < longest.length() && shortest.charAt(index) == longest.charAt(index)) { + while (index < first.length() && index < last.length() && first.charAt(index) == last.charAt(index)) { index++; } - return shortest.substring(0, index); + return first.substring(0, index); } } diff --git a/src/main/java/com/thealgorithms/others/RemoveDuplicateFromString.java b/src/main/java/com/thealgorithms/strings/RemoveDuplicateFromString.java similarity index 96% rename from src/main/java/com/thealgorithms/others/RemoveDuplicateFromString.java rename to src/main/java/com/thealgorithms/strings/RemoveDuplicateFromString.java index 5b6466f6fa07..84d4fd8f72ce 100644 --- a/src/main/java/com/thealgorithms/others/RemoveDuplicateFromString.java +++ b/src/main/java/com/thealgorithms/strings/RemoveDuplicateFromString.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.strings; /** * @author Varun Upadhyay (https://github.com/varunu28) diff --git a/src/main/java/com/thealgorithms/strings/ReverseString.java b/src/main/java/com/thealgorithms/strings/ReverseString.java index 46a0494fcbb4..7b918ebe1a59 100644 --- a/src/main/java/com/thealgorithms/strings/ReverseString.java +++ b/src/main/java/com/thealgorithms/strings/ReverseString.java @@ -1,5 +1,7 @@ package com.thealgorithms.strings; +import java.util.Stack; + /** * Reverse String using different version */ @@ -36,4 +38,65 @@ public static String reverse2(String str) { } return new String(value); } + + /** + * Reverse version 3 the given string using a StringBuilder. + * This method converts the string to a character array, + * iterates through it in reverse order, and appends each character + * to a StringBuilder. + * + * @param string The input string to be reversed. + * @return The reversed string. + */ + public static String reverse3(String string) { + if (string.isEmpty()) { + return string; + } + char[] chars = string.toCharArray(); + StringBuilder sb = new StringBuilder(); + for (int i = string.length() - 1; i >= 0; i--) { + sb.append(chars[i]); + } + return sb.toString(); + } + /** + * Reverses the given string using a stack. + * This method uses a stack to reverse the characters of the string. + * * @param str The input string to be reversed. + * @return The reversed string. + */ + public static String reverseStringUsingStack(String str) { + // Check if the input string is null + if (str == null) { + throw new IllegalArgumentException("Input string cannot be null"); + } + Stack stack = new Stack<>(); + StringBuilder reversedString = new StringBuilder(); + // Check if the input string is empty + if (str.isEmpty()) { + return str; + } + // Push each character of the string onto the stack + for (char ch : str.toCharArray()) { + stack.push(ch); + } + // Pop each character from the stack and append to the StringBuilder + while (!stack.isEmpty()) { + reversedString.append(stack.pop()); + } + return reversedString.toString(); + } + + /** + * Reverse the String using Recursion + * @param str string to be reversed + * @return reversed string + */ + public static String reverseStringUsingRecursion(String str) { + if (str.isEmpty()) { + return str; + } else { + return reverseStringUsingRecursion(str.substring(1)) + str.charAt(0); + } + } } diff --git a/src/main/java/com/thealgorithms/strings/ReverseStringRecursive.java b/src/main/java/com/thealgorithms/strings/ReverseStringRecursive.java deleted file mode 100644 index 817b0a8ccd09..000000000000 --- a/src/main/java/com/thealgorithms/strings/ReverseStringRecursive.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.thealgorithms.strings; - -/** - * Reverse String using Recursion - */ -public final class ReverseStringRecursive { - private ReverseStringRecursive() { - } - - /** - * @param str string to be reversed - * @return reversed string - */ - public static String reverse(String str) { - if (str.isEmpty()) { - return str; - } else { - return reverse(str.substring(1)) + str.charAt(0); - } - } -} diff --git a/src/main/java/com/thealgorithms/strings/SuffixArray.java b/src/main/java/com/thealgorithms/strings/SuffixArray.java new file mode 100644 index 000000000000..dbcac147b658 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/SuffixArray.java @@ -0,0 +1,60 @@ +package com.thealgorithms.strings; + +import java.util.Arrays; + +/** + * Suffix Array implementation in Java. + * Builds an array of indices that represent all suffixes of a string in sorted order. + * Wikipedia Reference: https://en.wikipedia.org/wiki/Suffix_array + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + */ + +public final class SuffixArray { + + private SuffixArray() { + } + + public static int[] buildSuffixArray(String text) { + int n = text.length(); + Integer[] suffixArray = new Integer[n]; + int[] rank = new int[n]; + int[] tempRank = new int[n]; + + // Initial ranking based on characters + for (int i = 0; i < n; i++) { + suffixArray[i] = i; + rank[i] = text.charAt(i); + } + + for (int k = 1; k < n; k *= 2) { + final int step = k; + + // Comparator: first by rank, then by rank + step + Arrays.sort(suffixArray, (a, b) -> { + if (rank[a] != rank[b]) { + return Integer.compare(rank[a], rank[b]); + } + int ra = (a + step < n) ? rank[a + step] : -1; + int rb = (b + step < n) ? rank[b + step] : -1; + return Integer.compare(ra, rb); + }); + + // Re-rank + tempRank[suffixArray[0]] = 0; + for (int i = 1; i < n; i++) { + int prev = suffixArray[i - 1]; + int curr = suffixArray[i]; + boolean sameRank = rank[prev] == rank[curr] && ((prev + step < n ? rank[prev + step] : -1) == (curr + step < n ? rank[curr + step] : -1)); + tempRank[curr] = sameRank ? tempRank[prev] : tempRank[prev] + 1; + } + + System.arraycopy(tempRank, 0, rank, 0, n); + + if (rank[suffixArray[n - 1]] == n - 1) { + break; + } + } + return Arrays.stream(suffixArray).mapToInt(Integer::intValue).toArray(); + } +} diff --git a/src/main/java/com/thealgorithms/strings/ValidParentheses.java b/src/main/java/com/thealgorithms/strings/ValidParentheses.java index 629fee495d84..25a72f379dec 100644 --- a/src/main/java/com/thealgorithms/strings/ValidParentheses.java +++ b/src/main/java/com/thealgorithms/strings/ValidParentheses.java @@ -1,59 +1,53 @@ package com.thealgorithms.strings; -// Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine -// if the input string is valid. An input string is valid if: Open brackets must be closed by -// the same type of brackets. Open brackets must be closed in the correct order. Every close -// bracket has a corresponding open bracket of the same type. +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map; + +/** + * Validates if a given string has valid matching parentheses. + *

+ * A string is considered valid if: + *

    + *
  • Open brackets are closed by the same type of brackets.
  • + *
  • Brackets are closed in the correct order.
  • + *
  • Every closing bracket has a corresponding open bracket of the same type.
  • + *
+ * + * Allowed characters: '(', ')', '{', '}', '[', ']' + */ public final class ValidParentheses { private ValidParentheses() { } + + private static final Map BRACKET_PAIRS = Map.of(')', '(', '}', '{', ']', '['); + + /** + * Checks if the input string has valid parentheses. + * + * @param s the string containing only bracket characters + * @return true if valid, false otherwise + * @throws IllegalArgumentException if the string contains invalid characters or is null + */ public static boolean isValid(String s) { - char[] stack = new char[s.length()]; - int head = 0; + if (s == null) { + throw new IllegalArgumentException("Input string cannot be null"); + } + + Deque stack = new ArrayDeque<>(); + for (char c : s.toCharArray()) { - switch (c) { - case '{': - case '[': - case '(': - stack[head++] = c; - break; - case '}': - if (head == 0 || stack[--head] != '{') { - return false; - } - break; - case ')': - if (head == 0 || stack[--head] != '(') { - return false; - } - break; - case ']': - if (head == 0 || stack[--head] != '[') { + if (BRACKET_PAIRS.containsValue(c)) { + stack.push(c); // opening bracket + } else if (BRACKET_PAIRS.containsKey(c)) { + if (stack.isEmpty() || stack.pop() != BRACKET_PAIRS.get(c)) { return false; } - break; - default: - throw new IllegalArgumentException("Unexpected character: " + c); - } - } - return head == 0; - } - public static boolean isValidParentheses(String s) { - int i = -1; - char[] stack = new char[s.length()]; - String openBrackets = "({["; - String closeBrackets = ")}]"; - for (char ch : s.toCharArray()) { - if (openBrackets.indexOf(ch) != -1) { - stack[++i] = ch; } else { - if (i >= 0 && openBrackets.indexOf(stack[i]) == closeBrackets.indexOf(ch)) { - i--; - } else { - return false; - } + throw new IllegalArgumentException("Unexpected character: " + c); } } - return i == -1; + + return stack.isEmpty(); } } diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java new file mode 100644 index 000000000000..236a23205180 --- /dev/null +++ b/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java @@ -0,0 +1,157 @@ +package com.thealgorithms.tree; + +import java.util.ArrayList; +import java.util.List; + +/** + * Heavy-Light Decomposition (HLD) implementation in Java. + * HLD is used to efficiently handle path queries on trees, such as maximum, + * sum, or updates. It decomposes the tree into heavy and light chains, + * enabling queries in O(log N) time. + * Wikipedia Reference: https://en.wikipedia.org/wiki/Heavy-light_decomposition + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + */ + +public class HeavyLightDecomposition { + private List> tree; + private int[] parent; + private int[] depth; + private int[] subtreeSize; + private int[] chainHead; + private int[] position; + private int[] nodeValue; + private int[] segmentTree; + private int positionIndex; + + public HeavyLightDecomposition(int n) { + tree = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + tree.add(new ArrayList<>()); + } + parent = new int[n + 1]; + depth = new int[n + 1]; + subtreeSize = new int[n + 1]; + chainHead = new int[n + 1]; + position = new int[n + 1]; + nodeValue = new int[n + 1]; + segmentTree = new int[4 * (n + 1)]; + for (int i = 0; i <= n; i++) { + chainHead[i] = -1; + } + positionIndex = 0; + } + + public int getPosition(int index) { + return position[index]; + } + + public int getPositionIndex() { + return positionIndex; + } + + public void addEdge(int u, int v) { + tree.get(u).add(v); + tree.get(v).add(u); + } + + private void dfsSize(int node, int parentNode) { + parent[node] = parentNode; + subtreeSize[node] = 1; + for (int child : tree.get(node)) { + if (child != parentNode) { + depth[child] = depth[node] + 1; + dfsSize(child, node); + subtreeSize[node] += subtreeSize[child]; + } + } + } + + private void decompose(int node, int head) { + chainHead[node] = head; + position[node] = positionIndex++; + int heavyChild = -1; + int maxSubtreeSize = -1; + for (int child : tree.get(node)) { + if (child != parent[node] && subtreeSize[child] > maxSubtreeSize) { + heavyChild = child; + maxSubtreeSize = subtreeSize[child]; + } + } + if (heavyChild != -1) { + decompose(heavyChild, head); + } + for (int child : tree.get(node)) { + if (child != parent[node] && child != heavyChild) { + decompose(child, child); + } + } + } + + private void buildSegmentTree(int node, int start, int end) { + if (start == end) { + segmentTree[node] = nodeValue[start]; + return; + } + int mid = (start + end) / 2; + buildSegmentTree(2 * node, start, mid); + buildSegmentTree(2 * node + 1, mid + 1, end); + segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); + } + + public void updateSegmentTree(int node, int start, int end, int index, int value) { + if (start == end) { + segmentTree[node] = value; + return; + } + int mid = (start + end) / 2; + if (index <= mid) { + updateSegmentTree(2 * node, start, mid, index, value); + } else { + updateSegmentTree(2 * node + 1, mid + 1, end, index, value); + } + segmentTree[node] = Math.max(segmentTree[2 * node], segmentTree[2 * node + 1]); + } + + public int querySegmentTree(int node, int start, int end, int left, int right) { + if (left > end || right < start) { + return Integer.MIN_VALUE; + } + if (left <= start && end <= right) { + return segmentTree[node]; + } + int mid = (start + end) / 2; + int leftQuery = querySegmentTree(2 * node, start, mid, left, right); + int rightQuery = querySegmentTree(2 * node + 1, mid + 1, end, left, right); + return Math.max(leftQuery, rightQuery); + } + + public int queryMaxInPath(int u, int v) { + int result = Integer.MIN_VALUE; + while (chainHead[u] != chainHead[v]) { + if (depth[chainHead[u]] < depth[chainHead[v]]) { + int temp = u; + u = v; + v = temp; + } + result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[chainHead[u]], position[u])); + u = parent[chainHead[u]]; + } + if (depth[u] > depth[v]) { + int temp = u; + u = v; + v = temp; + } + result = Math.max(result, querySegmentTree(1, 0, positionIndex - 1, position[u], position[v])); + return result; + } + + public void initialize(int root, int[] values) { + dfsSize(root, -1); + decompose(root, root); + for (int i = 0; i < values.length; i++) { + nodeValue[position[i]] = values[i]; + } + buildSegmentTree(1, 0, positionIndex - 1); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java b/src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java index 40de770e0c66..9d888d056453 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/BitSwapTest.java @@ -1,13 +1,62 @@ package com.thealgorithms.bitmanipulation; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; -import org.junit.jupiter.api.Test; -public class BitSwapTest { - @Test - void testHighestSetBit() { - assertEquals(3, BitSwap.bitSwap(3, 0, 1)); - assertEquals(5, BitSwap.bitSwap(6, 0, 1)); - assertEquals(7, BitSwap.bitSwap(7, 1, 1)); +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class BitSwapTest { + + @ParameterizedTest(name = "Additional cases: data={0}, posA={1}, posB={2} -> expected={3}") + @MethodSource("provideAdditionalCases") + void testAdditionalCases(int data, int posA, int posB, int expected) { + assertEquals(expected, BitSwap.bitSwap(data, posA, posB)); + } + + @ParameterizedTest(name = "Swap different bits: data={0}, posA={1}, posB={2} -> expected={3}") + @MethodSource("provideDifferentBitsCases") + void swapDifferentBits(int data, int posA, int posB, int expected) { + assertEquals(expected, BitSwap.bitSwap(data, posA, posB)); + } + + @ParameterizedTest(name = "Swap same bits: data={0}, posA={1}, posB={2} should not change") + @MethodSource("provideSameBitsCases") + void swapSameBits(int data, int posA, int posB) { + assertEquals(data, BitSwap.bitSwap(data, posA, posB)); + } + + @ParameterizedTest(name = "Edge cases: data={0}, posA={1}, posB={2} -> expected={3}") + @MethodSource("provideEdgeCases") + void testEdgeCases(int data, int posA, int posB, int expected) { + assertEquals(expected, BitSwap.bitSwap(data, posA, posB)); + } + + @ParameterizedTest(name = "Invalid positions: data={0}, posA={1}, posB={2} should throw") + @MethodSource("provideInvalidPositions") + void invalidPositionThrowsException(int data, int posA, int posB) { + assertThrows(IllegalArgumentException.class, () -> BitSwap.bitSwap(data, posA, posB)); + } + + static Stream provideAdditionalCases() { + return Stream.of(Arguments.of(3, 0, 1, 3), Arguments.of(6, 0, 1, 5), Arguments.of(7, 1, 1, 7)); + } + + static Stream provideDifferentBitsCases() { + return Stream.of(Arguments.of(0b01, 0, 1, 0b10)); + } + + static Stream provideSameBitsCases() { + return Stream.of(Arguments.of(0b111, 0, 2), Arguments.of(0b0, 1, 3), Arguments.of(0b1010, 1, 3), Arguments.of(-1, 5, 5)); + } + + static Stream provideEdgeCases() { + return Stream.of(Arguments.of(Integer.MIN_VALUE, 31, 0, 1), Arguments.of(0, 0, 31, 0)); + } + + static Stream provideInvalidPositions() { + return Stream.of(Arguments.of(0, -1, 0), Arguments.of(0, 0, 32), Arguments.of(0, -5, 33), Arguments.of(0, Integer.MIN_VALUE, Integer.MAX_VALUE)); } } diff --git a/src/test/java/com/thealgorithms/bitmanipulation/BitwiseGCDTest.java b/src/test/java/com/thealgorithms/bitmanipulation/BitwiseGCDTest.java new file mode 100644 index 000000000000..207fdc1e57a0 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/BitwiseGCDTest.java @@ -0,0 +1,110 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.math.BigInteger; +import org.junit.jupiter.api.Test; + +public class BitwiseGCDTest { + + @Test + public void testGcdBasic() { + assertEquals(6L, BitwiseGCD.gcd(48L, 18L)); + } + + @Test + public void testGcdZeroAndNonZero() { + assertEquals(5L, BitwiseGCD.gcd(0L, 5L)); + assertEquals(5L, BitwiseGCD.gcd(5L, 0L)); + } + + @Test + public void testGcdBothZero() { + assertEquals(0L, BitwiseGCD.gcd(0L, 0L)); + } + + @Test + public void testGcdNegativeInputs() { + assertEquals(6L, BitwiseGCD.gcd(-48L, 18L)); + assertEquals(6L, BitwiseGCD.gcd(48L, -18L)); + assertEquals(6L, BitwiseGCD.gcd(-48L, -18L)); + } + + @Test + public void testGcdIntOverload() { + assertEquals(6, BitwiseGCD.gcd(48, 18)); + } + + @Test + public void testGcdArray() { + long[] values = {48L, 18L, 6L}; + assertEquals(6L, BitwiseGCD.gcd(values)); + } + + @Test + public void testGcdEmptyArray() { + long[] empty = {}; + assertEquals(0L, BitwiseGCD.gcd(empty)); + } + + @Test + public void testGcdCoprime() { + assertEquals(1L, BitwiseGCD.gcd(17L, 13L)); + } + + @Test + public void testGcdPowersOfTwo() { + assertEquals(1024L, BitwiseGCD.gcd(1L << 20, 1L << 10)); + } + + @Test + public void testGcdLargeNumbers() { + assertEquals(6L, BitwiseGCD.gcd(270L, 192L)); + } + + @Test + public void testGcdEarlyExitArray() { + long[] manyCoprimes = {7L, 11L, 13L, 17L, 19L, 23L, 29L}; + assertEquals(1L, BitwiseGCD.gcd(manyCoprimes)); + } + + @Test + public void testGcdSameNumbers() { + assertEquals(42L, BitwiseGCD.gcd(42L, 42L)); + } + + @Test + public void testGcdLongMinValueBigInteger() { + // gcd(Long.MIN_VALUE, 0) = |Long.MIN_VALUE| = 2^63; must use BigInteger to represent it + BigInteger expected = BigInteger.ONE.shiftLeft(63); // 2^63 + assertEquals(expected, BitwiseGCD.gcdBig(Long.MIN_VALUE, 0L)); + } + + @Test + public void testGcdLongMinValueLongOverloadThrows() { + // The long overload cannot return 2^63 as a positive signed long, so it must throw + assertThrows(ArithmeticException.class, () -> BitwiseGCD.gcd(Long.MIN_VALUE, 0L)); + } + + @Test + public void testGcdWithLongMinAndOther() { + // gcd(Long.MIN_VALUE, 2^10) should be 2^10 + long p = 1L << 10; + BigInteger expected = BigInteger.valueOf(p); + assertEquals(expected, BitwiseGCD.gcdBig(Long.MIN_VALUE, p)); + } + + @Test + public void testGcdWithBothLongMin() { + // gcd(Long.MIN_VALUE, Long.MIN_VALUE) = 2^63 + BigInteger expected = BigInteger.ONE.shiftLeft(63); + assertEquals(expected, BitwiseGCD.gcdBig(Long.MIN_VALUE, Long.MIN_VALUE)); + } + + @Test + public void testGcdEdgeCasesMixed() { + assertEquals(1L, BitwiseGCD.gcd(1L, Long.MAX_VALUE)); + assertEquals(1L, BitwiseGCD.gcd(Long.MAX_VALUE, 1L)); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountBitsFlipTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountBitsFlipTest.java new file mode 100644 index 000000000000..f33fa0fefbc0 --- /dev/null +++ b/src/test/java/com/thealgorithms/bitmanipulation/CountBitsFlipTest.java @@ -0,0 +1,94 @@ +package com.thealgorithms.bitmanipulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Random; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for CountBitsFlip. + * Covers: + * - simple examples + * - zeros and identical values + * - negative numbers and two's complement edge cases + * - Long.MIN_VALUE / Long.MAX_VALUE + * - randomized consistency checks between two implementations + */ +@DisplayName("CountBitsFlip Tests") +class CountBitsFlipTest { + + @Test + @DisplayName("Example: A=10, B=20 => 4 bits") + void exampleTenTwenty() { + long a = 10L; + long b = 20L; + long expected = 4L; + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b), "Brian Kernighan implementation should return 4"); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b), "Long.bitCount implementation should return 4"); + } + + @Test + @DisplayName("Identical values => 0 bits") + void identicalValues() { + long a = 123456789L; + long b = 123456789L; + long expected = 0L; + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b)); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b)); + } + + @Test + @DisplayName("Both zeros => 0 bits") + void bothZeros() { + assertEquals(0L, CountBitsFlip.countBitsFlip(0L, 0L)); + assertEquals(0L, CountBitsFlip.countBitsFlipAlternative(0L, 0L)); + } + + @Test + @DisplayName("Small example: A=15 (1111), B=8 (1000) => 3 bits") + void smallExample() { + long a = 15L; // 1111 + long b = 8L; // 1000 + long expected = 3L; // differs in three low bits + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b)); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b)); + } + + @Test + @DisplayName("Negative values: -1 vs 0 => 64 bits (two's complement all ones)") + void negativeVsZero() { + long a = -1L; + long b = 0L; + long expected = 64L; // all 64 bits differ + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b)); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b)); + } + + @Test + @DisplayName("Long.MIN_VALUE vs Long.MAX_VALUE => 64 bits") + void minMaxLongs() { + long a = Long.MIN_VALUE; + long b = Long.MAX_VALUE; + long expected = 64L; // MAX ^ MIN yields all ones on 64-bit long + assertEquals(expected, CountBitsFlip.countBitsFlip(a, b)); + assertEquals(expected, CountBitsFlip.countBitsFlipAlternative(a, b)); + } + + @Test + @DisplayName("Randomized consistency: both implementations agree across many pairs") + void randomizedConsistency() { + final int iterations = 1000; + final Random rnd = new Random(12345L); // deterministic seed for reproducibility + + for (int i = 0; i < iterations; i++) { + long a = rnd.nextLong(); + long b = rnd.nextLong(); + + long res1 = CountBitsFlip.countBitsFlip(a, b); + long res2 = CountBitsFlip.countBitsFlipAlternative(a, b); + + assertEquals(res2, res1, () -> String.format("Mismatch for a=%d, b=%d: impl1=%d, impl2=%d", a, b, res1, res2)); + } + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java b/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java index 6be4eb595f79..0e90ed79f587 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/OnesComplementTest.java @@ -1,8 +1,12 @@ package com.thealgorithms.bitmanipulation; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; /** * Test case for Highest Set Bit @@ -39,9 +43,16 @@ public void testOnesComplementMixedBits() { assertEquals("1001", OnesComplement.onesComplement("0110")); } + @ParameterizedTest + @NullAndEmptySource + public void testOnesComplementNullOrEmptyInputThrowsException(String input) { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> OnesComplement.onesComplement(input)); + assertEquals("Input must be a non-empty binary string.", exception.getMessage()); + } + @Test - public void testOnesComplementEmptyString() { - // Test empty string scenario - assertEquals("", OnesComplement.onesComplement("")); + public void testOnesComplementInvalidCharactersThrowsException() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> OnesComplement.onesComplement("10a1")); + assertTrue(exception.getMessage().startsWith("Input must contain only '0' and '1'")); } } diff --git a/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java b/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java index 90147a61207b..1654c8ddfc1e 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/ParityCheckTest.java @@ -6,10 +6,30 @@ import org.junit.jupiter.api.Test; public class ParityCheckTest { + @Test + public void testIsEvenParity() { + assertTrue(ParityCheck.checkParity(0)); // 0 -> 0 ones + assertTrue(ParityCheck.checkParity(3)); // 11 -> 2 ones + assertTrue(ParityCheck.checkParity(5)); // 101 -> 2 ones + assertTrue(ParityCheck.checkParity(10)); // 1010 -> 2 ones + assertTrue(ParityCheck.checkParity(15)); // 1111 -> 4 ones + assertTrue(ParityCheck.checkParity(1023)); // 10 ones + } + @Test public void testIsOddParity() { - assertTrue(ParityCheck.checkParity(5)); // 101 has 2 ones (even parity) - assertFalse(ParityCheck.checkParity(7)); // 111 has 3 ones (odd parity) - assertFalse(ParityCheck.checkParity(8)); // 1000 has 1 one (odd parity) + assertFalse(ParityCheck.checkParity(1)); // 1 -> 1 one + assertFalse(ParityCheck.checkParity(2)); // 10 -> 1 one + assertFalse(ParityCheck.checkParity(7)); // 111 -> 3 ones + assertFalse(ParityCheck.checkParity(8)); // 1000 -> 1 one + assertFalse(ParityCheck.checkParity(11)); // 1011 -> 3 ones + assertFalse(ParityCheck.checkParity(31)); // 11111 -> 5 ones + } + + @Test + public void testLargeNumbers() { + assertTrue(ParityCheck.checkParity(0b10101010)); // 4 ones + assertFalse(ParityCheck.checkParity(0b100000000)); // 1 one + assertTrue(ParityCheck.checkParity(0xAAAAAAAA)); // Alternating bits, 16 ones } } diff --git a/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java new file mode 100644 index 000000000000..4ba6787cc97e --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java @@ -0,0 +1,323 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class PermutationCipherTest { + + private final PermutationCipher cipher = new PermutationCipher(); + + @Test + void testBasicEncryption() { + // given + String plaintext = "HELLO"; + int[] key = {3, 1, 2}; // Move 3rd position to 1st, 1st to 2nd, 2nd to 3rd + + // when + String encrypted = cipher.encrypt(plaintext, key); + + // then + // "HELLO" becomes "HEL" + "LOX" (padded) + // "HEL" with key {3,1,2} becomes "LHE" (L=3rd, H=1st, E=2nd) + // "LOX" with key {3,1,2} becomes "XLO" (X=3rd, L=1st, O=2nd) + assertEquals("LHEXLO", encrypted); + } + + @Test + void testBasicDecryption() { + // given + String ciphertext = "LHEXLO"; + int[] key = {3, 1, 2}; + + // when + String decrypted = cipher.decrypt(ciphertext, key); + + // then + assertEquals("HELLO", decrypted); + } + + @Test + void testEncryptDecryptRoundTrip() { + // given + String plaintext = "THIS IS A TEST MESSAGE"; + int[] key = {4, 2, 1, 3}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("THISISATESTMESSAGE", decrypted); // Spaces are removed during encryption + } + + @Test + void testSingleCharacterKey() { + // given + String plaintext = "ABCDEF"; + int[] key = {1}; // Identity permutation + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("ABCDEF", encrypted); // Should remain unchanged + assertEquals("ABCDEF", decrypted); + } + + @Test + void testLargerKey() { + // given + String plaintext = "PERMUTATION"; + int[] key = {5, 3, 1, 4, 2}; // 5-character permutation + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("PERMUTATION", decrypted); + } + + @Test + void testExactBlockSize() { + // given + String plaintext = "ABCDEF"; // Length 6, divisible by key length 3 + int[] key = {2, 3, 1}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("ABCDEF", decrypted); + } + + @Test + void testEmptyString() { + // given + String plaintext = ""; + int[] key = {2, 1, 3}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("", encrypted); + assertEquals("", decrypted); + } + + @Test + void testNullString() { + // given + String plaintext = null; + int[] key = {2, 1, 3}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals(null, encrypted); + assertEquals(null, decrypted); + } + + @Test + void testStringWithSpaces() { + // given + String plaintext = "A B C D E F"; + int[] key = {2, 1}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("ABCDEF", decrypted); // Spaces should be removed + } + + @Test + void testLowercaseConversion() { + // given + String plaintext = "hello world"; + int[] key = {3, 1, 2}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("HELLOWORLD", decrypted); // Should be converted to uppercase + } + + @Test + void testInvalidKeyNull() { + // given + String plaintext = "HELLO"; + int[] key = null; + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyEmpty() { + // given + String plaintext = "HELLO"; + int[] key = {}; + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyOutOfRange() { + // given + String plaintext = "HELLO"; + int[] key = {1, 2, 4}; // 4 is out of range for key length 3 + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyZero() { + // given + String plaintext = "HELLO"; + int[] key = {0, 1, 2}; // 0 is invalid (should be 1-based) + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyDuplicate() { + // given + String plaintext = "HELLO"; + int[] key = {1, 2, 2}; // Duplicate position + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testInvalidKeyMissingPosition() { + // given + String plaintext = "HELLO"; + int[] key = {1, 3}; // Missing position 2 + + // when & then + assertThrows(IllegalArgumentException.class, () -> cipher.encrypt(plaintext, key)); + assertThrows(IllegalArgumentException.class, () -> cipher.decrypt(plaintext, key)); + } + + @Test + void testReverseKey() { + // given + String plaintext = "ABCD"; + int[] key = {4, 3, 2, 1}; // Reverse order + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("DCBA", encrypted); // Should be reversed + assertEquals("ABCD", decrypted); + } + + @Test + void testSpecificExampleFromDescription() { + // given + String plaintext = "HELLO"; + int[] key = {3, 1, 2}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + + // then + // Block 1: "HEL" -> positions {3,1,2} -> "LHE" + // Block 2: "LOX" -> positions {3,1,2} -> "XLO" + assertEquals("LHEXLO", encrypted); + // Verify decryption + String decrypted = cipher.decrypt(encrypted, key); + assertEquals("HELLO", decrypted); + } + + @Test + void testPaddingCharacterGetter() { + // when + char paddingChar = cipher.getPaddingChar(); + + // then + assertEquals('X', paddingChar); + } + + @Test + void testLongText() { + // given + String plaintext = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"; + int[] key = {4, 1, 3, 2}; + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("THEQUICKBROWNFOXJUMPSOVERTHELAZYDOG", decrypted); + } + + @Test + void testIdentityPermutation() { + // given + String plaintext = "IDENTITY"; + int[] key = {1, 2, 3, 4}; // Identity permutation + + // when + String encrypted = cipher.encrypt(plaintext, key); + String decrypted = cipher.decrypt(encrypted, key); + + // then + assertEquals("IDENTITY", encrypted); // Should remain unchanged + assertEquals("IDENTITY", decrypted); + } + + @Test + void testEmptyStringRemovePadding() { + // given - Test to cover line 178 (empty string case in removePadding) + String ciphertext = ""; + int[] key = {2, 1, 3}; + + // when + String decrypted = cipher.decrypt(ciphertext, key); + + // then + assertEquals("", decrypted); // Should return empty string directly + } + + @Test + void testBlockShorterThanKey() { + // given - Test to cover line 139 (block length != key length case) + // This is a defensive case where permuteBlock might receive a block shorter than key + // We can test this by manually creating a scenario with malformed ciphertext + String malformedCiphertext = "AB"; // Length 2, but key length is 3 + int[] key = {3, 1, 2}; // Key length is 3 + + // when - This should trigger the padding logic in permuteBlock during decryption + String decrypted = cipher.decrypt(malformedCiphertext, key); + + // then - The method should handle the short block gracefully + // "AB" gets padded to "ABX", then permuted with inverse key {2,3,1} + // inverse key {2,3,1} means: pos 2โ†’1st, pos 3โ†’2nd, pos 1โ†’3rd = "BXA" + // Padding removal only removes trailing X's, so "BXA" remains as is + assertEquals("BXA", decrypted); + } +} diff --git a/src/test/java/com/thealgorithms/compression/ArithmeticCodingTest.java b/src/test/java/com/thealgorithms/compression/ArithmeticCodingTest.java new file mode 100644 index 000000000000..8e51fe5eb463 --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/ArithmeticCodingTest.java @@ -0,0 +1,154 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ArithmeticCodingTest { + + @Test + void testThrowsExceptionForNullOrEmptyInput() { + // Test that null input throws IllegalArgumentException + assertThrows(IllegalArgumentException.class, () -> ArithmeticCoding.compress(null)); + + // Test that empty string throws IllegalArgumentException + assertThrows(IllegalArgumentException.class, () -> ArithmeticCoding.compress("")); + } + + @Test + void testCompressionAndDecompressionSimple() { + String original = "BABA"; + Map probTable = ArithmeticCoding.calculateProbabilities(original); + BigDecimal compressed = ArithmeticCoding.compress(original); + + // Verify that compression produces a valid number in [0, 1) + assertNotNull(compressed); + assertTrue(compressed.compareTo(BigDecimal.ZERO) >= 0); + assertTrue(compressed.compareTo(BigDecimal.ONE) < 0); + + // Verify decompression restores the original string + String decompressed = ArithmeticCoding.decompress(compressed, original.length(), probTable); + assertEquals(original, decompressed); + } + + @Test + void testSymmetryWithComplexString() { + String original = "THE_QUICK_BROWN_FOX_JUMPS_OVER_THE_LAZY_DOG"; + Map probTable = ArithmeticCoding.calculateProbabilities(original); + BigDecimal compressed = ArithmeticCoding.compress(original); + + // Verify compression produces a number in valid range + assertTrue(compressed.compareTo(BigDecimal.ZERO) >= 0); + assertTrue(compressed.compareTo(BigDecimal.ONE) < 0); + + // Verify symmetry: decompress(compress(x)) == x + String decompressed = ArithmeticCoding.decompress(compressed, original.length(), probTable); + assertEquals(original, decompressed); + } + + @Test + void testSymmetryWithRepetitions() { + String original = "MISSISSIPPI"; + Map probTable = ArithmeticCoding.calculateProbabilities(original); + BigDecimal compressed = ArithmeticCoding.compress(original); + + // Verify compression produces a number in valid range + assertTrue(compressed.compareTo(BigDecimal.ZERO) >= 0); + assertTrue(compressed.compareTo(BigDecimal.ONE) < 0); + + // Verify the compression-decompression cycle + String decompressed = ArithmeticCoding.decompress(compressed, original.length(), probTable); + assertEquals(original, decompressed); + } + + @Test + void testSingleCharacterString() { + String original = "AAAAA"; + Map probTable = ArithmeticCoding.calculateProbabilities(original); + BigDecimal compressed = ArithmeticCoding.compress(original); + + // Even with a single unique character, compression should work + assertTrue(compressed.compareTo(BigDecimal.ZERO) >= 0); + assertTrue(compressed.compareTo(BigDecimal.ONE) < 0); + + String decompressed = ArithmeticCoding.decompress(compressed, original.length(), probTable); + assertEquals(original, decompressed); + } + + @Test + void testCompressionOutputDemo() { + // Demonstrate actual compression output similar to LZW test + String original = "BABA"; + BigDecimal compressed = ArithmeticCoding.compress(original); + + // Example: "BABA" compresses to approximately 0.625 + // This shows that the entire message is encoded as a single number + System.out.println("Original: " + original); + System.out.println("Compressed to: " + compressed); + System.out.println("Compression: " + original.length() + " characters -> 1 BigDecimal number"); + + // Verify the compressed value is in valid range [0, 1) + assertTrue(compressed.compareTo(BigDecimal.ZERO) >= 0); + assertTrue(compressed.compareTo(BigDecimal.ONE) < 0); + } + + @Test + void testProbabilityTableCalculation() { + // Test that probability table is calculated correctly + String text = "AABBC"; + Map probTable = ArithmeticCoding.calculateProbabilities(text); + + // Verify all characters are in the table + assertTrue(probTable.containsKey('A')); + assertTrue(probTable.containsKey('B')); + assertTrue(probTable.containsKey('C')); + + // Verify probability ranges are valid + for (ArithmeticCoding.Symbol symbol : probTable.values()) { + assertTrue(symbol.low().compareTo(BigDecimal.ZERO) >= 0); + assertTrue(symbol.high().compareTo(BigDecimal.ONE) <= 0); + assertTrue(symbol.low().compareTo(symbol.high()) < 0); + } + } + + @Test + void testDecompressionWithMismatchedProbabilityTable() { + // Test decompression with a probability table that doesn't match the original + String original = "ABCD"; + BigDecimal compressed = ArithmeticCoding.compress(original); + + // Create a different probability table (for "XYZ" instead of "ABCD") + Map wrongProbTable = ArithmeticCoding.calculateProbabilities("XYZ"); + + // Decompression with wrong probability table should produce incorrect output + String decompressed = ArithmeticCoding.decompress(compressed, original.length(), wrongProbTable); + + // The decompressed string will be different from original (likely all 'X', 'Y', or 'Z') + // This tests the edge case where the compressed value doesn't fall into expected ranges + assertNotNull(decompressed); + assertEquals(original.length(), decompressed.length()); + } + + @Test + void testDecompressionWithValueOutsideSymbolRanges() { + // Create a custom probability table + Map probTable = new HashMap<>(); + probTable.put('A', new ArithmeticCoding.Symbol(new BigDecimal("0.0"), new BigDecimal("0.5"))); + probTable.put('B', new ArithmeticCoding.Symbol(new BigDecimal("0.5"), new BigDecimal("1.0"))); + + // Use a compressed value that should decode properly + BigDecimal compressed = new BigDecimal("0.25"); // Falls in 'A' range + + String decompressed = ArithmeticCoding.decompress(compressed, 3, probTable); + + // Verify decompression completes (even if result might not be meaningful) + assertNotNull(decompressed); + assertEquals(3, decompressed.length()); + } +} diff --git a/src/test/java/com/thealgorithms/compression/BurrowsWheelerTransformTest.java b/src/test/java/com/thealgorithms/compression/BurrowsWheelerTransformTest.java new file mode 100644 index 000000000000..b6e10e0d796d --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/BurrowsWheelerTransformTest.java @@ -0,0 +1,124 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class BurrowsWheelerTransformTest { + + @Test + public void testTransformAndInverseBanana() { + String original = "banana$"; + BurrowsWheelerTransform.BWTResult expectedTransform = new BurrowsWheelerTransform.BWTResult("annb$aa", 4); + + // Test forward transform + BurrowsWheelerTransform.BWTResult actualTransform = BurrowsWheelerTransform.transform(original); + assertEquals(expectedTransform, actualTransform); + + // Test inverse transform + String reconstructed = BurrowsWheelerTransform.inverseTransform(actualTransform.transformed, actualTransform.originalIndex); + assertEquals(original, reconstructed); + } + + @Test + public void testTransformAndInverseAbracadabra() { + String original = "abracadabra$"; + BurrowsWheelerTransform.BWTResult expectedTransform = new BurrowsWheelerTransform.BWTResult("ard$rcaaaabb", 3); + + // Test forward transform + BurrowsWheelerTransform.BWTResult actualTransform = BurrowsWheelerTransform.transform(original); + assertEquals(expectedTransform, actualTransform); + + // Test inverse transform + String reconstructed = BurrowsWheelerTransform.inverseTransform(actualTransform.transformed, actualTransform.originalIndex); + assertEquals(original, reconstructed); + } + + @Test + public void testTransformAndInverseSixMixPixFix() { + String original = "SIX.MIX.PIX.FIX$"; + BurrowsWheelerTransform.BWTResult expectedTransform = new BurrowsWheelerTransform.BWTResult("XXXX.FPSM..$IIII", 11); + + // Test forward transform + BurrowsWheelerTransform.BWTResult actualTransform = BurrowsWheelerTransform.transform(original); + assertEquals(expectedTransform, actualTransform); + + // Test inverse transform + String reconstructed = BurrowsWheelerTransform.inverseTransform(actualTransform.transformed, actualTransform.originalIndex); + assertEquals(original, reconstructed); + } + + @Test + public void testEmptyString() { + String original = ""; + BurrowsWheelerTransform.BWTResult expectedTransform = new BurrowsWheelerTransform.BWTResult("", -1); + + BurrowsWheelerTransform.BWTResult actualTransform = BurrowsWheelerTransform.transform(original); + assertEquals(expectedTransform, actualTransform); + + String reconstructed = BurrowsWheelerTransform.inverseTransform(actualTransform.transformed, actualTransform.originalIndex); + assertEquals(original, reconstructed); + } + + @Test + public void testSingleCharacter() { + String original = "a"; + BurrowsWheelerTransform.BWTResult expectedTransform = new BurrowsWheelerTransform.BWTResult("a", 0); + + BurrowsWheelerTransform.BWTResult actualTransform = BurrowsWheelerTransform.transform(original); + assertEquals(expectedTransform, actualTransform); + + String reconstructed = BurrowsWheelerTransform.inverseTransform(actualTransform.transformed, actualTransform.originalIndex); + assertEquals(original, reconstructed); + } + + @Test + public void testTransformNull() { + assertEquals(new BurrowsWheelerTransform.BWTResult("", -1), BurrowsWheelerTransform.transform(null)); + } + + @Test + public void testInverseTransformNullString() { + // bwtString == null + assertEquals("", BurrowsWheelerTransform.inverseTransform(null, 1)); + // bwtString.isEmpty() + assertEquals("", BurrowsWheelerTransform.inverseTransform("", 0)); + } + + @Test + public void testInverseTransformIndexOutOfBounds() { + String bwt = "annb$aa"; + int n = bwt.length(); // n = 7 + + // originalIndex >= n + assertThrows(IllegalArgumentException.class, () -> BurrowsWheelerTransform.inverseTransform(bwt, n)); + assertThrows(IllegalArgumentException.class, () -> BurrowsWheelerTransform.inverseTransform(bwt, 8)); + + // originalIndex < 0 + assertThrows(IllegalArgumentException.class, () -> BurrowsWheelerTransform.inverseTransform(bwt, -2)); + } + + @Test + public void testBWTResultHelpers() { + BurrowsWheelerTransform.BWTResult res1 = new BurrowsWheelerTransform.BWTResult("annb$aa", 4); + BurrowsWheelerTransform.BWTResult res2 = new BurrowsWheelerTransform.BWTResult("annb$aa", 4); + BurrowsWheelerTransform.BWTResult res3 = new BurrowsWheelerTransform.BWTResult("other", 4); + BurrowsWheelerTransform.BWTResult res4 = new BurrowsWheelerTransform.BWTResult("annb$aa", 1); + + assertEquals(res1, res1); + assertEquals(res1, res2); + assertNotEquals(res1, null); // obj == null + assertNotEquals(res1, new Object()); // different class + assertNotEquals(res1, res3); // different transformed + assertNotEquals(res1, res4); // different originalIndex + + assertEquals(res1.hashCode(), res2.hashCode()); + assertNotEquals(res1.hashCode(), res3.hashCode()); + + assertTrue(res1.toString().contains("annb$aa")); + assertTrue(res1.toString().contains("originalIndex=4")); + } +} diff --git a/src/test/java/com/thealgorithms/compression/LZ77Test.java b/src/test/java/com/thealgorithms/compression/LZ77Test.java new file mode 100644 index 000000000000..86732d48a54a --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/LZ77Test.java @@ -0,0 +1,223 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class LZ77Test { + + @Test + @DisplayName("Test compression and decompression of a simple repeating string") + void testSimpleRepeatingString() { + String original = "ababcbababaa"; + List compressed = LZ77.compress(original, 10, 4); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test compression and decompression of a string with no repeats initially") + void testNoInitialRepeats() { + String original = "abcdefgh"; + List compressed = LZ77.compress(original); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test compression and decompression of a longer example") + void testLongerExample() { + String original = "TOBEORNOTTOBEORTOBEORNOT"; + List compressed = LZ77.compress(original, 20, 10); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test empty string compression and decompression") + void testEmptyString() { + String original = ""; + List compressed = LZ77.compress(original); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + assertTrue(compressed.isEmpty()); + } + + @Test + @DisplayName("Test null string compression") + void testNullStringCompress() { + List compressed = LZ77.compress(null); + assertTrue(compressed.isEmpty()); + } + + @Test + @DisplayName("Test null list decompression") + void testNullListDecompress() { + String decompressed = LZ77.decompress(null); + assertEquals("", decompressed); + } + + @Test + @DisplayName("Test invalid buffer sizes throw exception") + void testInvalidBufferSizes() { + assertThrows(IllegalArgumentException.class, () -> LZ77.compress("test", 0, 5)); + assertThrows(IllegalArgumentException.class, () -> LZ77.compress("test", 5, 0)); + assertThrows(IllegalArgumentException.class, () -> LZ77.compress("test", -1, 5)); + assertThrows(IllegalArgumentException.class, () -> LZ77.compress("test", 5, -1)); + } + + @Test + @DisplayName("Test string with all same characters") + void testAllSameCharacters() { + String original = "AAAAAA"; + List compressed = LZ77.compress(original, 10, 5); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + + // Should achieve good compression for repeated characters + assertTrue(compressed.size() < original.length()); + } + + @Test + @DisplayName("Test string with all unique characters") + void testAllUniqueCharacters() { + String original = "abcdefghijklmnop"; + List compressed = LZ77.compress(original, 10, 5); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + + // No compression expected for unique characters + assertEquals(original.length(), compressed.size()); + } + + @Test + @DisplayName("Test single character string") + void testSingleCharacter() { + String original = "a"; + List compressed = LZ77.compress(original); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + assertEquals(1, compressed.size()); + } + + @Test + @DisplayName("Test match that goes exactly to the end") + void testMatchToEnd() { + String original = "abcabc"; + List compressed = LZ77.compress(original, 10, 10); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test with very small window size") + void testSmallWindowSize() { + String original = "ababababab"; + List compressed = LZ77.compress(original, 2, 4); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test with very small lookahead buffer") + void testSmallLookaheadBuffer() { + String original = "ababcbababaa"; + List compressed = LZ77.compress(original, 10, 2); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test repeating pattern at the end") + void testRepeatingPatternAtEnd() { + String original = "xyzabcabcabcabc"; + List compressed = LZ77.compress(original, 15, 8); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test overlapping matches (run-length encoding case)") + void testOverlappingMatches() { + String original = "aaaaaa"; + List compressed = LZ77.compress(original, 10, 10); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test complex pattern with multiple repeats") + void testComplexPattern() { + String original = "abcabcabcxyzxyzxyz"; + List compressed = LZ77.compress(original, 20, 10); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test with special characters") + void testSpecialCharacters() { + String original = "hello world! @#$%^&*()"; + List compressed = LZ77.compress(original); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test with numbers") + void testWithNumbers() { + String original = "1234567890123456"; + List compressed = LZ77.compress(original, 15, 8); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test long repeating sequence") + void testLongRepeatingSequence() { + String original = "abcdefgh".repeat(10); + List compressed = LZ77.compress(original, 50, 20); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + + // Should achieve significant compression + assertTrue(compressed.size() < original.length() / 2); + } + + @Test + @DisplayName("Test compression effectiveness") + void testCompressionEffectiveness() { + String original = "ababababababab"; + List compressed = LZ77.compress(original, 20, 10); + + // Verify that compression actually reduces the data size + // Each token represents potentially multiple characters + assertTrue(compressed.size() <= original.length()); + + // Verify decompression + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test with mixed case letters") + void testMixedCase() { + String original = "AaBbCcAaBbCc"; + List compressed = LZ77.compress(original); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test default parameters") + void testDefaultParameters() { + String original = "This is a test string with some repeated patterns. This is repeated."; + List compressed = LZ77.compress(original); + String decompressed = LZ77.decompress(compressed); + assertEquals(original, decompressed); + } +} diff --git a/src/test/java/com/thealgorithms/compression/LZ78Test.java b/src/test/java/com/thealgorithms/compression/LZ78Test.java new file mode 100644 index 000000000000..7889b50b76f3 --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/LZ78Test.java @@ -0,0 +1,295 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class LZ78Test { + + @Test + @DisplayName("Test compression and decompression of a simple repeating string") + void testSimpleRepeatingString() { + String original = "ababcbababaa"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test compression and decompression example ABAABABAABAB") + void testStandardExample() { + String original = "ABAABABAABAB"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // Verify the compression produces expected tokens + // Expected: (0,A)(0,B)(1,A)(2,B)(3,A)(4,B) + // Where dictionary builds as: 1:A, 2:B, 3:AA, 4:BA, 5:ABA, 6:BAB + assertEquals(6, compressed.size()); + assertEquals(0, compressed.get(0).index()); + assertEquals('A', compressed.get(0).nextChar()); + assertEquals(0, compressed.get(1).index()); + assertEquals('B', compressed.get(1).nextChar()); + assertEquals(1, compressed.get(2).index()); + assertEquals('A', compressed.get(2).nextChar()); + } + + @Test + @DisplayName("Test compression and decompression of a longer example") + void testLongerExample() { + String original = "TOBEORNOTTOBEORTOBEORNOT"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test empty string compression and decompression") + void testEmptyString() { + String original = ""; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + assertTrue(compressed.isEmpty()); + } + + @Test + @DisplayName("Test null string compression") + void testNullStringCompress() { + List compressed = LZ78.compress(null); + assertTrue(compressed.isEmpty()); + } + + @Test + @DisplayName("Test null list decompression") + void testNullListDecompress() { + String decompressed = LZ78.decompress(null); + assertEquals("", decompressed); + } + + @Test + @DisplayName("Test string with all same characters") + void testAllSameCharacters() { + String original = "AAAAAA"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // Should achieve good compression: (0,A)(1,A)(2,A)... + assertTrue(compressed.size() <= 4); // Builds: A, AA, AAA, etc. + } + + @Test + @DisplayName("Test string with all unique characters") + void testAllUniqueCharacters() { + String original = "abcdefg"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // No compression for unique characters + assertEquals(original.length(), compressed.size()); + + // Each token should have index 0 (empty prefix) + for (LZ78.Token token : compressed) { + assertEquals(0, token.index()); + } + } + + @Test + @DisplayName("Test single character string") + void testSingleCharacter() { + String original = "a"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + assertEquals(1, compressed.size()); + assertEquals(0, compressed.getFirst().index()); + assertEquals('a', compressed.getFirst().nextChar()); + } + + @Test + @DisplayName("Test two character string") + void testTwoCharacters() { + String original = "ab"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + assertEquals(2, compressed.size()); + } + + @Test + @DisplayName("Test repeating pairs") + void testRepeatingPairs() { + String original = "ababab"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // Should compress well: (0,a)(0,b)(1,b) or similar + assertTrue(compressed.size() < original.length()); + } + + @Test + @DisplayName("Test growing patterns") + void testGrowingPatterns() { + String original = "abcabcdabcde"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test dictionary building correctness") + void testDictionaryBuilding() { + String original = "aabaabaab"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // Verify first few tokens + // Expected pattern: (0,a)(1,b)(2,a)(3,b) building dictionary 1:a, 2:ab, 3:aa, 4:aab + assertTrue(compressed.size() > 0); + assertEquals(0, compressed.getFirst().index()); // First char always has index 0 + } + + @Test + @DisplayName("Test with special characters") + void testSpecialCharacters() { + String original = "hello world! hello!"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test with numbers") + void testWithNumbers() { + String original = "1234512345"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // Should achieve compression + assertTrue(compressed.size() < original.length()); + } + + @Test + @DisplayName("Test long repeating sequence") + void testLongRepeatingSequence() { + String original = "abcdefgh".repeat(5); + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // LZ78 should achieve some compression for repeating sequences + assertTrue(compressed.size() < original.length(), "Compressed size should be less than original length"); + } + + @Test + @DisplayName("Test alternating characters") + void testAlternatingCharacters() { + String original = "ababababab"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test compression effectiveness") + void testCompressionEffectiveness() { + String original = "the quick brown fox jumps over the lazy dog the quick brown fox"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // Should achieve some compression due to repeated phrases + assertTrue(compressed.size() < original.length()); + } + + @Test + @DisplayName("Test with mixed case letters") + void testMixedCase() { + String original = "AaBbCcAaBbCc"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test palindrome string") + void testPalindrome() { + String original = "abccba"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test highly compressible pattern") + void testHighlyCompressible() { + String original = "aaaaaaaaaa"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + + // Should achieve excellent compression ratio + assertTrue(compressed.size() <= 4); + } + + @Test + @DisplayName("Test empty list decompression") + void testEmptyListDecompress() { + List compressed = List.of(); + String decompressed = LZ78.decompress(compressed); + assertEquals("", decompressed); + } + + @Test + @DisplayName("Test binary-like pattern") + void testBinaryPattern() { + String original = "0101010101"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test nested patterns") + void testNestedPatterns() { + String original = "abcabcdefabcdefghi"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test whitespace handling") + void testWhitespace() { + String original = "a b c a b c"; + List compressed = LZ78.compress(original); + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + @DisplayName("Test token structure correctness") + void testTokenStructure() { + String original = "abc"; + List compressed = LZ78.compress(original); + + // All tokens should have valid indices (>= 0) + for (LZ78.Token token : compressed) { + assertTrue(token.index() >= 0); + assertNotNull(token.nextChar()); + } + + String decompressed = LZ78.decompress(compressed); + assertEquals(original, decompressed); + } +} diff --git a/src/test/java/com/thealgorithms/compression/LZWTest.java b/src/test/java/com/thealgorithms/compression/LZWTest.java new file mode 100644 index 000000000000..7f0c7503c822 --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/LZWTest.java @@ -0,0 +1,104 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +class LZWTest { + + @Test + void testNullAndEmptyInputs() { + // Test that a null input to compress returns an empty list + assertTrue(LZW.compress(null).isEmpty()); + + // Test that a null input to decompress returns an empty string + assertEquals("", LZW.decompress(null)); + + // Test that an empty input to compress returns an empty list + assertTrue(LZW.compress("").isEmpty()); + + // Test that an empty input to decompress returns an empty string + assertEquals("", LZW.decompress(Collections.emptyList())); + } + + @Test + void testCompressionAndDecompressionWithSimpleString() { + // Test a classic example string + String original = "TOBEORNOTTOBEORTOBEORNOT"; + List compressed = LZW.compress(original); + + // Create the expected output list + List expectedOutput = List.of(84, 79, 66, 69, 79, 82, 78, 79, 84, 256, 258, 260, 265, 259, 261, 263); + + // This assertion will fail if the output is not what we expect + assertEquals(expectedOutput, compressed); + + // This assertion ensures the decompressed string is correct + String decompressed = LZW.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + void testCompressionWithRepeatedChars() { + // Test a string with long runs of the same character + String original = "AAAAABBBBBAAAAA"; + List compressed = LZW.compress(original); + String decompressed = LZW.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + void testCompressionWithUniqueChars() { + // Test a string with no repetitions + String original = "ABCDEFG"; + List compressed = LZW.compress(original); + String decompressed = LZW.decompress(compressed); + assertEquals(original, decompressed); + } + + @Test + void testSymmetry() { + // Test that compressing and then decompressing a complex string returns the + // original + String original = "THE_QUICK_BROWN_FOX_JUMPS_OVER_THE_LAZY_DOG"; + List compressed = LZW.compress(original); + String decompressed = LZW.decompress(compressed); + assertEquals(original, decompressed); + + // Another symmetry test with special characters and patterns + String original2 = "ababcbababa"; + List compressed2 = LZW.compress(original2); + String decompressed2 = LZW.decompress(compressed2); + assertEquals(original2, decompressed2); + } + + @Test + void testInvalidCompressedData() { + // Test that decompressing with an invalid code throws IllegalArgumentException + // Create a list with a code that doesn't exist in the dictionary + List invalidCompressed = new ArrayList<>(); + invalidCompressed.add(65); // 'A' - valid + invalidCompressed.add(999); // Invalid code (not in dictionary) + + // This should throw IllegalArgumentException with message "Bad compressed k: 999" + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> LZW.decompress(invalidCompressed)); + + assertTrue(exception.getMessage().contains("Bad compressed k: 999")); + } + + @Test + void testDecompressionWithGapInDictionary() { + // Test with codes that skip dictionary entries + List invalidCompressed = new ArrayList<>(); + invalidCompressed.add(84); // 'T' - valid + invalidCompressed.add(500); // Way beyond current dictionary size + + // This should throw IllegalArgumentException + assertThrows(IllegalArgumentException.class, () -> LZW.decompress(invalidCompressed)); + } +} diff --git a/src/test/java/com/thealgorithms/compression/MoveToFrontTest.java b/src/test/java/com/thealgorithms/compression/MoveToFrontTest.java new file mode 100644 index 000000000000..42ef6c9cd675 --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/MoveToFrontTest.java @@ -0,0 +1,92 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class MoveToFrontTest { + + @Test + public void testTransformAndInverseBananaExample() { + String original = "annb$aa"; + String alphabet = "$abn"; + List expectedTransform = List.of(1, 3, 0, 3, 3, 3, 0); + + // Test forward transform + List actualTransform = MoveToFront.transform(original, alphabet); + assertEquals(expectedTransform, actualTransform); + + // Test inverse transform + String reconstructed = MoveToFront.inverseTransform(actualTransform, alphabet); + assertEquals(original, reconstructed); + } + + @Test + public void testTransformAndInverseCabaaExample() { + String original = "cabaa"; + String alphabet = "abcdef"; + List expectedTransform = List.of(2, 1, 2, 1, 0); + + // Test forward transform + List actualTransform = MoveToFront.transform(original, alphabet); + assertEquals(expectedTransform, actualTransform); + + // Test inverse transform + String reconstructed = MoveToFront.inverseTransform(actualTransform, alphabet); + assertEquals(original, reconstructed); + } + + @Test + public void testEmptyInput() { + String original = ""; + String alphabet = "abc"; + List expectedTransform = List.of(); + + List actualTransform = MoveToFront.transform(original, alphabet); + assertEquals(expectedTransform, actualTransform); + + String reconstructed = MoveToFront.inverseTransform(actualTransform, alphabet); + assertEquals(original, reconstructed); + } + + @Test + public void testEmptyAlphabet() { + assertThrows(IllegalArgumentException.class, () -> MoveToFront.transform("abc", "")); + + assertEquals("", MoveToFront.inverseTransform(List.of(1, 2), "")); + } + + @Test + public void testSymbolNotInAlphabet() { + // 'd' is not in "abc" + assertThrows(IllegalArgumentException.class, () -> MoveToFront.transform("abd", "abc")); + } + + @Test + public void testIndexOutOfBounds() { + // Index 5 is out of bounds for alphabet "abc" + // 1. test index >= alphabet.size() + assertThrows(IllegalArgumentException.class, () -> MoveToFront.inverseTransform(List.of(1, 2, 5), "abc")); + + // 2. test index < 0 + assertThrows(IllegalArgumentException.class, () -> MoveToFront.inverseTransform(List.of(1, -1, 2), "abc")); + } + + @Test + public void testTransformNull() { + List expected = List.of(); + assertEquals(expected, MoveToFront.transform(null, "abc")); + assertThrows(IllegalArgumentException.class, () -> MoveToFront.transform("abc", null)); + } + + @Test + public void testInverseTransformNulls() { + // 1. test indices == null + assertEquals("", MoveToFront.inverseTransform(null, "abc")); + + // 2. test initialAlphabet == null + assertEquals("", MoveToFront.inverseTransform(List.of(1, 2), null)); + } +} diff --git a/src/test/java/com/thealgorithms/compression/RunLengthEncodingTest.java b/src/test/java/com/thealgorithms/compression/RunLengthEncodingTest.java new file mode 100644 index 000000000000..049a7fac9ae9 --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/RunLengthEncodingTest.java @@ -0,0 +1,90 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class RunLengthEncodingTest { + + @Test + void testNullInputs() { + // Test that a null input to compress returns an empty string + assertEquals("", RunLengthEncoding.compress(null)); + + // Test that a null input to decompress returns an empty string + assertEquals("", RunLengthEncoding.decompress(null)); + } + + @Test + void testCompressionSimple() { + // Test a typical string with multiple runs + String input = "AAAABBBCCDAA"; + String expected = "4A3B2C1D2A"; + assertEquals(expected, RunLengthEncoding.compress(input)); + } + + @Test + void testCompressionWithNoRuns() { + // Test a string with no consecutive characters + String input = "ABCDE"; + String expected = "1A1B1C1D1E"; + assertEquals(expected, RunLengthEncoding.compress(input)); + } + + @Test + void testCompressionEdgeCases() { + // Test with an empty string + assertEquals("", RunLengthEncoding.compress("")); + + // Test with a single character + assertEquals("1A", RunLengthEncoding.compress("A")); + + // Test with a long run of a single character + assertEquals("10Z", RunLengthEncoding.compress("ZZZZZZZZZZ")); + } + + @Test + void testDecompressionSimple() { + // Test decompression of a typical RLE string + String input = "4A3B2C1D2A"; + String expected = "AAAABBBCCDAA"; + assertEquals(expected, RunLengthEncoding.decompress(input)); + } + + @Test + void testDecompressionWithNoRuns() { + // Test decompression of a string with single characters + String input = "1A1B1C1D1E"; + String expected = "ABCDE"; + assertEquals(expected, RunLengthEncoding.decompress(input)); + } + + @Test + void testDecompressionWithMultiDigitCount() { + // Test decompression where a run count is greater than 9 + String input = "12A1B3C"; + String expected = "AAAAAAAAAAAABCCC"; + assertEquals(expected, RunLengthEncoding.decompress(input)); + } + + @Test + void testDecompressionEdgeCases() { + // Test with an empty string + assertEquals("", RunLengthEncoding.decompress("")); + + // Test with a single character run + assertEquals("A", RunLengthEncoding.decompress("1A")); + } + + @Test + void testSymmetry() { + // Test that compressing and then decompressing returns the original string + String original1 = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"; + String compressed = RunLengthEncoding.compress(original1); + String decompressed = RunLengthEncoding.decompress(compressed); + assertEquals(original1, decompressed); + + String original2 = "A"; + assertEquals(original2, RunLengthEncoding.decompress(RunLengthEncoding.compress(original2))); + } +} diff --git a/src/test/java/com/thealgorithms/compression/ShannonFanoTest.java b/src/test/java/com/thealgorithms/compression/ShannonFanoTest.java new file mode 100644 index 000000000000..ce34088dacca --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/ShannonFanoTest.java @@ -0,0 +1,71 @@ +package com.thealgorithms.compression; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ShannonFanoTest { + + @Test + void testNullInput() { + // Test with a null string, should return an empty map + assertTrue(ShannonFano.generateCodes(null).isEmpty()); + } + + @Test + void testSimpleString() { + // A simple string to test basic code generation + String text = "AAABBC"; + Map codes = ShannonFano.generateCodes(text); + + assertEquals(3, codes.size()); + assertEquals("0", codes.get('A')); + assertEquals("10", codes.get('B')); + assertEquals("11", codes.get('C')); + } + + @Test + void testExampleFromStringIssue() { + // Example from the original issue proposal: A:15, B:7, C:6, D:6, E:5 + // The code finds a more optimal split: {A,B} | {C,D,E} -> |22-17|=5 + // instead of {A} | {B,C,D,E} -> |15-24|=9. + String text = "AAAAAAAAAAAAAAABBBBBBBCCCCCCDDDDDDEEEEE"; + Map codes = ShannonFano.generateCodes(text); + + assertEquals(5, codes.size()); + assertEquals("00", codes.get('A')); + assertEquals("01", codes.get('B')); + assertEquals("10", codes.get('C')); + assertEquals("110", codes.get('D')); + assertEquals("111", codes.get('E')); + } + + @Test + void testEdgeCases() { + // Test with an empty string + assertTrue(ShannonFano.generateCodes("").isEmpty()); + + // Test with a single character + Map singleCharCodes = ShannonFano.generateCodes("AAAAA"); + assertEquals(1, singleCharCodes.size()); + assertEquals("0", singleCharCodes.get('A')); // A single symbol gets code "0" + + // Test with all unique characters + String uniqueCharsText = "ABCDEF"; + Map uniqueCharCodes = ShannonFano.generateCodes(uniqueCharsText); + assertEquals(6, uniqueCharCodes.size()); + // Check that codes are unique and have varying lengths as expected + assertEquals(6, uniqueCharCodes.values().stream().distinct().count()); + } + + @Test + void testStringWithTwoChars() { + String text = "ABABAB"; + Map codes = ShannonFano.generateCodes(text); + + assertEquals(2, codes.size()); + assertTrue(codes.get('A').equals("0") && codes.get('B').equals("1") || codes.get('A').equals("1") && codes.get('B').equals("0")); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java new file mode 100644 index 000000000000..fbc220c0ca95 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java @@ -0,0 +1,183 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Test cases for Base64 encoding and decoding. + * + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + */ + +class Base64Test { + + @Test + void testBase64Alphabet() { + // Test that all Base64 characters are handled correctly + String allChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + String encoded = Base64.encode(allChars); + String decoded = Base64.decodeToString(encoded); + assertEquals(allChars, decoded); + } + + @ParameterizedTest + @CsvSource({"'', ''", "A, QQ==", "AB, QUI=", "ABC, QUJD", "ABCD, QUJDRA==", "Hello, SGVsbG8=", "'Hello World', SGVsbG8gV29ybGQ=", "'Hello, World!', 'SGVsbG8sIFdvcmxkIQ=='", "'The quick brown fox jumps over the lazy dog', 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=='", + "123456789, MTIzNDU2Nzg5", "'Base64 encoding test', 'QmFzZTY0IGVuY29kaW5nIHRlc3Q='"}) + void + testStringEncoding(String input, String expected) { + assertEquals(expected, Base64.encode(input)); + } + + @ParameterizedTest + @CsvSource({"'', ''", "QQ==, A", "QUI=, AB", "QUJD, ABC", "QUJDRA==, ABCD", "SGVsbG8=, Hello", "'SGVsbG8gV29ybGQ=', 'Hello World'", "'SGVsbG8sIFdvcmxkIQ==', 'Hello, World!'", "'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==', 'The quick brown fox jumps over the lazy dog'", + "MTIzNDU2Nzg5, 123456789", "'QmFzZTY0IGVuY29kaW5nIHRlc3Q=', 'Base64 encoding test'"}) + void + testStringDecoding(String input, String expected) { + assertEquals(expected, Base64.decodeToString(input)); + } + + @Test + void testByteArrayEncoding() { + byte[] input = {72, 101, 108, 108, 111}; + String expected = "SGVsbG8="; + assertEquals(expected, Base64.encode(input)); + } + + @Test + void testByteArrayDecoding() { + String input = "SGVsbG8="; + byte[] expected = {72, 101, 108, 108, 111}; + assertArrayEquals(expected, Base64.decode(input)); + } + + @Test + void testRoundTripEncoding() { + String[] testStrings = {"", "A", "AB", "ABC", "Hello, World!", "The quick brown fox jumps over the lazy dog", "1234567890", "Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?", + "Unicode: เฎตเฎฃเฎ•เฏเฎ•เฎฎเฏ", // Tamil for "Hello" + "Multi-line\nstring\rwith\tdifferent\nwhitespace"}; + + for (String original : testStrings) { + String encoded = Base64.encode(original); + String decoded = Base64.decodeToString(encoded); + assertEquals(original, decoded, "Round trip failed for: " + original); + } + } + + @Test + void testRoundTripByteArrayEncoding() { + byte[][] testArrays = {{}, {0}, {-1}, {0, 1, 2, 3, 4, 5}, {-128, -1, 0, 1, 127}, {72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}}; + + for (byte[] original : testArrays) { + String encoded = Base64.encode(original); + byte[] decoded = Base64.decode(encoded); + assertArrayEquals(original, decoded, "Round trip failed for byte array"); + } + } + + @Test + void testBinaryData() { + // Test with binary data that might contain null bytes + byte[] binaryData = new byte[256]; + for (int i = 0; i < 256; i++) { + binaryData[i] = (byte) i; + } + + String encoded = Base64.encode(binaryData); + byte[] decoded = Base64.decode(encoded); + assertArrayEquals(binaryData, decoded); + } + + @Test + void testNullInputEncoding() { + assertThrows(IllegalArgumentException.class, () -> Base64.encode((String) null)); + assertThrows(IllegalArgumentException.class, () -> Base64.encode((byte[]) null)); + } + + @Test + void testNullInputDecoding() { + assertThrows(IllegalArgumentException.class, () -> Base64.decode(null)); + assertThrows(IllegalArgumentException.class, () -> Base64.decodeToString(null)); + } + + @Test + void testInvalidBase64Characters() { + assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8@")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8#")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8$")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8%")); + } + + @Test + void testInvalidLength() { + // Length must be multiple of 4 + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQ")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQQ")); + } + + @Test + void testInvalidPaddingPosition() { + // '=' can only appear at the end + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=QQ")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=Q=")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("=QQQ")); + } + + @Test + void testPaddingVariations() { + // Test different padding scenarios '=' + assertEquals("A", Base64.decodeToString("QQ==")); + assertEquals("AB", Base64.decodeToString("QUI=")); + assertEquals("ABC", Base64.decodeToString("QUJD")); + } + + @Test + void testPaddingConsistency() { + // Ensure that strings requiring different amounts of padding encode/decode correctly + String[] testCases = {"A", "AB", "ABC", "ABCD", "ABCDE", "ABCDEF"}; + + for (String test : testCases) { + String encoded = Base64.encode(test); + String decoded = Base64.decodeToString(encoded); + assertEquals(test, decoded); + + // Verify padding is correct + int expectedPadding = (3 - (test.length() % 3)) % 3; + int actualPadding = 0; + for (int i = encoded.length() - 1; i >= 0 && encoded.charAt(i) == '='; i--) { + actualPadding++; + } + assertEquals(expectedPadding, actualPadding, "Incorrect padding for: " + test); + } + } + + @Test + void testLargeData() { + // Test with larger data to ensure scalability + StringBuilder largeString = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + largeString.append("This is a test string for Base64 encoding. "); + } + + String original = largeString.toString(); + String encoded = Base64.encode(original); + String decoded = Base64.decodeToString(encoded); + assertEquals(original, decoded); + } + + @Test + void testEmptyAndSingleCharacter() { + // Test edge cases + assertEquals("", Base64.encode("")); + assertEquals("", Base64.decodeToString("")); + + assertEquals("QQ==", Base64.encode("A")); + assertEquals("A", Base64.decodeToString("QQ==")); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java b/src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java index 9045d100285e..e11a86b4c006 100644 --- a/src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java +++ b/src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java @@ -18,6 +18,12 @@ public void testBinaryToDecimal() { assertEquals(5, BinaryToDecimal.binaryToDecimal(101)); assertEquals(63, BinaryToDecimal.binaryToDecimal(111111)); assertEquals(512, BinaryToDecimal.binaryToDecimal(1000000000)); + + assertEquals(0, BinaryToDecimal.binaryStringToDecimal("0")); + assertEquals(1, BinaryToDecimal.binaryStringToDecimal("1")); + assertEquals(5, BinaryToDecimal.binaryStringToDecimal("101")); + assertEquals(63, BinaryToDecimal.binaryStringToDecimal("111111")); + assertEquals(512, BinaryToDecimal.binaryStringToDecimal("1000000000")); } @Test @@ -25,6 +31,9 @@ public void testBinaryToDecimal() { public void testNegativeBinaryToDecimal() { assertEquals(-1, BinaryToDecimal.binaryToDecimal(-1)); assertEquals(-42, BinaryToDecimal.binaryToDecimal(-101010)); + + assertEquals(-1, BinaryToDecimal.binaryStringToDecimal("-1")); + assertEquals(-42, BinaryToDecimal.binaryStringToDecimal("-101010")); } @Test @@ -32,11 +41,16 @@ public void testNegativeBinaryToDecimal() { public void testLargeBinaryToDecimal() { assertEquals(262144L, BinaryToDecimal.binaryToDecimal(1000000000000000000L)); assertEquals(524287L, BinaryToDecimal.binaryToDecimal(1111111111111111111L)); + + assertEquals(262144L, BinaryToDecimal.binaryStringToDecimal("1000000000000000000")); + assertEquals(524287L, BinaryToDecimal.binaryStringToDecimal("1111111111111111111")); } @ParameterizedTest @CsvSource({"2", "1234", "11112", "101021"}) void testNotCorrectBinaryInput(long binaryNumber) { assertThrows(IllegalArgumentException.class, () -> BinaryToDecimal.binaryToDecimal(binaryNumber)); + + assertThrows(IllegalArgumentException.class, () -> BinaryToDecimal.binaryStringToDecimal(Long.toString(binaryNumber))); } } diff --git a/src/test/java/com/thealgorithms/conversions/CoordinateConverterTest.java b/src/test/java/com/thealgorithms/conversions/CoordinateConverterTest.java new file mode 100644 index 000000000000..e41e9160478d --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/CoordinateConverterTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class CoordinateConverterTest { + + @ParameterizedTest + @CsvSource({"0, 0, 0, 0", "1, 0, 1, 0", "0, 1, 1, 90", "-1, 0, 1, 180", "0, -1, 1, -90", "3, 4, 5, 53.13010235415599"}) + void testCartesianToPolar(double x, double y, double expectedR, double expectedTheta) { + assertArrayEquals(new double[] {expectedR, expectedTheta}, CoordinateConverter.cartesianToPolar(x, y), 1e-9); + } + + @ParameterizedTest + @CsvSource({"1, 0, 1, 0", "1, 90, 0, 1", "1, 180, -1, 0", "1, -90, 0, -1", "5, 53.13010235415599, 3, 4"}) + void testPolarToCartesian(double r, double theta, double expectedX, double expectedY) { + assertArrayEquals(new double[] {expectedX, expectedY}, CoordinateConverter.polarToCartesian(r, theta), 1e-9); + } + + @ParameterizedTest + @CsvSource({"NaN, 1", "1, NaN", "Infinity, 1", "1, Infinity", "-Infinity, 1", "1, -Infinity"}) + void testCartesianToPolarInvalidInputs(double x, double y) { + assertThrows(IllegalArgumentException.class, () -> CoordinateConverter.cartesianToPolar(x, y)); + } + + @ParameterizedTest + @CsvSource({"-1, 0", "1, NaN", "1, Infinity", "1, -Infinity"}) + void testPolarToCartesianInvalidInputs(double r, double theta) { + assertThrows(IllegalArgumentException.class, () -> CoordinateConverter.polarToCartesian(r, theta)); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/TimeConverterTest.java b/src/test/java/com/thealgorithms/conversions/TimeConverterTest.java new file mode 100644 index 000000000000..4e10318d4563 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/TimeConverterTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +class TimeConverterTest { + + @ParameterizedTest(name = "{0} {1} -> {2} {3}") + @CsvSource({"60, seconds, minutes, 1", "120, seconds, minutes, 2", "2, minutes, seconds, 120", "2, hours, minutes, 120", "1, days, hours, 24", "1, weeks, days, 7", "1, months, days, 30.438", "1, years, days, 365.25", "3600, seconds, hours, 1", "86400, seconds, days, 1", + "604800, seconds, weeks, 1", "31557600, seconds, years, 1"}) + void + testValidConversions(double value, String from, String to, double expected) { + assertEquals(expected, TimeConverter.convertTime(value, from, to)); + } + + @Test + @DisplayName("Zero conversion returns zero") + void testZeroValue() { + assertEquals(0.0, TimeConverter.convertTime(0, "seconds", "hours")); + } + + @Test + @DisplayName("Same-unit conversion returns original value") + void testSameUnitConversion() { + assertEquals(123.456, TimeConverter.convertTime(123.456, "minutes", "minutes")); + } + + @Test + @DisplayName("Negative value throws exception") + void testNegativeValue() { + assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(-5, "seconds", "minutes")); + } + + @ParameterizedTest + @CsvSource({"lightyears, seconds", "minutes, centuries"}) + void testInvalidUnits(String from, String to) { + assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, from, to)); + } + + @Test + @DisplayName("Null unit throws exception") + void testNullUnit() { + assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, null, "seconds")); + + assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, "minutes", null)); + + assertThrows(IllegalArgumentException.class, () -> TimeConverter.convertTime(10, null, null)); + } + + static Stream roundTripCases() { + return Stream.of(org.junit.jupiter.params.provider.Arguments.of(1.0, "hours", "minutes"), org.junit.jupiter.params.provider.Arguments.of(2.5, "days", "hours"), org.junit.jupiter.params.provider.Arguments.of(1000, "seconds", "minutes")); + } + + @ParameterizedTest + @MethodSource("roundTripCases") + @DisplayName("Round-trip conversion returns original value") + void testRoundTripConversion(double value, String from, String to) { + double converted = TimeConverter.convertTime(value, from, to); + double roundTrip = TimeConverter.convertTime(converted, to, from); + assertEquals(Math.round(value * 1000.0) / 1000.0, Math.round(roundTrip * 1000.0) / 1000.0, 0.05); + } +} diff --git a/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java b/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java new file mode 100644 index 000000000000..fbf63e37946b --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/WordsToNumberTest.java @@ -0,0 +1,114 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; + +public class WordsToNumberTest { + + @Test + void testNullInput() { + WordsToNumberException exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert(null)); + assertEquals(WordsToNumberException.ErrorType.NULL_INPUT, exception.getErrorType(), "Exception should be of type NULL_INPUT"); + } + + @Test + void testStandardCases() { + assertEquals("0", WordsToNumber.convert("zero"), "'zero' should convert to '0'"); + assertEquals("5", WordsToNumber.convert("five"), "'five' should convert to '5'"); + assertEquals("21", WordsToNumber.convert("twenty one"), "'twenty one' should convert to '21'"); + assertEquals("101", WordsToNumber.convert("one hundred one"), "'one hundred' should convert to '101'"); + assertEquals("342", WordsToNumber.convert("three hundred and forty two"), "'three hundred and forty two' should convert to '342'"); + } + + @Test + void testLargeNumbers() { + assertEquals("1000000", WordsToNumber.convert("one million"), "'one million' should convert to '1000000'"); + assertEquals("1000000000", WordsToNumber.convert("one billion"), "'one billion' should convert to '1000000000'"); + assertEquals("1000000000000", WordsToNumber.convert("one trillion"), "'one trillion' should convert to '1000000000000'"); + assertEquals("999000000900999", WordsToNumber.convert("nine hundred ninety nine trillion nine hundred thousand nine hundred and ninety nine"), "'nine hundred ninety nine trillion nine hundred thousand nine hundred and ninety nine' should convert to '999000000900999'"); + } + + @Test + void testNegativeNumbers() { + assertEquals("-5", WordsToNumber.convert("negative five"), "'negative five' should convert to '-5'"); + assertEquals("-120", WordsToNumber.convert("negative one hundred and twenty"), "'negative one hundred and twenty' should convert correctly"); + } + + @Test + void testNegativeLargeNumbers() { + assertEquals("-1000000000000", WordsToNumber.convert("negative one trillion"), "'negative one trillion' should convert to '-1000000000000'"); + assertEquals("-9876543210987", WordsToNumber.convert("Negative Nine Trillion Eight Hundred Seventy Six Billion Five Hundred Forty Three Million Two Hundred Ten Thousand Nine Hundred Eighty Seven"), ""); + } + + @Test + void testDecimalNumbers() { + assertEquals("3.1415", WordsToNumber.convert("three point one four one five"), "'three point one four one five' should convert to '3.1415'"); + assertEquals("-2.718", WordsToNumber.convert("negative two point seven one eight"), "'negative two point seven one eight' should convert to '-2.718'"); + assertEquals("-1E-7", WordsToNumber.convert("negative zero point zero zero zero zero zero zero one"), "'negative zero point zero zero zero zero zero zero one' should convert to '-1E-7'"); + } + + @Test + void testLargeDecimalNumbers() { + assertEquals("1000000000.0000000001", WordsToNumber.convert("one billion point zero zero zero zero zero zero zero zero zero one"), "Tests a large whole number with a tiny fractional part"); + assertEquals("999999999999999.9999999999999", + WordsToNumber.convert("nine hundred ninety nine trillion nine hundred ninety nine billion nine hundred ninety nine million nine hundred ninety nine thousand nine hundred ninety nine point nine nine nine nine nine nine nine nine nine nine nine nine nine"), + "Tests maximum scale handling for large decimal numbers"); + assertEquals("0.505", WordsToNumber.convert("zero point five zero five"), "Tests a decimal with an internal zero, ensuring correct parsing"); + assertEquals("42.00000000000001", WordsToNumber.convert("forty two point zero zero zero zero zero zero zero zero zero zero zero zero zero one"), "Tests a decimal with leading zeros before a significant figure"); + assertEquals("7.89E-7", WordsToNumber.convert("zero point zero zero zero zero zero zero seven eight nine"), "Tests scientific notation for a small decimal with multiple digits"); + assertEquals("0.999999", WordsToNumber.convert("zero point nine nine nine nine nine nine"), "Tests a decimal close to one with multiple repeated digits"); + } + + @Test + void testCaseInsensitivity() { + assertEquals("21", WordsToNumber.convert("TWENTY-ONE"), "Uppercase should still convert correctly"); + assertEquals("-100.0001", WordsToNumber.convert("negAtiVe OnE HuNdReD, point ZeRO Zero zERo ONE"), "Mixed case should still convert correctly"); + assertEquals("-225647.00019", WordsToNumber.convert("nEgative twO HundRed, and twenty-Five thOusaNd, six huNdred Forty-Seven, Point zero zero zero One nInE")); + } + + @Test + void testInvalidInputs() { + WordsToNumberException exception; + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("negative one hundred AlPha")); + assertEquals(WordsToNumberException.ErrorType.UNKNOWN_WORD, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("twenty thirteen")); + assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("negative negative ten")); + assertEquals(WordsToNumberException.ErrorType.MULTIPLE_NEGATIVES, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one hundred hundred")); + assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one thousand and hundred")); + assertEquals(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one thousand hundred")); + assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("nine hundred and nine hundred")); + assertEquals(WordsToNumberException.ErrorType.INVALID_CONJUNCTION, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("forty two point")); + assertEquals(WordsToNumberException.ErrorType.MISSING_DECIMAL_NUMBERS, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("sixty seven point hello")); + assertEquals(WordsToNumberException.ErrorType.UNEXPECTED_WORD_AFTER_POINT, exception.getErrorType()); + + exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convert("one negative")); + assertEquals(WordsToNumberException.ErrorType.INVALID_NEGATIVE, exception.getErrorType()); + } + + @Test + void testConvertToBigDecimal() { + assertEquals(new BigDecimal("-100000000000000.056"), WordsToNumber.convertToBigDecimal("negative one hundred trillion point zero five six"), "should convert to appropriate BigDecimal value"); + + WordsToNumberException exception = assertThrows(WordsToNumberException.class, () -> WordsToNumber.convertToBigDecimal(null)); + assertEquals(WordsToNumberException.ErrorType.NULL_INPUT, exception.getErrorType(), "Exception should be of type NULL_INPUT"); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java index b7e64851383c..8212793dfb79 100644 --- a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java +++ b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java @@ -6,7 +6,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.thealgorithms.datastructures.bags.Bag; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; import org.junit.jupiter.api.Test; class BagTest { @@ -156,4 +159,116 @@ void testIteratorWithDuplicates() { } assertEquals(3, count, "Iterator should traverse all 3 items including duplicates"); } + + @Test + void testCollectionElements() { + Bag> bag = new Bag<>(); + List list1 = new ArrayList<>(); + list1.add("a"); + list1.add("b"); + + List list2 = new ArrayList<>(); + list2.add("c"); + + List emptyList = new ArrayList<>(); + + bag.add(list1); + bag.add(list2); + bag.add(emptyList); + bag.add(list1); // Duplicate + + assertEquals(4, bag.size(), "Bag should contain 4 list elements"); + assertTrue(bag.contains(list1), "Bag should contain list1"); + assertTrue(bag.contains(list2), "Bag should contain list2"); + assertTrue(bag.contains(emptyList), "Bag should contain empty list"); + } + + @Test + void testIteratorConsistency() { + Bag bag = new Bag<>(); + bag.add("first"); + bag.add("second"); + bag.add("third"); + + // Multiple iterations should return same elements + List firstIteration = new ArrayList<>(); + for (String item : bag) { + firstIteration.add(item); + } + + List secondIteration = new ArrayList<>(); + for (String item : bag) { + secondIteration.add(item); + } + + assertEquals(firstIteration.size(), secondIteration.size(), "Both iterations should have same size"); + assertEquals(3, firstIteration.size(), "First iteration should have 3 elements"); + assertEquals(3, secondIteration.size(), "Second iteration should have 3 elements"); + } + + @Test + void testMultipleIterators() { + Bag bag = new Bag<>(); + bag.add("item1"); + bag.add("item2"); + + Iterator iter1 = bag.iterator(); + Iterator iter2 = bag.iterator(); + + assertTrue(iter1.hasNext(), "First iterator should have next element"); + assertTrue(iter2.hasNext(), "Second iterator should have next element"); + + String first1 = iter1.next(); + String first2 = iter2.next(); + + org.junit.jupiter.api.Assertions.assertNotNull(first1, "First iterator should return non-null element"); + org.junit.jupiter.api.Assertions.assertNotNull(first2, "Second iterator should return non-null element"); + } + + @Test + void testIteratorHasNextConsistency() { + Bag bag = new Bag<>(); + bag.add("single"); + + Iterator iter = bag.iterator(); + assertTrue(iter.hasNext(), "hasNext should return true"); + assertTrue(iter.hasNext(), "hasNext should still return true after multiple calls"); + + String item = iter.next(); + assertEquals("single", item, "Next should return the single item"); + + assertFalse(iter.hasNext(), "hasNext should return false after consuming element"); + assertFalse(iter.hasNext(), "hasNext should still return false"); + } + + @Test + void testIteratorNextOnEmptyBag() { + Bag bag = new Bag<>(); + Iterator iter = bag.iterator(); + + assertFalse(iter.hasNext(), "hasNext should return false for empty bag"); + assertThrows(NoSuchElementException.class, iter::next, "next() should throw NoSuchElementException on empty bag"); + } + + @Test + void testBagOrderIndependence() { + Bag bag1 = new Bag<>(); + Bag bag2 = new Bag<>(); + + // Add same elements in different order + bag1.add("first"); + bag1.add("second"); + bag1.add("third"); + + bag2.add("third"); + bag2.add("first"); + bag2.add("second"); + + assertEquals(bag1.size(), bag2.size(), "Bags should have same size"); + + // Both bags should contain all elements + assertTrue(bag1.contains("first") && bag2.contains("first")); + assertTrue(bag1.contains("second") && bag2.contains("second")); + assertTrue(bag1.contains("third") && bag2.contains("third")); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java b/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java index 048eb7e481a7..9e1ba88adaee 100644 --- a/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java +++ b/src/test/java/com/thealgorithms/datastructures/bloomfilter/BloomFilterTest.java @@ -1,5 +1,12 @@ package com.thealgorithms.datastructures.bloomfilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -113,4 +120,140 @@ void testBoundaryConditions() { Assertions.assertTrue(filter.contains("d"), "Filter should contain 'd'"); Assertions.assertFalse(filter.contains("e"), "Filter should not contain 'e' which was not inserted"); } + + @Test + void testLongDataType() { + BloomFilter filter = new BloomFilter<>(5, 1000); + Long[] values = {Long.MIN_VALUE, Long.MAX_VALUE}; + + for (Long value : values) { + filter.insert(value); + } + + for (Long value : values) { + Assertions.assertTrue(filter.contains(value), "Filter should contain " + value); + } + } + + @Test + void testFloatDataType() { + BloomFilter filter = new BloomFilter<>(3, 200); + Float[] values = {1.5f, -3.7f, 0.0f, Float.MAX_VALUE, Float.MIN_VALUE}; + + for (Float value : values) { + filter.insert(value); + } + + for (Float value : values) { + Assertions.assertTrue(filter.contains(value), "Filter should contain " + value); + } + + Assertions.assertFalse(filter.contains(88.88f), "Filter should not contain uninserted value"); + } + + @Test + void testBooleanDataType() { + BloomFilter filter = new BloomFilter<>(2, 50); + filter.insert(Boolean.TRUE); + filter.insert(Boolean.FALSE); + + Assertions.assertTrue(filter.contains(Boolean.TRUE), "Filter should contain true"); + Assertions.assertTrue(filter.contains(Boolean.FALSE), "Filter should contain false"); + } + + @Test + void testListDataType() { + BloomFilter> filter = new BloomFilter<>(4, 200); + List list1 = Arrays.asList("apple", "banana"); + List list2 = Arrays.asList("cat", "dog"); + List emptyList = new ArrayList<>(); + + filter.insert(list1); + filter.insert(list2); + filter.insert(emptyList); + + Assertions.assertTrue(filter.contains(list1), "Filter should contain list1"); + Assertions.assertTrue(filter.contains(list2), "Filter should contain list2"); + Assertions.assertTrue(filter.contains(emptyList), "Filter should contain empty list"); + Assertions.assertFalse(filter.contains(Arrays.asList("elephant", "tiger")), "Filter should not contain uninserted list"); + } + + @Test + void testMapDataType() { + BloomFilter> filter = new BloomFilter<>(3, 150); + Map map1 = new HashMap<>(); + map1.put("key1", 1); + map1.put("key2", 2); + + Map map2 = new HashMap<>(); + map2.put("key3", 3); + + Map emptyMap = new HashMap<>(); + + filter.insert(map1); + filter.insert(map2); + filter.insert(emptyMap); + + Assertions.assertTrue(filter.contains(map1), "Filter should contain map1"); + Assertions.assertTrue(filter.contains(map2), "Filter should contain map2"); + Assertions.assertTrue(filter.contains(emptyMap), "Filter should contain empty map"); + } + + @Test + void testSetDataType() { + BloomFilter> filter = new BloomFilter<>(3, 100); + Set set1 = new HashSet<>(Arrays.asList(1, 2, 3)); + Set set2 = new HashSet<>(Arrays.asList(4, 5)); + Set emptySet = new HashSet<>(); + + filter.insert(set1); + filter.insert(set2); + filter.insert(emptySet); + + Assertions.assertTrue(filter.contains(set1), "Filter should contain set1"); + Assertions.assertTrue(filter.contains(set2), "Filter should contain set2"); + Assertions.assertTrue(filter.contains(emptySet), "Filter should contain empty set"); + Assertions.assertFalse(filter.contains(new HashSet<>(Arrays.asList(6, 7, 8))), "Filter should not contain uninserted set"); + } + + @Test + void testArrayDataType() { + BloomFilter filter = new BloomFilter<>(3, 100); + int[] array1 = {1, 2, 3}; + int[] array2 = {4, 5}; + int[] emptyArray = {}; + + filter.insert(array1); + filter.insert(array2); + filter.insert(emptyArray); + + Assertions.assertTrue(filter.contains(array1), "Filter should contain array1"); + Assertions.assertTrue(filter.contains(array2), "Filter should contain array2"); + Assertions.assertTrue(filter.contains(emptyArray), "Filter should contain empty array"); + Assertions.assertFalse(filter.contains(new int[] {6, 7, 8}), "Filter should not contain different array"); + } + + @Test + void testSpecialFloatingPointValues() { + BloomFilter filter = new BloomFilter<>(3, 100); + Double[] specialValues = {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, -0.0, 0.0}; + + for (Double value : specialValues) { + filter.insert(value); + } + + for (Double value : specialValues) { + Assertions.assertTrue(filter.contains(value), "Filter should contain " + value); + } + } + + @Test + void testVerySmallBloomFilter() { + BloomFilter smallFilter = new BloomFilter<>(1, 5); + smallFilter.insert("test1"); + smallFilter.insert("test2"); + + Assertions.assertTrue(smallFilter.contains("test1")); + Assertions.assertTrue(smallFilter.contains("test2")); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java b/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java index b115fc187b1a..69af422e7175 100644 --- a/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java +++ b/src/test/java/com/thealgorithms/datastructures/buffers/CircularBufferTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -68,11 +67,11 @@ void testFullBuffer() { @Test void testIllegalArguments() { - assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(0)); - assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(-1)); + org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(0)); + org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(-1)); CircularBuffer buffer = new CircularBuffer<>(1); - assertThrows(IllegalArgumentException.class, () -> buffer.put(null)); + org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> buffer.put(null)); } @Test @@ -85,4 +84,149 @@ void testLargeBuffer() { buffer.put(1000); // This should overwrite 0 assertEquals(1, buffer.get()); } + + @Test + void testPutAfterGet() { + CircularBuffer buffer = new CircularBuffer<>(2); + buffer.put(10); + buffer.put(20); + assertEquals(10, buffer.get()); + buffer.put(30); + assertEquals(20, buffer.get()); + assertEquals(30, buffer.get()); + assertNull(buffer.get()); + } + + @Test + void testMultipleWrapArounds() { + CircularBuffer buffer = new CircularBuffer<>(3); + for (int i = 1; i <= 6; i++) { + buffer.put(i); + buffer.get(); // add and immediately remove + } + assertTrue(buffer.isEmpty()); + assertNull(buffer.get()); + } + + @Test + void testOverwriteMultipleTimes() { + CircularBuffer buffer = new CircularBuffer<>(2); + buffer.put("X"); + buffer.put("Y"); + buffer.put("Z"); // overwrites "X" + buffer.put("W"); // overwrites "Y" + assertEquals("Z", buffer.get()); + assertEquals("W", buffer.get()); + assertNull(buffer.get()); + } + + @Test + void testIsEmptyAndIsFullTransitions() { + CircularBuffer buffer = new CircularBuffer<>(2); + assertTrue(buffer.isEmpty()); + org.junit.jupiter.api.Assertions.assertFalse(buffer.isFull()); + + buffer.put(1); + org.junit.jupiter.api.Assertions.assertFalse(buffer.isEmpty()); + org.junit.jupiter.api.Assertions.assertFalse(buffer.isFull()); + + buffer.put(2); + assertTrue(buffer.isFull()); + + buffer.get(); + org.junit.jupiter.api.Assertions.assertFalse(buffer.isFull()); + + buffer.get(); + assertTrue(buffer.isEmpty()); + } + + @Test + void testInterleavedPutAndGet() { + CircularBuffer buffer = new CircularBuffer<>(3); + buffer.put("A"); + buffer.put("B"); + assertEquals("A", buffer.get()); + buffer.put("C"); + assertEquals("B", buffer.get()); + assertEquals("C", buffer.get()); + assertNull(buffer.get()); + } + + @Test + void testRepeatedNullInsertionThrows() { + CircularBuffer buffer = new CircularBuffer<>(5); + for (int i = 0; i < 3; i++) { + int finalI = i; + org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> buffer.put(null), "Iteration: " + finalI); + } + } + @Test + void testFillThenEmptyThenReuseBuffer() { + CircularBuffer buffer = new CircularBuffer<>(3); + + buffer.put(1); + buffer.put(2); + buffer.put(3); + assertTrue(buffer.isFull()); + + assertEquals(1, buffer.get()); + assertEquals(2, buffer.get()); + assertEquals(3, buffer.get()); + + assertTrue(buffer.isEmpty()); + + buffer.put(4); + buffer.put(5); + assertEquals(4, buffer.get()); + assertEquals(5, buffer.get()); + assertTrue(buffer.isEmpty()); + } + + @Test + void testPutReturnsTrueOnlyIfPreviouslyEmpty() { + CircularBuffer buffer = new CircularBuffer<>(2); + + assertTrue(buffer.put("one")); // was empty + org.junit.jupiter.api.Assertions.assertFalse(buffer.put("two")); // not empty + org.junit.jupiter.api.Assertions.assertFalse(buffer.put("three")); // overwrite + } + + @Test + void testOverwriteAndGetAllElementsCorrectly() { + CircularBuffer buffer = new CircularBuffer<>(3); + + buffer.put(1); + buffer.put(2); + buffer.put(3); + buffer.put(4); // Overwrites 1 + buffer.put(5); // Overwrites 2 + + assertEquals(3, buffer.get()); + assertEquals(4, buffer.get()); + assertEquals(5, buffer.get()); + assertNull(buffer.get()); + } + + @Test + void testBufferWithOneElementCapacity() { + CircularBuffer buffer = new CircularBuffer<>(1); + + assertTrue(buffer.put("first")); + assertEquals("first", buffer.get()); + assertNull(buffer.get()); + + assertTrue(buffer.put("second")); + assertEquals("second", buffer.get()); + } + + @Test + void testPointerWraparoundWithExactMultipleOfCapacity() { + CircularBuffer buffer = new CircularBuffer<>(3); + for (int i = 0; i < 6; i++) { + buffer.put(i); + buffer.get(); // keep buffer size at 0 + } + assertTrue(buffer.isEmpty()); + assertNull(buffer.get()); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/caches/FIFOCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/FIFOCacheTest.java new file mode 100644 index 000000000000..5f250e6ca3fd --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/caches/FIFOCacheTest.java @@ -0,0 +1,330 @@ +package com.thealgorithms.datastructures.caches; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +class FIFOCacheTest { + private FIFOCache cache; + private Set evictedKeys; + private List evictedValues; + + @BeforeEach + void setUp() { + evictedKeys = new HashSet<>(); + evictedValues = new ArrayList<>(); + + cache = new FIFOCache.Builder(3) + .defaultTTL(1000) + .evictionListener((k, v) -> { + evictedKeys.add(k); + evictedValues.add(v); + }) + .build(); + } + + @Test + void testPutAndGet() { + cache.put("a", "apple"); + Assertions.assertEquals("apple", cache.get("a")); + } + + @Test + void testOverwriteValue() { + cache.put("a", "apple"); + cache.put("a", "avocado"); + Assertions.assertEquals("avocado", cache.get("a")); + } + + @Test + void testExpiration() throws InterruptedException { + cache.put("temp", "value", 100); + Thread.sleep(200); + Assertions.assertNull(cache.get("temp")); + Assertions.assertTrue(evictedKeys.contains("temp")); + } + + @Test + void testEvictionOnCapacity() { + cache.put("a", "alpha"); + cache.put("b", "bravo"); + cache.put("c", "charlie"); + cache.put("d", "delta"); + + int size = cache.size(); + Assertions.assertEquals(3, size); + Assertions.assertEquals(1, evictedKeys.size()); + Assertions.assertEquals(1, evictedValues.size()); + } + + @Test + void testEvictionListener() { + cache.put("x", "one"); + cache.put("y", "two"); + cache.put("z", "three"); + cache.put("w", "four"); + + Assertions.assertFalse(evictedKeys.isEmpty()); + Assertions.assertFalse(evictedValues.isEmpty()); + } + + @Test + void testHitsAndMisses() { + cache.put("a", "apple"); + Assertions.assertEquals("apple", cache.get("a")); + Assertions.assertNull(cache.get("b")); + Assertions.assertEquals(1, cache.getHits()); + Assertions.assertEquals(1, cache.getMisses()); + } + + @Test + void testSizeExcludesExpired() throws InterruptedException { + cache.put("a", "a", 100); + cache.put("b", "b", 100); + cache.put("c", "c", 100); + Thread.sleep(150); + Assertions.assertEquals(0, cache.size()); + } + + @Test + void testSizeIncludesFresh() { + cache.put("a", "a", 1000); + cache.put("b", "b", 1000); + cache.put("c", "c", 1000); + Assertions.assertEquals(3, cache.size()); + } + + @Test + void testToStringDoesNotExposeExpired() throws InterruptedException { + cache.put("live", "alive"); + cache.put("dead", "gone", 100); + Thread.sleep(150); + String result = cache.toString(); + Assertions.assertTrue(result.contains("live")); + Assertions.assertFalse(result.contains("dead")); + } + + @Test + void testNullKeyGetThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.get(null)); + } + + @Test + void testPutNullKeyThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put(null, "v")); + } + + @Test + void testPutNullValueThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put("k", null)); + } + + @Test + void testPutNegativeTTLThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put("k", "v", -1)); + } + + @Test + void testBuilderNegativeCapacityThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new FIFOCache.Builder<>(0)); + } + + @Test + void testBuilderNullEvictionListenerThrows() { + FIFOCache.Builder builder = new FIFOCache.Builder<>(1); + Assertions.assertThrows(IllegalArgumentException.class, () -> builder.evictionListener(null)); + } + + @Test + void testEvictionListenerExceptionDoesNotCrash() { + FIFOCache listenerCache = new FIFOCache.Builder(1).evictionListener((k, v) -> { throw new RuntimeException("Exception"); }).build(); + + listenerCache.put("a", "a"); + listenerCache.put("b", "b"); + Assertions.assertDoesNotThrow(() -> listenerCache.get("a")); + } + + @Test + void testTtlZeroThrowsIllegalArgumentException() { + Executable exec = () -> new FIFOCache.Builder(3).defaultTTL(-1).build(); + Assertions.assertThrows(IllegalArgumentException.class, exec); + } + + @Test + void testPeriodicEvictionStrategyEvictsAtInterval() throws InterruptedException { + FIFOCache periodicCache = new FIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new FIFOCache.PeriodicEvictionStrategy<>(3)).build(); + + periodicCache.put("x", "1"); + Thread.sleep(100); + int ev1 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + int ev2 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + int ev3 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + + Assertions.assertEquals(0, ev1); + Assertions.assertEquals(0, ev2); + Assertions.assertEquals(1, ev3, "Eviction should happen on the 3rd access"); + Assertions.assertEquals(0, periodicCache.size()); + } + + @Test + void testPeriodicEvictionStrategyThrowsExceptionIfIntervalLessThanOrEqual0() { + Executable executable = () -> new FIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new FIFOCache.PeriodicEvictionStrategy<>(0)).build(); + + Assertions.assertThrows(IllegalArgumentException.class, executable); + } + + @Test + void testImmediateEvictionStrategyStrategyEvictsOnEachCall() throws InterruptedException { + FIFOCache immediateEvictionStrategy = new FIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new FIFOCache.ImmediateEvictionStrategy<>()).build(); + + immediateEvictionStrategy.put("x", "1"); + Thread.sleep(100); + int evicted = immediateEvictionStrategy.getEvictionStrategy().onAccess(immediateEvictionStrategy); + + Assertions.assertEquals(1, evicted); + } + + @Test + void testBuilderThrowsExceptionIfEvictionStrategyNull() { + Executable executable = () -> new FIFOCache.Builder(10).defaultTTL(50).evictionStrategy(null).build(); + + Assertions.assertThrows(IllegalArgumentException.class, executable); + } + + @Test + void testReturnsCorrectStrategyInstance() { + FIFOCache.EvictionStrategy strategy = new FIFOCache.ImmediateEvictionStrategy<>(); + + FIFOCache newCache = new FIFOCache.Builder(10).defaultTTL(1000).evictionStrategy(strategy).build(); + + Assertions.assertSame(strategy, newCache.getEvictionStrategy(), "Returned strategy should be the same instance"); + } + + @Test + void testDefaultStrategyIsImmediateEvictionStrategy() { + FIFOCache newCache = new FIFOCache.Builder(5).defaultTTL(1000).build(); + + Assertions.assertTrue(newCache.getEvictionStrategy() instanceof FIFOCache.ImmediateEvictionStrategy, "Default strategy should be ImmediateEvictionStrategyStrategy"); + } + + @Test + void testGetEvictionStrategyIsNotNull() { + FIFOCache newCache = new FIFOCache.Builder(5).build(); + + Assertions.assertNotNull(newCache.getEvictionStrategy(), "Eviction strategy should never be null"); + } + + @Test + void testRemoveKeyRemovesExistingKey() { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + + Assertions.assertEquals("Alpha", cache.get("A")); + Assertions.assertEquals("Beta", cache.get("B")); + + String removed = cache.removeKey("A"); + Assertions.assertEquals("Alpha", removed); + + Assertions.assertNull(cache.get("A")); + Assertions.assertEquals(1, cache.size()); + } + + @Test + void testRemoveKeyReturnsNullIfKeyNotPresent() { + cache.put("X", "X-ray"); + + Assertions.assertNull(cache.removeKey("NonExistent")); + Assertions.assertEquals(1, cache.size()); + } + + @Test + void testRemoveKeyHandlesExpiredEntry() throws InterruptedException { + FIFOCache expiringCache = new FIFOCache.Builder(2).defaultTTL(100).evictionStrategy(new FIFOCache.ImmediateEvictionStrategy<>()).build(); + + expiringCache.put("T", "Temporary"); + + Thread.sleep(200); + + String removed = expiringCache.removeKey("T"); + Assertions.assertEquals("Temporary", removed); + Assertions.assertNull(expiringCache.get("T")); + } + + @Test + void testRemoveKeyThrowsIfKeyIsNull() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.removeKey(null)); + } + + @Test + void testRemoveKeyTriggersEvictionListener() { + AtomicInteger evictedCount = new AtomicInteger(); + + FIFOCache localCache = new FIFOCache.Builder(2).evictionListener((key, value) -> evictedCount.incrementAndGet()).build(); + + localCache.put("A", "Apple"); + localCache.put("B", "Banana"); + + localCache.removeKey("A"); + + Assertions.assertEquals(1, evictedCount.get(), "Eviction listener should have been called once"); + } + + @Test + void testRemoveKeyDoestNotAffectOtherKeys() { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + cache.put("C", "Gamma"); + + cache.removeKey("B"); + + Assertions.assertEquals("Alpha", cache.get("A")); + Assertions.assertNull(cache.get("B")); + Assertions.assertEquals("Gamma", cache.get("C")); + } + + @Test + void testEvictionListenerExceptionDoesNotPropagate() { + FIFOCache localCache = new FIFOCache.Builder(1).evictionListener((key, value) -> { throw new RuntimeException(); }).build(); + + localCache.put("A", "Apple"); + + Assertions.assertDoesNotThrow(() -> localCache.put("B", "Beta")); + } + + @Test + void testGetKeysReturnsAllFreshKeys() { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + cache.put("G", "Gamma"); + + Set expectedKeys = Set.of("A", "B", "G"); + Assertions.assertEquals(expectedKeys, cache.getAllKeys()); + } + + @Test + void testGetKeysIgnoresExpiredKeys() throws InterruptedException { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + cache.put("G", "Gamma", 100); + + Set expectedKeys = Set.of("A", "B"); + Thread.sleep(200); + Assertions.assertEquals(expectedKeys, cache.getAllKeys()); + } + + @Test + void testClearRemovesAllEntries() { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + cache.put("G", "Gamma"); + + cache.clear(); + Assertions.assertEquals(0, cache.size()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java new file mode 100644 index 000000000000..df60a393b136 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/caches/LIFOCacheTest.java @@ -0,0 +1,341 @@ +package com.thealgorithms.datastructures.caches; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +class LIFOCacheTest { + private LIFOCache cache; + private Set evictedKeys; + private List evictedValues; + + @BeforeEach + void setUp() { + evictedKeys = new HashSet<>(); + evictedValues = new ArrayList<>(); + + cache = new LIFOCache.Builder(3) + .defaultTTL(1000) + .evictionListener((k, v) -> { + evictedKeys.add(k); + evictedValues.add(v); + }) + .build(); + } + + @Test + void testPutAndGet() { + cache.put("a", "apple"); + Assertions.assertEquals("apple", cache.get("a")); + } + + @Test + void testOverwriteValue() { + cache.put("a", "apple"); + cache.put("a", "avocado"); + Assertions.assertEquals("avocado", cache.get("a")); + } + + @Test + void testExpiration() throws InterruptedException { + cache.put("temp", "value", 100); + Thread.sleep(200); + Assertions.assertNull(cache.get("temp")); + Assertions.assertTrue(evictedKeys.contains("temp")); + } + + @Test + void testEvictionOnCapacity() { + cache.put("a", "alpha"); + cache.put("b", "bravo"); + cache.put("c", "charlie"); + cache.put("d", "delta"); + + int size = cache.size(); + Assertions.assertEquals(3, size); + Assertions.assertEquals(1, evictedKeys.size()); + Assertions.assertEquals(1, evictedValues.size()); + Assertions.assertEquals("charlie", evictedValues.getFirst()); + } + + @Test + void testEvictionListener() { + cache.put("x", "one"); + cache.put("y", "two"); + cache.put("z", "three"); + cache.put("w", "four"); + + Assertions.assertFalse(evictedKeys.isEmpty()); + Assertions.assertFalse(evictedValues.isEmpty()); + } + + @Test + void testHitsAndMisses() { + cache.put("a", "apple"); + Assertions.assertEquals("apple", cache.get("a")); + Assertions.assertNull(cache.get("b")); + Assertions.assertEquals(1, cache.getHits()); + Assertions.assertEquals(1, cache.getMisses()); + } + + @Test + void testSizeExcludesExpired() throws InterruptedException { + cache.put("a", "a", 100); + cache.put("b", "b", 100); + cache.put("c", "c", 100); + Thread.sleep(150); + Assertions.assertEquals(0, cache.size()); + } + + @Test + void testSizeIncludesFresh() { + cache.put("a", "a", 1000); + cache.put("b", "b", 1000); + cache.put("c", "c", 1000); + Assertions.assertEquals(3, cache.size()); + } + + @Test + void testToStringDoesNotExposeExpired() throws InterruptedException { + cache.put("live", "alive"); + cache.put("dead", "gone", 100); + Thread.sleep(150); + String result = cache.toString(); + Assertions.assertTrue(result.contains("live")); + Assertions.assertFalse(result.contains("dead")); + } + + @Test + void testNullKeyGetThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.get(null)); + } + + @Test + void testPutNullKeyThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put(null, "v")); + } + + @Test + void testPutNullValueThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put("k", null)); + } + + @Test + void testPutNegativeTTLThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put("k", "v", -1)); + } + + @Test + void testBuilderNegativeCapacityThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new LIFOCache.Builder<>(0)); + } + + @Test + void testBuilderNullEvictionListenerThrows() { + LIFOCache.Builder builder = new LIFOCache.Builder<>(1); + Assertions.assertThrows(IllegalArgumentException.class, () -> builder.evictionListener(null)); + } + + @Test + void testEvictionListenerExceptionDoesNotCrash() { + LIFOCache listenerCache = new LIFOCache.Builder(1).evictionListener((k, v) -> { throw new RuntimeException("Exception"); }).build(); + + listenerCache.put("a", "a"); + listenerCache.put("b", "b"); + Assertions.assertDoesNotThrow(() -> listenerCache.get("a")); + } + + @Test + void testTtlZeroThrowsIllegalArgumentException() { + Executable exec = () -> new LIFOCache.Builder(3).defaultTTL(-1).build(); + Assertions.assertThrows(IllegalArgumentException.class, exec); + } + + @Test + void testPeriodicEvictionStrategyEvictsAtInterval() throws InterruptedException { + LIFOCache periodicCache = new LIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new LIFOCache.PeriodicEvictionStrategy<>(3)).build(); + + periodicCache.put("x", "1"); + Thread.sleep(100); + int ev1 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + int ev2 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + int ev3 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + + Assertions.assertEquals(0, ev1); + Assertions.assertEquals(0, ev2); + Assertions.assertEquals(1, ev3, "Eviction should happen on the 3rd access"); + Assertions.assertEquals(0, periodicCache.size()); + } + + @Test + void testPeriodicEvictionStrategyThrowsExceptionIfIntervalLessThanOrEqual0() { + Executable executable = () -> new LIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new LIFOCache.PeriodicEvictionStrategy<>(0)).build(); + + Assertions.assertThrows(IllegalArgumentException.class, executable); + } + + @Test + void testImmediateEvictionStrategyStrategyEvictsOnEachCall() throws InterruptedException { + LIFOCache immediateEvictionStrategy = new LIFOCache.Builder(10).defaultTTL(50).evictionStrategy(new LIFOCache.ImmediateEvictionStrategy<>()).build(); + + immediateEvictionStrategy.put("x", "1"); + Thread.sleep(100); + int evicted = immediateEvictionStrategy.getEvictionStrategy().onAccess(immediateEvictionStrategy); + + Assertions.assertEquals(1, evicted); + } + + @Test + void testBuilderThrowsExceptionIfEvictionStrategyNull() { + Executable executable = () -> new LIFOCache.Builder(10).defaultTTL(50).evictionStrategy(null).build(); + + Assertions.assertThrows(IllegalArgumentException.class, executable); + } + + @Test + void testReturnsCorrectStrategyInstance() { + LIFOCache.EvictionStrategy strategy = new LIFOCache.ImmediateEvictionStrategy<>(); + + LIFOCache newCache = new LIFOCache.Builder(10).defaultTTL(1000).evictionStrategy(strategy).build(); + + Assertions.assertSame(strategy, newCache.getEvictionStrategy(), "Returned strategy should be the same instance"); + } + + @Test + void testDefaultStrategyIsImmediateEvictionStrategy() { + LIFOCache newCache = new LIFOCache.Builder(5).defaultTTL(1000).build(); + + Assertions.assertInstanceOf(LIFOCache.ImmediateEvictionStrategy.class, newCache.getEvictionStrategy(), "Default strategy should be ImmediateEvictionStrategyStrategy"); + } + + @Test + void testGetEvictionStrategyIsNotNull() { + LIFOCache newCache = new LIFOCache.Builder(5).build(); + + Assertions.assertNotNull(newCache.getEvictionStrategy(), "Eviction strategy should never be null"); + } + + @Test + void testRemoveKeyRemovesExistingKey() { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + + Assertions.assertEquals("Alpha", cache.get("A")); + Assertions.assertEquals("Beta", cache.get("B")); + + String removed = cache.removeKey("A"); + Assertions.assertEquals("Alpha", removed); + + Assertions.assertNull(cache.get("A")); + Assertions.assertEquals(1, cache.size()); + } + + @Test + void testRemoveKeyReturnsNullIfKeyNotPresent() { + cache.put("X", "X-ray"); + + Assertions.assertNull(cache.removeKey("NonExistent")); + Assertions.assertEquals(1, cache.size()); + } + + @Test + void testRemoveKeyHandlesExpiredEntry() throws InterruptedException { + LIFOCache expiringCache = new LIFOCache.Builder(2).defaultTTL(100).evictionStrategy(new LIFOCache.ImmediateEvictionStrategy<>()).build(); + + expiringCache.put("T", "Temporary"); + + Thread.sleep(200); + + String removed = expiringCache.removeKey("T"); + Assertions.assertEquals("Temporary", removed); + Assertions.assertNull(expiringCache.get("T")); + } + + @Test + void testRemoveKeyThrowsIfKeyIsNull() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.removeKey(null)); + } + + @Test + void testRemoveKeyTriggersEvictionListener() { + AtomicInteger evictedCount = new AtomicInteger(); + + LIFOCache localCache = new LIFOCache.Builder(2).evictionListener((key, value) -> evictedCount.incrementAndGet()).build(); + + localCache.put("A", "Apple"); + localCache.put("B", "Banana"); + + localCache.removeKey("A"); + + Assertions.assertEquals(1, evictedCount.get(), "Eviction listener should have been called once"); + } + + @Test + void testRemoveKeyDoestNotAffectOtherKeys() { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + cache.put("C", "Gamma"); + + cache.removeKey("B"); + + Assertions.assertEquals("Alpha", cache.get("A")); + Assertions.assertNull(cache.get("B")); + Assertions.assertEquals("Gamma", cache.get("C")); + } + + @Test + void testEvictionListenerExceptionDoesNotPropagate() { + LIFOCache localCache = new LIFOCache.Builder(1).evictionListener((key, value) -> { throw new RuntimeException(); }).build(); + + localCache.put("A", "Apple"); + + Assertions.assertDoesNotThrow(() -> localCache.put("B", "Beta")); + } + + @Test + void testGetKeysReturnsAllFreshKeys() { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + cache.put("G", "Gamma"); + + Set expectedKeys = Set.of("A", "B", "G"); + Assertions.assertEquals(expectedKeys, cache.getAllKeys()); + } + + @Test + void testGetKeysIgnoresExpiredKeys() throws InterruptedException { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + cache.put("G", "Gamma", 100); + + Set expectedKeys = Set.of("A", "B"); + Thread.sleep(200); + Assertions.assertEquals(expectedKeys, cache.getAllKeys()); + } + + @Test + void testClearRemovesAllEntries() { + cache.put("A", "Alpha"); + cache.put("B", "Beta"); + cache.put("G", "Gamma"); + + cache.clear(); + Assertions.assertEquals(0, cache.size()); + } + + @Test + void testGetExpiredKeyIncrementsMissesCount() throws InterruptedException { + LIFOCache localCache = new LIFOCache.Builder(3).evictionStrategy(cache -> 0).defaultTTL(10).build(); + localCache.put("A", "Alpha"); + Thread.sleep(100); + String value = localCache.get("A"); + Assertions.assertEquals(1, localCache.getMisses()); + Assertions.assertNull(value); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/caches/RRCacheTest.java b/src/test/java/com/thealgorithms/datastructures/caches/RRCacheTest.java new file mode 100644 index 000000000000..100c73ea2a5b --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/caches/RRCacheTest.java @@ -0,0 +1,222 @@ +package com.thealgorithms.datastructures.caches; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +class RRCacheTest { + + private RRCache cache; + private Set evictedKeys; + private List evictedValues; + + @BeforeEach + void setUp() { + evictedKeys = new HashSet<>(); + evictedValues = new ArrayList<>(); + + cache = new RRCache.Builder(3) + .defaultTTL(1000) + .random(new Random(0)) + .evictionListener((k, v) -> { + evictedKeys.add(k); + evictedValues.add(v); + }) + .build(); + } + + @Test + void testPutAndGet() { + cache.put("a", "apple"); + Assertions.assertEquals("apple", cache.get("a")); + } + + @Test + void testOverwriteValue() { + cache.put("a", "apple"); + cache.put("a", "avocado"); + Assertions.assertEquals("avocado", cache.get("a")); + } + + @Test + void testExpiration() throws InterruptedException { + cache.put("temp", "value", 100); // short TTL + Thread.sleep(200); + Assertions.assertNull(cache.get("temp")); + Assertions.assertTrue(evictedKeys.contains("temp")); + } + + @Test + void testEvictionOnCapacity() { + cache.put("a", "alpha"); + cache.put("b", "bravo"); + cache.put("c", "charlie"); + cache.put("d", "delta"); // triggers eviction + + int size = cache.size(); + Assertions.assertEquals(3, size); + Assertions.assertEquals(1, evictedKeys.size()); + Assertions.assertEquals(1, evictedValues.size()); + } + + @Test + void testEvictionListener() { + cache.put("x", "one"); + cache.put("y", "two"); + cache.put("z", "three"); + cache.put("w", "four"); // one of x, y, z will be evicted + + Assertions.assertFalse(evictedKeys.isEmpty()); + Assertions.assertFalse(evictedValues.isEmpty()); + } + + @Test + void testHitsAndMisses() { + cache.put("a", "apple"); + Assertions.assertEquals("apple", cache.get("a")); + Assertions.assertNull(cache.get("b")); + Assertions.assertEquals(1, cache.getHits()); + Assertions.assertEquals(1, cache.getMisses()); + } + + @Test + void testSizeExcludesExpired() throws InterruptedException { + cache.put("a", "a", 100); + cache.put("b", "b", 100); + cache.put("c", "c", 100); + Thread.sleep(150); + Assertions.assertEquals(0, cache.size()); + } + + @Test + void testToStringDoesNotExposeExpired() throws InterruptedException { + cache.put("live", "alive"); + cache.put("dead", "gone", 100); + Thread.sleep(150); + String result = cache.toString(); + Assertions.assertTrue(result.contains("live")); + Assertions.assertFalse(result.contains("dead")); + } + + @Test + void testNullKeyGetThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.get(null)); + } + + @Test + void testPutNullKeyThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put(null, "v")); + } + + @Test + void testPutNullValueThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put("k", null)); + } + + @Test + void testPutNegativeTTLThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> cache.put("k", "v", -1)); + } + + @Test + void testBuilderNegativeCapacityThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new RRCache.Builder<>(0)); + } + + @Test + void testBuilderNullRandomThrows() { + RRCache.Builder builder = new RRCache.Builder<>(1); + Assertions.assertThrows(IllegalArgumentException.class, () -> builder.random(null)); + } + + @Test + void testBuilderNullEvictionListenerThrows() { + RRCache.Builder builder = new RRCache.Builder<>(1); + Assertions.assertThrows(IllegalArgumentException.class, () -> builder.evictionListener(null)); + } + + @Test + void testEvictionListenerExceptionDoesNotCrash() { + RRCache listenerCache = new RRCache.Builder(1).evictionListener((k, v) -> { throw new RuntimeException("Exception"); }).build(); + + listenerCache.put("a", "a"); + listenerCache.put("b", "b"); // causes eviction but should not crash + Assertions.assertDoesNotThrow(() -> listenerCache.get("a")); + } + + @Test + void testTtlZeroThrowsIllegalArgumentException() { + Executable exec = () -> new RRCache.Builder(3).defaultTTL(-1).build(); + Assertions.assertThrows(IllegalArgumentException.class, exec); + } + + @Test + void testPeriodicEvictionStrategyEvictsAtInterval() throws InterruptedException { + RRCache periodicCache = new RRCache.Builder(10).defaultTTL(50).evictionStrategy(new RRCache.PeriodicEvictionStrategy<>(3)).build(); + + periodicCache.put("x", "1"); + Thread.sleep(100); + int ev1 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + int ev2 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + int ev3 = periodicCache.getEvictionStrategy().onAccess(periodicCache); + + Assertions.assertEquals(0, ev1); + Assertions.assertEquals(0, ev2); + Assertions.assertEquals(1, ev3, "Eviction should happen on the 3rd access"); + Assertions.assertEquals(0, periodicCache.size()); + } + + @Test + void testPeriodicEvictionStrategyThrowsExceptionIfIntervalLessThanOrEqual0() { + Executable executable = () -> new RRCache.Builder(10).defaultTTL(50).evictionStrategy(new RRCache.PeriodicEvictionStrategy<>(0)).build(); + + Assertions.assertThrows(IllegalArgumentException.class, executable); + } + + @Test + void testNoEvictionStrategyEvictsOnEachCall() throws InterruptedException { + RRCache noEvictionStrategyCache = new RRCache.Builder(10).defaultTTL(50).evictionStrategy(new RRCache.NoEvictionStrategy<>()).build(); + + noEvictionStrategyCache.put("x", "1"); + Thread.sleep(100); + int evicted = noEvictionStrategyCache.getEvictionStrategy().onAccess(noEvictionStrategyCache); + + Assertions.assertEquals(1, evicted); + } + + @Test + void testBuilderThrowsExceptionIfEvictionStrategyNull() { + Executable executable = () -> new RRCache.Builder(10).defaultTTL(50).evictionStrategy(null).build(); + + Assertions.assertThrows(IllegalArgumentException.class, executable); + } + + @Test + void testReturnsCorrectStrategyInstance() { + RRCache.EvictionStrategy strategy = new RRCache.NoEvictionStrategy<>(); + + RRCache newCache = new RRCache.Builder(10).defaultTTL(1000).evictionStrategy(strategy).build(); + + Assertions.assertSame(strategy, newCache.getEvictionStrategy(), "Returned strategy should be the same instance"); + } + + @Test + void testDefaultStrategyIsNoEviction() { + RRCache newCache = new RRCache.Builder(5).defaultTTL(1000).build(); + + Assertions.assertTrue(newCache.getEvictionStrategy() instanceof RRCache.PeriodicEvictionStrategy, "Default strategy should be NoEvictionStrategy"); + } + + @Test + void testGetEvictionStrategyIsNotNull() { + RRCache newCache = new RRCache.Builder(5).build(); + + Assertions.assertNotNull(newCache.getEvictionStrategy(), "Eviction strategy should never be null"); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java b/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java index 36593d6669f8..0356949a8f69 100644 --- a/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java +++ b/src/test/java/com/thealgorithms/datastructures/crdt/LWWElementSetTest.java @@ -3,106 +3,96 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.BeforeEach; +import java.time.Instant; import org.junit.jupiter.api.Test; class LWWElementSetTest { - private LWWElementSet set; - private final Bias bias = Bias.ADDS; - - @BeforeEach - void setUp() { - set = new LWWElementSet(); - } - @Test - void testAdd() { - Element element = new Element("key1", 1, bias); - set.add(element); - - assertTrue(set.lookup(element)); + void testAddElement() { + LWWElementSet set = new LWWElementSet<>(); + set.add("A"); + assertTrue(set.lookup("A")); } @Test - void testRemove() { - Element element = new Element("key1", 1, bias); - set.add(element); - set.remove(element); - - assertFalse(set.lookup(element)); + void testRemoveElement() { + LWWElementSet set = new LWWElementSet<>(); + set.add("A"); + set.remove("A"); + assertFalse(set.lookup("A")); } @Test - void testRemoveNonexistentElement() { - Element element = new Element("key1", 1, bias); - set.remove(element); - - assertFalse(set.lookup(element)); + void testLookupWithoutAdding() { + LWWElementSet set = new LWWElementSet<>(); + assertFalse(set.lookup("A")); } @Test - void testLookupNonexistentElement() { - Element element = new Element("key1", 1, bias); + void testLookupLaterTimestampsFalse() { + LWWElementSet set = new LWWElementSet<>(); + + set.addSet.put("A", new Element<>("A", Instant.now())); + set.removeSet.put("A", new Element<>("A", Instant.now().plusSeconds(10))); - assertFalse(set.lookup(element)); + assertFalse(set.lookup("A")); } @Test - void testCompareEqualSets() { - LWWElementSet otherSet = new LWWElementSet(); + void testLookupEarlierTimestampsTrue() { + LWWElementSet set = new LWWElementSet<>(); - Element element = new Element("key1", 1, bias); - set.add(element); - otherSet.add(element); + set.addSet.put("A", new Element<>("A", Instant.now())); + set.removeSet.put("A", new Element<>("A", Instant.now().minusSeconds(10))); - assertTrue(set.compare(otherSet)); - - otherSet.add(new Element("key2", 2, bias)); - assertTrue(set.compare(otherSet)); + assertTrue(set.lookup("A")); } @Test - void testCompareDifferentSets() { - LWWElementSet otherSet = new LWWElementSet(); - - Element element1 = new Element("key1", 1, bias); - Element element2 = new Element("key2", 2, bias); - - set.add(element1); - otherSet.add(element2); - - assertFalse(set.compare(otherSet)); + void testLookupWithConcurrentTimestamps() { + LWWElementSet set = new LWWElementSet<>(); + Instant now = Instant.now(); + set.addSet.put("A", new Element<>("A", now)); + set.removeSet.put("A", new Element<>("A", now)); + assertFalse(set.lookup("A")); } @Test - void testMerge() { - LWWElementSet otherSet = new LWWElementSet(); + void testMergeTwoSets() { + LWWElementSet set1 = new LWWElementSet<>(); + LWWElementSet set2 = new LWWElementSet<>(); - Element element1 = new Element("key1", 1, bias); - Element element2 = new Element("key2", 2, bias); + set1.add("A"); + set2.add("B"); + set2.remove("A"); - set.add(element1); - otherSet.add(element2); + set1.merge(set2); - set.merge(otherSet); - - assertTrue(set.lookup(element1)); - assertTrue(set.lookup(element2)); + assertFalse(set1.lookup("A")); + assertTrue(set1.lookup("B")); } @Test - void testCompareTimestampsEqualTimestamps() { - LWWElementSet lwwElementSet = new LWWElementSet(); + void testMergeWithConflictingTimestamps() { + LWWElementSet set1 = new LWWElementSet<>(); + LWWElementSet set2 = new LWWElementSet<>(); - Element e1 = new Element("key1", 10, Bias.REMOVALS); - Element e2 = new Element("key1", 10, Bias.REMOVALS); + Instant now = Instant.now(); + set1.addSet.put("A", new Element<>("A", now.minusSeconds(10))); + set2.addSet.put("A", new Element<>("A", now)); - assertTrue(lwwElementSet.compareTimestamps(e1, e2)); + set1.merge(set2); - e1 = new Element("key1", 10, Bias.ADDS); - e2 = new Element("key1", 10, Bias.ADDS); + assertTrue(set1.lookup("A")); + } - assertFalse(lwwElementSet.compareTimestamps(e1, e2)); + @Test + void testRemoveOlderThanAdd() { + LWWElementSet set = new LWWElementSet<>(); + Instant now = Instant.now(); + set.addSet.put("A", new Element<>("A", now)); + set.removeSet.put("A", new Element<>("A", now.minusSeconds(10))); + assertTrue(set.lookup("A")); } } diff --git a/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySizeTest.java b/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySizeTest.java new file mode 100644 index 000000000000..71dade9796dc --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionBySizeTest.java @@ -0,0 +1,146 @@ +package com.thealgorithms.datastructures.disjointsetunion; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +public class DisjointSetUnionBySizeTest { + + @Test + public void testMakeSet() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node = dsu.makeSet(1); + assertNotNull(node); + assertEquals(node, node.parent); + assertEquals(1, node.size); + } + + @Test + public void testUnionFindSet() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + DisjointSetUnionBySize.Node node4 = dsu.makeSet(4); + + dsu.unionSets(node1, node2); + dsu.unionSets(node3, node2); + dsu.unionSets(node3, node4); + dsu.unionSets(node1, node3); + + DisjointSetUnionBySize.Node root1 = dsu.findSet(node1); + DisjointSetUnionBySize.Node root2 = dsu.findSet(node2); + DisjointSetUnionBySize.Node root3 = dsu.findSet(node3); + DisjointSetUnionBySize.Node root4 = dsu.findSet(node4); + + assertEquals(root1, root2); + assertEquals(root1, root3); + assertEquals(root1, root4); + assertEquals(4, root1.size); + } + + @Test + public void testFindSetOnSingleNode() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node = dsu.makeSet("A"); + assertEquals(node, dsu.findSet(node)); + } + + @Test + public void testUnionAlreadyConnectedNodes() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + // Union nodes that are already connected + dsu.unionSets(node1, node3); + + // All should have the same root + DisjointSetUnionBySize.Node root = dsu.findSet(node1); + assertEquals(root, dsu.findSet(node2)); + assertEquals(root, dsu.findSet(node3)); + assertEquals(3, root.size); + } + + @Test + public void testMultipleMakeSets() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + + assertNotEquals(node1, node2); + assertNotEquals(node2, node3); + assertNotEquals(node1, node3); + + assertEquals(node1, node1.parent); + assertEquals(node2, node2.parent); + assertEquals(node3, node3.parent); + assertEquals(1, node1.size); + assertEquals(1, node2.size); + assertEquals(1, node3.size); + } + + @Test + public void testPathCompression() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + // After findSet, path compression should update parent to root directly + DisjointSetUnionBySize.Node root = dsu.findSet(node3); + assertEquals(root, node1); + assertEquals(node1, node3.parent); + assertEquals(3, root.size); + } + + @Test + public void testMultipleDisjointSets() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node node1 = dsu.makeSet(1); + DisjointSetUnionBySize.Node node2 = dsu.makeSet(2); + DisjointSetUnionBySize.Node node3 = dsu.makeSet(3); + DisjointSetUnionBySize.Node node4 = dsu.makeSet(4); + DisjointSetUnionBySize.Node node5 = dsu.makeSet(5); + DisjointSetUnionBySize.Node node6 = dsu.makeSet(6); + + // Create two separate components + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + dsu.unionSets(node4, node5); + dsu.unionSets(node5, node6); + + // Verify they are separate + assertEquals(dsu.findSet(node1), dsu.findSet(node2)); + assertEquals(dsu.findSet(node2), dsu.findSet(node3)); + assertEquals(dsu.findSet(node4), dsu.findSet(node5)); + assertEquals(dsu.findSet(node5), dsu.findSet(node6)); + + assertNotEquals(dsu.findSet(node1), dsu.findSet(node4)); + assertNotEquals(dsu.findSet(node3), dsu.findSet(node6)); + } + + @Test + public void testEmptyValues() { + DisjointSetUnionBySize dsu = new DisjointSetUnionBySize<>(); + DisjointSetUnionBySize.Node emptyNode = dsu.makeSet(""); + DisjointSetUnionBySize.Node nullNode = dsu.makeSet(null); + + assertEquals(emptyNode, dsu.findSet(emptyNode)); + assertEquals(nullNode, dsu.findSet(nullNode)); + + dsu.unionSets(emptyNode, nullNode); + assertEquals(dsu.findSet(emptyNode), dsu.findSet(nullNode)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java b/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java index a10a99d40496..581bac6151dd 100644 --- a/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java +++ b/src/test/java/com/thealgorithms/datastructures/disjointsetunion/DisjointSetUnionTest.java @@ -1,8 +1,9 @@ package com.thealgorithms.datastructures.disjointsetunion; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class DisjointSetUnionTest { @@ -12,7 +13,7 @@ public void testMakeSet() { DisjointSetUnion dsu = new DisjointSetUnion<>(); Node node = dsu.makeSet(1); assertNotNull(node); - Assertions.assertEquals(node, node.parent); + assertEquals(node, node.parent); } @Test @@ -33,19 +34,197 @@ public void testUnionFindSet() { Node root3 = dsu.findSet(node3); Node root4 = dsu.findSet(node4); - Assertions.assertEquals(node1, node1.parent); - Assertions.assertEquals(node1, node2.parent); - Assertions.assertEquals(node1, node3.parent); - Assertions.assertEquals(node1, node4.parent); + assertEquals(node1, node1.parent); + assertEquals(node1, node2.parent); + assertEquals(node1, node3.parent); + assertEquals(node1, node4.parent); - Assertions.assertEquals(node1, root1); - Assertions.assertEquals(node1, root2); - Assertions.assertEquals(node1, root3); - Assertions.assertEquals(node1, root4); + assertEquals(node1, root1); + assertEquals(node1, root2); + assertEquals(node1, root3); + assertEquals(node1, root4); - Assertions.assertEquals(1, node1.rank); - Assertions.assertEquals(0, node2.rank); - Assertions.assertEquals(0, node3.rank); - Assertions.assertEquals(0, node4.rank); + assertEquals(1, node1.rank); + assertEquals(0, node2.rank); + assertEquals(0, node3.rank); + assertEquals(0, node4.rank); + } + + @Test + public void testFindSetOnSingleNode() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node = dsu.makeSet("A"); + assertEquals(node, dsu.findSet(node)); + } + + @Test + public void testUnionAlreadyConnectedNodes() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node1 = dsu.makeSet(1); + Node node2 = dsu.makeSet(2); + Node node3 = dsu.makeSet(3); + + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + // Union nodes that are already connected + dsu.unionSets(node1, node3); + + // All should have the same root + Node root = dsu.findSet(node1); + assertEquals(root, dsu.findSet(node2)); + assertEquals(root, dsu.findSet(node3)); + } + + @Test + public void testRankIncrease() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node1 = dsu.makeSet(1); + Node node2 = dsu.makeSet(2); + Node node3 = dsu.makeSet(3); + Node node4 = dsu.makeSet(4); + + dsu.unionSets(node1, node2); // rank of node1 should increase + dsu.unionSets(node3, node4); // rank of node3 should increase + dsu.unionSets(node1, node3); // union two trees of same rank -> rank increases + + assertEquals(2, dsu.findSet(node1).rank); + } + + @Test + public void testMultipleMakeSets() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node1 = dsu.makeSet(1); + Node node2 = dsu.makeSet(2); + Node node3 = dsu.makeSet(3); + + assertNotEquals(node1, node2); + assertNotEquals(node2, node3); + assertNotEquals(node1, node3); + + assertEquals(node1, node1.parent); + assertEquals(node2, node2.parent); + assertEquals(node3, node3.parent); + } + + @Test + public void testPathCompression() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node1 = dsu.makeSet(1); + Node node2 = dsu.makeSet(2); + Node node3 = dsu.makeSet(3); + + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + // After findSet, path compression should update parent to root directly + Node root = dsu.findSet(node3); + assertEquals(root, node1); + assertEquals(node1, node3.parent); + } + + @Test + public void testUnionByRankSmallerToLarger() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node1 = dsu.makeSet(1); + Node node2 = dsu.makeSet(2); + Node node3 = dsu.makeSet(3); + + // Create tree with node1 as root and rank 1 + dsu.unionSets(node1, node2); + assertEquals(1, node1.rank); + assertEquals(0, node2.rank); + + // Union single node (rank 0) with tree (rank 1) + // Smaller rank tree should attach to larger rank tree + dsu.unionSets(node3, node1); + assertEquals(node1, dsu.findSet(node3)); + assertEquals(1, node1.rank); // Rank should not increase + } + + @Test + public void testUnionByRankEqualRanks() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node1 = dsu.makeSet(1); + Node node2 = dsu.makeSet(2); + Node node3 = dsu.makeSet(3); + Node node4 = dsu.makeSet(4); + + // Create two trees of equal rank (1) + dsu.unionSets(node1, node2); + dsu.unionSets(node3, node4); + assertEquals(1, node1.rank); + assertEquals(1, node3.rank); + + // Union two trees of equal rank + dsu.unionSets(node1, node3); + Node root = dsu.findSet(node1); + assertEquals(2, root.rank); // Rank should increase by 1 + } + + @Test + public void testLargeChainPathCompression() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node1 = dsu.makeSet(1); + Node node2 = dsu.makeSet(2); + Node node3 = dsu.makeSet(3); + Node node4 = dsu.makeSet(4); + Node node5 = dsu.makeSet(5); + + // Create a long chain: 1 -> 2 -> 3 -> 4 -> 5 + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + dsu.unionSets(node3, node4); + dsu.unionSets(node4, node5); + + // Find from the deepest node + Node root = dsu.findSet(node5); + + // Path compression should make all nodes point directly to root + assertEquals(root, node5.parent); + assertEquals(root, node4.parent); + assertEquals(root, node3.parent); + assertEquals(root, node2.parent); + assertEquals(root, node1.parent); + } + + @Test + public void testMultipleDisjointSets() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node node1 = dsu.makeSet(1); + Node node2 = dsu.makeSet(2); + Node node3 = dsu.makeSet(3); + Node node4 = dsu.makeSet(4); + Node node5 = dsu.makeSet(5); + Node node6 = dsu.makeSet(6); + + // Create two separate components + dsu.unionSets(node1, node2); + dsu.unionSets(node2, node3); + + dsu.unionSets(node4, node5); + dsu.unionSets(node5, node6); + + // Verify they are separate + assertEquals(dsu.findSet(node1), dsu.findSet(node2)); + assertEquals(dsu.findSet(node2), dsu.findSet(node3)); + assertEquals(dsu.findSet(node4), dsu.findSet(node5)); + assertEquals(dsu.findSet(node5), dsu.findSet(node6)); + + assertNotEquals(dsu.findSet(node1), dsu.findSet(node4)); + assertNotEquals(dsu.findSet(node3), dsu.findSet(node6)); + } + + @Test + public void testEmptyValues() { + DisjointSetUnion dsu = new DisjointSetUnion<>(); + Node emptyNode = dsu.makeSet(""); + Node nullNode = dsu.makeSet(null); + + assertEquals(emptyNode, dsu.findSet(emptyNode)); + assertEquals(nullNode, dsu.findSet(nullNode)); + + dsu.unionSets(emptyNode, nullNode); + assertEquals(dsu.findSet(emptyNode), dsu.findSet(nullNode)); } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/DialsAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/DialsAlgorithmTest.java new file mode 100644 index 000000000000..2350455d4329 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/DialsAlgorithmTest.java @@ -0,0 +1,88 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +final class DialsAlgorithmTest { + + private List> graph; + private static final int NUM_VERTICES = 6; + private static final int MAX_EDGE_WEIGHT = 10; + + @BeforeEach + void setUp() { + graph = new ArrayList<>(); + for (int i = 0; i < NUM_VERTICES; i++) { + graph.add(new ArrayList<>()); + } + } + + private void addEdge(int u, int v, int weight) { + graph.get(u).add(new DialsAlgorithm.Edge(v, weight)); + } + + @Test + @DisplayName("Test with a simple connected graph") + void testSimpleGraph() { + // Build graph from a standard example + addEdge(0, 1, 2); + addEdge(0, 2, 4); + addEdge(1, 2, 1); + addEdge(1, 3, 7); + addEdge(2, 4, 3); + addEdge(3, 5, 1); + addEdge(4, 3, 2); + addEdge(4, 5, 5); + + int[] expectedDistances = {0, 2, 3, 8, 6, 9}; + int[] actualDistances = DialsAlgorithm.run(graph, 0, MAX_EDGE_WEIGHT); + assertArrayEquals(expectedDistances, actualDistances); + } + + @Test + @DisplayName("Test with a disconnected node") + void testDisconnectedNode() { + addEdge(0, 1, 5); + addEdge(1, 2, 5); + // Node 3, 4, 5 are disconnected + + int[] expectedDistances = {0, 5, 10, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE}; + int[] actualDistances = DialsAlgorithm.run(graph, 0, MAX_EDGE_WEIGHT); + assertArrayEquals(expectedDistances, actualDistances); + } + + @Test + @DisplayName("Test with source as destination") + void testSourceIsDestination() { + addEdge(0, 1, 10); + int[] expectedDistances = {0, 10, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE}; + // Run with source 0 + int[] actualDistances = DialsAlgorithm.run(graph, 0, MAX_EDGE_WEIGHT); + assertArrayEquals(expectedDistances, actualDistances); + } + + @Test + @DisplayName("Test graph with multiple paths to a node") + void testMultiplePaths() { + addEdge(0, 1, 10); + addEdge(0, 2, 3); + addEdge(2, 1, 2); // Shorter path to 1 is via 2 (3+2=5) + + int[] expectedDistances = {0, 5, 3, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE}; + int[] actualDistances = DialsAlgorithm.run(graph, 0, MAX_EDGE_WEIGHT); + assertArrayEquals(expectedDistances, actualDistances); + } + + @Test + @DisplayName("Test with an invalid source vertex") + void testInvalidSource() { + assertThrows(IllegalArgumentException.class, () -> DialsAlgorithm.run(graph, -1, MAX_EDGE_WEIGHT)); + assertThrows(IllegalArgumentException.class, () -> DialsAlgorithm.run(graph, NUM_VERTICES, MAX_EDGE_WEIGHT)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java index b18f161ef1a6..7291cd6c319c 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/KruskalTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +@SuppressWarnings({"rawtypes", "unchecked"}) public class KruskalTest { private Kruskal kruskal; diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/TwoSatTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/TwoSatTest.java new file mode 100644 index 000000000000..15e77b357f83 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/TwoSatTest.java @@ -0,0 +1,125 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Testcases for 2-SAT. + * Please note thea whlie checking for boolean assignments always keep n + 1 elements and the first element should be always false. + */ +public class TwoSatTest { + private TwoSat twoSat; + + /** + * Case 1: Basic satisfiable case. + * Simple 3 clauses with consistent assignments. + */ + @Test + public void testSatisfiableBasicCase() { + twoSat = new TwoSat(5); + + twoSat.addClause(1, false, 2, false); // (x1 โˆจ x2) + twoSat.addClause(3, true, 2, false); // (ยฌx3 โˆจ x2) + twoSat.addClause(4, false, 5, true); // (x4 โˆจ ยฌx5) + + twoSat.solve(); + + assertTrue(twoSat.isSolutionExists(), "Expected solution to exist"); + boolean[] expected = {false, true, true, true, true, true}; + assertArrayEquals(expected, twoSat.getSolutions()); + } + + /** + * Case 2: Unsatisfiable due to direct contradiction. + * (x1 โˆจ x1) โˆง (ยฌx1 โˆจ ยฌx1) makes x1 and ยฌx1 both required. + */ + @Test + public void testUnsatisfiableContradiction() { + twoSat = new TwoSat(1); + + twoSat.addClause(1, false, 1, false); // (x1 โˆจ x1) + twoSat.addClause(1, true, 1, true); // (ยฌx1 โˆจ ยฌx1) + + twoSat.solve(); + + assertFalse(twoSat.isSolutionExists(), "Expected no solution (contradiction)"); + } + + /** + * Case 3: Single variable, trivially satisfiable. + * Only (x1 โˆจ x1) exists. + */ + @Test + public void testSingleVariableTrivialSatisfiable() { + twoSat = new TwoSat(1); + + twoSat.addClause(1, false, 1, false); // (x1 โˆจ x1) + + twoSat.solve(); + + assertTrue(twoSat.isSolutionExists(), "Expected solution to exist"); + boolean[] expected = {false, true}; + assertArrayEquals(expected, twoSat.getSolutions()); + } + + /** + * Case 4: Larger satisfiable system with dependencies. + * (x1 โˆจ x2), (ยฌx2 โˆจ x3), (ยฌx3 โˆจ x4), (ยฌx4 โˆจ x5) + */ + @Test + public void testChainedDependenciesSatisfiable() { + twoSat = new TwoSat(5); + + twoSat.addClause(1, false, 2, false); + twoSat.addClause(2, true, 3, false); + twoSat.addClause(3, true, 4, false); + twoSat.addClause(4, true, 5, false); + + twoSat.solve(); + + assertTrue(twoSat.isSolutionExists(), "Expected solution to exist"); + boolean[] solution = twoSat.getSolutions(); + for (int i = 1; i <= 5; i++) { + assertTrue(solution[i], "Expected x" + i + " to be true"); + } + } + + /** + * Case 5: Contradiction due to dependency cycle. + * (x1 โˆจ x2), (ยฌx1 โˆจ ยฌx2), (x1 โˆจ ยฌx2), (ยฌx1 โˆจ x2) + * These clauses form a circular dependency making it impossible. + */ + @Test + public void testUnsatisfiableCycle() { + twoSat = new TwoSat(2); + + twoSat.addClause(1, false, 2, false); + twoSat.addClause(1, true, 2, true); + twoSat.addClause(1, false, 2, true); + twoSat.addClause(1, true, 2, false); + + twoSat.solve(); + + assertFalse(twoSat.isSolutionExists(), "Expected no solution due to contradictory cycle"); + } + + /** + * Testcase from CSES + */ + @Test + public void test6() { + twoSat = new TwoSat(2); + + twoSat.addClause(1, true, 2, false); + twoSat.addClause(2, true, 1, false); + twoSat.addClause(1, true, 1, true); + twoSat.addClause(2, false, 2, false); + + twoSat.solve(); + + assertFalse(twoSat.isSolutionExists(), "Expected no solution."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedListTest.java new file mode 100644 index 000000000000..faa2765a3264 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/CircularDoublyLinkedListTest.java @@ -0,0 +1,120 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CircularDoublyLinkedListTest { + + private CircularDoublyLinkedList list; + + @BeforeEach + public void setUp() { + list = new CircularDoublyLinkedList<>(); + } + + @Test + public void testInitialSize() { + assertEquals(0, list.getSize(), "Initial size should be 0."); + } + + @Test + public void testAppendAndSize() { + list.append(10); + list.append(20); + list.append(30); + + assertEquals(3, list.getSize(), "Size after appends should be 3."); + assertEquals("[ 10, 20, 30 ]", list.toString(), "List content should match appended values."); + } + + @Test + public void testRemove() { + list.append(10); + list.append(20); + list.append(30); + + int removed = list.remove(1); + assertEquals(20, removed, "Removed element at index 1 should be 20."); + + assertEquals("[ 10, 30 ]", list.toString(), "List content should reflect removal."); + assertEquals(2, list.getSize(), "Size after removal should be 2."); + + removed = list.remove(0); + assertEquals(10, removed, "Removed element at index 0 should be 10."); + assertEquals("[ 30 ]", list.toString(), "List content should reflect second removal."); + assertEquals(1, list.getSize(), "Size after second removal should be 1."); + } + + @Test + public void testRemoveInvalidIndex() { + list.append(10); + list.append(20); + + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(2), "Removing at invalid index 2 should throw exception."); + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1), "Removing at negative index should throw exception."); + } + + @Test + public void testToStringEmpty() { + assertEquals("[]", list.toString(), "Empty list should display as []."); + } + + @Test + public void testSingleElement() { + list.append(10); + + assertEquals(1, list.getSize(), "Size after adding single element should be 1."); + assertEquals("[ 10 ]", list.toString(), "Single element list string should be formatted correctly."); + int removed = list.remove(0); + assertEquals(10, removed, "Removed element should be the one appended."); + assertEquals("[]", list.toString(), "List should be empty after removing last element."); + assertEquals(0, list.getSize(), "Size after removing last element should be 0."); + } + + @Test + public void testNullAppend() { + assertThrows(NullPointerException.class, () -> list.append(null), "Appending null should throw NullPointerException."); + } + + @Test + public void testRemoveLastPosition() { + list.append(10); + list.append(20); + list.append(30); + int removed = list.remove(list.getSize() - 1); + assertEquals(30, removed, "Last element removed should be 30."); + assertEquals(2, list.getSize(), "Size should decrease after removing last element."); + } + + @Test + public void testRemoveFromEmptyThrows() { + assertThrows(IndexOutOfBoundsException.class, () -> list.remove(0), "Remove from empty list should throw."); + } + + @Test + public void testRepeatedAppendAndRemove() { + for (int i = 0; i < 100; i++) { + list.append(i); + } + assertEquals(100, list.getSize()); + + for (int i = 99; i >= 0; i--) { + int removed = list.remove(i); + assertEquals(i, removed, "Removed element should match appended value."); + } + assertEquals(0, list.getSize(), "List should be empty after all removes."); + } + + @Test + public void testToStringAfterMultipleRemoves() { + list.append(1); + list.append(2); + list.append(3); + list.remove(2); + list.remove(0); + assertEquals("[ 2 ]", list.toString(), "ToString should correctly represent remaining elements."); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java index 1a3efe8a5572..3d3f62fc5132 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/CountSinglyLinkedListRecursionTest.java @@ -1,8 +1,10 @@ package com.thealgorithms.datastructures.lists; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; public class CountSinglyLinkedListRecursionTest { @@ -15,35 +17,112 @@ public void setUp() { } @Test + @DisplayName("Count of an empty list should be 0") public void testCountEmptyList() { - // An empty list should have a count of 0 - assertEquals(0, list.count(), "Count of an empty list should be 0."); + assertEquals(0, list.count()); } @Test + @DisplayName("Count after inserting a single element should be 1") public void testCountSingleElementList() { - // Insert a single element and check the count list.insert(1); - assertEquals(1, list.count(), "Count of a single-element list should be 1."); + assertEquals(1, list.count()); } @Test + @DisplayName("Count after inserting multiple distinct elements") public void testCountMultipleElements() { - // Insert multiple elements and check the count for (int i = 1; i <= 5; i++) { list.insert(i); } - assertEquals(5, list.count(), "Count of a list with 5 elements should be 5."); + assertEquals(5, list.count()); } @Test + @DisplayName("Count should reflect total number of nodes with duplicate values") public void testCountWithDuplicateElements() { - // Insert duplicate elements and verify the count is correct - list.insert(1); list.insert(2); list.insert(2); list.insert(3); list.insert(3); - assertEquals(5, list.count(), "Count of a list with duplicate elements should match total node count."); + list.insert(1); + assertEquals(5, list.count()); + } + + @Test + @DisplayName("Count should return 0 after clearing the list") + public void testCountAfterClearingList() { + for (int i = 1; i <= 4; i++) { + list.insert(i); + } + list.clear(); // assumed to exist + assertEquals(0, list.count()); + } + + @Test + @DisplayName("Count on a very large list should be accurate") + public void testCountOnVeryLargeList() { + int n = 1000; + for (int i = 0; i < n; i++) { + list.insert(i); + } + assertEquals(n, list.count()); + } + + @Test + @DisplayName("Count should work correctly with negative values") + public void testCountOnListWithNegativeNumbers() { + list.insert(-1); + list.insert(-2); + list.insert(-3); + assertEquals(3, list.count()); + } + + @Test + @DisplayName("Calling count multiple times should return the same value if list is unchanged") + public void testCountIsConsistentWithoutModification() { + list.insert(1); + list.insert(2); + int count1 = list.count(); + int count2 = list.count(); + assertEquals(count1, count2); + } + + @Test + @DisplayName("Count should reflect total even if all values are the same") + public void testCountAllSameValues() { + for (int i = 0; i < 5; i++) { + list.insert(42); + } + assertEquals(5, list.count()); + } + + @Test + @DisplayName("Count should remain correct after multiple interleaved insert and count operations") + public void testCountAfterEachInsert() { + assertEquals(0, list.count()); + list.insert(1); + assertEquals(1, list.count()); + list.insert(2); + assertEquals(2, list.count()); + list.insert(3); + assertEquals(3, list.count()); + } + + @Test + @DisplayName("List should not throw on edge count (0 nodes)") + public void testEdgeCaseNoElements() { + assertDoesNotThrow(() -> list.count()); + } + + @Test + @DisplayName("Should count accurately after inserting then removing all elements") + public void testCountAfterInsertAndClear() { + for (int i = 0; i < 10; i++) { + list.insert(i); + } + assertEquals(10, list.count()); + list.clear(); + assertEquals(0, list.count()); } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java index bf5501826994..20bf24d79159 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/CursorLinkedListTest.java @@ -109,4 +109,189 @@ void testMemoryLimitExceeded() { } }); } + + @Test + void testSingleElementOperations() { + // Test operations with just one element + list.append("Only"); + assertEquals("Only", list.get(0)); + assertEquals(0, list.indexOf("Only")); + + list.remove("Only"); + assertNull(list.get(0)); + assertEquals(-1, list.indexOf("Only")); + } + + @Test + void testDuplicateElements() { + // Test handling of duplicate elements + list.append("Duplicate"); + list.append("Other"); + list.append("Duplicate"); + + assertEquals(0, list.indexOf("Duplicate")); // Should return first occurrence + assertEquals("Duplicate", list.get(0)); + assertEquals("Duplicate", list.get(2)); + + list.remove("Duplicate"); // Should remove first occurrence + assertEquals("Other", list.get(0)); + assertEquals("Duplicate", list.get(1)); + } + + @Test + void testRemoveByIndexEdgeCases() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + // Test removing invalid indices + list.removeByIndex(-1); // Should not crash + list.removeByIndex(10); // Should not crash + + // Verify list unchanged + assertEquals("First", list.get(0)); + assertEquals("Second", list.get(1)); + assertEquals("Third", list.get(2)); + + // Test removing first element by index + list.removeByIndex(0); + assertEquals("Second", list.get(0)); + assertEquals("Third", list.get(1)); + } + + @Test + void testRemoveByIndexLastElement() { + list.append("First"); + list.append("Second"); + list.append("Third"); + + // Remove last element by index + list.removeByIndex(2); + assertEquals("First", list.get(0)); + assertEquals("Second", list.get(1)); + assertNull(list.get(2)); + } + + @Test + void testConsecutiveOperations() { + // Test multiple consecutive operations + list.append("A"); + list.append("B"); + list.append("C"); + list.append("D"); + + list.remove("B"); + list.remove("D"); + + assertEquals("A", list.get(0)); + assertEquals("C", list.get(1)); + assertNull(list.get(2)); + + list.append("E"); + assertEquals("E", list.get(2)); + } + + @Test + void testMemoryReclamation() { + // Test that removed elements free up memory space + for (int i = 0; i < 50; i++) { + list.append("Element" + i); + } + + // Remove some elements + for (int i = 0; i < 25; i++) { + list.remove("Element" + i); + } + + // Should be able to add more elements (testing memory reclamation) + for (int i = 100; i < 150; i++) { + list.append("New" + i); + } + + // Verify some elements exist + assertEquals("Element25", list.get(0)); + assertEquals("New100", list.get(25)); + } + + @Test + void testSpecialCharacters() { + // Test with strings containing special characters + list.append("Hello World!"); + list.append("Test@123"); + list.append("Special#$%"); + list.append(""); // Empty string + + assertEquals("Hello World!", list.get(0)); + assertEquals("Test@123", list.get(1)); + assertEquals("Special#$%", list.get(2)); + assertEquals("", list.get(3)); + + assertEquals(3, list.indexOf("")); + } + + @Test + void testLargeIndices() { + list.append("Test"); + + // Test very large indices + assertNull(list.get(Integer.MAX_VALUE)); + assertNull(list.get(1000)); + } + + @Test + void testSequentialRemovalByIndex() { + list.append("A"); + list.append("B"); + list.append("C"); + list.append("D"); + + // Remove elements sequentially by index + list.removeByIndex(1); // Remove "B" + assertEquals("A", list.get(0)); + assertEquals("C", list.get(1)); + assertEquals("D", list.get(2)); + + list.removeByIndex(1); // Remove "C" + assertEquals("A", list.get(0)); + assertEquals("D", list.get(1)); + assertNull(list.get(2)); + } + + @Test + void testAppendAfterRemoval() { + list.append("First"); + list.append("Second"); + + list.remove("First"); + list.append("Third"); + + assertEquals("Second", list.get(0)); + assertEquals("Third", list.get(1)); + assertEquals(1, list.indexOf("Third")); + } + + @Test + void testPerformanceWithManyOperations() { + // Test with many mixed operations + for (int i = 0; i < 50; i++) { + list.append("Item" + i); + } + + // Remove every other element + for (int i = 0; i < 50; i += 2) { + list.remove("Item" + i); + } + + // Verify remaining elements + assertEquals("Item1", list.get(0)); + assertEquals("Item3", list.get(1)); + assertEquals("Item5", list.get(2)); + + // Add more elements + for (int i = 100; i < 110; i++) { + list.append("New" + i); + } + + assertEquals("New100", list.get(25)); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedListTest.java new file mode 100644 index 000000000000..667f9fcf5700 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/FlattenMultilevelLinkedListTest.java @@ -0,0 +1,112 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +/** + * Unit tests for the FlattenMultilevelLinkedList class. + * This class tests the flattening logic with various list structures, + * including null lists, simple lists, and complex multilevel lists. + */ +final class FlattenMultilevelLinkedListTest { + + // A helper function to convert a flattened list (connected by child pointers) + // into a standard Java List for easy comparison. + private List toList(FlattenMultilevelLinkedList.Node head) { + List list = new ArrayList<>(); + FlattenMultilevelLinkedList.Node current = head; + while (current != null) { + list.add(current.data); + current = current.child; + } + return list; + } + + @Test + @DisplayName("Test with a null list") + void testFlattenNullList() { + assertNull(FlattenMultilevelLinkedList.flatten(null)); + } + + @Test + @DisplayName("Test with a simple, single-level list") + void testFlattenSingleLevelList() { + // Create a simple list: 1 -> 2 -> 3 + FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(1); + head.next = new FlattenMultilevelLinkedList.Node(2); + head.next.next = new FlattenMultilevelLinkedList.Node(3); + + // Flatten the list + FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head); + + // Expected output: 1 -> 2 -> 3 (vertically) + List expected = List.of(1, 2, 3); + assertEquals(expected, toList(flattenedHead)); + } + + @Test + @DisplayName("Test with a complex multilevel list") + void testFlattenComplexMultilevelList() { + // Create the multilevel structure from the problem description + // 5 -> 10 -> 19 -> 28 + // | | | | + // 7 20 22 35 + // | | | + // 8 50 40 + // | | + // 30 45 + FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(5); + head.child = new FlattenMultilevelLinkedList.Node(7); + head.child.child = new FlattenMultilevelLinkedList.Node(8); + head.child.child.child = new FlattenMultilevelLinkedList.Node(30); + + head.next = new FlattenMultilevelLinkedList.Node(10); + head.next.child = new FlattenMultilevelLinkedList.Node(20); + + head.next.next = new FlattenMultilevelLinkedList.Node(19); + head.next.next.child = new FlattenMultilevelLinkedList.Node(22); + head.next.next.child.child = new FlattenMultilevelLinkedList.Node(50); + + head.next.next.next = new FlattenMultilevelLinkedList.Node(28); + head.next.next.next.child = new FlattenMultilevelLinkedList.Node(35); + head.next.next.next.child.child = new FlattenMultilevelLinkedList.Node(40); + head.next.next.next.child.child.child = new FlattenMultilevelLinkedList.Node(45); + + // Flatten the list + FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head); + + // Expected sorted output + List expected = List.of(5, 7, 8, 10, 19, 20, 22, 28, 30, 35, 40, 45, 50); + assertEquals(expected, toList(flattenedHead)); + } + + @Test + @DisplayName("Test with some empty child lists") + void testFlattenWithEmptyChildLists() { + // Create a list: 5 -> 10 -> 12 + // | | + // 7 11 + // | + // 9 + FlattenMultilevelLinkedList.Node head = new FlattenMultilevelLinkedList.Node(5); + head.child = new FlattenMultilevelLinkedList.Node(7); + head.child.child = new FlattenMultilevelLinkedList.Node(9); + + head.next = new FlattenMultilevelLinkedList.Node(10); // No child list + head.next.child = null; + + head.next.next = new FlattenMultilevelLinkedList.Node(12); + head.next.next.child = new FlattenMultilevelLinkedList.Node(16); + + // Flatten the list + FlattenMultilevelLinkedList.Node flattenedHead = FlattenMultilevelLinkedList.flatten(head); + + // Expected sorted output + List expected = List.of(5, 7, 9, 10, 12, 16); + assertEquals(expected, toList(flattenedHead)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java index 99a890112d31..8d3150d18ed0 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeKSortedLinkedListTest.java @@ -90,4 +90,88 @@ private int[] getListValues(Node head) { } return Arrays.copyOf(values, i); // return only filled part } + + @Test + void testMergeWithNullListsInArray() { + Node list1 = new Node(1, new Node(3)); + Node list2 = null; + Node list3 = new Node(2, new Node(4)); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 2, 3, 4}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Should handle null lists mixed with valid lists"); + } + + @Test + void testMergeWithDuplicateValues() { + Node list1 = new Node(1, new Node(1, new Node(3))); + Node list2 = new Node(1, new Node(2, new Node(3))); + Node list3 = new Node(3, new Node(3)); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 1, 1, 2, 3, 3, 3, 3}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Should handle duplicate values correctly"); + } + + @Test + void testMergeWithZeroLength() { + Node[] lists = {}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, 0); + + assertNull(mergedHead, "Should return null for zero-length array"); + } + + @Test + void testMergeWithNegativeNumbers() { + Node list1 = new Node(-5, new Node(-1, new Node(3))); + Node list2 = new Node(-3, new Node(0, new Node(2))); + Node[] lists = {list1, list2}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {-5, -3, -1, 0, 2, 3}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Should handle negative numbers correctly"); + } + + @Test + void testMergeIdenticalLists() { + Node list1 = new Node(1, new Node(2, new Node(3))); + Node list2 = new Node(1, new Node(2, new Node(3))); + Node list3 = new Node(1, new Node(2, new Node(3))); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 1, 1, 2, 2, 2, 3, 3, 3}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Should merge identical lists correctly"); + } + + @Test + void testMergeAlreadySortedSequence() { + Node list1 = new Node(1, new Node(2)); + Node list2 = new Node(3, new Node(4)); + Node list3 = new Node(5, new Node(6)); + Node[] lists = {list1, list2, list3}; + + MergeKSortedLinkedList merger = new MergeKSortedLinkedList(); + Node mergedHead = merger.mergeKList(lists, lists.length); + + int[] expectedValues = {1, 2, 3, 4, 5, 6}; + int[] actualValues = getListValues(mergedHead); + assertArrayEquals(expectedValues, actualValues, "Should handle already sorted sequence"); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java b/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java index b2db478f692c..76b7ab063de4 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/ReverseKGroupTest.java @@ -20,8 +20,8 @@ public void testReverseKGroupWithEmptyList() { @Test public void testReverseKGroupWithSingleNodeList() { ReverseKGroup reverser = new ReverseKGroup(); - Node singleNode = new Node(5); - Node result = reverser.reverseKGroup(singleNode, 2); + SinglyLinkedListNode singleNode = new SinglyLinkedListNode(5); + SinglyLinkedListNode result = reverser.reverseKGroup(singleNode, 2); assertEquals(5, result.value); assertNull(result.next); } @@ -31,15 +31,15 @@ public void testReverseKGroupWithKEqualTo2() { ReverseKGroup reverser = new ReverseKGroup(); // Create a list with multiple elements (1 -> 2 -> 3 -> 4 -> 5) - Node head; - 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); + SinglyLinkedListNode head; + head = new SinglyLinkedListNode(1); + head.next = new SinglyLinkedListNode(2); + head.next.next = new SinglyLinkedListNode(3); + head.next.next.next = new SinglyLinkedListNode(4); + head.next.next.next.next = new SinglyLinkedListNode(5); // Test reverse with k=2 - Node result1 = reverser.reverseKGroup(head, 2); + SinglyLinkedListNode result1 = reverser.reverseKGroup(head, 2); assertEquals(2, result1.value); assertEquals(1, result1.next.value); assertEquals(4, result1.next.next.value); @@ -53,15 +53,15 @@ public void testReverseKGroupWithKEqualTo3() { ReverseKGroup reverser = new ReverseKGroup(); // Create a list with multiple elements (1 -> 2 -> 3 -> 4 -> 5) - Node head; - 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); + SinglyLinkedListNode head; + head = new SinglyLinkedListNode(1); + head.next = new SinglyLinkedListNode(2); + head.next.next = new SinglyLinkedListNode(3); + head.next.next.next = new SinglyLinkedListNode(4); + head.next.next.next.next = new SinglyLinkedListNode(5); // Test reverse with k=3 - Node result = reverser.reverseKGroup(head, 3); + SinglyLinkedListNode result = reverser.reverseKGroup(head, 3); assertEquals(3, result.value); assertEquals(2, result.next.value); assertEquals(1, result.next.next.value); diff --git a/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java b/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java index 70c0dfccafa4..c476ad1b0203 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/RotateSinglyLinkedListsTest.java @@ -14,24 +14,24 @@ public class RotateSinglyLinkedListsTest { private final RotateSinglyLinkedLists rotator = new RotateSinglyLinkedLists(); // Helper method to create a linked list from an array of values - private Node createLinkedList(int[] values) { + private SinglyLinkedListNode createLinkedList(int[] values) { if (values.length == 0) { return null; } - Node head = new Node(values[0]); - Node current = head; + SinglyLinkedListNode head = new SinglyLinkedListNode(values[0]); + SinglyLinkedListNode current = head; for (int i = 1; i < values.length; i++) { - current.next = new Node(values[i]); + current.next = new SinglyLinkedListNode(values[i]); current = current.next; } return head; } // Helper method to convert a linked list to a string for easy comparison - private String linkedListToString(Node head) { + private String linkedListToString(SinglyLinkedListNode head) { StringBuilder sb = new StringBuilder(); - Node current = head; + SinglyLinkedListNode current = head; while (current != null) { sb.append(current.value); if (current.next != null) { @@ -51,55 +51,55 @@ public void testRotateRightEmptyList() { @Test public void testRotateRightSingleNodeList() { // Rotate a list with a single element - Node singleNode = new Node(5); - Node rotatedSingleNode = rotator.rotateRight(singleNode, 3); + SinglyLinkedListNode singleNode = new SinglyLinkedListNode(5); + SinglyLinkedListNode rotatedSingleNode = rotator.rotateRight(singleNode, 3); assertEquals("5", linkedListToString(rotatedSingleNode)); } @Test public void testRotateRightMultipleElementsList() { // Rotate a list with multiple elements (rotate by 2) - Node head = createLinkedList(new int[] {1, 2, 3, 4, 5}); - Node rotated = rotator.rotateRight(head, 2); + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 2); assertEquals("4 -> 5 -> 1 -> 2 -> 3", linkedListToString(rotated)); } @Test public void testRotateRightFullRotation() { // Rotate by more than the length of the list - Node head = createLinkedList(new int[] {1, 2, 3, 4, 5}); - Node rotated = rotator.rotateRight(head, 7); + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 7); assertEquals("4 -> 5 -> 1 -> 2 -> 3", linkedListToString(rotated)); } @Test public void testRotateRightZeroRotation() { // Rotate a list by k = 0 (no rotation) - Node head = createLinkedList(new int[] {1, 2, 3, 4, 5}); - Node rotated = rotator.rotateRight(head, 0); + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 0); assertEquals("1 -> 2 -> 3 -> 4 -> 5", linkedListToString(rotated)); } @Test public void testRotateRightByListLength() { // Rotate a list by k equal to list length (no change) - Node head = createLinkedList(new int[] {1, 2, 3, 4, 5}); - Node rotated = rotator.rotateRight(head, 5); + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 5); assertEquals("1 -> 2 -> 3 -> 4 -> 5", linkedListToString(rotated)); } @Test public void testRotateRightByMultipleOfListLength() { - Node head = createLinkedList(new int[] {1, 2, 3, 4, 5}); - Node rotated = rotator.rotateRight(head, 10); // k = 2 * list length + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 10); // k = 2 * list length assertEquals("1 -> 2 -> 3 -> 4 -> 5", linkedListToString(rotated)); } @Test public void testRotateRightLongerList() { // Rotate a longer list by a smaller k - Node head = createLinkedList(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9}); - Node rotated = rotator.rotateRight(head, 4); + SinglyLinkedListNode head = createLinkedList(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9}); + SinglyLinkedListNode rotated = rotator.rotateRight(head, 4); assertEquals("6 -> 7 -> 8 -> 9 -> 1 -> 2 -> 3 -> 4 -> 5", linkedListToString(rotated)); } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java index a47434083cdb..f80c6b5055f0 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/SinglyLinkedListTest.java @@ -18,9 +18,9 @@ public class SinglyLinkedListTest { * @return linked list with pre-defined number of nodes */ private SinglyLinkedList createSampleList(int length) { - List nodeList = new ArrayList<>(); + List nodeList = new ArrayList<>(); for (int i = 1; i <= length; i++) { - Node node = new Node(i); + SinglyLinkedListNode node = new SinglyLinkedListNode(i); nodeList.add(node); } @@ -34,10 +34,10 @@ private SinglyLinkedList createSampleList(int length) { @Test void detectLoop() { // List has cycle - Node firstNode = new Node(1); - Node secondNode = new Node(2); - Node thirdNode = new Node(3); - Node fourthNode = new Node(4); + SinglyLinkedListNode firstNode = new SinglyLinkedListNode(1); + SinglyLinkedListNode secondNode = new SinglyLinkedListNode(2); + SinglyLinkedListNode thirdNode = new SinglyLinkedListNode(3); + SinglyLinkedListNode fourthNode = new SinglyLinkedListNode(4); firstNode.next = secondNode; secondNode.next = thirdNode; @@ -112,13 +112,13 @@ void reverseList() { // Reversing the LinkedList using reverseList() method and storing the head of the reversed // linkedlist in a head node The reversed linkedlist will be 4->3->2->1->null - Node head = list.reverseListIter(list.getHead()); + SinglyLinkedListNode head = list.reverseListIter(list.getHead()); // Recording the Nodes after reversing the LinkedList - Node firstNode = head; // 4 - Node secondNode = firstNode.next; // 3 - Node thirdNode = secondNode.next; // 2 - Node fourthNode = thirdNode.next; // 1 + SinglyLinkedListNode firstNode = head; // 4 + SinglyLinkedListNode secondNode = firstNode.next; // 3 + SinglyLinkedListNode thirdNode = secondNode.next; // 2 + SinglyLinkedListNode fourthNode = thirdNode.next; // 1 // Checking whether the LinkedList is reversed or not by comparing the original list and // reversed list nodes @@ -134,10 +134,10 @@ void reverseList() { void reverseListNullPointer() { // Creating a linkedlist with first node assigned to null SinglyLinkedList list = new SinglyLinkedList(); - Node first = list.getHead(); + SinglyLinkedListNode first = list.getHead(); // Reversing the linkedlist - Node head = list.reverseListIter(first); + SinglyLinkedListNode head = list.reverseListIter(first); // checking whether the method works fine if the input is null assertEquals(head, first); @@ -151,10 +151,10 @@ void reverseListTest() { // Reversing the LinkedList using reverseList() method and storing the head of the reversed // linkedlist in a head node - Node head = list.reverseListIter(list.getHead()); + SinglyLinkedListNode head = list.reverseListIter(list.getHead()); // Storing the head in a temp variable, so that we cannot loose the track of head - Node temp = head; + SinglyLinkedListNode temp = head; int i = 20; // This is for the comparison of values of nodes of the reversed linkedlist // Checking whether the reverseList() method performed its task @@ -171,7 +171,7 @@ void recursiveReverseList() { SinglyLinkedList list = createSampleList(5); // Reversing the linked list using reverseList() method - Node head = list.reverseListRec(list.getHead()); + SinglyLinkedListNode head = list.reverseListRec(list.getHead()); // Check if the reversed list is: 5 -> 4 -> 3 -> 2 -> 1 assertEquals(5, head.value); @@ -185,10 +185,10 @@ void recursiveReverseList() { void recursiveReverseListNullPointer() { // Create an empty linked list SinglyLinkedList list = new SinglyLinkedList(); - Node first = list.getHead(); + SinglyLinkedListNode first = list.getHead(); // Reversing the empty linked list - Node head = list.reverseListRec(first); + SinglyLinkedListNode head = list.reverseListRec(first); // Check if the head remains the same (null) assertNull(head); @@ -200,11 +200,11 @@ void recursiveReverseListTest() { SinglyLinkedList list = createSampleList(20); // Reversing the linked list using reverseList() method - Node head = list.reverseListRec(list.getHead()); + SinglyLinkedListNode head = list.reverseListRec(list.getHead()); // Check if the reversed list has the correct values int i = 20; - Node temp = head; + SinglyLinkedListNode temp = head; while (temp != null && i > 0) { assertEquals(i, temp.value); temp = temp.next; diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java index c572739ffbbf..16d1a015a4d9 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java @@ -1,110 +1,115 @@ package com.thealgorithms.datastructures.lists; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class SkipListTest { + private SkipList skipList; + + @BeforeEach + void setUp() { + skipList = new SkipList<>(); + } + @Test - void add() { - SkipList skipList = new SkipList<>(); + @DisplayName("Add element and verify size and retrieval") + void testAdd() { assertEquals(0, skipList.size()); skipList.add("value"); - print(skipList); assertEquals(1, skipList.size()); + assertEquals("value", skipList.get(0)); } @Test - void get() { - SkipList skipList = new SkipList<>(); + @DisplayName("Get retrieves correct element by index") + void testGet() { skipList.add("value"); - - String actualValue = skipList.get(0); - - print(skipList); - assertEquals("value", actualValue); + assertEquals("value", skipList.get(0)); } @Test - void contains() { - SkipList skipList = createSkipList(); - print(skipList); - - boolean contains = skipList.contains("b"); - - assertTrue(contains); + @DisplayName("Contains returns true if element exists") + void testContains() { + skipList = createSkipList(); + assertTrue(skipList.contains("b")); + assertTrue(skipList.contains("a")); + assertFalse(skipList.contains("z")); // negative test } @Test - void removeFromHead() { - SkipList skipList = createSkipList(); - String mostLeftElement = skipList.get(0); + @DisplayName("Remove element from head and check size and order") + void testRemoveFromHead() { + skipList = createSkipList(); + String first = skipList.get(0); int initialSize = skipList.size(); - print(skipList); - skipList.remove(mostLeftElement); + skipList.remove(first); - print(skipList); assertEquals(initialSize - 1, skipList.size()); + assertFalse(skipList.contains(first)); } @Test - void removeFromTail() { - SkipList skipList = createSkipList(); - String mostRightValue = skipList.get(skipList.size() - 1); + @DisplayName("Remove element from tail and check size and order") + void testRemoveFromTail() { + skipList = createSkipList(); + String last = skipList.get(skipList.size() - 1); int initialSize = skipList.size(); - print(skipList); - skipList.remove(mostRightValue); + skipList.remove(last); - print(skipList); assertEquals(initialSize - 1, skipList.size()); + assertFalse(skipList.contains(last)); } @Test - void checkSortedOnLowestLayer() { - SkipList skipList = new SkipList<>(); + @DisplayName("Elements should be sorted at base level") + void testSortedOrderOnBaseLevel() { String[] values = {"d", "b", "a", "c"}; Arrays.stream(values).forEach(skipList::add); - print(skipList); String[] actualOrder = IntStream.range(0, values.length).mapToObj(skipList::get).toArray(String[] ::new); - assertArrayEquals(new String[] {"a", "b", "c", "d"}, actualOrder); + org.junit.jupiter.api.Assertions.assertArrayEquals(new String[] {"a", "b", "c", "d"}, actualOrder); } - private SkipList createSkipList() { - SkipList skipList = new SkipList<>(); - String[] values = { - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - }; + @Test + @DisplayName("Duplicate elements can be added and count correctly") + void testAddDuplicates() { + skipList.add("x"); + skipList.add("x"); + assertEquals(2, skipList.size()); + assertEquals("x", skipList.get(0)); + assertEquals("x", skipList.get(1)); + } + + @Test + @DisplayName("Add multiple and remove all") + void testClearViaRemovals() { + String[] values = {"a", "b", "c"}; Arrays.stream(values).forEach(skipList::add); - return skipList; + + for (String v : values) { + skipList.remove(v); + } + + assertEquals(0, skipList.size()); } - /** - * Print Skip List representation to console. - * Optional method not involved in testing process. Used only for visualisation purposes. - * @param skipList to print - */ - private void print(SkipList skipList) { - System.out.println(skipList); + private SkipList createSkipList() { + SkipList s = new SkipList<>(); + String[] values = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"}; + Arrays.stream(values).forEach(s::add); + return s; } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java index 82e0853da374..71f932465eef 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/SortedLinkedListTest.java @@ -128,4 +128,81 @@ public void testIsEmptyAfterDeletion() { list.delete(10); assertTrue(list.isEmpty()); } + + @Test + public void testInsertNegativeNumbers() { + list.insert(-10); + list.insert(-5); + list.insert(-20); + assertEquals("[-20, -10, -5]", list.toString()); + } + + @Test + public void testInsertMixedPositiveAndNegativeNumbers() { + list.insert(0); + list.insert(-1); + list.insert(1); + assertEquals("[-1, 0, 1]", list.toString()); + } + + @Test + public void testMultipleDeletesUntilEmpty() { + list.insert(2); + list.insert(4); + list.insert(6); + assertTrue(list.delete(4)); + assertTrue(list.delete(2)); + assertTrue(list.delete(6)); + assertTrue(list.isEmpty()); + assertEquals("[]", list.toString()); + } + + @Test + public void testDeleteDuplicateValuesOnlyDeletesOneInstance() { + list.insert(5); + list.insert(5); + list.insert(5); + assertTrue(list.delete(5)); + assertEquals("[5, 5]", list.toString()); + assertTrue(list.delete(5)); + assertEquals("[5]", list.toString()); + assertTrue(list.delete(5)); + assertEquals("[]", list.toString()); + } + + @Test + public void testSearchOnListWithDuplicates() { + list.insert(7); + list.insert(7); + list.insert(7); + assertTrue(list.search(7)); + assertFalse(list.search(10)); + } + + @Test + public void testToStringOnEmptyList() { + assertEquals("[]", list.toString()); + } + + @Test + public void testDeleteAllDuplicates() { + list.insert(4); + list.insert(4); + list.insert(4); + assertTrue(list.delete(4)); + assertTrue(list.delete(4)); + assertTrue(list.delete(4)); + assertFalse(list.delete(4)); // nothing left to delete + assertEquals("[]", list.toString()); + } + + @Test + public void testInsertAfterDeletion() { + list.insert(1); + list.insert(3); + list.insert(5); + assertTrue(list.delete(3)); + list.insert(2); + assertEquals("[1, 2, 5]", list.toString()); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/TortoiseHareAlgoTest.java b/src/test/java/com/thealgorithms/datastructures/lists/TortoiseHareAlgoTest.java new file mode 100644 index 000000000000..2804534c988c --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/TortoiseHareAlgoTest.java @@ -0,0 +1,46 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +class TortoiseHareAlgoTest { + + @Test + void testAppendAndToString() { + TortoiseHareAlgo list = new TortoiseHareAlgo<>(); + list.append(10); + list.append(20); + list.append(30); + assertEquals("[10, 20, 30]", list.toString()); + } + + @Test + void testGetMiddleOdd() { + TortoiseHareAlgo list = new TortoiseHareAlgo<>(); + list.append(1); + list.append(2); + list.append(3); + list.append(4); + list.append(5); + assertEquals(3, list.getMiddle()); + } + + @Test + void testGetMiddleEven() { + TortoiseHareAlgo list = new TortoiseHareAlgo<>(); + list.append(1); + list.append(2); + list.append(3); + list.append(4); + assertEquals(3, list.getMiddle()); // returns second middle + } + + @Test + void testEmptyList() { + TortoiseHareAlgo list = new TortoiseHareAlgo<>(); + assertNull(list.getMiddle()); + assertEquals("[]", list.toString()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java b/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java index 1244a2e260d2..ada314383c74 100644 --- a/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java +++ b/src/test/java/com/thealgorithms/datastructures/queues/DequeTest.java @@ -87,4 +87,111 @@ void testToString() { deque.addFirst(5); assertEquals("Head -> 5 <-> 10 <-> 20 <- Tail", deque.toString()); } + + @Test + void testAlternatingAddRemove() { + Deque deque = new Deque<>(); + deque.addFirst(1); + deque.addLast(2); + deque.addFirst(0); + assertEquals(0, deque.pollFirst()); + assertEquals(2, deque.pollLast()); + assertEquals(1, deque.pollFirst()); + org.junit.jupiter.api.Assertions.assertTrue(deque.isEmpty()); + } + + @Test + void testSizeAfterOperations() { + Deque deque = new Deque<>(); + assertEquals(0, deque.size()); + deque.addFirst(1); + deque.addLast(2); + deque.addFirst(3); + assertEquals(3, deque.size()); + deque.pollFirst(); + deque.pollLast(); + assertEquals(1, deque.size()); + } + + @Test + void testNullValues() { + Deque deque = new Deque<>(); + deque.addFirst(null); + assertNull(deque.peekFirst()); + assertNull(deque.pollFirst()); + org.junit.jupiter.api.Assertions.assertTrue(deque.isEmpty()); + } + + @Test + void testMultipleAddFirst() { + Deque deque = new Deque<>(); + deque.addFirst(1); + deque.addFirst(2); + deque.addFirst(3); + + assertEquals(3, deque.peekFirst(), "First element should be the last added to front"); + assertEquals(1, deque.peekLast(), "Last element should be the first added to front"); + assertEquals(3, deque.size(), "Size should reflect all additions"); + } + + @Test + void testMultipleAddLast() { + Deque deque = new Deque<>(); + deque.addLast(1); + deque.addLast(2); + deque.addLast(3); + + assertEquals(1, deque.peekFirst(), "First element should be the first added to back"); + assertEquals(3, deque.peekLast(), "Last element should be the last added to back"); + assertEquals(3, deque.size(), "Size should reflect all additions"); + } + + @Test + void testSingleElementOperations() { + Deque deque = new Deque<>(); + deque.addFirst(1); + + assertEquals(1, deque.peekFirst(), "Single element should be both first and last"); + assertEquals(1, deque.peekLast(), "Single element should be both first and last"); + assertEquals(1, deque.size()); + + assertEquals(1, deque.pollFirst(), "Should be able to poll single element from front"); + org.junit.jupiter.api.Assertions.assertTrue(deque.isEmpty(), "Deque should be empty after polling single element"); + } + + @Test + void testSingleElementPollLast() { + Deque deque = new Deque<>(); + deque.addLast(1); + + assertEquals(1, deque.pollLast(), "Should be able to poll single element from back"); + org.junit.jupiter.api.Assertions.assertTrue(deque.isEmpty(), "Deque should be empty after polling single element"); + } + + @Test + void testMixedNullAndValues() { + Deque deque = new Deque<>(); + deque.addFirst("first"); + deque.addLast(null); + deque.addFirst(null); + deque.addLast("last"); + + assertEquals(4, deque.size(), "Should handle mixed null and non-null values"); + assertNull(deque.pollFirst(), "Should poll null from front"); + assertEquals("first", deque.pollFirst(), "Should poll non-null value"); + assertNull(deque.pollLast().equals("last") ? null : deque.peekLast(), "Should handle null correctly"); + } + + @Test + void testSymmetricOperations() { + Deque deque = new Deque<>(); + + // Test that addFirst/pollFirst and addLast/pollLast are symmetric + deque.addFirst(1); + deque.addLast(2); + + assertEquals(1, deque.pollFirst(), "addFirst/pollFirst should be symmetric"); + assertEquals(2, deque.pollLast(), "addLast/pollLast should be symmetric"); + org.junit.jupiter.api.Assertions.assertTrue(deque.isEmpty(), "Deque should be empty after symmetric operations"); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java b/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java index 1a3b5aadebb2..e97fe091c556 100644 --- a/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java +++ b/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java @@ -55,4 +55,60 @@ void testPQExtra() { Assertions.assertEquals(myQueue.peek(), 2); Assertions.assertEquals(myQueue.getSize(), 1); } + + @Test + void testInsertUntilFull() { + PriorityQueue pq = new PriorityQueue(3); + pq.insert(1); + pq.insert(4); + pq.insert(2); + Assertions.assertTrue(pq.isFull()); + Assertions.assertEquals(4, pq.peek()); + } + + @Test + void testRemoveFromEmpty() { + PriorityQueue pq = new PriorityQueue(3); + Assertions.assertThrows(RuntimeException.class, pq::remove); + } + + @Test + void testInsertDuplicateValues() { + PriorityQueue pq = new PriorityQueue(5); + pq.insert(5); + pq.insert(5); + pq.insert(3); + Assertions.assertEquals(5, pq.peek()); + pq.remove(); + Assertions.assertEquals(5, pq.peek()); + pq.remove(); + Assertions.assertEquals(3, pq.peek()); + } + + @Test + void testSizeAfterInsertAndRemove() { + PriorityQueue pq = new PriorityQueue(4); + Assertions.assertEquals(0, pq.getSize()); + pq.insert(2); + Assertions.assertEquals(1, pq.getSize()); + pq.insert(10); + Assertions.assertEquals(2, pq.getSize()); + pq.remove(); + Assertions.assertEquals(1, pq.getSize()); + pq.remove(); + Assertions.assertEquals(0, pq.getSize()); + } + + @Test + void testInsertAndRemoveAll() { + PriorityQueue pq = new PriorityQueue(3); + pq.insert(8); + pq.insert(1); + pq.insert(6); + Assertions.assertTrue(pq.isFull()); + pq.remove(); + pq.remove(); + pq.remove(); + Assertions.assertTrue(pq.isEmpty()); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java b/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java index 87f136a84631..491cb7634302 100644 --- a/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java +++ b/src/test/java/com/thealgorithms/datastructures/queues/QueueByTwoStacksTest.java @@ -33,19 +33,19 @@ public void testDequeue() { queue.put(10); queue.put(20); queue.put(30); - assertEquals(10, queue.get()); // First item out - assertEquals(20, queue.get()); // Second item out - assertEquals(30, queue.get()); // Third item out + assertEquals(10, queue.get()); + assertEquals(20, queue.get()); + assertEquals(30, queue.get()); } @Test public void testInterleavedOperations() { queue.put(10); queue.put(20); - assertEquals(10, queue.get()); // Dequeue first item + assertEquals(10, queue.get()); queue.put(30); - assertEquals(20, queue.get()); // Dequeue second item - assertEquals(30, queue.get()); // Dequeue third item + assertEquals(20, queue.get()); + assertEquals(30, queue.get()); } @Test @@ -62,8 +62,76 @@ public void testQueueSize() { @Test public void testEmptyQueueException() { - assertThrows(NoSuchElementException.class, () -> { - queue.get(); // Attempting to dequeue from empty queue - }); + assertThrows(NoSuchElementException.class, () -> queue.get()); + } + + @Test + public void testDequeueAllElements() { + for (int i = 1; i <= 5; i++) { + queue.put(i); + } + for (int i = 1; i <= 5; i++) { + assertEquals(i, queue.get()); + } + assertEquals(0, queue.size()); + } + + @Test + public void testLargeNumberOfOperations() { + int n = 1000; + for (int i = 0; i < n; i++) { + queue.put(i); + } + for (int i = 0; i < n; i++) { + assertEquals(i, queue.get()); + } + assertEquals(0, queue.size()); + } + + @Test + public void testRefillDuringDequeue() { + queue.put(1); + queue.put(2); + assertEquals(1, queue.get()); + queue.put(3); + queue.put(4); + assertEquals(2, queue.get()); + assertEquals(3, queue.get()); + assertEquals(4, queue.get()); + } + + @Test + public void testAlternatingPutAndGet() { + queue.put(1); + assertEquals(1, queue.get()); + queue.put(2); + queue.put(3); + assertEquals(2, queue.get()); + queue.put(4); + assertEquals(3, queue.get()); + assertEquals(4, queue.get()); + } + + @Test + public void testSizeStability() { + queue.put(100); + int size1 = queue.size(); + int size2 = queue.size(); + assertEquals(size1, size2); + } + + @Test + public void testMultipleEmptyDequeues() { + assertThrows(NoSuchElementException.class, () -> queue.get()); + assertThrows(NoSuchElementException.class, () -> queue.get()); + } + + @Test + public void testQueueWithStrings() { + QueueByTwoStacks stringQueue = new QueueByTwoStacks<>(); + stringQueue.put("a"); + stringQueue.put("b"); + assertEquals("a", stringQueue.get()); + assertEquals("b", stringQueue.get()); } } diff --git a/src/test/java/com/thealgorithms/datastructures/queues/QueueTest.java b/src/test/java/com/thealgorithms/datastructures/queues/QueueTest.java index 9a4f50d5e216..86ef940beab6 100644 --- a/src/test/java/com/thealgorithms/datastructures/queues/QueueTest.java +++ b/src/test/java/com/thealgorithms/datastructures/queues/QueueTest.java @@ -124,4 +124,204 @@ void testQueueCapacityException() { Assertions.assertThrows(IllegalArgumentException.class, () -> new Queue<>(0)); Assertions.assertThrows(IllegalArgumentException.class, () -> new Queue<>(-5)); } + + @Test + void testCircularBehavior() { + // Test that queue behaves correctly after multiple insert/remove cycles + queue.insert(1); + queue.insert(2); + queue.insert(3); + + // Remove all elements + queue.remove(); // removes 1 + queue.remove(); // removes 2 + queue.remove(); // removes 3 + + // Add elements again to test circular behavior + queue.insert(4); + queue.insert(5); + queue.insert(6); + + Assertions.assertEquals(4, queue.peekFront()); + Assertions.assertEquals(6, queue.peekRear()); + Assertions.assertEquals(3, queue.getSize()); + Assertions.assertTrue(queue.isFull()); + } + + @Test + void testMixedInsertRemoveOperations() { + // Test interleaved insert and remove operations + queue.insert(1); + queue.insert(2); + + Assertions.assertEquals(1, queue.remove()); + queue.insert(3); + queue.insert(4); + + Assertions.assertEquals(2, queue.remove()); + Assertions.assertEquals(3, queue.remove()); + + queue.insert(5); + queue.insert(6); + + Assertions.assertEquals(4, queue.peekFront()); + Assertions.assertEquals(6, queue.peekRear()); + Assertions.assertEquals(3, queue.getSize()); + } + + @Test + void testSingleElementOperations() { + // Test operations with single element + queue.insert(42); + + Assertions.assertEquals(42, queue.peekFront()); + Assertions.assertEquals(42, queue.peekRear()); + Assertions.assertEquals(1, queue.getSize()); + Assertions.assertFalse(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + + Assertions.assertEquals(42, queue.remove()); + Assertions.assertTrue(queue.isEmpty()); + Assertions.assertEquals(0, queue.getSize()); + } + + @Test + void testNullValueHandling() { + // Test queue with null values (if supported) + Queue stringQueue = new Queue<>(3); + + Assertions.assertTrue(stringQueue.insert(null)); + Assertions.assertTrue(stringQueue.insert("test")); + Assertions.assertTrue(stringQueue.insert(null)); + + Assertions.assertNull(stringQueue.peekFront()); + Assertions.assertNull(stringQueue.peekRear()); + Assertions.assertEquals(3, stringQueue.getSize()); + + Assertions.assertNull(stringQueue.remove()); + Assertions.assertEquals("test", stringQueue.peekFront()); + } + + @Test + void testStringDataType() { + // Test queue with String data type + Queue stringQueue = new Queue<>(2); + stringQueue.insert("first"); + stringQueue.insert("second"); + + Assertions.assertEquals("first", stringQueue.peekFront()); + Assertions.assertEquals("second", stringQueue.peekRear()); + } + + @Test + void testLargerCapacityQueue() { + // Test queue with larger capacity + Queue largeQueue = new Queue<>(10); + + // Fill the queue + for (int i = 1; i <= 10; i++) { + Assertions.assertTrue(largeQueue.insert(i)); + } + + Assertions.assertTrue(largeQueue.isFull()); + Assertions.assertFalse(largeQueue.insert(11)); + + // Remove half the elements + for (int i = 1; i <= 5; i++) { + Assertions.assertEquals(i, largeQueue.remove()); + } + + Assertions.assertEquals(6, largeQueue.peekFront()); + Assertions.assertEquals(10, largeQueue.peekRear()); + Assertions.assertEquals(5, largeQueue.getSize()); + } + + @Test + void testQueueCapacityOne() { + // Test queue with capacity of 1 + Queue singleQueue = new Queue<>(1); + + Assertions.assertTrue(singleQueue.isEmpty()); + Assertions.assertFalse(singleQueue.isFull()); + + Assertions.assertTrue(singleQueue.insert(100)); + Assertions.assertTrue(singleQueue.isFull()); + Assertions.assertFalse(singleQueue.isEmpty()); + Assertions.assertFalse(singleQueue.insert(200)); + + Assertions.assertEquals(100, singleQueue.peekFront()); + Assertions.assertEquals(100, singleQueue.peekRear()); + + Assertions.assertEquals(100, singleQueue.remove()); + Assertions.assertTrue(singleQueue.isEmpty()); + } + + @Test + void testQueueWraparoundIndexing() { + // Test that internal array indexing wraps around correctly + queue.insert(1); + queue.insert(2); + queue.insert(3); // Queue full + + // Remove one element + queue.remove(); // removes 1 + + // Add another element (should wrap around) + queue.insert(4); + + Assertions.assertEquals("[2, 3, 4]", queue.toString()); + Assertions.assertEquals(2, queue.peekFront()); + Assertions.assertEquals(4, queue.peekRear()); + + // Continue the pattern + queue.remove(); // removes 2 + queue.insert(5); + + Assertions.assertEquals(3, queue.peekFront()); + Assertions.assertEquals(5, queue.peekRear()); + } + + @Test + void testQueueStateAfterMultipleCycles() { + // Test queue state after multiple complete fill/empty cycles + for (int cycle = 0; cycle < 3; cycle++) { + // Fill the queue + for (int i = 1; i <= 3; i++) { + queue.insert(i + cycle * 10); + } + + // Verify state + Assertions.assertTrue(queue.isFull()); + Assertions.assertEquals(3, queue.getSize()); + + // Empty the queue + for (int i = 1; i <= 3; i++) { + queue.remove(); + } + + // Verify empty state + Assertions.assertTrue(queue.isEmpty()); + Assertions.assertEquals(0, queue.getSize()); + } + } + + @Test + void testQueueConsistencyAfterOperations() { + // Test that queue maintains consistency after various operations + queue.insert(10); + queue.insert(20); + + int firstRemoved = queue.remove(); + queue.insert(30); + queue.insert(40); + + int secondRemoved = queue.remove(); + queue.insert(50); + + // Verify the order is maintained + Assertions.assertEquals(10, firstRemoved); + Assertions.assertEquals(20, secondRemoved); + Assertions.assertEquals(30, queue.peekFront()); + Assertions.assertEquals(50, queue.peekRear()); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java index 7ac0d8bc324b..8a89382211ba 100644 --- a/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java +++ b/src/test/java/com/thealgorithms/datastructures/stacks/NodeStackTest.java @@ -1,15 +1,26 @@ package com.thealgorithms.datastructures.stacks; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class NodeStackTest { + private NodeStack intStack; + private NodeStack stringStack; + + @BeforeEach + void setUp() { + intStack = new NodeStack<>(); + stringStack = new NodeStack<>(); + } + @Test + @DisplayName("Test push operation") void testPush() { NodeStack stack = new NodeStack<>(); stack.push(10); @@ -18,6 +29,7 @@ void testPush() { } @Test + @DisplayName("Test pop operation") void testPop() { NodeStack stack = new NodeStack<>(); stack.push("First"); @@ -27,12 +39,14 @@ void testPop() { } @Test + @DisplayName("Test pop on empty stack throws exception") void testPopOnEmptyStack() { NodeStack stack = new NodeStack<>(); assertThrows(IllegalStateException.class, stack::pop, "Popping an empty stack should throw IllegalStateException."); } @Test + @DisplayName("Test peek operation") void testPeek() { NodeStack stack = new NodeStack<>(); stack.push(5); @@ -43,22 +57,25 @@ void testPeek() { } @Test + @DisplayName("Test peek on empty stack throws exception") void testPeekOnEmptyStack() { NodeStack stack = new NodeStack<>(); assertThrows(IllegalStateException.class, stack::peek, "Peeking an empty stack should throw IllegalStateException."); } @Test + @DisplayName("Test isEmpty method") void testIsEmpty() { NodeStack stack = new NodeStack<>(); assertTrue(stack.isEmpty(), "Newly initialized stack should be empty."); stack.push('A'); - assertFalse(stack.isEmpty(), "Stack should not be empty after a push operation."); + org.junit.jupiter.api.Assertions.assertFalse(stack.isEmpty(), "Stack should not be empty after a push operation."); stack.pop(); assertTrue(stack.isEmpty(), "Stack should be empty after popping the only element."); } @Test + @DisplayName("Test size method") void testSize() { NodeStack stack = new NodeStack<>(); assertEquals(0, stack.size(), "Size of empty stack should be 0."); @@ -70,4 +87,164 @@ void testSize() { stack.pop(); assertEquals(0, stack.size(), "Size should be 0 after popping all elements."); } + + @Test + @DisplayName("Test push and pop with null values") + void testPushPopWithNull() { + stringStack.push(null); + stringStack.push("not null"); + stringStack.push(null); + + assertEquals(3, stringStack.size(), "Stack should contain 3 elements including nulls"); + org.junit.jupiter.api.Assertions.assertNull(stringStack.pop(), "Should pop null value"); + assertEquals("not null", stringStack.pop(), "Should pop 'not null' value"); + org.junit.jupiter.api.Assertions.assertNull(stringStack.pop(), "Should pop null value"); + assertTrue(stringStack.isEmpty(), "Stack should be empty after popping all elements"); + } + + @Test + @DisplayName("Test LIFO (Last In First Out) behavior") + void testLifoBehavior() { + int[] values = {1, 2, 3, 4, 5}; + + // Push values in order + for (int value : values) { + intStack.push(value); + } + + // Pop values should be in reverse order + for (int i = values.length - 1; i >= 0; i--) { + assertEquals(values[i], intStack.pop(), "Elements should be popped in LIFO order"); + } + } + + @Test + @DisplayName("Test peek doesn't modify stack") + void testPeekDoesNotModifyStack() { + intStack.push(1); + intStack.push(2); + intStack.push(3); + + int originalSize = intStack.size(); + int peekedValue = intStack.peek(); + + assertEquals(3, peekedValue, "Peek should return top element"); + assertEquals(originalSize, intStack.size(), "Peek should not change stack size"); + assertEquals(3, intStack.peek(), "Multiple peeks should return same value"); + org.junit.jupiter.api.Assertions.assertFalse(intStack.isEmpty(), "Peek should not make stack empty"); + } + + @Test + @DisplayName("Test mixed push and pop operations") + void testMixedOperations() { + // Test interleaved push/pop operations + intStack.push(1); + assertEquals(1, intStack.pop()); + assertTrue(intStack.isEmpty()); + + intStack.push(2); + intStack.push(3); + assertEquals(3, intStack.pop()); + intStack.push(4); + assertEquals(4, intStack.peek()); + assertEquals(2, intStack.size()); + + assertEquals(4, intStack.pop()); + assertEquals(2, intStack.pop()); + assertTrue(intStack.isEmpty()); + } + + @Test + @DisplayName("Test stack with duplicate values") + void testStackWithDuplicates() { + intStack.push(1); + intStack.push(1); + intStack.push(1); + + assertEquals(3, intStack.size(), "Stack should handle duplicate values"); + assertEquals(1, intStack.peek(), "Peek should return duplicate value"); + + assertEquals(1, intStack.pop(), "Should pop first duplicate"); + assertEquals(1, intStack.pop(), "Should pop second duplicate"); + assertEquals(1, intStack.pop(), "Should pop third duplicate"); + assertTrue(intStack.isEmpty(), "Stack should be empty after popping all duplicates"); + } + + @Test + @DisplayName("Test stack with different data types") + void testDifferentDataTypes() { + NodeStack charStack = new NodeStack<>(); + NodeStack booleanStack = new NodeStack<>(); + + // Test with Character + charStack.push('A'); + charStack.push('Z'); + assertEquals('Z', charStack.peek(), "Should handle Character values"); + + // Test with Boolean + booleanStack.push(Boolean.TRUE); + booleanStack.push(Boolean.FALSE); + assertEquals(Boolean.FALSE, booleanStack.peek(), "Should handle Boolean values"); + } + + @Test + @DisplayName("Test stack state consistency after exceptions") + void testStateConsistencyAfterExceptions() { + // Stack should remain consistent after exception-throwing operations + intStack.push(1); + intStack.push(2); + + // Try to peek and pop normally first + assertEquals(2, intStack.peek()); + assertEquals(2, intStack.pop()); + assertEquals(1, intStack.size()); + + // Pop remaining element + assertEquals(1, intStack.pop()); + assertTrue(intStack.isEmpty()); + + // Now stack is empty, operations should throw exceptions + assertThrows(IllegalStateException.class, intStack::peek); + assertThrows(IllegalStateException.class, intStack::pop); + + // Stack should still be in valid empty state + assertTrue(intStack.isEmpty()); + assertEquals(0, intStack.size()); + + // Should be able to push after exceptions + intStack.push(3); + org.junit.jupiter.api.Assertions.assertFalse(intStack.isEmpty()); + assertEquals(1, intStack.size()); + assertEquals(3, intStack.peek()); + } + + @Test + @DisplayName("Test single element stack operations") + void testSingleElementStack() { + intStack.push(2); + + org.junit.jupiter.api.Assertions.assertFalse(intStack.isEmpty(), "Stack with one element should not be empty"); + assertEquals(1, intStack.size(), "Size should be 1"); + assertEquals(2, intStack.peek(), "Peek should return the single element"); + assertEquals(1, intStack.size(), "Peek should not change size"); + + assertEquals(2, intStack.pop(), "Pop should return the single element"); + assertTrue(intStack.isEmpty(), "Stack should be empty after popping single element"); + assertEquals(0, intStack.size(), "Size should be 0 after popping single element"); + } + + @Test + @DisplayName("Test toString method if implemented") + void testToString() { + // This test assumes NodeStack has a toString method + // If not implemented, this test can be removed or NodeStack can be enhanced + intStack.push(1); + intStack.push(2); + intStack.push(3); + + String stackString = intStack.toString(); + // Basic check that toString doesn't throw exception and returns something + assertTrue(stackString != null, "toString should not return null"); + assertTrue(stackString.length() > 0, "toString should return non-empty string"); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java index 2e2bc5adae3a..a4e781c84127 100644 --- a/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java +++ b/src/test/java/com/thealgorithms/datastructures/stacks/ReverseStackTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.datastructures.stacks; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Stack; @@ -8,6 +9,11 @@ class ReverseStackTest { + @Test + void testReverseNullStack() { + assertThrows(IllegalArgumentException.class, () -> ReverseStack.reverseStack(null), "Reversing a null stack should throw an IllegalArgumentException."); + } + @Test void testReverseEmptyStack() { Stack stack = new Stack<>(); diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java index 74de7ad6435a..392cdf2329fc 100644 --- a/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/stacks/StackArrayTest.java @@ -126,4 +126,62 @@ void testToString() { stack.push(3); Assertions.assertEquals("StackArray [1, 2, 3]", stack.toString()); } + + @Test + void testSingleElementOperations() { + // Test operations with a single element + stack.push(2); + Assertions.assertEquals(1, stack.size()); + Assertions.assertFalse(stack.isEmpty()); + Assertions.assertEquals(2, stack.peek()); + Assertions.assertEquals(2, stack.pop()); + Assertions.assertTrue(stack.isEmpty()); + } + + @Test + void testAlternatingPushPop() { + // Test alternating push and pop operations + stack.push(1); + Assertions.assertEquals(1, stack.pop()); + + stack.push(2); + stack.push(3); + Assertions.assertEquals(3, stack.pop()); + + stack.push(4); + Assertions.assertEquals(4, stack.pop()); + Assertions.assertEquals(2, stack.pop()); + Assertions.assertTrue(stack.isEmpty()); + } + + @Test + void testPushNullElements() { + // Test pushing null values + stack.push(null); + Assertions.assertEquals(1, stack.size()); + Assertions.assertNull(stack.peek()); + Assertions.assertNull(stack.pop()); + + // Mix null and non-null values + stack.push(1); + stack.push(null); + stack.push(2); + + Assertions.assertEquals(2, stack.pop()); + Assertions.assertNull(stack.pop()); + Assertions.assertEquals(1, stack.pop()); + } + + @Test + void testWithDifferentDataTypes() { + // Test with String type + StackArray stringStack = new StackArray<>(3); + stringStack.push("first"); + stringStack.push("second"); + stringStack.push("third"); + + Assertions.assertEquals("third", stringStack.pop()); + Assertions.assertEquals("second", stringStack.peek()); + Assertions.assertEquals(2, stringStack.size()); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java index 58af66bc38f4..2dfe4c242e1c 100644 --- a/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/stacks/StackOfLinkedListTest.java @@ -118,4 +118,104 @@ public void testSequentialPushAndPop() { } assertTrue(stack.isEmpty(), "Stack should be empty after popping all elements"); } + + @Test + public void testPushZeroAndNegativeValues() { + stack.push(0); + stack.push(-1); + stack.push(-1); + + assertEquals(-1, stack.pop(), "Should handle negative values correctly"); + assertEquals(-1, stack.pop(), "Should handle negative values correctly"); + assertEquals(0, stack.pop(), "Should handle zero value correctly"); + } + + @Test + public void testPushDuplicateValues() { + stack.push(1); + stack.push(1); + stack.push(1); + + assertEquals(3, stack.getSize(), "Should allow duplicate values"); + assertEquals(1, stack.pop()); + assertEquals(1, stack.pop()); + assertEquals(1, stack.pop()); + } + + @Test + public void testPushAfterEmptyingStack() { + stack.push(1); + stack.push(2); + stack.pop(); + stack.pop(); + + assertTrue(stack.isEmpty(), "Stack should be empty"); + + stack.push(10); + assertEquals(10, stack.peek(), "Should work correctly after emptying and refilling"); + assertEquals(1, stack.getSize(), "Size should be correct after refilling"); + } + + @Test + public void testPeekDoesNotModifyStack() { + stack.push(1); + + int firstPeek = stack.peek(); + int secondPeek = stack.peek(); + int thirdPeek = stack.peek(); + + assertEquals(firstPeek, secondPeek, "Multiple peeks should return same value"); + assertEquals(secondPeek, thirdPeek, "Multiple peeks should return same value"); + assertEquals(1, stack.getSize(), "Peek should not modify stack size"); + assertEquals(1, stack.pop(), "Element should still be poppable after peeking"); + } + + @Test + public void testAlternatingPushAndPop() { + stack.push(1); + assertEquals(1, stack.pop()); + + stack.push(2); + stack.push(3); + assertEquals(3, stack.pop()); + + stack.push(4); + assertEquals(4, stack.pop()); + assertEquals(2, stack.pop()); + + assertTrue(stack.isEmpty(), "Stack should be empty after alternating operations"); + } + + @Test + public void testToStringWithSingleElement() { + stack.push(42); + assertEquals("42", stack.toString(), "String representation with single element should not have arrows"); + } + + @Test + public void testStackIntegrity() { + // Test that internal state remains consistent + for (int i = 0; i < 10; i++) { + stack.push(i); + assertEquals(i + 1, stack.getSize(), "Size should be consistent during pushes"); + assertEquals(i, stack.peek(), "Peek should return last pushed value"); + } + + for (int i = 9; i >= 0; i--) { + assertEquals(i, stack.peek(), "Peek should return correct value before pop"); + assertEquals(i, stack.pop(), "Pop should return values in LIFO order"); + assertEquals(i, stack.getSize(), "Size should be consistent during pops"); + } + } + + @Test + public void testMixedDataTypes() { + // If your stack supports Object types, test with different data types + + stack.push(1); + stack.push(2); + + assertEquals(Integer.valueOf(2), stack.pop()); + assertEquals(Integer.valueOf(1), stack.pop()); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/trees/BSTRecursiveGenericTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BSTRecursiveGenericTest.java new file mode 100644 index 000000000000..5ec0080c5fb8 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/BSTRecursiveGenericTest.java @@ -0,0 +1,169 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for BSTRecursiveGeneric class. + * Covers insertion, deletion, search, traversal, sorting, and display. + * + * Author: Udaya Krishnan M + * GitHub: https://github.com/UdayaKrishnanM/ + */ +class BSTRecursiveGenericTest { + + private BSTRecursiveGeneric intTree; + private BSTRecursiveGeneric stringTree; + + /** + * Initializes test trees before each test. + */ + @BeforeEach + void setUp() { + intTree = new BSTRecursiveGeneric<>(); + stringTree = new BSTRecursiveGeneric<>(); + } + + /** + * Tests insertion and search of integer values. + */ + @Test + void testAddAndFindInteger() { + intTree.add(10); + intTree.add(5); + intTree.add(15); + assertTrue(intTree.find(10)); + assertTrue(intTree.find(5)); + assertTrue(intTree.find(15)); + assertFalse(intTree.find(20)); + } + + /** + * Tests insertion and search of string values. + */ + @Test + void testAddAndFindString() { + stringTree.add("apple"); + stringTree.add("banana"); + stringTree.add("cherry"); + assertTrue(stringTree.find("banana")); + assertFalse(stringTree.find("date")); + } + + /** + * Tests deletion of existing and non-existing elements. + */ + @Test + void testRemoveElements() { + intTree.add(10); + intTree.add(5); + intTree.add(15); + assertTrue(intTree.find(5)); + intTree.remove(5); + assertFalse(intTree.find(5)); + intTree.remove(100); // non-existent + assertFalse(intTree.find(100)); + } + + /** + * Tests inorder traversal output. + */ + @Test + void testInorderTraversal() { + intTree.add(20); + intTree.add(10); + intTree.add(30); + intTree.inorder(); // visually verify output + assertTrue(true); + } + + /** + * Tests preorder traversal output. + */ + @Test + void testPreorderTraversal() { + intTree.add(20); + intTree.add(10); + intTree.add(30); + intTree.preorder(); // visually verify output + assertTrue(true); + } + + /** + * Tests postorder traversal output. + */ + @Test + void testPostorderTraversal() { + intTree.add(20); + intTree.add(10); + intTree.add(30); + intTree.postorder(); // visually verify output + assertTrue(true); + } + + /** + * Tests inorderSort returns sorted list. + */ + @Test + void testInorderSort() { + intTree.add(30); + intTree.add(10); + intTree.add(20); + List sorted = intTree.inorderSort(); + assertEquals(List.of(10, 20, 30), sorted); + } + + /** + * Tests prettyDisplay method for visual tree structure. + */ + @Test + void testPrettyDisplay() { + intTree.add(50); + intTree.add(30); + intTree.add(70); + intTree.add(20); + intTree.add(40); + intTree.add(60); + intTree.add(80); + intTree.prettyDisplay(); // visually verify output + assertTrue(true); + } + + /** + * Tests edge case: empty tree. + */ + @Test + void testEmptyTree() { + assertFalse(intTree.find(1)); + List sorted = intTree.inorderSort(); + assertTrue(sorted.isEmpty()); + } + + /** + * Tests edge case: single node tree. + */ + @Test + void testSingleNodeTree() { + intTree.add(42); + assertTrue(intTree.find(42)); + intTree.remove(42); + assertFalse(intTree.find(42)); + } + + /** + * Tests duplicate insertions. + */ + @Test + void testDuplicateInsertions() { + intTree.add(10); + intTree.add(10); + intTree.add(10); + List sorted = intTree.inorderSort(); + assertEquals(List.of(10), sorted); // assuming duplicates are ignored + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/BTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BTreeTest.java new file mode 100644 index 000000000000..da29e117f25d --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/BTreeTest.java @@ -0,0 +1,94 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class BTreeTest { + + @Test + public void testInsertSearchDelete() { + BTree bTree = new BTree(3); // Minimum degree t = 3 + + int[] values = {10, 20, 5, 6, 12, 30, 7, 17}; + for (int val : values) { + bTree.insert(val); + } + + for (int val : values) { + assertTrue(bTree.search(val), "Should find inserted value: " + val); + } + + ArrayList traversal = new ArrayList<>(); + bTree.traverse(traversal); + assertEquals(Arrays.asList(5, 6, 7, 10, 12, 17, 20, 30), traversal); + + bTree.delete(6); + assertFalse(bTree.search(6)); + traversal.clear(); + bTree.traverse(traversal); + assertEquals(Arrays.asList(5, 7, 10, 12, 17, 20, 30), traversal); + } + + @Test + public void testEmptyTreeSearch() { + BTree bTree = new BTree(3); + assertFalse(bTree.search(42), "Search in empty tree should return false."); + } + + @Test + public void testDuplicateInsertions() { + BTree bTree = new BTree(3); + bTree.insert(15); + bTree.insert(15); // Attempt duplicate + bTree.insert(15); // Another duplicate + + ArrayList traversal = new ArrayList<>(); + bTree.traverse(traversal); + + // Should contain only one 15 + long count = traversal.stream().filter(x -> x == 15).count(); + assertEquals(1, count, "Duplicate keys should not be inserted."); + } + + @Test + public void testDeleteNonExistentKey() { + BTree bTree = new BTree(3); + bTree.insert(10); + bTree.insert(20); + bTree.delete(99); // Doesn't exist + assertTrue(bTree.search(10)); + assertTrue(bTree.search(20)); + } + + @Test + public void testComplexInsertDelete() { + BTree bTree = new BTree(2); // Smaller degree to trigger splits more easily + int[] values = {1, 3, 7, 10, 11, 13, 14, 15, 18, 16, 19, 24, 25, 26, 21, 4, 5, 20, 22, 2, 17, 12, 6}; + + for (int val : values) { + bTree.insert(val); + } + + for (int val : values) { + assertTrue(bTree.search(val)); + } + + int[] toDelete = {6, 13, 7, 4, 2, 16}; + for (int val : toDelete) { + bTree.delete(val); + assertFalse(bTree.search(val)); + } + + ArrayList remaining = new ArrayList<>(); + bTree.traverse(remaining); + + for (int val : toDelete) { + assertFalse(remaining.contains(val)); + } + } +} diff --git a/src/test/java/com/thealgorithms/devutils/entities/ProcessDetailsTest.java b/src/test/java/com/thealgorithms/devutils/entities/ProcessDetailsTest.java new file mode 100644 index 000000000000..b5c96c601e4f --- /dev/null +++ b/src/test/java/com/thealgorithms/devutils/entities/ProcessDetailsTest.java @@ -0,0 +1,288 @@ +package com.thealgorithms.devutils.entities; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Test class for ProcessDetails + * Tests the ProcessDetails entity used in scheduling algorithms + * + * @author Sourav Saha (yashsaha555) + */ +class ProcessDetailsTest { + + private ProcessDetails processWithPriority; + private ProcessDetails processWithoutPriority; + + @BeforeEach + void setUp() { + // Initialize test objects before each test + processWithPriority = new ProcessDetails("P1", 0, 10, 5); + processWithoutPriority = new ProcessDetails("P2", 2, 8); + } + + @Test + void testConstructorWithPriority() { + // Test constructor with priority parameter + ProcessDetails process = new ProcessDetails("P3", 1, 15, 3); + + assertEquals("P3", process.getProcessId()); + assertEquals(1, process.getArrivalTime()); + assertEquals(15, process.getBurstTime()); + assertEquals(3, process.getPriority()); + assertEquals(0, process.getWaitingTime()); // Default value + assertEquals(0, process.getTurnAroundTimeTime()); // Default value + } + + @Test + void testConstructorWithoutPriority() { + // Test constructor without priority parameter + ProcessDetails process = new ProcessDetails("P4", 3, 12); + + assertEquals("P4", process.getProcessId()); + assertEquals(3, process.getArrivalTime()); + assertEquals(12, process.getBurstTime()); + assertEquals(0, process.getPriority()); // Default value + assertEquals(0, process.getWaitingTime()); // Default value + assertEquals(0, process.getTurnAroundTimeTime()); // Default value + } + + @Test + void testGetProcessId() { + assertEquals("P1", processWithPriority.getProcessId()); + assertEquals("P2", processWithoutPriority.getProcessId()); + } + + @Test + void testGetArrivalTime() { + assertEquals(0, processWithPriority.getArrivalTime()); + assertEquals(2, processWithoutPriority.getArrivalTime()); + } + + @Test + void testGetBurstTime() { + assertEquals(10, processWithPriority.getBurstTime()); + assertEquals(8, processWithoutPriority.getBurstTime()); + } + + @Test + void testGetWaitingTime() { + // Initial waiting time should be 0 + assertEquals(0, processWithPriority.getWaitingTime()); + assertEquals(0, processWithoutPriority.getWaitingTime()); + } + + @Test + void testGetTurnAroundTimeTime() { + // Initial turnaround time should be 0 + assertEquals(0, processWithPriority.getTurnAroundTimeTime()); + assertEquals(0, processWithoutPriority.getTurnAroundTimeTime()); + } + + @Test + void testGetPriority() { + assertEquals(5, processWithPriority.getPriority()); + assertEquals(0, processWithoutPriority.getPriority()); // Default for constructor without priority + } + + @Test + void testSetProcessId() { + processWithPriority.setProcessId("NewP1"); + assertEquals("NewP1", processWithPriority.getProcessId()); + + // Test setting null process ID + processWithPriority.setProcessId(null); + assertNull(processWithPriority.getProcessId()); + + // Test setting empty process ID + processWithPriority.setProcessId(""); + assertEquals("", processWithPriority.getProcessId()); + } + + @Test + void testSetArrivalTime() { + processWithPriority.setArrivalTime(5); + assertEquals(5, processWithPriority.getArrivalTime()); + + // Test setting negative arrival time + processWithPriority.setArrivalTime(-1); + assertEquals(-1, processWithPriority.getArrivalTime()); + + // Test setting zero arrival time + processWithPriority.setArrivalTime(0); + assertEquals(0, processWithPriority.getArrivalTime()); + } + + @Test + void testSetBurstTime() { + processWithPriority.setBurstTime(20); + assertEquals(20, processWithPriority.getBurstTime()); + + // Test setting zero burst time + processWithPriority.setBurstTime(0); + assertEquals(0, processWithPriority.getBurstTime()); + + // Test setting very large burst time + processWithPriority.setBurstTime(Integer.MAX_VALUE); + assertEquals(Integer.MAX_VALUE, processWithPriority.getBurstTime()); + } + + @Test + void testSetWaitingTime() { + processWithPriority.setWaitingTime(15); + assertEquals(15, processWithPriority.getWaitingTime()); + + // Test setting negative waiting time + processWithPriority.setWaitingTime(-5); + assertEquals(-5, processWithPriority.getWaitingTime()); + + // Test setting zero waiting time + processWithPriority.setWaitingTime(0); + assertEquals(0, processWithPriority.getWaitingTime()); + } + + @Test + void testSetTurnAroundTimeTime() { + processWithPriority.setTurnAroundTimeTime(25); + assertEquals(25, processWithPriority.getTurnAroundTimeTime()); + + // Test setting negative turnaround time + processWithPriority.setTurnAroundTimeTime(-10); + assertEquals(-10, processWithPriority.getTurnAroundTimeTime()); + + // Test setting zero turnaround time + processWithPriority.setTurnAroundTimeTime(0); + assertEquals(0, processWithPriority.getTurnAroundTimeTime()); + } + + @Test + void testCompleteProcessLifecycle() { + // Test a complete process lifecycle with realistic scheduling values + ProcessDetails process = new ProcessDetails("P5", 0, 10, 2); + + // Simulate process execution + process.setWaitingTime(5); // Process waited 5 time units + process.setTurnAroundTimeTime(15); // Total time from arrival to completion + + assertEquals("P5", process.getProcessId()); + assertEquals(0, process.getArrivalTime()); + assertEquals(10, process.getBurstTime()); + assertEquals(5, process.getWaitingTime()); + assertEquals(15, process.getTurnAroundTimeTime()); + assertEquals(2, process.getPriority()); + } + + @Test + void testProcessWithMinimumValues() { + // Test process with minimum possible values + ProcessDetails process = new ProcessDetails("", 0, 1, 0); + + assertEquals("", process.getProcessId()); + assertEquals(0, process.getArrivalTime()); + assertEquals(1, process.getBurstTime()); + assertEquals(0, process.getPriority()); + } + + @Test + void testProcessWithMaximumValues() { + // Test process with large values + ProcessDetails process = new ProcessDetails("LongProcessName", Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + + assertEquals("LongProcessName", process.getProcessId()); + assertEquals(Integer.MAX_VALUE, process.getArrivalTime()); + assertEquals(Integer.MAX_VALUE, process.getBurstTime()); + assertEquals(Integer.MAX_VALUE, process.getPriority()); + } + + @Test + void testProcessModificationAfterCreation() { + // Test that all fields can be modified after object creation + ProcessDetails process = new ProcessDetails("Original", 1, 5, 3); + + // Modify all fields + process.setProcessId("Modified"); + process.setArrivalTime(10); + process.setBurstTime(20); + process.setWaitingTime(8); + process.setTurnAroundTimeTime(28); + + // Verify all modifications + assertEquals("Modified", process.getProcessId()); + assertEquals(10, process.getArrivalTime()); + assertEquals(20, process.getBurstTime()); + assertEquals(8, process.getWaitingTime()); + assertEquals(28, process.getTurnAroundTimeTime()); + assertEquals(3, process.getPriority()); // Priority has no setter, should remain unchanged + } + + @Test + void testMultipleProcessesIndependence() { + // Test that multiple ProcessDetails objects are independent + ProcessDetails process1 = new ProcessDetails("P1", 0, 5, 1); + ProcessDetails process2 = new ProcessDetails("P2", 2, 8, 2); + + // Modify first process + process1.setWaitingTime(10); + process1.setTurnAroundTimeTime(15); + + // Verify first process was modified correctly + assertEquals("P1", process1.getProcessId()); + assertEquals(0, process1.getArrivalTime()); + assertEquals(5, process1.getBurstTime()); + assertEquals(1, process1.getPriority()); + assertEquals(10, process1.getWaitingTime()); + assertEquals(15, process1.getTurnAroundTimeTime()); + + // Verify second process is unchanged + assertEquals("P2", process2.getProcessId()); + assertEquals(2, process2.getArrivalTime()); + assertEquals(8, process2.getBurstTime()); + assertEquals(2, process2.getPriority()); + assertEquals(0, process2.getWaitingTime()); + assertEquals(0, process2.getTurnAroundTimeTime()); + } + + @Test + void testConstructorParameterOrder() { + // Test that constructor parameters are assigned to correct fields + ProcessDetails process = new ProcessDetails("TestProcess", 123, 456, 789); + + assertEquals("TestProcess", process.getProcessId()); + assertEquals(123, process.getArrivalTime()); + assertEquals(456, process.getBurstTime()); + assertEquals(789, process.getPriority()); + } + + @Test + void testTypicalSchedulingScenario() { + // Test a typical scheduling scenario with multiple processes + ProcessDetails[] processes = {new ProcessDetails("P1", 0, 8, 3), new ProcessDetails("P2", 1, 4, 1), new ProcessDetails("P3", 2, 9, 4), new ProcessDetails("P4", 3, 5, 2)}; + + // Simulate FCFS scheduling calculations + int currentTime = 0; + for (ProcessDetails process : processes) { + if (currentTime < process.getArrivalTime()) { + currentTime = process.getArrivalTime(); + } + process.setWaitingTime(currentTime - process.getArrivalTime()); + currentTime += process.getBurstTime(); + process.setTurnAroundTimeTime(process.getWaitingTime() + process.getBurstTime()); + } + + // Verify calculations + assertEquals(0, processes[0].getWaitingTime()); // P1: arrives at 0, starts immediately + assertEquals(8, processes[0].getTurnAroundTimeTime()); // P1: 0 + 8 + + assertEquals(7, processes[1].getWaitingTime()); // P2: arrives at 1, starts at 8 + assertEquals(11, processes[1].getTurnAroundTimeTime()); // P2: 7 + 4 + + assertEquals(10, processes[2].getWaitingTime()); // P3: arrives at 2, starts at 12 + assertEquals(19, processes[2].getTurnAroundTimeTime()); // P3: 10 + 9 + + assertEquals(18, processes[3].getWaitingTime()); // P4: arrives at 3, starts at 21 + assertEquals(23, processes[3].getTurnAroundTimeTime()); // P4: 18 + 5 + } +} diff --git a/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java b/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java index d12614d6fd06..f8356a87eb31 100644 --- a/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java +++ b/src/test/java/com/thealgorithms/divideandconquer/CountingInversionsTest.java @@ -29,4 +29,35 @@ public void testAllInversions() { int[] arr = {5, 4, 3, 2, 1}; assertEquals(10, CountingInversions.countInversions(arr)); } + + @Test + public void testEmptyArray() { + int[] arr = {}; + assertEquals(0, CountingInversions.countInversions(arr)); + } + + @Test + public void testArrayWithDuplicates() { + int[] arr = {1, 3, 2, 3, 1}; + // Inversions: (3,2), (3,1), (3,1), (2,1) + assertEquals(4, CountingInversions.countInversions(arr)); + } + + @Test + public void testLargeArray() { + int n = 1000; + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = n - i; // descending order -> max inversions = n*(n-1)/2 + } + int expected = n * (n - 1) / 2; + assertEquals(expected, CountingInversions.countInversions(arr)); + } + + @Test + public void testArrayWithAllSameElements() { + int[] arr = {7, 7, 7, 7}; + // No inversions since all elements are equal + assertEquals(0, CountingInversions.countInversions(arr)); + } } diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistanceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistanceTest.java new file mode 100644 index 000000000000..063d7bef6e1b --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistanceTest.java @@ -0,0 +1,194 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the {@code DamerauLevenshteinDistance} class. + * Tests cover edge cases, basic operations, and complex transposition scenarios. + */ +class DamerauLevenshteinDistanceTest { + + @Test + @DisplayName("Should throw exception for null first string") + void testNullFirstString() { + assertThrows(IllegalArgumentException.class, () -> { DamerauLevenshteinDistance.distance(null, "test"); }); + } + + @Test + @DisplayName("Should throw exception for null second string") + void testNullSecondString() { + assertThrows(IllegalArgumentException.class, () -> { DamerauLevenshteinDistance.distance("test", null); }); + } + + @Test + @DisplayName("Should throw exception for both null strings") + void testBothNullStrings() { + assertThrows(IllegalArgumentException.class, () -> { DamerauLevenshteinDistance.distance(null, null); }); + } + + @Test + @DisplayName("Should return 0 for identical strings") + void testIdenticalStrings() { + assertEquals(0, DamerauLevenshteinDistance.distance("", "")); + assertEquals(0, DamerauLevenshteinDistance.distance("a", "a")); + assertEquals(0, DamerauLevenshteinDistance.distance("abc", "abc")); + assertEquals(0, DamerauLevenshteinDistance.distance("hello", "hello")); + } + + @Test + @DisplayName("Should return length when one string is empty") + void testEmptyStrings() { + assertEquals(3, DamerauLevenshteinDistance.distance("", "abc")); + assertEquals(5, DamerauLevenshteinDistance.distance("hello", "")); + assertEquals(0, DamerauLevenshteinDistance.distance("", "")); + } + + @Test + @DisplayName("Should handle single character insertions") + void testSingleInsertion() { + assertEquals(1, DamerauLevenshteinDistance.distance("cat", "cats")); + assertEquals(1, DamerauLevenshteinDistance.distance("ab", "abc")); + assertEquals(1, DamerauLevenshteinDistance.distance("", "a")); + } + + @Test + @DisplayName("Should handle single character deletions") + void testSingleDeletion() { + assertEquals(1, DamerauLevenshteinDistance.distance("cats", "cat")); + assertEquals(1, DamerauLevenshteinDistance.distance("abc", "ab")); + assertEquals(1, DamerauLevenshteinDistance.distance("a", "")); + } + + @Test + @DisplayName("Should handle single character substitutions") + void testSingleSubstitution() { + assertEquals(1, DamerauLevenshteinDistance.distance("cat", "bat")); + assertEquals(1, DamerauLevenshteinDistance.distance("abc", "adc")); + assertEquals(1, DamerauLevenshteinDistance.distance("x", "y")); + } + + @Test + @DisplayName("Should handle adjacent character transpositions") + void testAdjacentTransposition() { + assertEquals(1, DamerauLevenshteinDistance.distance("ab", "ba")); + assertEquals(1, DamerauLevenshteinDistance.distance("abc", "bac")); + assertEquals(1, DamerauLevenshteinDistance.distance("hello", "ehllo")); + } + + @Test + @DisplayName("Should correctly compute distance for CA to ABC") + void testCAtoABC() { + // This is the critical test case that differentiates full DL from OSA + // Full DL: 2 (insert A at start, insert B in middle) + // OSA would give: 3 + assertEquals(2, DamerauLevenshteinDistance.distance("CA", "ABC")); + } + + @Test + @DisplayName("Should handle non-adjacent transpositions") + void testNonAdjacentTransposition() { + assertEquals(2, DamerauLevenshteinDistance.distance("abc", "cba")); + assertEquals(3, DamerauLevenshteinDistance.distance("abcd", "dcba")); + } + + @Test + @DisplayName("Should handle multiple operations") + void testMultipleOperations() { + assertEquals(3, DamerauLevenshteinDistance.distance("kitten", "sitting")); + assertEquals(3, DamerauLevenshteinDistance.distance("saturday", "sunday")); + assertEquals(5, DamerauLevenshteinDistance.distance("intention", "execution")); + } + + @Test + @DisplayName("Should handle completely different strings") + void testCompletelyDifferentStrings() { + assertEquals(3, DamerauLevenshteinDistance.distance("abc", "xyz")); + assertEquals(4, DamerauLevenshteinDistance.distance("hello", "world")); + } + + @Test + @DisplayName("Should handle strings with repeated characters") + void testRepeatedCharacters() { + assertEquals(0, DamerauLevenshteinDistance.distance("aaa", "aaa")); + assertEquals(1, DamerauLevenshteinDistance.distance("aaa", "aab")); + assertEquals(1, DamerauLevenshteinDistance.distance("aaa", "aba")); + } + + @Test + @DisplayName("Should be symmetric") + void testSymmetry() { + assertEquals(DamerauLevenshteinDistance.distance("abc", "def"), DamerauLevenshteinDistance.distance("def", "abc")); + assertEquals(DamerauLevenshteinDistance.distance("hello", "world"), DamerauLevenshteinDistance.distance("world", "hello")); + } + + @Test + @DisplayName("Should handle case sensitivity") + void testCaseSensitivity() { + assertEquals(1, DamerauLevenshteinDistance.distance("Hello", "hello")); + assertEquals(5, DamerauLevenshteinDistance.distance("HELLO", "hello")); + } + + @Test + @DisplayName("Should handle single character strings") + void testSingleCharacterStrings() { + assertEquals(1, DamerauLevenshteinDistance.distance("a", "b")); + assertEquals(0, DamerauLevenshteinDistance.distance("a", "a")); + assertEquals(2, DamerauLevenshteinDistance.distance("a", "abc")); + } + + @Test + @DisplayName("Should handle long strings efficiently") + void testLongStrings() { + String s1 = "abcdefghijklmnopqrstuvwxyz"; + String s2 = "abcdefghijklmnopqrstuvwxyz"; + assertEquals(0, DamerauLevenshteinDistance.distance(s1, s2)); + + String s3 = "abcdefghijklmnopqrstuvwxyz"; + String s4 = "zyxwvutsrqponmlkjihgfedcba"; + assertEquals(25, DamerauLevenshteinDistance.distance(s3, s4)); + } + + @Test + @DisplayName("Should satisfy triangle inequality") + void testTriangleInequality() { + // d(a,c) <= d(a,b) + d(b,c) + String a = "cat"; + String b = "hat"; + String c = "rat"; + + int ab = DamerauLevenshteinDistance.distance(a, b); + int bc = DamerauLevenshteinDistance.distance(b, c); + int ac = DamerauLevenshteinDistance.distance(a, c); + + assertTrue(ac <= ab + bc); + } + + @Test + @DisplayName("Should handle special characters") + void testSpecialCharacters() { + assertEquals(0, DamerauLevenshteinDistance.distance("hello!", "hello!")); + assertEquals(1, DamerauLevenshteinDistance.distance("hello!", "hello?")); + assertEquals(1, DamerauLevenshteinDistance.distance("a@b", "a#b")); + } + + @Test + @DisplayName("Should handle numeric strings") + void testNumericStrings() { + assertEquals(1, DamerauLevenshteinDistance.distance("123", "124")); + assertEquals(1, DamerauLevenshteinDistance.distance("123", "213")); + assertEquals(0, DamerauLevenshteinDistance.distance("999", "999")); + } + + @Test + @DisplayName("Should handle unicode characters") + void testUnicodeCharacters() { + assertEquals(0, DamerauLevenshteinDistance.distance("cafรฉ", "cafรฉ")); + assertEquals(1, DamerauLevenshteinDistance.distance("cafรฉ", "cafe")); + assertEquals(0, DamerauLevenshteinDistance.distance("ไฝ ๅฅฝ", "ไฝ ๅฅฝ")); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java new file mode 100644 index 000000000000..6c61ad2130e3 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java @@ -0,0 +1,78 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class KnapsackZeroOneTabulationTest { + + @Test + public void basicCheck() { + int[] values = {60, 100, 120}; + int[] weights = {10, 20, 30}; + int capacity = 50; + int itemCount = values.length; + + int expected = 220; // Best choice: item 1 (100) and item 2 (120) + int result = KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount); + assertEquals(expected, result); + } + + @Test + public void emptyKnapsack() { + int[] values = {}; + int[] weights = {}; + int capacity = 50; + int itemCount = 0; + + assertEquals(0, KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount)); + } + + @Test + public void zeroCapacity() { + int[] values = {60, 100, 120}; + int[] weights = {10, 20, 30}; + int capacity = 0; + int itemCount = values.length; + + assertEquals(0, KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount)); + } + + @Test + public void negativeCapacity() { + int[] values = {10, 20, 30}; + int[] weights = {1, 1, 1}; + int capacity = -10; + int itemCount = values.length; + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount)); + assertEquals("Capacity must not be negative.", exception.getMessage()); + } + + @Test + public void mismatchedLengths() { + int[] values = {60, 100}; // Only 2 values + int[] weights = {10, 20, 30}; // 3 weights + int capacity = 50; + int itemCount = 2; // Matches `values.length` + + // You could either expect 0 or throw an IllegalArgumentException in your compute function + assertThrows(IllegalArgumentException.class, () -> { KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount); }); + } + + @Test + public void nullInputs() { + int[] weights = {1, 2, 3}; + int capacity = 10; + int itemCount = 3; + + IllegalArgumentException exception1 = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(null, weights, capacity, itemCount)); + assertEquals("Values and weights arrays must not be null.", exception1.getMessage()); + + int[] values = {1, 2, 3}; + + IllegalArgumentException exception2 = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(values, null, capacity, itemCount)); + assertEquals("Values and weights arrays must not be null.", exception2.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java new file mode 100644 index 000000000000..0ca2b91fc273 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java @@ -0,0 +1,68 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class KnapsackZeroOneTest { + + @Test + void basicCheck() { + int[] values = {60, 100, 120}; + int[] weights = {10, 20, 30}; + int capacity = 50; + int expected = 220; + + int result = KnapsackZeroOne.compute(values, weights, capacity, values.length); + assertEquals(expected, result); + } + + @Test + void zeroCapacity() { + int[] values = {10, 20, 30}; + int[] weights = {1, 1, 1}; + int capacity = 0; + + int result = KnapsackZeroOne.compute(values, weights, capacity, values.length); + assertEquals(0, result); + } + + @Test + void zeroItems() { + int[] values = {}; + int[] weights = {}; + int capacity = 10; + + int result = KnapsackZeroOne.compute(values, weights, capacity, 0); + assertEquals(0, result); + } + + @Test + void weightsExceedingCapacity() { + int[] values = {10, 20}; + int[] weights = {100, 200}; + int capacity = 50; + + int result = KnapsackZeroOne.compute(values, weights, capacity, values.length); + assertEquals(0, result); + } + + @Test + void throwsOnNullArrays() { + assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(null, new int[] {1}, 10, 1)); + assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {1}, null, 10, 1)); + } + + @Test + void throwsOnMismatchedArrayLengths() { + assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10, 20}, new int[] {5}, 15, 2)); + } + + @Test + void throwsOnNegativeInputs() { + assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10}, new int[] {5}, -1, 1)); + + assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10}, new int[] {5}, 5, -1)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java new file mode 100644 index 000000000000..dc87d6751460 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestIncreasingSubsequenceNLogNTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class LongestIncreasingSubsequenceNLogNTest { + + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new int[] {10, 9, 2, 5, 3, 7, 101, 18}, 4), Arguments.of(new int[] {0, 1, 0, 3, 2, 3}, 4), Arguments.of(new int[] {7, 7, 7, 7, 7}, 1), Arguments.of(new int[] {1, 3, 5, 4, 7}, 4), Arguments.of(new int[] {}, 0), Arguments.of(new int[] {10}, 1), + Arguments.of(new int[] {3, 10, 2, 1, 20}, 3), Arguments.of(new int[] {50, 3, 10, 7, 40, 80}, 4)); + } + + @ParameterizedTest + @MethodSource("provideTestCases") + public void testLengthOfLIS(int[] input, int expected) { + assertEquals(expected, LongestIncreasingSubsequenceNLogN.lengthOfLIS(input)); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/MaximumProductSubarrayTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/MaximumProductSubarrayTest.java new file mode 100644 index 000000000000..0f499bf2a2f7 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/MaximumProductSubarrayTest.java @@ -0,0 +1,152 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class MaximumProductSubarrayTest { + + /** + * Test case for an array with all positive numbers. + * The expected maximum product is the product of all elements. + */ + @Test + void testAllPositiveNumbers() { + int[] nums = {2, 3, 4}; + int expected = 24; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 24."); + } + + /** + * Test case for an array with positive and negative numbers. + * The expected maximum product is 24 (subarray [2, -3, -4]). + */ + @Test + void testMixedPositiveAndNegative() { + int[] nums = {2, -3, -4, 1}; + int expected = 24; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 24."); + } + + /** + * Test case for an array containing zeros. + * The expected maximum product is 24 (subarray [4, 6]). + */ + @Test + void testArrayWithZeros() { + int[] nums = {2, 3, 0, 4, 6}; + int expected = 24; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 24."); + } + + /** + * Test case for an array with a single element. + * The expected maximum product is the element itself. + */ + @Test + void testSingleElement() { + int[] nums = {5}; + int expected = 5; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 5."); + } + + /** + * Test case for an array with all negative numbers. + * The expected maximum product is 12 (subarray [-3, -4]). + */ + @Test + void testAllNegativeNumbers() { + int[] nums = {-2, -3, -4}; + int expected = 12; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 12."); + } + + /** + * Test case for an array with negative numbers where odd count of negatives + * breaks the chain. The expected maximum product is 60 (subarray [-2, -3, 10]). + */ + @Test + void testOddNegativeNumbers() { + int[] nums = {-2, -3, 10, -1}; + int expected = 60; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 60."); + } + + /** + * Test case for an empty array. + * The expected maximum product is 0. + */ + @Test + void testEmptyArray() { + int[] nums = {}; + int expected = 0; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 0 for an empty array."); + } + + /** + * Test case for a null array. + * The expected maximum product is 0. + */ + @Test + void testNullArray() { + int[] nums = null; + int expected = 0; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 0 for a null array."); + } + + /** + * Test case for an array with alternating positive and negative numbers. + * The expected maximum product is 6 (subarray [2, 3]). + */ + @Test + void testAlternatingNumbers() { + int[] nums = {2, 3, -2, 4}; + int expected = 6; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 6."); + } + + /** + * Test case for an array with large positive and negative numbers. + * The expected maximum product is 360 (subarray [6, -3, -20]). + */ + @Test + void testLargeNumbers() { + int[] nums = {6, -3, -20, 0, 5}; + int expected = 360; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 360."); + } + + /** + * Test case for an array with single negative number. + * The expected maximum product is the negative number itself. + */ + @Test + void testSingleNegativeElement() { + int[] nums = {-8}; + int expected = -8; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be -8."); + } + + /** + * Test case for an array with multiple zeros. + * The expected maximum product is 6 (subarray [2, 3]). + */ + @Test + void testMultipleZeros() { + int[] nums = {0, 2, 3, 0, 4}; + int expected = 6; + int actual = MaximumProductSubarray.maxProduct(nums); + assertEquals(expected, actual, "The maximum product should be 6."); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/NeedlemanWunschTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/NeedlemanWunschTest.java new file mode 100644 index 000000000000..f7ba5eea8bce --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/NeedlemanWunschTest.java @@ -0,0 +1,60 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Unit Tests for the {@code NeedlemanWunsch} class + */ +class NeedlemanWunschTest { + + @Test + void testIdenticalStrings() { + int score = NeedlemanWunsch.align("GATTACA", "GATTACA", 1, -1, -2); + assertEquals(7, score); // All matches, 7*1 + } + + @Test + void testSimpleMismatch() { + int score = NeedlemanWunsch.align("GATTACA", "GACTATA", 1, -1, -2); + assertEquals(3, score); + } + + @Test + void testInsertion() { + int score = NeedlemanWunsch.align("GATTACA", "GATACA", 1, -1, -2); + // One deletion (gap penalty) + assertEquals(4, score); + } + + @Test + void testEmptyStrings() { + assertEquals(0, NeedlemanWunsch.align("", "", 1, -1, -2)); + } + + @Test + void testOneEmpty() { + assertEquals(-14, NeedlemanWunsch.align("GATTACA", "", 1, -1, -2)); // 7 gaps ร— -2 + } + + @Test + void testGapHeavyAlignment() { + int score = NeedlemanWunsch.align("AAAA", "AA", 1, -1, -2); + assertEquals(-2, score); // Two matches (2*1) + two gaps (2*-2) + } + + @ParameterizedTest + @CsvSource({"null,ABC", "ABC,null", "null,null"}) + void testNullInputs(String s1, String s2) { + // Interpret "null" literal as Java null + String first = "null".equals(s1) ? null : s1; + String second = "null".equals(s2) ? null : s2; + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> NeedlemanWunsch.align(first, second, 1, -1, -2)); + assertEquals("Input strings must not be null.", ex.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/SmithWatermanTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/SmithWatermanTest.java new file mode 100644 index 000000000000..46c47e376989 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/SmithWatermanTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Unit tests for the {@code SmithWaterman} class. + */ +class SmithWatermanTest { + + @Test + void testIdenticalStrings() { + int score = SmithWaterman.align("GATTACA", "GATTACA", 2, -1, -2); + assertEquals(14, score); // full match, 7*2 + } + + @Test + void testPartialMatch() { + int score = SmithWaterman.align("GATTACA", "TTAC", 2, -1, -2); + assertEquals(8, score); // best local alignment "TTAC" + } + + @Test + void testNoMatch() { + int score = SmithWaterman.align("AAAA", "TTTT", 1, -1, -2); + assertEquals(0, score); // no alignment worth keeping + } + + @Test + void testInsertionDeletion() { + int score = SmithWaterman.align("ACGT", "ACGGT", 1, -1, -2); + assertEquals(3, score); // local alignment "ACG" + } + + @Test + void testEmptyStrings() { + assertEquals(0, SmithWaterman.align("", "", 1, -1, -2)); + } + + @ParameterizedTest + @CsvSource({"null,ABC", "ABC,null", "null,null"}) + void testNullInputs(String s1, String s2) { + String first = "null".equals(s1) ? null : s1; + String second = "null".equals(s2) ? null : s2; + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> SmithWaterman.align(first, second, 1, -1, -2)); + assertEquals("Input strings must not be null.", ex.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/TreeMatchingTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/TreeMatchingTest.java new file mode 100644 index 000000000000..d5418770a5d1 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/TreeMatchingTest.java @@ -0,0 +1,120 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.thealgorithms.datastructures.graphs.UndirectedAdjacencyListGraph; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TreeMatchingTest { + UndirectedAdjacencyListGraph graph; + + @BeforeEach + void setUp() { + graph = new UndirectedAdjacencyListGraph(); + for (int i = 0; i < 14; i++) { + graph.addNode(); + } + } + + @Test + void testMaxMatchingForGeneralTree() { + graph.addEdge(0, 1, 20); + graph.addEdge(0, 2, 30); + graph.addEdge(1, 3, 40); + graph.addEdge(1, 4, 10); + graph.addEdge(2, 5, 20); + graph.addEdge(3, 6, 30); + graph.addEdge(3, 7, 30); + graph.addEdge(5, 8, 40); + graph.addEdge(5, 9, 10); + + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(110, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testMaxMatchingForBalancedTree() { + graph.addEdge(0, 1, 20); + graph.addEdge(0, 2, 30); + graph.addEdge(0, 3, 40); + graph.addEdge(1, 4, 10); + graph.addEdge(1, 5, 20); + graph.addEdge(2, 6, 20); + graph.addEdge(3, 7, 30); + graph.addEdge(5, 8, 10); + graph.addEdge(5, 9, 20); + graph.addEdge(7, 10, 10); + graph.addEdge(7, 11, 10); + graph.addEdge(7, 12, 5); + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(100, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testMaxMatchingForTreeWithVariedEdgeWeights() { + graph.addEdge(0, 1, 20); + graph.addEdge(0, 2, 30); + graph.addEdge(0, 3, 40); + graph.addEdge(0, 4, 50); + graph.addEdge(1, 5, 20); + graph.addEdge(2, 6, 20); + graph.addEdge(3, 7, 30); + graph.addEdge(5, 8, 10); + graph.addEdge(5, 9, 20); + graph.addEdge(7, 10, 10); + graph.addEdge(4, 11, 50); + graph.addEdge(4, 12, 20); + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(140, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void emptyTree() { + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(0, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testSingleNodeTree() { + UndirectedAdjacencyListGraph singleNodeGraph = new UndirectedAdjacencyListGraph(); + singleNodeGraph.addNode(); + + TreeMatching treeMatching = new TreeMatching(singleNodeGraph); + assertEquals(0, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testLinearTree() { + graph.addEdge(0, 1, 10); + graph.addEdge(1, 2, 20); + graph.addEdge(2, 3, 30); + graph.addEdge(3, 4, 40); + + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(60, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testStarShapedTree() { + graph.addEdge(0, 1, 15); + graph.addEdge(0, 2, 25); + graph.addEdge(0, 3, 35); + graph.addEdge(0, 4, 45); + + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(45, treeMatching.getMaxMatching(0, -1)); + } + + @Test + void testUnbalancedTree() { + graph.addEdge(0, 1, 10); + graph.addEdge(0, 2, 20); + graph.addEdge(1, 3, 30); + graph.addEdge(2, 4, 40); + graph.addEdge(4, 5, 50); + + TreeMatching treeMatching = new TreeMatching(graph); + assertEquals(100, treeMatching.getMaxMatching(0, -1)); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java new file mode 100644 index 000000000000..99240566a2f3 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java @@ -0,0 +1,324 @@ +package com.thealgorithms.geometry; + +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Comprehensive unit tests for {@link BentleyOttmann}. + * + *

This test suite validates the correctness of the Bentleyโ€“Ottmann algorithm + * implementation by checking intersection points between multiple line segment configurations.

+ * + *

Test cases include typical, edge, degenerate geometrical setups, and performance tests.

+ */ +public class BentleyOttmannTest { + + private static final double EPS = 1e-6; + + @Test + void testSingleIntersection() { + List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 5, 5, 1)); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0)); + } + + @Test + void testVerticalIntersection() { + List segments = List.of(newSegment(3, 0, 3, 6), newSegment(1, 1, 5, 5)); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0)); + } + + @Test + void testNoIntersection() { + List segments = List.of(newSegment(0, 0, 1, 1), newSegment(2, 2, 3, 3)); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertTrue(intersections.isEmpty()); + } + + @Test + void testCoincidentSegments() { + List segments = List.of(newSegment(1, 1, 5, 5), newSegment(1, 1, 5, 5)); + + Set intersections = BentleyOttmann.findIntersections(segments); + + Assertions.assertEquals(2, intersections.size(), "Two identical segments should report 2 intersection points (both endpoints)"); + Assertions.assertTrue(containsPoint(intersections, 1.0, 1.0)); + Assertions.assertTrue(containsPoint(intersections, 5.0, 5.0)); + } + + @Test + void testHorizontalIntersection() { + List segments = List.of(newSegment(0, 2, 4, 2), newSegment(2, 0, 2, 4)); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); + } + + @Test + void testEmptyList() { + List segments = List.of(); + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertTrue(intersections.isEmpty()); + } + + @Test + void testSingleSegment() { + List segments = List.of(newSegment(0, 0, 5, 5)); + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertTrue(intersections.isEmpty()); + } + + @Test + void testNullListThrowsException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> BentleyOttmann.findIntersections(null)); + } + + @Test + void testParallelSegments() { + // Test 1: Parallel diagonal segments + List diagonalSegments = List.of(newSegment(0, 0, 4, 4), newSegment(1, 0, 5, 4), newSegment(2, 0, 6, 4)); + Assertions.assertTrue(BentleyOttmann.findIntersections(diagonalSegments).isEmpty()); + + // Test 2: Parallel vertical segments + List verticalSegments = List.of(newSegment(1, 0, 1, 5), newSegment(2, 0, 2, 5), newSegment(3, 0, 3, 5)); + Assertions.assertTrue(BentleyOttmann.findIntersections(verticalSegments).isEmpty()); + + // Test 3: Parallel horizontal segments + List horizontalSegments = List.of(newSegment(0, 1, 5, 1), newSegment(0, 2, 5, 2), newSegment(0, 3, 5, 3)); + Assertions.assertTrue(BentleyOttmann.findIntersections(horizontalSegments).isEmpty()); + } + + @Test + void testTouchingEndpoints() { + List segments = List.of(newSegment(0, 0, 2, 2), newSegment(2, 2, 4, 0)); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); + } + + @Test + void testOverlappingCollinearSegments() { + List segments = List.of(newSegment(0, 0, 4, 4), newSegment(2, 2, 6, 6)); + + Set intersections = BentleyOttmann.findIntersections(segments); + // Overlapping collinear segments share the point (2,2) where second starts + // and (4,4) where first ends - at least one should be detected + Assertions.assertFalse(intersections.isEmpty(), "Should find at least one overlap point"); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain either (2,2) or (4,4)"); + } + + @Test + void testMultipleSegmentsAtOnePoint() { + // Star pattern: 4 segments meeting at (2, 2) + List segments = List.of(newSegment(0, 2, 4, 2), // horizontal + newSegment(2, 0, 2, 4), // vertical + newSegment(0, 0, 4, 4), // diagonal / + newSegment(0, 4, 4, 0) // diagonal \ + ); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); + // All segments meet at (2, 2), so should be reported once + Assertions.assertEquals(1, intersections.size()); + } + + @Test + void testGridPattern() { + // 3x3 grid: should have 9 intersection points + List segments = new ArrayList<>(); + + // Vertical lines at x = 0, 1, 2 + for (int i = 0; i <= 2; i++) { + segments.add(newSegment(i, 0, i, 2)); + } + + // Horizontal lines at y = 0, 1, 2 + for (int i = 0; i <= 2; i++) { + segments.add(newSegment(0, i, 2, i)); + } + + Set intersections = BentleyOttmann.findIntersections(segments); + + // Each vertical line crosses each horizontal line + // 3 vertical ร— 3 horizontal = 9 intersections + Assertions.assertEquals(9, intersections.size(), "3x3 grid should have 9 intersections"); + + // Verify all grid points are present + for (int x = 0; x <= 2; x++) { + for (int y = 0; y <= 2; y++) { + Assertions.assertTrue(containsPoint(intersections, x, y), String.format("Grid point (%d, %d) should be present", x, y)); + } + } + } + + @Test + void testTriangleIntersections() { + // Three segments forming a triangle + List segments = List.of(newSegment(0, 0, 4, 0), // base + newSegment(0, 0, 2, 3), // left side + newSegment(4, 0, 2, 3) // right side + ); + + Set intersections = BentleyOttmann.findIntersections(segments); + // Triangle vertices are intersections + Assertions.assertTrue(containsPoint(intersections, 0.0, 0.0)); + Assertions.assertTrue(containsPoint(intersections, 4.0, 0.0)); + Assertions.assertTrue(containsPoint(intersections, 2.0, 3.0)); + Assertions.assertEquals(3, intersections.size()); + } + + @Test + void testCrossingDiagonals() { + // X pattern with multiple crossings + List segments = List.of(newSegment(0, 0, 10, 10), newSegment(0, 10, 10, 0), newSegment(5, 0, 5, 10), newSegment(0, 5, 10, 5)); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertTrue(containsPoint(intersections, 5.0, 5.0), "Center point should be present"); + Assertions.assertEquals(1, intersections.size()); + } + + @Test + void testVerySmallSegments() { + List segments = List.of(newSegment(0.001, 0.001, 0.002, 0.002), newSegment(0.001, 0.002, 0.002, 0.001)); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 0.0015, 0.0015)); + } + + @Test + void testSegmentsShareCommonPoint() { + List segmentsSameStart = List.of(newSegment(0, 0, 4, 4), newSegment(0, 0, 4, -4), newSegment(0, 0, -4, 4)); + + Set intersectionsSameStart = BentleyOttmann.findIntersections(segmentsSameStart); + Assertions.assertTrue(containsPoint(intersectionsSameStart, 0.0, 0.0)); + List segmentsSameEnd = List.of(newSegment(0, 0, 4, 4), newSegment(8, 4, 4, 4), newSegment(4, 8, 4, 4)); + + Set intersectionsSameEnd = BentleyOttmann.findIntersections(segmentsSameEnd); + Assertions.assertTrue(containsPoint(intersectionsSameEnd, 4.0, 4.0)); + } + + @Test + void testSegmentsAtAngles() { + // Segments at 45, 90, 135 degrees + List segments = List.of(newSegment(0, 2, 4, 2), // horizontal + newSegment(2, 0, 2, 4), // vertical + newSegment(0, 0, 4, 4), // 45 degrees + newSegment(0, 4, 4, 0) // 135 degrees + ); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); + } + + @Test + void testPerformanceWithManySegments() { + // Generate 100 random segments + Random random = new Random(42); // Fixed seed for reproducibility + List segments = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + double x1 = random.nextDouble() * 100; + double y1 = random.nextDouble() * 100; + double x2 = random.nextDouble() * 100; + double y2 = random.nextDouble() * 100; + segments.add(newSegment(x1, y1, x2, y2)); + } + + long startTime = System.currentTimeMillis(); + Set intersections = BentleyOttmann.findIntersections(segments); + long endTime = System.currentTimeMillis(); + + long duration = endTime - startTime; + + // Should complete in reasonable time (< 1 second for 100 segments) + Assertions.assertTrue(duration < 1000, "Algorithm should complete in less than 1 second for 100 segments. Took: " + duration + "ms"); + + // Just verify it returns a valid result + Assertions.assertNotNull(intersections); + System.out.println("Performance test: 100 segments processed in " + duration + "ms, found " + intersections.size() + " intersections"); + } + + @Test + void testIssueExample() { + // Example from the GitHub issue + List segments = List.of(newSegment(1, 1, 5, 5), // Segment A + newSegment(1, 5, 5, 1), // Segment B + newSegment(3, 0, 3, 6) // Segment C + ); + + Set intersections = BentleyOttmann.findIntersections(segments); + + // Expected output: [(3, 3)] + Assertions.assertEquals(1, intersections.size(), "Should find exactly one intersection"); + Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0), "Intersection should be at (3, 3)"); + } + + @Test + void testEventTypeOrdering() { + // Multiple events at the same point with different types + List segments = List.of(newSegment(2, 2, 6, 2), // ends at (2,2) + newSegment(0, 2, 2, 2), // ends at (2,2) + newSegment(2, 2, 2, 6), // starts at (2,2) + newSegment(2, 0, 2, 2) // ends at (2,2) + ); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0)); + } + + @Test + void testCollinearOverlapWithInteriorPoint() { + // Test collinear segments where one segment's interior overlaps another + List segments = List.of(newSegment(0, 0, 6, 6), newSegment(2, 2, 4, 4)); + Set intersections = BentleyOttmann.findIntersections(segments); + + // Should find at least one overlap point (where segments touch/overlap) + Assertions.assertFalse(intersections.isEmpty(), "Should find overlap points for collinear segments"); + Assertions.assertTrue(containsPoint(intersections, 2.0, 2.0) || containsPoint(intersections, 4.0, 4.0), "Should contain overlap boundary point"); + } + + @Test + void testCollinearTouchingAtBothEndpoints() { + // Test collinear segments that touch at both endpoints + // This triggers the "endpoint of both" logic (line 354-355) + List segments = List.of(newSegment(0, 0, 4, 4), newSegment(4, 4, 8, 8)); + + Set intersections = BentleyOttmann.findIntersections(segments); + Assertions.assertEquals(1, intersections.size()); + Assertions.assertTrue(containsPoint(intersections, 4.0, 4.0), "Should find touching point"); + } + + @Test + void testCollinearOverlapPartialInterior() { + // Test case where segments overlap but one point is inside, one is endpoint + List segments = List.of(newSegment(0, 0, 5, 5), newSegment(3, 3, 7, 7)); + + Set intersections = BentleyOttmann.findIntersections(segments); + + // Should detect the overlap region + Assertions.assertFalse(intersections.isEmpty()); + // The algorithm should return at least one of the boundary points + Assertions.assertTrue(containsPoint(intersections, 3.0, 3.0) || containsPoint(intersections, 5.0, 5.0)); + } + + private static BentleyOttmann.Segment newSegment(double x1, double y1, double x2, double y2) { + return new BentleyOttmann.Segment(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); + } + + private static boolean containsPoint(Set points, double x, double y) { + return points.stream().anyMatch(p -> Math.abs(p.x - x) < EPS && Math.abs(p.y - y) < EPS); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java b/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java index e3e32e43c6de..d3ca0df65829 100644 --- a/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java +++ b/src/test/java/com/thealgorithms/geometry/ConvexHullTest.java @@ -1,7 +1,9 @@ package com.thealgorithms.geometry; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; @@ -10,14 +12,17 @@ public class ConvexHullTest { @Test void testConvexHullBruteForce() { + // Test 1: Triangle with intermediate point List points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); List expected = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); assertEquals(expected, ConvexHull.convexHullBruteForce(points)); + // Test 2: Collinear points points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 0)); expected = Arrays.asList(new Point(0, 0), new Point(10, 0)); assertEquals(expected, ConvexHull.convexHullBruteForce(points)); + // Test 3: Complex polygon points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3)); expected = Arrays.asList(new Point(2, -4), new Point(1, -3), new Point(0, 0), new Point(3, 0), new Point(0, 3), new Point(3, 3)); assertEquals(expected, ConvexHull.convexHullBruteForce(points)); @@ -25,16 +30,109 @@ void testConvexHullBruteForce() { @Test void testConvexHullRecursive() { + // Test 1: Triangle - CCW order starting from bottom-left + // The algorithm includes (1,0) as it's detected as an extreme point List points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); + List result = ConvexHull.convexHullRecursive(points); List expected = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 1)); - assertEquals(expected, ConvexHull.convexHullRecursive(points)); + assertEquals(expected, result); + assertTrue(isCounterClockwise(result), "Points should be in counter-clockwise order"); + // Test 2: Collinear points points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(10, 0)); + result = ConvexHull.convexHullRecursive(points); expected = Arrays.asList(new Point(0, 0), new Point(10, 0)); - assertEquals(expected, ConvexHull.convexHullRecursive(points)); + assertEquals(expected, result); + // Test 3: Complex polygon + // Convex hull vertices in CCW order from bottom-most point (2,-4): + // (2,-4) -> (3,0) -> (3,3) -> (0,3) -> (0,0) -> (1,-3) -> back to (2,-4) points = Arrays.asList(new Point(0, 3), new Point(2, 2), new Point(1, 1), new Point(2, 1), new Point(3, 0), new Point(0, 0), new Point(3, 3), new Point(2, -1), new Point(2, -4), new Point(1, -3)); - expected = Arrays.asList(new Point(2, -4), new Point(1, -3), new Point(0, 0), new Point(3, 0), new Point(0, 3), new Point(3, 3)); - assertEquals(expected, ConvexHull.convexHullRecursive(points)); + result = ConvexHull.convexHullRecursive(points); + expected = Arrays.asList(new Point(2, -4), new Point(3, 0), new Point(3, 3), new Point(0, 3), new Point(0, 0), new Point(1, -3)); + assertEquals(expected, result); + assertTrue(isCounterClockwise(result), "Points should be in counter-clockwise order"); + } + + @Test + void testConvexHullRecursiveAdditionalCases() { + // Test 4: Square (all corners on hull) + List points = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(2, 2), new Point(0, 2)); + List result = ConvexHull.convexHullRecursive(points); + List expected = Arrays.asList(new Point(0, 0), new Point(2, 0), new Point(2, 2), new Point(0, 2)); + assertEquals(expected, result); + assertTrue(isCounterClockwise(result), "Square points should be in CCW order"); + + // Test 5: Pentagon with interior point + points = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(5, 3), new Point(2, 5), new Point(-1, 3), new Point(2, 2) // (2,2) is interior + ); + result = ConvexHull.convexHullRecursive(points); + // CCW from (0,0): (0,0) -> (4,0) -> (5,3) -> (2,5) -> (-1,3) + expected = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(5, 3), new Point(2, 5), new Point(-1, 3)); + assertEquals(expected, result); + assertTrue(isCounterClockwise(result), "Pentagon points should be in CCW order"); + + // Test 6: Simple triangle (clearly convex) + points = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); + result = ConvexHull.convexHullRecursive(points); + expected = Arrays.asList(new Point(0, 0), new Point(4, 0), new Point(2, 3)); + assertEquals(expected, result); + assertTrue(isCounterClockwise(result), "Triangle points should be in CCW order"); + } + + /** + * Helper method to verify if points are in counter-clockwise order. + * Uses the signed area method: positive area means CCW. + */ + private boolean isCounterClockwise(List points) { + if (points.size() < 3) { + return true; // Less than 3 points, trivially true + } + + long signedArea = 0; + for (int i = 0; i < points.size(); i++) { + Point p1 = points.get(i); + Point p2 = points.get((i + 1) % points.size()); + signedArea += (long) p1.x() * p2.y() - (long) p2.x() * p1.y(); + } + + return signedArea > 0; // Positive signed area means counter-clockwise + } + + @Test + void testRecursiveHullForCoverage() { + // 1. Test the base cases of the convexHullRecursive method (covering scenarios with < 3 input points). + + // Test Case: 0 points + List pointsEmpty = new ArrayList<>(); + List resultEmpty = ConvexHull.convexHullRecursive(pointsEmpty); + assertTrue(resultEmpty.isEmpty(), "Should return an empty list for an empty input list"); + + // Test Case: 1 point + List pointsOne = List.of(new Point(5, 5)); + // Pass a new ArrayList because the original method modifies the input list. + List resultOne = ConvexHull.convexHullRecursive(new ArrayList<>(pointsOne)); + List expectedOne = List.of(new Point(5, 5)); + assertEquals(expectedOne, resultOne, "Should return the single point for a single-point input"); + + // Test Case: 2 points + List pointsTwo = Arrays.asList(new Point(10, 1), new Point(0, 0)); + List resultTwo = ConvexHull.convexHullRecursive(new ArrayList<>(pointsTwo)); + List expectedTwo = Arrays.asList(new Point(0, 0), new Point(10, 1)); // Should return the two points, sorted. + assertEquals(expectedTwo, resultTwo, "Should return the two sorted points for a two-point input"); + + // 2. Test the logic for handling collinear points in the sortCounterClockwise method. + + // Construct a scenario where multiple collinear points lie on an edge of the convex hull. + // The expected convex hull vertices are (0,0), (10,0), and (5,5). + // When (0,0) is used as the pivot for polar angle sorting, (5,0) and (10,0) are collinear. + // This will trigger the crossProduct == 0 branch in the sortCounterClockwise method. + List pointsWithCollinearOnHull = Arrays.asList(new Point(0, 0), new Point(5, 0), new Point(10, 0), new Point(5, 5), new Point(2, 2)); + + List resultCollinear = ConvexHull.convexHullRecursive(new ArrayList<>(pointsWithCollinearOnHull)); + List expectedCollinear = Arrays.asList(new Point(0, 0), new Point(10, 0), new Point(5, 5)); + + assertEquals(expectedCollinear, resultCollinear, "Should correctly handle collinear points on the hull edge"); + assertTrue(isCounterClockwise(resultCollinear), "The result of the collinear test should be in counter-clockwise order"); } } diff --git a/src/test/java/com/thealgorithms/geometry/DDALineTest.java b/src/test/java/com/thealgorithms/geometry/DDALineTest.java new file mode 100644 index 000000000000..d3d639aa6468 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/DDALineTest.java @@ -0,0 +1,29 @@ +package com.thealgorithms.geometry; + +import java.awt.Point; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * The {@code DDALineTest} class contains unit tests for the + * {@code DDALine} class, specifically testing the {@code findLine} method. + */ +class DDALineTest { + + static Stream linePointsProvider() { + return Stream.of(Arguments.of(0, 0, 5, 5, List.of(new Point(0, 0), new Point(1, 1), new Point(2, 2), new Point(3, 3), new Point(4, 4), new Point(5, 5))), Arguments.of(0, 0, 5, 0, List.of(new Point(0, 0), new Point(1, 0), new Point(2, 0), new Point(3, 0), new Point(4, 0), new Point(5, 0))), + Arguments.of(0, 0, 0, 5, List.of(new Point(0, 0), new Point(0, 1), new Point(0, 2), new Point(0, 3), new Point(0, 4), new Point(0, 5))), Arguments.of(-2, -2, -5, -5, List.of(new Point(-2, -2), new Point(-3, -3), new Point(-4, -4), new Point(-5, -5))), + Arguments.of(1, 1, 1, 1, List.of(new Point(1, 1))), Arguments.of(0, 0, 1, 5, List.of(new Point(0, 0), new Point(0, 1), new Point(0, 2), new Point(1, 3), new Point(1, 4), new Point(1, 5)))); + } + + @ParameterizedTest + @MethodSource("linePointsProvider") + void testFindLine(int x0, int y0, int x1, int y1, List expected) { + List actual = DDALine.findLine(x0, y0, x1, y1); + Assertions.assertEquals(expected, actual, "The DDA algorithm should generate the expected ordered points."); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/HaversineTest.java b/src/test/java/com/thealgorithms/geometry/HaversineTest.java new file mode 100644 index 000000000000..b4dcca30a8ac --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/HaversineTest.java @@ -0,0 +1,60 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Unit tests for the Haversine formula implementation. + * This class uses parameterized tests to verify the distance calculation + * between various geographical coordinates. + */ +final class HaversineTest { + + // A small tolerance for comparing double values, since floating-point + // arithmetic is not always exact. A 1km tolerance is reasonable for these distances. + private static final double DELTA = 1.0; + + /** + * Provides test cases for the haversine distance calculation. + * Each argument contains: lat1, lon1, lat2, lon2, and the expected distance in kilometers. + * + * @return a stream of arguments for the parameterized test. + */ + static Stream haversineTestProvider() { + return Stream.of( + // Case 1: Distance between Paris, France and Tokyo, Japan + Arguments.of(48.8566, 2.3522, 35.6895, 139.6917, 9712.0), + + // Case 2: Distance between New York, USA and London, UK + Arguments.of(40.7128, -74.0060, 51.5074, -0.1278, 5570.0), + + // Case 3: Zero distance (same point) + Arguments.of(52.5200, 13.4050, 52.5200, 13.4050, 0.0), + + // Case 4: Antipodal points (opposite sides of the Earth) + // Should be approximately half the Earth's circumference (PI * radius) + Arguments.of(0.0, 0.0, 0.0, 180.0, 20015.0)); + } + + /** + * Tests the haversine method with various sets of coordinates. + * + * @param lat1 Latitude of the first point. + * @param lon1 Longitude of the first point. + * @param lat2 Latitude of the second point. + * @param lon2 Longitude of the second point. + * @param expectedDistance The expected distance in kilometers. + */ + @ParameterizedTest + @MethodSource("haversineTestProvider") + @DisplayName("Test Haversine distance calculation for various coordinates") + void testHaversine(double lat1, double lon1, double lat2, double lon2, double expectedDistance) { + double actualDistance = Haversine.haversine(lat1, lon1, lat2, lon2); + assertEquals(expectedDistance, actualDistance, DELTA); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/PointTest.java b/src/test/java/com/thealgorithms/geometry/PointTest.java new file mode 100644 index 000000000000..12901364b458 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/PointTest.java @@ -0,0 +1,117 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class PointTest { + + @Test + void testCompareTo() { + Point p1 = new Point(1, 2); + Point p2 = new Point(5, -1); + Point p3 = new Point(3, 9); + Point p4 = new Point(3, 9); + assertEquals(1, p1.compareTo(p2)); + assertEquals(-1, p2.compareTo(p3)); + assertEquals(0, p3.compareTo(p4)); + } + + @Test + void testToString() { + Point p = new Point(-3, 5); + assertEquals("(-3, 5)", p.toString()); + } + + @Test + void testPolarOrder() { + Point p = new Point(0, 0); + assertNotNull(p.polarOrder()); + } + + @Test + void testOrientation() { + // setup points + Point pA = new Point(0, 0); + Point pB = new Point(1, 0); + Point pC = new Point(1, 1); + + // test for left curve + assertEquals(1, Point.orientation(pA, pB, pC)); + + // test for right curve + pB = new Point(0, 1); + assertEquals(-1, Point.orientation(pA, pB, pC)); + + // test for left curve + pC = new Point(-1, 1); + assertEquals(1, Point.orientation(pA, pB, pC)); + + // test for right curve + pB = new Point(1, 0); + pC = new Point(1, -1); + assertEquals(-1, Point.orientation(pA, pB, pC)); + + // test for collinearity + pB = new Point(1, 1); + pC = new Point(2, 2); + assertEquals(0, Point.orientation(pA, pB, pC)); + } + + @Test + void testPolarOrderCompare() { + Point ref = new Point(0, 0); + + Point pA = new Point(1, 1); + Point pB = new Point(1, -1); + assertTrue(ref.polarOrder().compare(pA, pB) < 0); + + pA = new Point(3, 0); + pB = new Point(2, 0); + assertTrue(ref.polarOrder().compare(pA, pB) < 0); + + pA = new Point(0, 1); + pB = new Point(-1, 1); + assertTrue(ref.polarOrder().compare(pA, pB) < 0); + + pA = new Point(1, 1); + pB = new Point(2, 2); + assertEquals(0, ref.polarOrder().compare(pA, pB)); + + pA = new Point(1, 2); + pB = new Point(2, 1); + assertTrue(ref.polarOrder().compare(pA, pB) > 0); + + pA = new Point(2, 1); + pB = new Point(1, 2); + assertTrue(ref.polarOrder().compare(pA, pB) < 0); + + pA = new Point(-1, 0); + pB = new Point(-2, 0); + assertTrue(ref.polarOrder().compare(pA, pB) < 0); + + pA = new Point(2, 3); + pB = new Point(2, 3); + assertEquals(0, ref.polarOrder().compare(pA, pB)); + + pA = new Point(0, 1); + pB = new Point(0, -1); + assertTrue(ref.polarOrder().compare(pA, pB) < 0); + + ref = new Point(1, 1); + + pA = new Point(1, 2); + pB = new Point(2, 2); + assertTrue(ref.polarOrder().compare(pA, pB) > 0); + + pA = new Point(2, 1); + pB = new Point(2, 0); + assertTrue(ref.polarOrder().compare(pA, pB) < 0); + + pA = new Point(0, 1); + pB = new Point(1, 0); + assertTrue(ref.polarOrder().compare(pA, pB) < 0); + } +} diff --git a/src/test/java/com/thealgorithms/geometry/WusLineTest.java b/src/test/java/com/thealgorithms/geometry/WusLineTest.java new file mode 100644 index 000000000000..0b7f36859e69 --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/WusLineTest.java @@ -0,0 +1,90 @@ +package com.thealgorithms.geometry; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the {@link WusLine} class. + */ +class WusLineTest { + + @Test + void testSimpleLineProducesPixels() { + List pixels = WusLine.drawLine(2, 2, 6, 4); + assertFalse(pixels.isEmpty(), "Line should produce non-empty pixel list"); + } + + @Test + void testEndpointsIncluded() { + List pixels = WusLine.drawLine(0, 0, 5, 3); + boolean hasStart = pixels.stream().anyMatch(p -> p.point.equals(new java.awt.Point(0, 0))); + boolean hasEnd = pixels.stream().anyMatch(p -> p.point.equals(new java.awt.Point(5, 3))); + assertTrue(hasStart, "Start point should be represented in the pixel list"); + assertTrue(hasEnd, "End point should be represented in the pixel list"); + } + + @Test + void testIntensityInRange() { + List pixels = WusLine.drawLine(1, 1, 8, 5); + for (WusLine.Pixel pixel : pixels) { + assertTrue(pixel.intensity >= 0.0 && pixel.intensity <= 1.0, "Intensity must be clamped between 0.0 and 1.0"); + } + } + + @Test + void testReversedEndpointsProducesSameLine() { + List forward = WusLine.drawLine(2, 2, 10, 5); + List backward = WusLine.drawLine(10, 5, 2, 2); + + // They should cover same coordinates (ignoring order) + var forwardPoints = forward.stream().map(p -> p.point).collect(java.util.stream.Collectors.toSet()); + var backwardPoints = backward.stream().map(p -> p.point).collect(java.util.stream.Collectors.toSet()); + + assertEquals(forwardPoints, backwardPoints, "Reversing endpoints should yield same line pixels"); + } + + @Test + void testSteepLineHasProperCoverage() { + // Steep line: ฮ”y > ฮ”x + List pixels = WusLine.drawLine(3, 2, 5, 10); + assertFalse(pixels.isEmpty()); + // Expect increasing y values + long increasing = 0; + for (int i = 1; i < pixels.size(); i++) { + if (pixels.get(i).point.y >= pixels.get(i - 1).point.y) { + increasing++; + } + } + assertTrue(increasing > pixels.size() / 2, "Steep line should have increasing y coordinates"); + } + + @Test + void testZeroLengthLineUsesDefaultGradient() { + // same start and end -> dx == 0 -> gradient should take the (dx == 0) ? 1.0 branch + List pixels = WusLine.drawLine(3, 3, 3, 3); + + // sanity checks: we produced pixels and the exact point is present + assertFalse(pixels.isEmpty(), "Zero-length line should produce at least one pixel"); + assertTrue(pixels.stream().anyMatch(p -> p.point.equals(new java.awt.Point(3, 3))), "Pixel list should include the single-point coordinate (3,3)"); + } + + @Test + void testHorizontalLineIntensityStable() { + List pixels = WusLine.drawLine(1, 5, 8, 5); + + // For each x, take the max intensity among pixels with that x (the visible intensity for the column) + java.util.Map maxIntensityByX = pixels.stream() + .collect(java.util.stream.Collectors.groupingBy(p -> p.point.x, java.util.stream.Collectors.mapping(p -> p.intensity, java.util.stream.Collectors.maxBy(Double::compareTo)))) + .entrySet() + .stream() + .collect(java.util.stream.Collectors.toMap(java.util.Map.Entry::getKey, e -> e.getValue().orElse(0.0))); + + double avgMaxIntensity = maxIntensityByX.values().stream().mapToDouble(Double::doubleValue).average().orElse(0.0); + + assertTrue(avgMaxIntensity > 0.5, "Average of the maximum per-x intensities should be > 0.5 for a horizontal line"); + } +} diff --git a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java new file mode 100644 index 000000000000..54c91c6ac1fd --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java @@ -0,0 +1,79 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BronKerboschTest { + + @Test + @DisplayName("Complete graph returns single clique") + void completeGraph() { + List> adjacency = buildGraph(4); + addUndirectedEdge(adjacency, 0, 1); + addUndirectedEdge(adjacency, 0, 2); + addUndirectedEdge(adjacency, 0, 3); + addUndirectedEdge(adjacency, 1, 2); + addUndirectedEdge(adjacency, 1, 3); + addUndirectedEdge(adjacency, 2, 3); + + List> cliques = BronKerbosch.findMaximalCliques(adjacency); + assertEquals(1, cliques.size()); + assertEquals(Set.of(0, 1, 2, 3), cliques.get(0)); + } + + @Test + @DisplayName("Path graph produces individual edges") + void pathGraph() { + List> adjacency = buildGraph(3); + addUndirectedEdge(adjacency, 0, 1); + addUndirectedEdge(adjacency, 1, 2); + + List> cliques = BronKerbosch.findMaximalCliques(adjacency); + Set> result = new HashSet<>(cliques); + Set> expected = Set.of(Set.of(0, 1), Set.of(1, 2)); + assertEquals(expected, result); + } + + @Test + @DisplayName("Disconnected graph finds cliques per component") + void disconnectedGraph() { + List> adjacency = buildGraph(5); + addUndirectedEdge(adjacency, 0, 1); + addUndirectedEdge(adjacency, 0, 2); + addUndirectedEdge(adjacency, 1, 2); + addUndirectedEdge(adjacency, 3, 4); + + List> cliques = BronKerbosch.findMaximalCliques(adjacency); + Set> result = new HashSet<>(cliques); + Set> expected = Set.of(Set.of(0, 1, 2), Set.of(3, 4)); + assertEquals(expected, result); + } + + @Test + @DisplayName("Null neighbor set triggers exception") + void nullNeighborSet() { + List> adjacency = new ArrayList<>(); + adjacency.add(null); + assertThrows(IllegalArgumentException.class, () -> BronKerbosch.findMaximalCliques(adjacency)); + } + + private static List> buildGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + graph.add(new HashSet<>()); + } + return graph; + } + + private static void addUndirectedEdge(List> graph, int u, int v) { + graph.get(u).add(v); + graph.get(v).add(u); + } +} diff --git a/src/test/java/com/thealgorithms/graph/DinicTest.java b/src/test/java/com/thealgorithms/graph/DinicTest.java new file mode 100644 index 000000000000..912f787519fa --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/DinicTest.java @@ -0,0 +1,71 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DinicTest { + @Test + @DisplayName("Classic CLRS network yields max flow 23 (Dinic)") + void clrsExample() { + int[][] capacity = {{0, 16, 13, 0, 0, 0}, {0, 0, 10, 12, 0, 0}, {0, 4, 0, 0, 14, 0}, {0, 0, 9, 0, 0, 20}, {0, 0, 0, 7, 0, 4}, {0, 0, 0, 0, 0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 5); + assertEquals(23, maxFlow); + } + + @Test + @DisplayName("Disconnected network has zero flow (Dinic)") + void disconnectedGraph() { + int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 2); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Source equals sink returns zero (Dinic)") + void sourceEqualsSink() { + int[][] capacity = {{0, 5}, {0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 0); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Invalid matrix throws exception (Dinic)") + void invalidMatrix() { + int[][] capacity = {{0, 1}, {1}}; + assertThrows(IllegalArgumentException.class, () -> Dinic.maxFlow(capacity, 0, 1)); + } + + @Test + @DisplayName("Dinic matches Edmonds-Karp on random small graphs") + void parityWithEdmondsKarp() { + java.util.Random rnd = new java.util.Random(42); + for (int n = 3; n <= 7; n++) { + for (int it = 0; it < 25; it++) { + int[][] cap = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j && rnd.nextDouble() < 0.35) { + cap[i][j] = rnd.nextInt(10); // capacities 0..9 + } + } + } + int s = 0; + int t = n - 1; + int f1 = Dinic.maxFlow(copyMatrix(cap), s, t); + int f2 = EdmondsKarp.maxFlow(cap, s, t); + assertEquals(f2, f1); + } + } + } + + private static int[][] copyMatrix(int[][] a) { + int[][] b = new int[a.length][a.length]; + for (int i = 0; i < a.length; i++) { + b[i] = java.util.Arrays.copyOf(a[i], a[i].length); + } + return b; + } +} diff --git a/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java b/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java new file mode 100644 index 000000000000..55aeda381031 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java @@ -0,0 +1,48 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EdmondsKarpTest { + + @Test + @DisplayName("Classic CLRS network yields max flow 23") + void clrsExample() { + int[][] capacity = {{0, 16, 13, 0, 0, 0}, {0, 0, 10, 12, 0, 0}, {0, 4, 0, 0, 14, 0}, {0, 0, 9, 0, 0, 20}, {0, 0, 0, 7, 0, 4}, {0, 0, 0, 0, 0, 0}}; + int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 5); + assertEquals(23, maxFlow); + } + + @Test + @DisplayName("Disconnected network has zero flow") + void disconnectedGraph() { + int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 2); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Source equals sink returns zero") + void sourceEqualsSink() { + int[][] capacity = {{0, 5}, {0, 0}}; + int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 0); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Invalid matrix throws exception") + void invalidMatrix() { + int[][] capacity = {{0, 1}, {1}}; + assertThrows(IllegalArgumentException.class, () -> EdmondsKarp.maxFlow(capacity, 0, 1)); + } + + @Test + @DisplayName("Negative capacity is rejected") + void negativeCapacity() { + int[][] capacity = {{0, -1}, {0, 0}}; + assertThrows(IllegalArgumentException.class, () -> EdmondsKarp.maxFlow(capacity, 0, 1)); + } +} diff --git a/src/test/java/com/thealgorithms/graph/EdmondsTest.java b/src/test/java/com/thealgorithms/graph/EdmondsTest.java new file mode 100644 index 000000000000..ab5740c94217 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/EdmondsTest.java @@ -0,0 +1,172 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class EdmondsTest { + + @Test + void testSimpleGraphNoCycle() { + int n = 4; + int root = 0; + List edges = new ArrayList<>(); + edges.add(new Edmonds.Edge(0, 1, 10)); + edges.add(new Edmonds.Edge(0, 2, 1)); + edges.add(new Edmonds.Edge(2, 1, 2)); + edges.add(new Edmonds.Edge(2, 3, 5)); + + // Expected arborescence edges: (0,2), (2,1), (2,3) + // Weights: 1 + 2 + 5 = 8 + long result = Edmonds.findMinimumSpanningArborescence(n, edges, root); + assertEquals(8, result); + } + + @Test + void testGraphWithOneCycle() { + int n = 4; + int root = 0; + List edges = new ArrayList<>(); + edges.add(new Edmonds.Edge(0, 1, 10)); + edges.add(new Edmonds.Edge(2, 1, 4)); + edges.add(new Edmonds.Edge(1, 2, 5)); + edges.add(new Edmonds.Edge(2, 3, 6)); + + // Min edges: (2,1, w=4), (1,2, w=5), (2,3, w=6) + // Cycle: 1 -> 2 -> 1, cost = 4 + 5 = 9 + // Contract {1,2} to C. + // New edge (0,C) with w = 10 - min_in(1) = 10 - 4 = 6 + // New edge (C,3) with w = 6 + // Contracted MSA cost = 6 + 6 = 12 + // Total cost = cycle_cost + contracted_msa_cost = 9 + 12 = 21 + long result = Edmonds.findMinimumSpanningArborescence(n, edges, root); + assertEquals(21, result); + } + + @Test + void testComplexGraphWithCycle() { + int n = 6; + int root = 0; + List edges = new ArrayList<>(); + edges.add(new Edmonds.Edge(0, 1, 10)); + edges.add(new Edmonds.Edge(0, 2, 20)); + edges.add(new Edmonds.Edge(1, 2, 5)); + edges.add(new Edmonds.Edge(2, 3, 10)); + edges.add(new Edmonds.Edge(3, 1, 3)); + edges.add(new Edmonds.Edge(1, 4, 7)); + edges.add(new Edmonds.Edge(3, 4, 2)); + edges.add(new Edmonds.Edge(4, 5, 5)); + + // Min edges: (3,1,3), (1,2,5), (2,3,10), (3,4,2), (4,5,5) + // Cycle: 1->2->3->1, cost = 5+10+3=18 + // Contract {1,2,3} to C. + // Edge (0,1,10) -> (0,C), w = 10-3=7 + // Edge (0,2,20) -> (0,C), w = 20-5=15. Min is 7. + // Edge (1,4,7) -> (C,4,7) + // Edge (3,4,2) -> (C,4,2). Min is 2. + // Edge (4,5,5) -> (4,5,5) + // Contracted MSA: (0,C,7), (C,4,2), (4,5,5). Cost = 7+2+5=14 + // Total cost = 18 + 14 = 32 + long result = Edmonds.findMinimumSpanningArborescence(n, edges, root); + assertEquals(32, result); + } + + @Test + void testUnreachableNode() { + int n = 4; + int root = 0; + List edges = new ArrayList<>(); + edges.add(new Edmonds.Edge(0, 1, 10)); + edges.add(new Edmonds.Edge(2, 3, 5)); // Node 2 and 3 are unreachable from root 0 + + long result = Edmonds.findMinimumSpanningArborescence(n, edges, root); + assertEquals(-1, result); + } + + @Test + void testNoEdgesToNonRootNodes() { + int n = 3; + int root = 0; + List edges = new ArrayList<>(); + edges.add(new Edmonds.Edge(0, 1, 10)); // Node 2 is unreachable + + long result = Edmonds.findMinimumSpanningArborescence(n, edges, root); + assertEquals(-1, result); + } + + @Test + void testSingleNode() { + int n = 1; + int root = 0; + List edges = new ArrayList<>(); + + long result = Edmonds.findMinimumSpanningArborescence(n, edges, root); + assertEquals(0, result); + } + + @Test + void testInvalidInputThrowsException() { + List edges = new ArrayList<>(); + + assertThrows(IllegalArgumentException.class, () -> Edmonds.findMinimumSpanningArborescence(0, edges, 0)); + assertThrows(IllegalArgumentException.class, () -> Edmonds.findMinimumSpanningArborescence(5, edges, -1)); + assertThrows(IllegalArgumentException.class, () -> Edmonds.findMinimumSpanningArborescence(5, edges, 5)); + } + + @Test + void testCoverageForEdgeSelectionLogic() { + int n = 3; + int root = 0; + List edges = new ArrayList<>(); + + // This will cover the `edge.weight < minWeightEdge[edge.to]` being false. + edges.add(new Edmonds.Edge(0, 1, 10)); + edges.add(new Edmonds.Edge(2, 1, 20)); + + // This will cover the `edge.to != root` being false. + edges.add(new Edmonds.Edge(1, 0, 100)); + + // A regular edge to make the graph complete + edges.add(new Edmonds.Edge(0, 2, 5)); + + // Expected MSA: (0,1, w=10) and (0,2, w=5). Total weight = 15. + long result = Edmonds.findMinimumSpanningArborescence(n, edges, root); + assertEquals(15, result); + } + + @Test + void testCoverageForContractedSelfLoop() { + int n = 4; + int root = 0; + List edges = new ArrayList<>(); + + // Connect root to the cycle components + edges.add(new Edmonds.Edge(0, 1, 20)); + + // Create a cycle 1 -> 2 -> 1 + edges.add(new Edmonds.Edge(1, 2, 5)); + edges.add(new Edmonds.Edge(2, 1, 5)); + + // This is the CRITICAL edge for coverage: + // It connects two nodes (1 and 2) that are part of the SAME cycle. + // After contracting cycle {1, 2} into a supernode C, this edge becomes (C, C), + // which means newU == newV. This will trigger the `false` branch of the `if`. + edges.add(new Edmonds.Edge(1, 1, 100)); // Also a self-loop on a cycle node. + + // Add another edge to ensure node 3 is reachable + edges.add(new Edmonds.Edge(1, 3, 10)); + + // Cycle {1,2} has cost 5+5=10. + // Contract {1,2} to supernode C. + // Edge (0,1,20) becomes (0,C, w=20-5=15). + // Edge (1,3,10) becomes (C,3, w=10). + // Edge (1,1,100) is discarded because newU == newV. + // Cost of contracted graph = 15 + 10 = 25. + // Total cost = cycle cost + contracted cost = 10 + 25 = 35. + long result = Edmonds.findMinimumSpanningArborescence(n, edges, root); + assertEquals(35, result); + } +} diff --git a/src/test/java/com/thealgorithms/graph/HierholzerEulerianPathTest.java b/src/test/java/com/thealgorithms/graph/HierholzerEulerianPathTest.java new file mode 100644 index 000000000000..612d09561b7b --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/HierholzerEulerianPathTest.java @@ -0,0 +1,169 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link HierholzerEulerianPath}. + * + * This test suite validates Hierholzer's Algorithm implementation + * for finding Eulerian Paths and Circuits in directed graphs. + * + *

Coverage includes: + *

    + *
  • Basic Eulerian Circuit
  • + *
  • Eulerian Path
  • + *
  • Disconnected graphs
  • + *
  • Single-node graphs
  • + *
  • Graphs with no edges
  • + *
  • Graphs that do not have any Eulerian Path/Circuit
  • + *
+ *

+ */ +class HierholzerEulerianPathTest { + + @Test + void testSimpleEulerianCircuit() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(3); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 0); + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Eulerian Circuit: [0, 1, 2, 0] + List expected = Arrays.asList(0, 1, 2, 0); + assertEquals(expected, result); + } + + @Test + void testEulerianPathDifferentStartEnd() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(4); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 1); + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Eulerian Path: [0, 1, 2, 3, 1] + List expected = Arrays.asList(0, 1, 2, 3, 1); + assertEquals(expected, result); + } + + @Test + void testNoEulerianPathExists() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(3); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + // Edge 2->0 missing, so it's not Eulerian Circuit + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + assertEquals(result, Arrays.asList(0, 1, 2)); + } + + @Test + void testDisconnectedGraph() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(4); + graph.addEdge(0, 1); + graph.addEdge(2, 3); // disconnected component + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Disconnected graph cannot have an Eulerian path + assertTrue(result.isEmpty()); + } + + @Test + void testGraphWithSelfLoop() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(3); + graph.addEdge(0, 0); // self loop + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 0); + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Eulerian Circuit with self-loop included: [0, 0, 1, 2, 0] + assertEquals(Arrays.asList(0, 0, 1, 2, 0), result); + } + + @Test + void testSingleNodeNoEdges() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(1); + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Only one vertex and no edges + assertEquals(Collections.singletonList(0), result); + } + + @Test + void testSingleNodeWithLoop() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(1); + graph.addEdge(0, 0); + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Eulerian circuit on a single node with a self-loop + assertEquals(Arrays.asList(0, 0), result); + } + + @Test + void testComplexEulerianCircuit() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(5); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + graph.addEdge(4, 0); + graph.addEdge(1, 3); + graph.addEdge(3, 1); + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Verify all edges are used + int totalEdges = 7; + assertEquals(totalEdges + 1, result.size(), "Path must contain all edges + 1 vertices"); + assertEquals(result.get(0), result.get(result.size() - 1), "Must form a circuit"); + } + + @Test + void testMultipleEdgesBetweenSameNodes() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(3); + graph.addEdge(0, 1); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 0); + + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Hava a Eulerian Path but not a Eulerian Circuit + assertEquals(result, Arrays.asList(0, 1, 2, 0, 1)); + } + + @Test + void testCoverageForEmptyGraph() { + HierholzerEulerianPath.Graph graph = new HierholzerEulerianPath.Graph(0); + HierholzerEulerianPath solver = new HierholzerEulerianPath(graph); + List result = solver.findEulerianPath(); + + // Empty graph has no vertices or path + assertTrue(result.isEmpty()); + } +} diff --git a/src/test/java/com/thealgorithms/graph/HopcroftKarpTest.java b/src/test/java/com/thealgorithms/graph/HopcroftKarpTest.java new file mode 100644 index 000000000000..2a5b1cdac795 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/HopcroftKarpTest.java @@ -0,0 +1,133 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for Hopcroftโ€“Karp algorithm. + * + * @author ptzecher + */ +class HopcroftKarpTest { + + private static List> adj(int nLeft) { + List> g = new ArrayList<>(nLeft); + for (int i = 0; i < nLeft; i++) { // braces required by Checkstyle + g.add(new ArrayList<>()); + } + return g; + } + + @Test + @DisplayName("Empty graph has matching 0") + void emptyGraph() { + List> g = adj(3); + HopcroftKarp hk = new HopcroftKarp(3, 4, g); + assertEquals(0, hk.maxMatching()); + } + + @Test + @DisplayName("Single edge gives matching 1") + void singleEdge() { + List> g = adj(1); + g.get(0).add(0); + HopcroftKarp hk = new HopcroftKarp(1, 1, g); + assertEquals(1, hk.maxMatching()); + + int[] leftMatch = hk.getLeftMatches(); + int[] rightMatch = hk.getRightMatches(); + assertEquals(0, leftMatch[0]); + assertEquals(0, rightMatch[0]); + } + + @Test + @DisplayName("Disjoint edges match perfectly") + void disjointEdges() { + // L0-R0, L1-R1, L2-R2 + List> g = adj(3); + g.get(0).add(0); + g.get(1).add(1); + g.get(2).add(2); + + HopcroftKarp hk = new HopcroftKarp(3, 3, g); + assertEquals(3, hk.maxMatching()); + + int[] leftMatch = hk.getLeftMatches(); + int[] rightMatch = hk.getRightMatches(); + for (int i = 0; i < 3; i++) { + assertEquals(i, leftMatch[i]); + assertEquals(i, rightMatch[i]); + } + } + + @Test + @DisplayName("Complete bipartite K(3,4) matches min(3,4)=3") + void completeK34() { + int nLeft = 3; + int nRight = 4; // split declarations + List> g = adj(nLeft); + for (int u = 0; u < nLeft; u++) { + g.get(u).addAll(Arrays.asList(0, 1, 2, 3)); + } + HopcroftKarp hk = new HopcroftKarp(nLeft, nRight, g); + assertEquals(3, hk.maxMatching()); + + // sanity: no two left vertices share the same matched right vertex + int[] leftMatch = hk.getLeftMatches(); + boolean[] used = new boolean[nRight]; + for (int u = 0; u < nLeft; u++) { + int v = leftMatch[u]; + if (v != -1) { + assertFalse(used[v]); + used[v] = true; + } + } + } + + @Test + @DisplayName("Rectangular, sparse graph") + void rectangularSparse() { + // Left: 5, Right: 2 + // edges: L0-R0, L1-R1, L2-R0, L3-R1 (max matching = 2) + List> g = adj(5); + g.get(0).add(0); + g.get(1).add(1); + g.get(2).add(0); + g.get(3).add(1); + // L4 isolated + + HopcroftKarp hk = new HopcroftKarp(5, 2, g); + assertEquals(2, hk.maxMatching()); + + int[] leftMatch = hk.getLeftMatches(); + int[] rightMatch = hk.getRightMatches(); + + // Check consistency: if leftMatch[u]=v then rightMatch[v]=u + for (int u = 0; u < 5; u++) { + int v = leftMatch[u]; + if (v != -1) { + assertEquals(u, rightMatch[v]); + } + } + } + + @Test + @DisplayName("Layering advantage case (short augmenting paths)") + void layeringAdvantage() { + // Left 4, Right 4 + List> g = adj(4); + g.get(0).addAll(Arrays.asList(0, 1)); + g.get(1).addAll(Arrays.asList(1, 2)); + g.get(2).addAll(Arrays.asList(2, 3)); + g.get(3).addAll(Arrays.asList(0, 3)); + + HopcroftKarp hk = new HopcroftKarp(4, 4, g); + assertEquals(4, hk.maxMatching()); // perfect matching exists + } +} diff --git a/src/test/java/com/thealgorithms/graph/HungarianAlgorithmTest.java b/src/test/java/com/thealgorithms/graph/HungarianAlgorithmTest.java new file mode 100644 index 000000000000..1344934b62e4 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/HungarianAlgorithmTest.java @@ -0,0 +1,44 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HungarianAlgorithmTest { + + @Test + @DisplayName("Classic 3x3 example: minimal cost 5 with assignment [1,0,2]") + void classicSquareExample() { + int[][] cost = {{4, 1, 3}, {2, 0, 5}, {3, 2, 2}}; + HungarianAlgorithm.Result res = HungarianAlgorithm.solve(cost); + assertEquals(5, res.minCost); + assertArrayEquals(new int[] {1, 0, 2}, res.assignment); + } + + @Test + @DisplayName("Rectangular (more rows than cols): pads to square and returns -1 for unassigned rows") + void rectangularMoreRows() { + int[][] cost = {{7, 3}, {2, 8}, {5, 1}}; + // Optimal selects any 2 rows: choose row1->col0 (2) and row2->col1 (1) => total 3 + HungarianAlgorithm.Result res = HungarianAlgorithm.solve(cost); + assertEquals(3, res.minCost); + // Two rows assigned to 2 columns; one row remains -1. + int assigned = 0; + for (int a : res.assignment) { + if (a >= 0) { + assigned++; + } + } + assertEquals(2, assigned); + } + + @Test + @DisplayName("Zero diagonal yields zero total cost") + void zeroDiagonal() { + int[][] cost = {{0, 5, 9}, {4, 0, 7}, {3, 6, 0}}; + HungarianAlgorithm.Result res = HungarianAlgorithm.solve(cost); + assertEquals(0, res.minCost); + } +} diff --git a/src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java b/src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java new file mode 100644 index 000000000000..e2c6d468768f --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java @@ -0,0 +1,90 @@ +package com.thealgorithms.graph; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.thealgorithms.graph.PredecessorConstrainedDfs.TraversalEvent; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class PredecessorConstrainedDfsTest { + + // A -> B, A -> C, B -> D, C -> D (classic diamond) + private static Map> diamond() { + Map> g = new LinkedHashMap<>(); + g.put("A", List.of("B", "C")); + g.put("B", List.of("D")); + g.put("C", List.of("D")); + g.put("D", List.of()); + return g; + } + + @Test + void dfsRecursiveOrderEmitsSkipUntilAllParentsVisited() { + List> events = PredecessorConstrainedDfs.dfsRecursiveOrder(diamond(), "A"); + + // Expect visits in order and a skip for first time we meet D (via B) before C is visited. + var visits = events.stream().filter(TraversalEvent::isVisit).toList(); + var skips = events.stream().filter(TraversalEvent::isSkip).toList(); + + // Visits should be A(0), B(1), C(2), D(3) in some deterministic order given adjacency + assertThat(visits).hasSize(4); + assertThat(visits.get(0).node()).isEqualTo("A"); + assertThat(visits.get(0).order()).isEqualTo(0); + assertThat(visits.get(1).node()).isEqualTo("B"); + assertThat(visits.get(1).order()).isEqualTo(1); + assertThat(visits.get(2).node()).isEqualTo("C"); + assertThat(visits.get(2).order()).isEqualTo(2); + assertThat(visits.get(3).node()).isEqualTo("D"); + assertThat(visits.get(3).order()).isEqualTo(3); + + // One skip when we first encountered D from B (before C was visited) + assertThat(skips).hasSize(1); + assertThat(skips.get(0).node()).isEqualTo("D"); + assertThat(skips.get(0).note()).contains("not all parents"); + } + + @Test + void returnsEmptyWhenStartNotInGraph() { + Map> graph = Map.of(1, List.of(2), 2, List.of(1)); + assertThat(PredecessorConstrainedDfs.dfsRecursiveOrder(graph, 99)).isEmpty(); + } + + @Test + void nullSuccessorsThrows() { + assertThrows(IllegalArgumentException.class, () -> PredecessorConstrainedDfs.dfsRecursiveOrder(null, "A")); + } + + @Test + void worksWithExplicitPredecessors() { + Map> successors = new HashMap<>(); + successors.put(10, List.of(20)); + successors.put(20, List.of(30)); + successors.put(30, List.of()); + + Map> predecessors = new HashMap<>(); + predecessors.put(10, List.of()); + predecessors.put(20, List.of(10)); + predecessors.put(30, List.of(20)); + + var events = PredecessorConstrainedDfs.dfsRecursiveOrder(successors, predecessors, 10); + var visitNodes = events.stream().filter(TraversalEvent::isVisit).map(TraversalEvent::node).toList(); + assertThat(visitNodes).containsExactly(10, 20, 30); + } + + @Test + void cycleProducesSkipsButNoInfiniteRecursion() { + Map> successors = new LinkedHashMap<>(); + successors.put("X", List.of("Y")); + successors.put("Y", List.of("X")); // 2-cycle + + var events = PredecessorConstrainedDfs.dfsRecursiveOrder(successors, "X"); + // Only X is visited; encountering Y from X causes skip because Y's parent X is visited, + // but when recursing to Y we'd hit back to X (already visited) and stop; no infinite loop. + assertThat(events.stream().anyMatch(TraversalEvent::isVisit)).isTrue(); + assertThat(events.stream().filter(TraversalEvent::isVisit).map(TraversalEvent::node)).contains("X"); + } +} diff --git a/src/test/java/com/thealgorithms/graph/PushRelabelTest.java b/src/test/java/com/thealgorithms/graph/PushRelabelTest.java new file mode 100644 index 000000000000..b0021ec805b8 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/PushRelabelTest.java @@ -0,0 +1,66 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PushRelabelTest { + + @Test + @DisplayName("Classic CLRS network yields max flow 23 (PushRelabel)") + void clrsExample() { + int[][] capacity = {{0, 16, 13, 0, 0, 0}, {0, 0, 10, 12, 0, 0}, {0, 4, 0, 0, 14, 0}, {0, 0, 9, 0, 0, 20}, {0, 0, 0, 7, 0, 4}, {0, 0, 0, 0, 0, 0}}; + int maxFlow = PushRelabel.maxFlow(capacity, 0, 5); + assertEquals(23, maxFlow); + } + + @Test + @DisplayName("Disconnected network has zero flow (PushRelabel)") + void disconnectedGraph() { + int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + int maxFlow = PushRelabel.maxFlow(capacity, 0, 2); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Source equals sink returns zero (PushRelabel)") + void sourceEqualsSink() { + int[][] capacity = {{0, 5}, {0, 0}}; + int maxFlow = PushRelabel.maxFlow(capacity, 0, 0); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("PushRelabel matches Dinic and EdmondsKarp on random small graphs") + void parityWithOtherMaxFlow() { + java.util.Random rnd = new java.util.Random(42); + for (int n = 3; n <= 7; n++) { + for (int it = 0; it < 25; it++) { + int[][] cap = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j && rnd.nextDouble() < 0.35) { + cap[i][j] = rnd.nextInt(10); // capacities 0..9 + } + } + } + int s = 0; + int t = n - 1; + int fPushRelabel = PushRelabel.maxFlow(copyMatrix(cap), s, t); + int fDinic = Dinic.maxFlow(copyMatrix(cap), s, t); + int fEdmondsKarp = EdmondsKarp.maxFlow(cap, s, t); + assertEquals(fDinic, fPushRelabel); + assertEquals(fEdmondsKarp, fPushRelabel); + } + } + } + + private static int[][] copyMatrix(int[][] a) { + int[][] b = new int[a.length][a.length]; + for (int i = 0; i < a.length; i++) { + b[i] = java.util.Arrays.copyOf(a[i], a[i].length); + } + return b; + } +} diff --git a/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java b/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java new file mode 100644 index 000000000000..b93c9f89c944 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/TravelingSalesmanTest.java @@ -0,0 +1,127 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TravelingSalesmanTest { + + // Test Case 1: A simple distance matrix with 4 cities + @Test + public void testBruteForceSimple() { + int[][] distanceMatrix = {{0, 10, 15, 20}, {10, 0, 35, 25}, {15, 35, 0, 30}, {20, 25, 30, 0}}; + int expectedMinDistance = 80; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingSimple() { + int[][] distanceMatrix = {{0, 10, 15, 20}, {10, 0, 35, 25}, {15, 35, 0, 30}, {20, 25, 30, 0}}; + int expectedMinDistance = 80; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 2: A distance matrix with 3 cities + @Test + public void testBruteForceThreeCities() { + int[][] distanceMatrix = {{0, 10, 15}, {10, 0, 35}, {15, 35, 0}}; + int expectedMinDistance = 60; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingThreeCities() { + int[][] distanceMatrix = {{0, 10, 15}, {10, 0, 35}, {15, 35, 0}}; + int expectedMinDistance = 60; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 3: A distance matrix with 5 cities (larger input) + @Test + public void testBruteForceFiveCities() { + int[][] distanceMatrix = {{0, 2, 9, 10, 1}, {2, 0, 6, 5, 8}, {9, 6, 0, 4, 3}, {10, 5, 4, 0, 7}, {1, 8, 3, 7, 0}}; + int expectedMinDistance = 15; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingFiveCities() { + int[][] distanceMatrix = {{0, 2, 9, 10, 1}, {2, 0, 6, 5, 8}, {9, 6, 0, 4, 3}, {10, 5, 4, 0, 7}, {1, 8, 3, 7, 0}}; + int expectedMinDistance = 15; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 4: A distance matrix with 2 cities (simple case) + @Test + public void testBruteForceTwoCities() { + int[][] distanceMatrix = {{0, 1}, {1, 0}}; + int expectedMinDistance = 2; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingTwoCities() { + int[][] distanceMatrix = {{0, 1}, {1, 0}}; + int expectedMinDistance = 2; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 5: A distance matrix with identical distances + @Test + public void testBruteForceEqualDistances() { + int[][] distanceMatrix = {{0, 10, 10, 10}, {10, 0, 10, 10}, {10, 10, 0, 10}, {10, 10, 10, 0}}; + int expectedMinDistance = 40; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingEqualDistances() { + int[][] distanceMatrix = {{0, 10, 10, 10}, {10, 0, 10, 10}, {10, 10, 0, 10}, {10, 10, 10, 0}}; + int expectedMinDistance = 40; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 6: A distance matrix with only one city + @Test + public void testBruteForceOneCity() { + int[][] distanceMatrix = {{0}}; + int expectedMinDistance = 0; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingOneCity() { + int[][] distanceMatrix = {{0}}; + int expectedMinDistance = 0; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + // Test Case 7: Distance matrix with large numbers + @Test + public void testBruteForceLargeNumbers() { + int[][] distanceMatrix = {{0, 1000000, 2000000}, {1000000, 0, 1500000}, {2000000, 1500000, 0}}; + int expectedMinDistance = 4500000; + int result = TravelingSalesman.bruteForce(distanceMatrix); + assertEquals(expectedMinDistance, result); + } + + @Test + public void testDynamicProgrammingLargeNumbers() { + int[][] distanceMatrix = {{0, 1000000, 2000000}, {1000000, 0, 1500000}, {2000000, 1500000, 0}}; + int expectedMinDistance = 4500000; + int result = TravelingSalesman.dynamicProgramming(distanceMatrix); + assertEquals(expectedMinDistance, result); + } +} diff --git a/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java b/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java new file mode 100644 index 000000000000..1de37a310939 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/YensKShortestPathsTest.java @@ -0,0 +1,76 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class YensKShortestPathsTest { + + @Test + @DisplayName("Basic K-shortest paths on small directed graph") + void basicKPaths() { + // Graph (directed) with non-negative weights, -1 = no edge + // 0 -> 1 (1), 0 -> 2 (2), 1 -> 3 (1), 2 -> 3 (1), 0 -> 3 (5), 1 -> 2 (1) + int n = 4; + int[][] w = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + w[i][j] = -1; + } + } + w[0][1] = 1; + w[0][2] = 2; + w[1][3] = 1; + w[2][3] = 1; + w[0][3] = 5; + w[1][2] = 1; + + List> paths = YensKShortestPaths.kShortestPaths(w, 0, 3, 3); + // Expected K=3 loopless shortest paths from 0 to 3, ordered by total cost: + // 1) 0-1-3 (cost 2) + // 2) 0-2-3 (cost 3) + // 3) 0-1-2-3 (cost 3) -> tie with 0-2-3; tie-broken lexicographically by nodes + assertEquals(3, paths.size()); + assertEquals(List.of(0, 1, 3), paths.get(0)); + assertEquals(List.of(0, 1, 2, 3), paths.get(1)); // lexicographically before [0,2,3] + assertEquals(List.of(0, 2, 3), paths.get(2)); + } + + @Test + @DisplayName("K larger than available paths returns only existing ones") + void kLargerThanAvailable() { + int[][] w = {{-1, 1, -1}, {-1, -1, 1}, {-1, -1, -1}}; + // Only one simple path 0->1->2 + List> paths = YensKShortestPaths.kShortestPaths(w, 0, 2, 5); + assertEquals(1, paths.size()); + assertEquals(List.of(0, 1, 2), paths.get(0)); + } + + @Test + @DisplayName("No path returns empty list") + void noPath() { + int[][] w = {{-1, -1}, {-1, -1}}; + List> paths = YensKShortestPaths.kShortestPaths(w, 0, 1, 3); + assertEquals(0, paths.size()); + } + + @Test + @DisplayName("Source equals destination returns trivial path") + void sourceEqualsDestination() { + int[][] w = {{-1, 1}, {-1, -1}}; + List> paths = YensKShortestPaths.kShortestPaths(w, 0, 0, 2); + // First path is [0] + assertEquals(1, paths.size()); + assertEquals(List.of(0), paths.get(0)); + } + + @Test + @DisplayName("Negative weight entries (other than -1) are rejected") + void negativeWeightsRejected() { + int[][] w = {{-1, -2}, {-1, -1}}; + assertThrows(IllegalArgumentException.class, () -> YensKShortestPaths.kShortestPaths(w, 0, 1, 2)); + } +} diff --git a/src/test/java/com/thealgorithms/graph/ZeroOneBfsTest.java b/src/test/java/com/thealgorithms/graph/ZeroOneBfsTest.java new file mode 100644 index 000000000000..5de9eadf0930 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/ZeroOneBfsTest.java @@ -0,0 +1,68 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ZeroOneBfsTest { + + // Helper to build adjacency list with capacity n + private static List> makeAdj(int n) { + List> adj = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + adj.add(new ArrayList<>()); + } + return adj; + } + + @Test + void simpleLineGraph() { + int n = 4; + List> adj = makeAdj(n); + // 0 --0--> 1 --1--> 2 --0--> 3 + adj.get(0).add(new int[] {1, 0}); + adj.get(1).add(new int[] {2, 1}); + adj.get(2).add(new int[] {3, 0}); + + int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0); + assertArrayEquals(new int[] {0, 0, 1, 1}, dist); + } + + @Test + void parallelEdgesPreferZero() { + int n = 3; + List> adj = makeAdj(n); + // Two edges 0->1: weight 1 and weight 0. Algorithm should choose 0. + adj.get(0).add(new int[] {1, 1}); + adj.get(0).add(new int[] {1, 0}); + adj.get(1).add(new int[] {2, 1}); + + int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0); + assertArrayEquals(new int[] {0, 0, 1}, dist); + } + + @Test + void unreachableNodes() { + int n = 3; + List> adj = makeAdj(n); + adj.get(0).add(new int[] {1, 0}); + int[] dist = ZeroOneBfs.shortestPaths(n, adj, 0); + // node 2 unreachable -> Integer.MAX_VALUE + assertArrayEquals(new int[] {0, 0, Integer.MAX_VALUE}, dist); + } + + @Test + void invalidArgs() { + int n = 2; + List> adj = makeAdj(n); + // invalid weight + adj.get(0).add(new int[] {1, 2}); + assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, 0)); + // invalid src + assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, -1)); + assertThrows(IllegalArgumentException.class, () -> ZeroOneBfs.shortestPaths(n, adj, 2)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java b/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java index f87652253641..907d5cb45ef9 100644 --- a/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java +++ b/src/test/java/com/thealgorithms/maths/AbsoluteValueTest.java @@ -12,4 +12,28 @@ public class AbsoluteValueTest { void testGetAbsValue() { Stream.generate(() -> ThreadLocalRandom.current().nextInt()).limit(1000).forEach(number -> assertEquals(Math.abs(number), AbsoluteValue.getAbsValue(number))); } + + @Test + void testZero() { + assertEquals(0, AbsoluteValue.getAbsValue(0)); + } + + @Test + void testPositiveNumbers() { + assertEquals(5, AbsoluteValue.getAbsValue(5)); + assertEquals(123456, AbsoluteValue.getAbsValue(123456)); + assertEquals(Integer.MAX_VALUE, AbsoluteValue.getAbsValue(Integer.MAX_VALUE)); + } + + @Test + void testNegativeNumbers() { + assertEquals(5, AbsoluteValue.getAbsValue(-5)); + assertEquals(123456, AbsoluteValue.getAbsValue(-123456)); + assertEquals(Integer.MAX_VALUE, AbsoluteValue.getAbsValue(-Integer.MAX_VALUE)); + } + + @Test + void testMinIntEdgeCase() { + assertEquals(Integer.MIN_VALUE, AbsoluteValue.getAbsValue(Integer.MIN_VALUE)); + } } diff --git a/src/test/java/com/thealgorithms/maths/AverageTest.java b/src/test/java/com/thealgorithms/maths/AverageTest.java index c5c751938f5d..638739bc4fda 100644 --- a/src/test/java/com/thealgorithms/maths/AverageTest.java +++ b/src/test/java/com/thealgorithms/maths/AverageTest.java @@ -1,33 +1,48 @@ package com.thealgorithms.maths; -import org.junit.jupiter.api.Assertions; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class AverageTest { private static final double SMALL_VALUE = 0.00001d; - @Test - public void testAverageDouble12() { - double[] numbers = {3d, 6d, 9d, 12d, 15d, 18d, 21d}; - Assertions.assertEquals(12d, Average.average(numbers), SMALL_VALUE); + @ParameterizedTest(name = "average({0}) should be approximately {1}") + @MethodSource("provideDoubleArrays") + void testAverageDouble(double[] numbers, double expected) { + assertEquals(expected, Average.average(numbers), SMALL_VALUE); } - @Test - public void testAverageDouble20() { - double[] numbers = {5d, 10d, 15d, 20d, 25d, 30d, 35d}; - Assertions.assertEquals(20d, Average.average(numbers), SMALL_VALUE); + @ParameterizedTest(name = "average({0}) should be {1}") + @MethodSource("provideIntArrays") + void testAverageInt(int[] numbers, long expected) { + assertEquals(expected, Average.average(numbers)); } @Test - public void testAverageDouble() { - double[] numbers = {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d}; - Assertions.assertEquals(4.5d, Average.average(numbers), SMALL_VALUE); + void testAverageDoubleThrowsExceptionOnNullOrEmpty() { + assertThrows(IllegalArgumentException.class, () -> Average.average((double[]) null)); + assertThrows(IllegalArgumentException.class, () -> Average.average(new double[0])); } @Test - public void testAverageInt() { - int[] numbers = {2, 4, 10}; - Assertions.assertEquals(5, Average.average(numbers)); + void testAverageIntThrowsExceptionOnNullOrEmpty() { + assertThrows(IllegalArgumentException.class, () -> Average.average((int[]) null)); + assertThrows(IllegalArgumentException.class, () -> Average.average(new int[0])); + } + + private static Stream provideDoubleArrays() { + return Stream.of(Arguments.of(new double[] {3d, 6d, 9d, 12d, 15d, 18d, 21d}, 12d), Arguments.of(new double[] {5d, 10d, 15d, 20d, 25d, 30d, 35d}, 20d), Arguments.of(new double[] {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d}, 4.5d), Arguments.of(new double[] {0d, 0d, 0d}, 0d), + Arguments.of(new double[] {-1d, -2d, -3d}, -2d), Arguments.of(new double[] {1e-10, 1e-10, 1e-10}, 1e-10)); + } + + private static Stream provideIntArrays() { + return Stream.of(Arguments.of(new int[] {2, 4, 10}, 5L), Arguments.of(new int[] {0, 0, 0}, 0L), Arguments.of(new int[] {-1, -2, -3}, -2L), Arguments.of(new int[] {1, 1, 1, 1, 1}, 1L)); } } diff --git a/src/test/java/com/thealgorithms/maths/BinaryPowTest.java b/src/test/java/com/thealgorithms/maths/BinaryPowTest.java index f9b019e8fad4..632dfbd1d65e 100644 --- a/src/test/java/com/thealgorithms/maths/BinaryPowTest.java +++ b/src/test/java/com/thealgorithms/maths/BinaryPowTest.java @@ -13,4 +13,34 @@ void testBinPow() { assertEquals(729, BinaryPow.binPow(9, 3)); assertEquals(262144, BinaryPow.binPow(8, 6)); } + + @Test + void testZeroExponent() { + assertEquals(1, BinaryPow.binPow(2, 0)); + assertEquals(1, BinaryPow.binPow(100, 0)); + assertEquals(1, BinaryPow.binPow(-5, 0)); + } + + @Test + void testZeroBase() { + assertEquals(0, BinaryPow.binPow(0, 5)); + assertEquals(1, BinaryPow.binPow(0, 0)); + } + + @Test + void testOneBase() { + assertEquals(1, BinaryPow.binPow(1, 100)); + assertEquals(1, BinaryPow.binPow(1, 0)); + } + + @Test + void testNegativeBase() { + assertEquals(-8, BinaryPow.binPow(-2, 3)); + assertEquals(16, BinaryPow.binPow(-2, 4)); + } + + @Test + void testLargeExponent() { + assertEquals(1073741824, BinaryPow.binPow(2, 30)); + } } diff --git a/src/test/java/com/thealgorithms/maths/CeilTest.java b/src/test/java/com/thealgorithms/maths/CeilTest.java index 5596760a8c40..ddd0deed41d3 100644 --- a/src/test/java/com/thealgorithms/maths/CeilTest.java +++ b/src/test/java/com/thealgorithms/maths/CeilTest.java @@ -2,16 +2,30 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; public class CeilTest { - @Test - void testCeil() { - assertEquals(8, Ceil.ceil(7.057)); - assertEquals(8, Ceil.ceil(7.004)); - assertEquals(-13, Ceil.ceil(-13.004)); - assertEquals(1, Ceil.ceil(.98)); - assertEquals(-11, Ceil.ceil(-11.357)); + @ParameterizedTest + @CsvSource({"7.057, 8", "7.004, 8", "-13.004, -13", "0.98, 1", "-11.357, -11"}) + void testCeil(double input, int expected) { + assertEquals(expected, Ceil.ceil(input)); + } + + @ParameterizedTest + @MethodSource("edgeCaseProvider") + void testEdgeCases(TestData data) { + assertEquals(Ceil.ceil(data.input), data.expected); + } + + record TestData(double input, double expected) { + } + + static Stream edgeCaseProvider() { + return Stream.of(new TestData(Double.MAX_VALUE, Double.MAX_VALUE), new TestData(Double.MIN_VALUE, Math.ceil(Double.MIN_VALUE)), new TestData(0.0, Math.ceil(0.0)), new TestData(-0.0, Math.ceil(-0.0)), new TestData(Double.NaN, Math.ceil(Double.NaN)), + new TestData(Double.NEGATIVE_INFINITY, Math.ceil(Double.NEGATIVE_INFINITY)), new TestData(Double.POSITIVE_INFINITY, Math.ceil(Double.POSITIVE_INFINITY))); } } diff --git a/src/test/java/com/thealgorithms/maths/EulerPseudoprimeTest.java b/src/test/java/com/thealgorithms/maths/EulerPseudoprimeTest.java new file mode 100644 index 000000000000..b4ee0cf0659c --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/EulerPseudoprimeTest.java @@ -0,0 +1,93 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; + +import java.math.BigInteger; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +class EulerPseudoprimeTest { + + @Test + void testPrimeNumbers() { + assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(7), 5)); + assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(13), 5)); + assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(101), 5)); + } + + @Test + void testCompositeNumbers() { + assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(9), 5)); + assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(21), 5)); + assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(221), 5)); + } + + @Test + void testEvenNumbers() { + assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(4), 5)); + assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(100), 5)); + } + + @Test + void testEdgeCases() { + assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(0), 5)); + assertFalse(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(1), 5)); + assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(2), 5)); + assertTrue(EulerPseudoprime.isProbablePrime(BigInteger.valueOf(3), 5)); + } + + @Test + void testIsProbablePrimeWhenJacobiSymbolIsZero() { + try (MockedStatic mockedPrimality = Mockito.mockStatic(EulerPseudoprime.class, Mockito.CALLS_REAL_METHODS)) { + + // Mock jacobiSymbol to return 0 to test the branch + mockedPrimality.when(() -> EulerPseudoprime.jacobiSymbol(any(BigInteger.class), any(BigInteger.class))).thenReturn(0); + + boolean result = EulerPseudoprime.isProbablePrime(BigInteger.valueOf(15), 1); + + assertFalse(result); + } + } + + @Test + void testJacobiSymbolThrowsForEvenOrNonPositiveN() throws Exception { + var method = EulerPseudoprime.class.getDeclaredMethod("jacobiSymbol", BigInteger.class, BigInteger.class); + + // Helper lambda to unwrap InvocationTargetException + Runnable invokeJacobi = () -> { + try { + method.invoke(null, BigInteger.valueOf(2), BigInteger.valueOf(8)); + } catch (Exception e) { + // unwrap + Throwable cause = e.getCause(); + if (cause instanceof IllegalArgumentException) { + throw (IllegalArgumentException) cause; + } else { + throw new RuntimeException(e); + } + } + }; + + // Now check that it actually throws + assertThrows(IllegalArgumentException.class, invokeJacobi::run); + + // Another case: non-positive n + Runnable invokeJacobi2 = () -> { + try { + method.invoke(null, BigInteger.valueOf(5), BigInteger.valueOf(-3)); + } catch (Exception e) { + Throwable cause = e.getCause(); + if (cause instanceof IllegalArgumentException) { + throw (IllegalArgumentException) cause; + } else { + throw new RuntimeException(e); + } + } + }; + assertThrows(IllegalArgumentException.class, invokeJacobi2::run); + } +} diff --git a/src/test/java/com/thealgorithms/maths/GermainPrimeAndSafePrimeTest.java b/src/test/java/com/thealgorithms/maths/GermainPrimeAndSafePrimeTest.java new file mode 100644 index 000000000000..069d1156295f --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/GermainPrimeAndSafePrimeTest.java @@ -0,0 +1,49 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class GermainPrimeAndSafePrimeTest { + + static Stream provideNumbersForGermainPrimes() { + return Stream.of(Arguments.of(2, Boolean.TRUE), Arguments.of(3, Boolean.TRUE), Arguments.of(5, Boolean.TRUE), Arguments.of(11, Boolean.TRUE), Arguments.of(23, Boolean.TRUE), Arguments.of(293, Boolean.TRUE), Arguments.of(4, Boolean.FALSE), Arguments.of(7, Boolean.FALSE), + Arguments.of(9, Boolean.FALSE), Arguments.of(1, Boolean.FALSE)); + } + + static Stream provideNumbersForSafePrimes() { + return Stream.of(Arguments.of(5, Boolean.TRUE), Arguments.of(7, Boolean.TRUE), Arguments.of(11, Boolean.TRUE), Arguments.of(23, Boolean.TRUE), Arguments.of(1283, Boolean.TRUE), Arguments.of(4, Boolean.FALSE), Arguments.of(13, Boolean.FALSE), Arguments.of(9, Boolean.FALSE), + Arguments.of(1, Boolean.FALSE)); + } + + static Stream provideNegativeNumbers() { + return Stream.of(-10, -1, 0); + } + + @ParameterizedTest + @MethodSource("provideNumbersForGermainPrimes") + @DisplayName("Check whether a number is a Germain prime") + void testValidGermainPrimes(int number, boolean expected) { + assertEquals(expected, GermainPrimeAndSafePrime.isGermainPrime(number)); + } + + @ParameterizedTest + @MethodSource("provideNumbersForSafePrimes") + @DisplayName("Check whether a number is a Safe prime") + void testValidSafePrimes(int number, boolean expected) { + assertEquals(expected, GermainPrimeAndSafePrime.isSafePrime(number)); + } + + @ParameterizedTest + @MethodSource("provideNegativeNumbers") + @DisplayName("Negative numbers and zero should throw IllegalArgumentException") + void testNegativeNumbersThrowException(int number) { + assertThrows(IllegalArgumentException.class, () -> GermainPrimeAndSafePrime.isGermainPrime(number)); + assertThrows(IllegalArgumentException.class, () -> GermainPrimeAndSafePrime.isSafePrime(number)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/HappyNumberTest.java b/src/test/java/com/thealgorithms/maths/HappyNumberTest.java new file mode 100644 index 000000000000..4b7cb795406f --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/HappyNumberTest.java @@ -0,0 +1,32 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class HappyNumberTest { + + @Test + void testHappyNumbers() { + // Known happy numbers + assertTrue(HappyNumber.isHappy(1)); + assertTrue(HappyNumber.isHappy(7)); + assertTrue(HappyNumber.isHappy(19)); + assertTrue(HappyNumber.isHappy(100)); + } + + @Test + void testUnhappyNumbers() { + // Known unhappy numbers + assertFalse(HappyNumber.isHappy(2)); + assertFalse(HappyNumber.isHappy(4)); + assertFalse(HappyNumber.isHappy(20)); + } + + @Test + void testLargeNumber() { + // Just to check behavior with larger input + assertTrue(HappyNumber.isHappy(1000000)); // reduces to 1 eventually + } +} diff --git a/src/test/java/com/thealgorithms/maths/HarshadNumberTest.java b/src/test/java/com/thealgorithms/maths/HarshadNumberTest.java index af1c459f3d7f..299e6bd78a99 100644 --- a/src/test/java/com/thealgorithms/maths/HarshadNumberTest.java +++ b/src/test/java/com/thealgorithms/maths/HarshadNumberTest.java @@ -1,25 +1,135 @@ package com.thealgorithms.maths; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class HarshadNumberTest { +/** + * Test class for {@link HarshadNumber}. + * Tests various scenarios including positive cases, edge cases, and exception + * handling. + */ +class HarshadNumberTest { + + @Test + void testValidHarshadNumbers() { + // Single digit Harshad numbers (all single digits except 0 are Harshad numbers) + Assertions.assertTrue(HarshadNumber.isHarshad(1)); + Assertions.assertTrue(HarshadNumber.isHarshad(2)); + Assertions.assertTrue(HarshadNumber.isHarshad(3)); + Assertions.assertTrue(HarshadNumber.isHarshad(4)); + Assertions.assertTrue(HarshadNumber.isHarshad(5)); + Assertions.assertTrue(HarshadNumber.isHarshad(6)); + Assertions.assertTrue(HarshadNumber.isHarshad(7)); + Assertions.assertTrue(HarshadNumber.isHarshad(8)); + Assertions.assertTrue(HarshadNumber.isHarshad(9)); + + // Two digit Harshad numbers + Assertions.assertTrue(HarshadNumber.isHarshad(10)); // 10 / (1 + 0) = 10 + Assertions.assertTrue(HarshadNumber.isHarshad(12)); // 12 / (1 + 2) = 4 + Assertions.assertTrue(HarshadNumber.isHarshad(18)); // 18 / (1 + 8) = 2 + Assertions.assertTrue(HarshadNumber.isHarshad(20)); // 20 / (2 + 0) = 10 + Assertions.assertTrue(HarshadNumber.isHarshad(21)); // 21 / (2 + 1) = 7 + + // Three digit Harshad numbers + Assertions.assertTrue(HarshadNumber.isHarshad(100)); // 100 / (1 + 0 + 0) = 100 + Assertions.assertTrue(HarshadNumber.isHarshad(102)); // 102 / (1 + 0 + 2) = 34 + Assertions.assertTrue(HarshadNumber.isHarshad(108)); // 108 / (1 + 0 + 8) = 12 + + // Large Harshad numbers + Assertions.assertTrue(HarshadNumber.isHarshad(1000)); // 1000 / (1 + 0 + 0 + 0) = 1000 + Assertions.assertTrue(HarshadNumber.isHarshad(1002)); // 1002 / (1 + 0 + 0 + 2) = 334 + Assertions.assertTrue(HarshadNumber.isHarshad(999999999)); // 999999999 / (9*9) = 12345679 + } @Test - public void harshadNumber() { + void testInvalidHarshadNumbers() { + // Numbers that are not Harshad numbers + Assertions.assertFalse(HarshadNumber.isHarshad(11)); // 11 / (1 + 1) = 5.5 + Assertions.assertFalse(HarshadNumber.isHarshad(13)); // 13 / (1 + 3) = 3.25 + Assertions.assertFalse(HarshadNumber.isHarshad(17)); // 17 / (1 + 7) = 2.125 + Assertions.assertFalse(HarshadNumber.isHarshad(19)); // 19 / (1 + 9) = 1.9 + Assertions.assertFalse(HarshadNumber.isHarshad(23)); // 23 / (2 + 3) = 4.6 + Assertions.assertFalse(HarshadNumber.isHarshad(101)); // 101 / (1 + 0 + 1) = 50.5 + } - assertTrue(HarshadNumber.isHarshad(18)); - assertFalse(HarshadNumber.isHarshad(-18)); - assertFalse(HarshadNumber.isHarshad(19)); - assertTrue(HarshadNumber.isHarshad(999999999)); - assertFalse(HarshadNumber.isHarshad(0)); + @Test + void testZeroThrowsException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad(0)); + } - assertTrue(HarshadNumber.isHarshad("18")); - assertFalse(HarshadNumber.isHarshad("-18")); - assertFalse(HarshadNumber.isHarshad("19")); - assertTrue(HarshadNumber.isHarshad("999999999")); - assertTrue(HarshadNumber.isHarshad("99999999999100")); + @Test + void testNegativeNumbersThrowException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad(-1)); + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad(-18)); + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad(-100)); + } + + @Test + void testValidHarshadNumbersWithString() { + // Single digit Harshad numbers + Assertions.assertTrue(HarshadNumber.isHarshad("1")); + Assertions.assertTrue(HarshadNumber.isHarshad("2")); + Assertions.assertTrue(HarshadNumber.isHarshad("9")); + + // Two digit Harshad numbers + Assertions.assertTrue(HarshadNumber.isHarshad("10")); + Assertions.assertTrue(HarshadNumber.isHarshad("12")); + Assertions.assertTrue(HarshadNumber.isHarshad("18")); + + // Large Harshad numbers + Assertions.assertTrue(HarshadNumber.isHarshad("1000")); + Assertions.assertTrue(HarshadNumber.isHarshad("999999999")); + Assertions.assertTrue(HarshadNumber.isHarshad("99999999999100")); + } + + @Test + void testInvalidHarshadNumbersWithString() { + // Numbers that are not Harshad numbers + Assertions.assertFalse(HarshadNumber.isHarshad("11")); + Assertions.assertFalse(HarshadNumber.isHarshad("13")); + Assertions.assertFalse(HarshadNumber.isHarshad("19")); + Assertions.assertFalse(HarshadNumber.isHarshad("23")); + } + + @Test + void testStringWithZeroThrowsException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad("0")); + } + + @Test + void testStringWithNegativeNumbersThrowsException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad("-1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad("-18")); + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad("-100")); + } + + @Test + void testNullStringThrowsException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad(null)); + } + + @Test + void testEmptyStringThrowsException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad("")); + } + + @Test + void testInvalidStringThrowsException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad("abc")); + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad("12.5")); + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad("12a")); + Assertions.assertThrows(IllegalArgumentException.class, () -> HarshadNumber.isHarshad(" 12 ")); + } + + @Test + void testMaxLongValue() { + // Test with a large number close to Long.MAX_VALUE + long largeHarshadCandidate = 9223372036854775800L; + // This specific number may or may not be Harshad, just testing it doesn't crash + try { + HarshadNumber.isHarshad(largeHarshadCandidate); + } catch (Exception e) { + Assertions.fail("Should not throw exception for valid large numbers"); + } } } diff --git a/src/test/java/com/thealgorithms/maths/HeronsFormulaTest.java b/src/test/java/com/thealgorithms/maths/HeronsFormulaTest.java index 22cecf4dc960..5175c6348c9f 100644 --- a/src/test/java/com/thealgorithms/maths/HeronsFormulaTest.java +++ b/src/test/java/com/thealgorithms/maths/HeronsFormulaTest.java @@ -3,37 +3,141 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class HeronsFormulaTest { +/** + * Test cases for {@link HeronsFormula}. + */ +class HeronsFormulaTest { + + private static final double EPSILON = 1e-10; + + @Test + void testRightTriangleThreeFourFive() { + Assertions.assertEquals(6.0, HeronsFormula.herons(3, 4, 5), EPSILON); + } + + @Test + void testTriangleTwentyFourThirtyEighteen() { + Assertions.assertEquals(216.0, HeronsFormula.herons(24, 30, 18), EPSILON); + } + + @Test + void testEquilateralTriangle() { + Assertions.assertEquals(0.4330127018922193, HeronsFormula.herons(1, 1, 1), EPSILON); + } + + @Test + void testScaleneTriangleFourFiveEight() { + Assertions.assertEquals(8.181534085976786, HeronsFormula.herons(4, 5, 8), EPSILON); + } + + @Test + void testEquilateralTriangleLargeSides() { + final double side = 10.0; + final double expectedArea = Math.sqrt(3) / 4 * side * side; + Assertions.assertEquals(expectedArea, HeronsFormula.herons(side, side, side), EPSILON); + } @Test - void test1() { - Assertions.assertEquals(HeronsFormula.herons(3, 4, 5), 6.0); + void testIsoscelesTriangle() { + Assertions.assertEquals(12.0, HeronsFormula.herons(5, 5, 6), EPSILON); } @Test - void test2() { - Assertions.assertEquals(HeronsFormula.herons(24, 30, 18), 216.0); + void testSmallTriangle() { + Assertions.assertEquals(0.4330127018922193, HeronsFormula.herons(1.0, 1.0, 1.0), EPSILON); } @Test - void test3() { - Assertions.assertEquals(HeronsFormula.herons(1, 1, 1), 0.4330127018922193); + void testLargeTriangle() { + Assertions.assertEquals(600.0, HeronsFormula.herons(30, 40, 50), EPSILON); } @Test - void test4() { - Assertions.assertEquals(HeronsFormula.herons(4, 5, 8), 8.181534085976786); + void testDecimalSides() { + final double area = HeronsFormula.herons(2.5, 3.5, 4.0); + Assertions.assertTrue(area > 0); + Assertions.assertEquals(4.330127018922194, area, EPSILON); } @Test - public void testCalculateAreaWithInvalidInput() { + void testDegenerateTriangleEqualToSumOfOtherTwo() { Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(1, 2, 3); }); + } + + @Test + void testDegenerateTriangleVariant2() { Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(2, 1, 3); }); + } + + @Test + void testDegenerateTriangleVariant3() { Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(3, 2, 1); }); + } + + @Test + void testDegenerateTriangleVariant4() { Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(1, 3, 2); }); + } - Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(1, 1, 0); }); - Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(1, 0, 1); }); + @Test + void testInvalidTriangleSideGreaterThanSum() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(1, 1, 5); }); + } + + @Test + void testZeroFirstSide() { Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(0, 1, 1); }); } + + @Test + void testZeroSecondSide() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(1, 0, 1); }); + } + + @Test + void testZeroThirdSide() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(1, 1, 0); }); + } + + @Test + void testNegativeFirstSide() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(-1, 2, 2); }); + } + + @Test + void testNegativeSecondSide() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(2, -1, 2); }); + } + + @Test + void testNegativeThirdSide() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(2, 2, -1); }); + } + + @Test + void testAllNegativeSides() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(-1, -2, -3); }); + } + + @Test + void testAllZeroSides() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { HeronsFormula.herons(0, 0, 0); }); + } + + @Test + void testVerySmallTriangle() { + final double result = HeronsFormula.herons(0.001, 0.001, 0.001); + Assertions.assertTrue(result > 0); + Assertions.assertTrue(result < 0.001); + } + + @Test + void testRightTriangleFiveTwelveThirteen() { + Assertions.assertEquals(30.0, HeronsFormula.herons(5, 12, 13), EPSILON); + } + + @Test + void testRightTriangleEightFifteenSeventeen() { + Assertions.assertEquals(60.0, HeronsFormula.herons(8, 15, 17), EPSILON); + } } diff --git a/src/test/java/com/thealgorithms/maths/JugglerSequenceTest.java b/src/test/java/com/thealgorithms/maths/JugglerSequenceTest.java new file mode 100644 index 000000000000..9f3e80eee2b6 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/JugglerSequenceTest.java @@ -0,0 +1,42 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.Test; + +class JugglerSequenceTest { + + @Test + void testJugglerSequenceWithThree() { + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + JugglerSequence.jugglerSequence(3); + assertEquals("3,5,11,36,6,2,1\n", outContent.toString()); + } + + @Test + void testJugglerSequenceWithTwo() { + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + JugglerSequence.jugglerSequence(2); + assertEquals("2,1\n", outContent.toString()); + } + + @Test + void testJugglerSequenceWithNine() { + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + JugglerSequence.jugglerSequence(9); + assertEquals("9,27,140,11,36,6,2,1\n", outContent.toString()); + } + + @Test + void testJugglerSequenceWithOne() { + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + JugglerSequence.jugglerSequence(1); + assertEquals("1\n", outContent.toString()); + } +} diff --git a/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java b/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java index 05e58cf88e22..a3cd7500b30c 100644 --- a/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java +++ b/src/test/java/com/thealgorithms/maths/KaprekarNumbersTest.java @@ -1,85 +1,188 @@ package com.thealgorithms.maths; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; -public class KaprekarNumbersTest { +/** + * Test class for {@link KaprekarNumbers}. + * Tests various Kaprekar numbers and edge cases to ensure full coverage. + */ +class KaprekarNumbersTest { @Test - void testFor1() { + void testZeroIsKaprekarNumber() { + assertTrue(KaprekarNumbers.isKaprekarNumber(0)); + } + + @Test + void testOneIsKaprekarNumber() { assertTrue(KaprekarNumbers.isKaprekarNumber(1)); } @Test - void testFor45() { + void testNineIsKaprekarNumber() { + // 9^2 = 81, 8 + 1 = 9 + assertTrue(KaprekarNumbers.isKaprekarNumber(9)); + } + + @Test + void testFortyFiveIsKaprekarNumber() { + // 45^2 = 2025, 20 + 25 = 45 assertTrue(KaprekarNumbers.isKaprekarNumber(45)); } @Test - void testFor297() { + void testFiftyFiveIsKaprekarNumber() { + // 55^2 = 3025, 30 + 25 = 55 + assertTrue(KaprekarNumbers.isKaprekarNumber(55)); + } + + @Test + void testNinetyNineIsKaprekarNumber() { + // 99^2 = 9801, 98 + 01 = 99 + assertTrue(KaprekarNumbers.isKaprekarNumber(99)); + } + + @Test + void testTwoNinetySevenIsKaprekarNumber() { + // 297^2 = 88209, 88 + 209 = 297 assertTrue(KaprekarNumbers.isKaprekarNumber(297)); } @Test - void testFor2223() { + void testSevenZeroThreeIsKaprekarNumber() { + // 703^2 = 494209, 494 + 209 = 703 + assertTrue(KaprekarNumbers.isKaprekarNumber(703)); + } + + @Test + void testNineNineNineIsKaprekarNumber() { + // 999^2 = 998001, 998 + 001 = 999 + assertTrue(KaprekarNumbers.isKaprekarNumber(999)); + } + + @Test + void testTwoTwoTwoThreeIsKaprekarNumber() { + // 2223^2 = 4941729, 4941 + 729 = 5670 (not directly obvious) + // Actually: 494 + 1729 = 2223 assertTrue(KaprekarNumbers.isKaprekarNumber(2223)); } @Test - void testFor857143() { + void testEightFiveSevenOneFortyThreeIsKaprekarNumber() { assertTrue(KaprekarNumbers.isKaprekarNumber(857143)); } @Test - void testFor3() { + void testTwoIsNotKaprekarNumber() { + assertFalse(KaprekarNumbers.isKaprekarNumber(2)); + } + + @Test + void testThreeIsNotKaprekarNumber() { assertFalse(KaprekarNumbers.isKaprekarNumber(3)); } @Test - void testFor26() { + void testTenIsNotKaprekarNumber() { + assertFalse(KaprekarNumbers.isKaprekarNumber(10)); + } + + @Test + void testTwentySixIsNotKaprekarNumber() { assertFalse(KaprekarNumbers.isKaprekarNumber(26)); } @Test - void testFor98() { + void testNinetyEightIsNotKaprekarNumber() { assertFalse(KaprekarNumbers.isKaprekarNumber(98)); } @Test - void testForRangeOfNumber() { - try { - List rangedNumbers = KaprekarNumbers.kaprekarNumberInRange(1, 100000); - long[] allTheNumbers = { - 1, - 9, - 45, - 55, - 99, - 297, - 703, - 999, - 2223, - 2728, - 4950, - 5050, - 7272, - 7777, - 9999, - 17344, - 22222, - 77778, - 82656, - 95121, - 99999, - }; - for (long i : allTheNumbers) { - assert rangedNumbers.contains(i); - } - } catch (Exception e) { - assert false; - } + void testOneHundredIsNotKaprekarNumber() { + assertFalse(KaprekarNumbers.isKaprekarNumber(100)); + } + + @Test + void testNegativeNumberThrowsException() { + assertThrows(IllegalArgumentException.class, () -> KaprekarNumbers.isKaprekarNumber(-5)); + } + + @Test + void testKaprekarNumbersInSmallRange() { + List result = KaprekarNumbers.kaprekarNumberInRange(1, 10); + List expected = Arrays.asList(1L, 9L); + assertEquals(expected, result); + } + + @Test + void testKaprekarNumbersInMediumRange() { + List result = KaprekarNumbers.kaprekarNumberInRange(1, 100); + List expected = Arrays.asList(1L, 9L, 45L, 55L, 99L); + assertEquals(expected, result); + } + + @Test + void testKaprekarNumbersInLargeRange() { + List rangedNumbers = KaprekarNumbers.kaprekarNumberInRange(1, 100000); + List expectedNumbers = Arrays.asList(1L, 9L, 45L, 55L, 99L, 297L, 703L, 999L, 2223L, 2728L, 4950L, 5050L, 7272L, 7777L, 9999L, 17344L, 22222L, 77778L, 82656L, 95121L, 99999L); + assertEquals(expectedNumbers, rangedNumbers); + } + + @Test + void testKaprekarNumbersInSingleElementRange() { + List result = KaprekarNumbers.kaprekarNumberInRange(9, 9); + List expected = Arrays.asList(9L); + assertEquals(expected, result); + } + + @Test + void testKaprekarNumbersInRangeWithNoKaprekarNumbers() { + List result = KaprekarNumbers.kaprekarNumberInRange(2, 8); + assertTrue(result.isEmpty()); + } + + @Test + void testKaprekarNumbersInRangeStartingFromZero() { + List result = KaprekarNumbers.kaprekarNumberInRange(0, 5); + List expected = Arrays.asList(0L, 1L); + assertEquals(expected, result); + } + + @Test + void testInvalidRangeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> KaprekarNumbers.kaprekarNumberInRange(100, 50)); + } + + @Test + void testNegativeStartThrowsException() { + assertThrows(IllegalArgumentException.class, () -> KaprekarNumbers.kaprekarNumberInRange(-10, 100)); + } + + @Test + void testEmptyRange() { + List result = KaprekarNumbers.kaprekarNumberInRange(10, 44); + assertTrue(result.isEmpty()); + } + + @Test + void testLargeKaprekarNumber() { + // Test a larger known Kaprekar number + assertTrue(KaprekarNumbers.isKaprekarNumber(142857)); + } + + @Test + void testFourDigitKaprekarNumbers() { + // Test some 4-digit Kaprekar numbers + assertTrue(KaprekarNumbers.isKaprekarNumber(2728)); + assertTrue(KaprekarNumbers.isKaprekarNumber(4950)); + assertTrue(KaprekarNumbers.isKaprekarNumber(5050)); + assertTrue(KaprekarNumbers.isKaprekarNumber(7272)); } } diff --git a/src/test/java/com/thealgorithms/maths/KeithNumberTest.java b/src/test/java/com/thealgorithms/maths/KeithNumberTest.java new file mode 100644 index 000000000000..cac6b925e5c1 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/KeithNumberTest.java @@ -0,0 +1,153 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Test cases for {@link KeithNumber}. + */ +class KeithNumberTest { + + /** + * Tests single-digit Keith numbers. + * All single-digit numbers (1-9) are Keith numbers by definition. + */ + @Test + void testSingleDigitKeithNumbers() { + assertTrue(KeithNumber.isKeith(1)); + assertTrue(KeithNumber.isKeith(2)); + assertTrue(KeithNumber.isKeith(3)); + assertTrue(KeithNumber.isKeith(4)); + assertTrue(KeithNumber.isKeith(5)); + assertTrue(KeithNumber.isKeith(6)); + assertTrue(KeithNumber.isKeith(7)); + assertTrue(KeithNumber.isKeith(8)); + assertTrue(KeithNumber.isKeith(9)); + } + + /** + * Tests two-digit Keith numbers. + * Known two-digit Keith numbers: 14, 19, 28, 47, 61, 75. + */ + @Test + void testTwoDigitKeithNumbers() { + assertTrue(KeithNumber.isKeith(14)); + assertTrue(KeithNumber.isKeith(19)); + assertTrue(KeithNumber.isKeith(28)); + assertTrue(KeithNumber.isKeith(47)); + assertTrue(KeithNumber.isKeith(61)); + assertTrue(KeithNumber.isKeith(75)); + } + + /** + * Tests three-digit Keith numbers. + * Known three-digit Keith numbers: 197, 742. + */ + @Test + void testThreeDigitKeithNumbers() { + assertTrue(KeithNumber.isKeith(197)); + assertTrue(KeithNumber.isKeith(742)); + } + + /** + * Tests four-digit Keith numbers. + * Known four-digit Keith numbers: 1104, 1537, 2208, 2580, 3684, 4788, 7385, + * 7647, 7909. + */ + @Test + void testFourDigitKeithNumbers() { + assertTrue(KeithNumber.isKeith(1104)); + assertTrue(KeithNumber.isKeith(1537)); + assertTrue(KeithNumber.isKeith(2208)); + } + + /** + * Tests two-digit non-Keith numbers. + */ + @Test + void testTwoDigitNonKeithNumbers() { + assertFalse(KeithNumber.isKeith(10)); + assertFalse(KeithNumber.isKeith(11)); + assertFalse(KeithNumber.isKeith(12)); + assertFalse(KeithNumber.isKeith(13)); + assertFalse(KeithNumber.isKeith(15)); + assertFalse(KeithNumber.isKeith(20)); + assertFalse(KeithNumber.isKeith(30)); + assertFalse(KeithNumber.isKeith(50)); + } + + /** + * Tests three-digit non-Keith numbers. + */ + @Test + void testThreeDigitNonKeithNumbers() { + assertFalse(KeithNumber.isKeith(100)); + assertFalse(KeithNumber.isKeith(123)); + assertFalse(KeithNumber.isKeith(196)); + assertFalse(KeithNumber.isKeith(198)); + assertFalse(KeithNumber.isKeith(456)); + assertFalse(KeithNumber.isKeith(741)); + assertFalse(KeithNumber.isKeith(743)); + assertFalse(KeithNumber.isKeith(999)); + } + + /** + * Tests validation for edge case 14 in detail. + * 14 is a Keith number: 1, 4, 5 (1+4), 9 (4+5), 14 (5+9). + */ + @Test + void testKeithNumber14() { + assertTrue(KeithNumber.isKeith(14)); + } + + /** + * Tests validation for edge case 197 in detail. + * 197 is a Keith number: 1, 9, 7, 17, 33, 57, 107, 197. + */ + @Test + void testKeithNumber197() { + assertTrue(KeithNumber.isKeith(197)); + } + + /** + * Tests that zero throws an IllegalArgumentException. + */ + @Test + void testZeroThrowsException() { + assertThrows(IllegalArgumentException.class, () -> KeithNumber.isKeith(0)); + } + + /** + * Tests that negative numbers throw an IllegalArgumentException. + */ + @Test + void testNegativeNumbersThrowException() { + assertThrows(IllegalArgumentException.class, () -> KeithNumber.isKeith(-1)); + assertThrows(IllegalArgumentException.class, () -> KeithNumber.isKeith(-14)); + assertThrows(IllegalArgumentException.class, () -> KeithNumber.isKeith(-100)); + } + + /** + * Tests various edge cases with larger numbers. + */ + @Test + void testLargerNumbers() { + assertTrue(KeithNumber.isKeith(2208)); + assertFalse(KeithNumber.isKeith(2207)); + assertFalse(KeithNumber.isKeith(2209)); + } + + /** + * Tests the expected behavior with all two-digit Keith numbers. + */ + @Test + void testAllKnownTwoDigitKeithNumbers() { + int[] knownKeithNumbers = {14, 19, 28, 47, 61, 75}; + for (int number : knownKeithNumbers) { + assertTrue(KeithNumber.isKeith(number), "Expected " + number + " to be a Keith number"); + } + } +} diff --git a/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java b/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java index 595acde2b5d8..3c9d4f886b3d 100644 --- a/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java +++ b/src/test/java/com/thealgorithms/maths/KrishnamurthyNumberTest.java @@ -6,57 +6,115 @@ import org.junit.jupiter.api.Test; /** - * Unit tests for the KrishnamurthyNumber class. + * Unit tests for the {@link KrishnamurthyNumber} class. */ -public class KrishnamurthyNumberTest { +class KrishnamurthyNumberTest { /** - * Test the isKrishnamurthy method with a known Krishnamurthy number. + * Test with known Krishnamurthy number 145. + * 1! + 4! + 5! = 1 + 24 + 120 = 145 */ @Test - public void testIsKrishnamurthyTrue() { + void testIsKrishnamurthyWith145() { assertTrue(KrishnamurthyNumber.isKrishnamurthy(145)); } /** - * Test the isKrishnamurthy method with a number that is not a Krishnamurthy number. + * Test with a number that is not a Krishnamurthy number. */ @Test - public void testIsKrishnamurthyFalse() { + void testIsKrishnamurthyWithNonKrishnamurthyNumber() { assertFalse(KrishnamurthyNumber.isKrishnamurthy(123)); } /** - * Test the isKrishnamurthy method with zero. + * Test with zero, which is not a Krishnamurthy number. */ @Test - public void testIsKrishnamurthyZero() { + void testIsKrishnamurthyWithZero() { assertFalse(KrishnamurthyNumber.isKrishnamurthy(0)); } /** - * Test the isKrishnamurthy method with a negative number. + * Test with negative numbers, which cannot be Krishnamurthy numbers. */ @Test - public void testIsKrishnamurthyNegative() { + void testIsKrishnamurthyWithNegativeNumbers() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(-1)); assertFalse(KrishnamurthyNumber.isKrishnamurthy(-145)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(-100)); } /** - * Test the isKrishnamurthy method with a single-digit Krishnamurthy number. + * Test with single-digit Krishnamurthy numbers. + * 1! = 1 and 2! = 2, so both 1 and 2 are Krishnamurthy numbers. */ @Test - public void testIsKrishnamurthySingleDigitTrue() { + void testIsKrishnamurthyWithSingleDigitKrishnamurthyNumbers() { assertTrue(KrishnamurthyNumber.isKrishnamurthy(1)); assertTrue(KrishnamurthyNumber.isKrishnamurthy(2)); } /** - * Test the isKrishnamurthy method with a single-digit number that is not a Krishnamurthy number. + * Test with single-digit numbers that are not Krishnamurthy numbers. */ @Test - public void testIsKrishnamurthySingleDigitFalse() { + void testIsKrishnamurthyWithSingleDigitNonKrishnamurthyNumbers() { assertFalse(KrishnamurthyNumber.isKrishnamurthy(3)); assertFalse(KrishnamurthyNumber.isKrishnamurthy(4)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(5)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(6)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(7)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(8)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(9)); + } + + /** + * Test with the largest Krishnamurthy number: 40585. + * 4! + 0! + 5! + 8! + 5! = 24 + 1 + 120 + 40320 + 120 = 40585 + */ + @Test + void testIsKrishnamurthyWithLargestKrishnamurthyNumber() { + assertTrue(KrishnamurthyNumber.isKrishnamurthy(40585)); + } + + /** + * Test with various non-Krishnamurthy numbers. + */ + @Test + void testIsKrishnamurthyWithVariousNonKrishnamurthyNumbers() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(10)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(50)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(100)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(144)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(146)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(150)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(200)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(999)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(1000)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(40584)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(40586)); + } + + /** + * Test with numbers close to known Krishnamurthy numbers. + */ + @Test + void testIsKrishnamurthyWithNumbersCloseToKrishnamurthy() { + assertFalse(KrishnamurthyNumber.isKrishnamurthy(144)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(146)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(40584)); + assertFalse(KrishnamurthyNumber.isKrishnamurthy(40586)); + } + + /** + * Test with all known Krishnamurthy numbers in base 10. + */ + @Test + void testAllKnownKrishnamurthyNumbers() { + assertTrue(KrishnamurthyNumber.isKrishnamurthy(1)); + assertTrue(KrishnamurthyNumber.isKrishnamurthy(2)); + assertTrue(KrishnamurthyNumber.isKrishnamurthy(145)); + assertTrue(KrishnamurthyNumber.isKrishnamurthy(40585)); } } diff --git a/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java b/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java index 705f1a1006fa..baf4540cf239 100644 --- a/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java +++ b/src/test/java/com/thealgorithms/maths/LeonardoNumberTest.java @@ -1,29 +1,171 @@ package com.thealgorithms.maths; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class LeonardoNumberTest { +/** + * Test cases for {@link LeonardoNumber} class. + *

+ * Tests both recursive and iterative implementations with various input values + * including edge cases and boundary conditions. + */ +class LeonardoNumberTest { + + // Tests for recursive implementation + + @Test + void testLeonardoNumberNegative() { + Assertions.assertThrows(IllegalArgumentException.class, () -> LeonardoNumber.leonardoNumber(-1)); + } + + @Test + void testLeonardoNumberNegativeLarge() { + Assertions.assertThrows(IllegalArgumentException.class, () -> LeonardoNumber.leonardoNumber(-100)); + } + + @Test + void testLeonardoNumberZero() { + Assertions.assertEquals(1, LeonardoNumber.leonardoNumber(0)); + } + + @Test + void testLeonardoNumberOne() { + Assertions.assertEquals(1, LeonardoNumber.leonardoNumber(1)); + } + + @Test + void testLeonardoNumberTwo() { + Assertions.assertEquals(3, LeonardoNumber.leonardoNumber(2)); + } + + @Test + void testLeonardoNumberThree() { + Assertions.assertEquals(5, LeonardoNumber.leonardoNumber(3)); + } + + @Test + void testLeonardoNumberFour() { + Assertions.assertEquals(9, LeonardoNumber.leonardoNumber(4)); + } + + @Test + void testLeonardoNumberFive() { + Assertions.assertEquals(15, LeonardoNumber.leonardoNumber(5)); + } + + @Test + void testLeonardoNumberSix() { + Assertions.assertEquals(25, LeonardoNumber.leonardoNumber(6)); + } + + @Test + void testLeonardoNumberSeven() { + Assertions.assertEquals(41, LeonardoNumber.leonardoNumber(7)); + } + + @Test + void testLeonardoNumberEight() { + Assertions.assertEquals(67, LeonardoNumber.leonardoNumber(8)); + } + + @Test + void testLeonardoNumberTen() { + Assertions.assertEquals(177, LeonardoNumber.leonardoNumber(10)); + } + + @Test + void testLeonardoNumberFifteen() { + Assertions.assertEquals(1973, LeonardoNumber.leonardoNumber(15)); + } + @Test - void leonardoNumberNegative() { - assertThrows(ArithmeticException.class, () -> LeonardoNumber.leonardoNumber(-1)); + void testLeonardoNumberTwenty() { + Assertions.assertEquals(21891, LeonardoNumber.leonardoNumber(20)); } + + // Tests for iterative implementation + + @Test + void testLeonardoNumberIterativeNegative() { + Assertions.assertThrows(IllegalArgumentException.class, () -> LeonardoNumber.leonardoNumberIterative(-1)); + } + + @Test + void testLeonardoNumberIterativeNegativeLarge() { + Assertions.assertThrows(IllegalArgumentException.class, () -> LeonardoNumber.leonardoNumberIterative(-50)); + } + + @Test + void testLeonardoNumberIterativeZero() { + Assertions.assertEquals(1, LeonardoNumber.leonardoNumberIterative(0)); + } + + @Test + void testLeonardoNumberIterativeOne() { + Assertions.assertEquals(1, LeonardoNumber.leonardoNumberIterative(1)); + } + + @Test + void testLeonardoNumberIterativeTwo() { + Assertions.assertEquals(3, LeonardoNumber.leonardoNumberIterative(2)); + } + + @Test + void testLeonardoNumberIterativeThree() { + Assertions.assertEquals(5, LeonardoNumber.leonardoNumberIterative(3)); + } + + @Test + void testLeonardoNumberIterativeFour() { + Assertions.assertEquals(9, LeonardoNumber.leonardoNumberIterative(4)); + } + @Test - void leonardoNumberZero() { - assertEquals(1, LeonardoNumber.leonardoNumber(0)); + void testLeonardoNumberIterativeFive() { + Assertions.assertEquals(15, LeonardoNumber.leonardoNumberIterative(5)); } + @Test - void leonardoNumberOne() { - assertEquals(1, LeonardoNumber.leonardoNumber(1)); + void testLeonardoNumberIterativeSix() { + Assertions.assertEquals(25, LeonardoNumber.leonardoNumberIterative(6)); } + @Test - void leonardoNumberFive() { - assertEquals(15, LeonardoNumber.leonardoNumber(5)); + void testLeonardoNumberIterativeSeven() { + Assertions.assertEquals(41, LeonardoNumber.leonardoNumberIterative(7)); } + + @Test + void testLeonardoNumberIterativeEight() { + Assertions.assertEquals(67, LeonardoNumber.leonardoNumberIterative(8)); + } + + @Test + void testLeonardoNumberIterativeTen() { + Assertions.assertEquals(177, LeonardoNumber.leonardoNumberIterative(10)); + } + + @Test + void testLeonardoNumberIterativeFifteen() { + Assertions.assertEquals(1973, LeonardoNumber.leonardoNumberIterative(15)); + } + + @Test + void testLeonardoNumberIterativeTwenty() { + Assertions.assertEquals(21891, LeonardoNumber.leonardoNumberIterative(20)); + } + + @Test + void testLeonardoNumberIterativeTwentyFive() { + Assertions.assertEquals(242785, LeonardoNumber.leonardoNumberIterative(25)); + } + + // Consistency tests between recursive and iterative implementations + @Test - void leonardoNumberTwenty() { - assertEquals(21891, LeonardoNumber.leonardoNumber(20)); + void testConsistencyBetweenImplementations() { + for (int i = 0; i <= 15; i++) { + Assertions.assertEquals(LeonardoNumber.leonardoNumber(i), LeonardoNumber.leonardoNumberIterative(i), "Mismatch at index " + i); + } } } diff --git a/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java b/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java new file mode 100644 index 000000000000..c4205985dbfd --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java @@ -0,0 +1,330 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Test class for LinearDiophantineEquationsSolver. + * Tests various cases including: + * - Equations with solutions + * - Equations with no solutions + * - Special cases (zero coefficients, infinite solutions) + * - Edge cases (negative coefficients, large numbers) + */ +class LinearDiophantineEquationsSolverTest { + + /** + * Tests the example equation 3x + 4y = 7. + * Expected solution: x = -9, y = 8 (or other valid solutions). + */ + @Test + void testBasicEquation() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 4, 7); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + // Verify that the solution satisfies the equation + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with no solution: 2x + 4y = 5. + * Since gcd(2, 4) = 2 and 2 does not divide 5, no solution exists. + */ + @Test + void testNoSolution() { + final var equation = new LinearDiophantineEquationsSolver.Equation(2, 4, 5); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertEquals(LinearDiophantineEquationsSolver.Solution.NO_SOLUTION, solution); + } + + /** + * Tests the trivial equation 0x + 0y = 0. + * This has infinite solutions. + */ + @Test + void testInfiniteSolutions() { + final var equation = new LinearDiophantineEquationsSolver.Equation(0, 0, 0); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertEquals(LinearDiophantineEquationsSolver.Solution.INFINITE_SOLUTIONS, solution); + } + + /** + * Tests an equation where a = 0: 0x + 5y = 10. + * Expected solution: x = 0, y = 2. + */ + @Test + void testZeroCoefficient() { + final var equation = new LinearDiophantineEquationsSolver.Equation(0, 5, 10); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation where b = 0: 3x + 0y = 9. + * Expected solution: x = 3, y can be anything (solver will return y = 0). + */ + @Test + void testZeroCoefficientB() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 0, 9); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with negative coefficients: -3x + 4y = 7. + */ + @Test + void testNegativeCoefficients() { + final var equation = new LinearDiophantineEquationsSolver.Equation(-3, 4, 7); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with negative result: 3x + 4y = -7. + */ + @Test + void testNegativeResult() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 4, -7); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with coprime coefficients: 7x + 11y = 1. + * Since gcd(7, 11) = 1, a solution exists. + */ + @Test + void testCoprimeCoefficients() { + final var equation = new LinearDiophantineEquationsSolver.Equation(7, 11, 1); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with larger coefficients: 12x + 18y = 30. + * Since gcd(12, 18) = 6 and 6 divides 30, a solution exists. + */ + @Test + void testLargerCoefficients() { + final var equation = new LinearDiophantineEquationsSolver.Equation(12, 18, 30); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation that has no solution due to GCD: 6x + 9y = 5. + * Since gcd(6, 9) = 3 and 3 does not divide 5, no solution exists. + */ + @Test + void testNoSolutionGcdCheck() { + final var equation = new LinearDiophantineEquationsSolver.Equation(6, 9, 5); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertEquals(LinearDiophantineEquationsSolver.Solution.NO_SOLUTION, solution); + } + + /** + * Tests the equation x + y = 1. + * Simple case where gcd(1, 1) = 1. + */ + @Test + void testSimpleCase() { + final var equation = new LinearDiophantineEquationsSolver.Equation(1, 1, 1); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests Solution equality. + */ + @Test + void testSolutionEquality() { + final var solution1 = new LinearDiophantineEquationsSolver.Solution(3, 5); + final var solution2 = new LinearDiophantineEquationsSolver.Solution(3, 5); + final var solution3 = new LinearDiophantineEquationsSolver.Solution(3, 6); + + assertEquals(solution1, solution2); + assertNotEquals(solution3, solution1); + assertEquals(solution1, solution1); + assertNotEquals(null, solution1); + assertNotEquals("string", solution1); + } + + /** + * Tests Solution hashCode. + */ + @Test + void testSolutionHashCode() { + final var solution1 = new LinearDiophantineEquationsSolver.Solution(3, 5); + final var solution2 = new LinearDiophantineEquationsSolver.Solution(3, 5); + + assertEquals(solution1.hashCode(), solution2.hashCode()); + } + + /** + * Tests Solution toString. + */ + @Test + void testSolutionToString() { + final var solution = new LinearDiophantineEquationsSolver.Solution(3, 5); + final var str = solution.toString(); + + assertTrue(str.contains("3")); + assertTrue(str.contains("5")); + assertTrue(str.contains("Solution")); + } + + /** + * Tests GcdSolutionWrapper equality. + */ + @Test + void testGcdSolutionWrapperEquality() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + final var wrapper1 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + final var wrapper2 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + final var wrapper3 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(6, solution); + + assertEquals(wrapper1, wrapper2); + assertNotEquals(wrapper3, wrapper1); + assertEquals(wrapper1, wrapper1); + assertNotEquals(null, wrapper1); + assertNotEquals("string", wrapper1); + } + + /** + * Tests GcdSolutionWrapper hashCode. + */ + @Test + void testGcdSolutionWrapperHashCode() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + final var wrapper1 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + final var wrapper2 = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + + assertEquals(wrapper1.hashCode(), wrapper2.hashCode()); + } + + /** + * Tests GcdSolutionWrapper toString. + */ + @Test + void testGcdSolutionWrapperToString() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + final var wrapper = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + final var str = wrapper.toString(); + + assertTrue(str.contains("5")); + assertTrue(str.contains("GcdSolutionWrapper")); + } + + /** + * Tests Equation record functionality. + */ + @Test + void testEquationRecord() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 4, 7); + + assertEquals(3, equation.a()); + assertEquals(4, equation.b()); + assertEquals(7, equation.c()); + } + + /** + * Tests an equation with c = 0: 3x + 4y = 0. + * Expected solution: x = 0, y = 0. + */ + @Test + void testZeroResult() { + final var equation = new LinearDiophantineEquationsSolver.Equation(3, 4, 0); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests Solution setters. + */ + @Test + void testSolutionSetters() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + + solution.setX(10); + solution.setY(20); + + assertEquals(10, solution.getX()); + assertEquals(20, solution.getY()); + } + + /** + * Tests GcdSolutionWrapper setters. + */ + @Test + void testGcdSolutionWrapperSetters() { + final var solution = new LinearDiophantineEquationsSolver.Solution(1, 2); + final var wrapper = new LinearDiophantineEquationsSolver.GcdSolutionWrapper(5, solution); + + final var newSolution = new LinearDiophantineEquationsSolver.Solution(3, 4); + wrapper.setGcd(10); + wrapper.setSolution(newSolution); + + assertEquals(10, wrapper.getGcd()); + assertEquals(newSolution, wrapper.getSolution()); + } + + /** + * Tests an equation with both coefficients negative: -3x - 4y = -7. + */ + @Test + void testBothCoefficientsNegative() { + final var equation = new LinearDiophantineEquationsSolver.Equation(-3, -4, -7); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } + + /** + * Tests an equation with large prime coefficients: 97x + 101y = 198. + */ + @Test + void testLargePrimeCoefficients() { + final var equation = new LinearDiophantineEquationsSolver.Equation(97, 101, 198); + final var solution = LinearDiophantineEquationsSolver.findAnySolution(equation); + + assertNotNull(solution); + int result = equation.a() * solution.getX() + equation.b() * solution.getY(); + assertEquals(equation.c(), result); + } +} diff --git a/src/test/java/com/thealgorithms/maths/LucasSeriesTest.java b/src/test/java/com/thealgorithms/maths/LucasSeriesTest.java index 3576268c5d0c..4b6c1e41ecb6 100644 --- a/src/test/java/com/thealgorithms/maths/LucasSeriesTest.java +++ b/src/test/java/com/thealgorithms/maths/LucasSeriesTest.java @@ -1,28 +1,158 @@ package com.thealgorithms.maths; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; -public class LucasSeriesTest { +/** + * Test cases for {@link LucasSeries} class. + * Tests both recursive and iterative implementations for correctness, + * edge cases, and error handling. + */ +class LucasSeriesTest { + + /** + * Test the first Lucas number L(1) = 2 + */ @Test - void lucasSeriesTwo() { + void testFirstLucasNumber() { assertEquals(2, LucasSeries.lucasSeries(1)); assertEquals(2, LucasSeries.lucasSeriesIteration(1)); } + + /** + * Test the second Lucas number L(2) = 1 + */ @Test - void lucasSeriesOne() { + void testSecondLucasNumber() { assertEquals(1, LucasSeries.lucasSeries(2)); assertEquals(1, LucasSeries.lucasSeriesIteration(2)); } + + /** + * Test the third Lucas number L(3) = 3 + */ + @Test + void testThirdLucasNumber() { + assertEquals(3, LucasSeries.lucasSeries(3)); + assertEquals(3, LucasSeries.lucasSeriesIteration(3)); + } + + /** + * Test the fourth Lucas number L(4) = 4 + */ @Test - void lucasSeriesSeven() { + void testFourthLucasNumber() { + assertEquals(4, LucasSeries.lucasSeries(4)); + assertEquals(4, LucasSeries.lucasSeriesIteration(4)); + } + + /** + * Test the fifth Lucas number L(5) = 7 + */ + @Test + void testFifthLucasNumber() { assertEquals(7, LucasSeries.lucasSeries(5)); assertEquals(7, LucasSeries.lucasSeriesIteration(5)); } + + /** + * Test the sixth Lucas number L(6) = 11 + */ + @Test + void testSixthLucasNumber() { + assertEquals(11, LucasSeries.lucasSeries(6)); + assertEquals(11, LucasSeries.lucasSeriesIteration(6)); + } + + /** + * Test the seventh Lucas number L(7) = 18 + */ + @Test + void testSeventhLucasNumber() { + assertEquals(18, LucasSeries.lucasSeries(7)); + assertEquals(18, LucasSeries.lucasSeriesIteration(7)); + } + + /** + * Test the eighth Lucas number L(8) = 29 + */ + @Test + void testEighthLucasNumber() { + assertEquals(29, LucasSeries.lucasSeries(8)); + assertEquals(29, LucasSeries.lucasSeriesIteration(8)); + } + + /** + * Test the ninth Lucas number L(9) = 47 + */ @Test - void lucasSeriesEleven() { + void testNinthLucasNumber() { + assertEquals(47, LucasSeries.lucasSeries(9)); + assertEquals(47, LucasSeries.lucasSeriesIteration(9)); + } + + /** + * Test the tenth Lucas number L(10) = 76 + */ + @Test + void testTenthLucasNumber() { + assertEquals(76, LucasSeries.lucasSeries(10)); + assertEquals(76, LucasSeries.lucasSeriesIteration(10)); + } + + /** + * Test the eleventh Lucas number L(11) = 123 + */ + @Test + void testEleventhLucasNumber() { assertEquals(123, LucasSeries.lucasSeries(11)); assertEquals(123, LucasSeries.lucasSeriesIteration(11)); } + + /** + * Test larger Lucas numbers to ensure correctness + */ + @Test + void testLargerLucasNumbers() { + assertEquals(199, LucasSeries.lucasSeries(12)); + assertEquals(199, LucasSeries.lucasSeriesIteration(12)); + assertEquals(322, LucasSeries.lucasSeries(13)); + assertEquals(322, LucasSeries.lucasSeriesIteration(13)); + assertEquals(521, LucasSeries.lucasSeries(14)); + assertEquals(521, LucasSeries.lucasSeriesIteration(14)); + assertEquals(843, LucasSeries.lucasSeries(15)); + assertEquals(843, LucasSeries.lucasSeriesIteration(15)); + } + + /** + * Test that both methods produce the same results + */ + @Test + void testRecursiveAndIterativeConsistency() { + for (int i = 1; i <= 15; i++) { + assertEquals(LucasSeries.lucasSeries(i), LucasSeries.lucasSeriesIteration(i), "Mismatch at position " + i); + } + } + + /** + * Test invalid input: zero + */ + @Test + void testZeroInputThrowsException() { + assertThrows(IllegalArgumentException.class, () -> LucasSeries.lucasSeries(0)); + assertThrows(IllegalArgumentException.class, () -> LucasSeries.lucasSeriesIteration(0)); + } + + /** + * Test invalid input: negative number + */ + @Test + void testNegativeInputThrowsException() { + assertThrows(IllegalArgumentException.class, () -> LucasSeries.lucasSeries(-1)); + assertThrows(IllegalArgumentException.class, () -> LucasSeries.lucasSeriesIteration(-1)); + assertThrows(IllegalArgumentException.class, () -> LucasSeries.lucasSeries(-5)); + assertThrows(IllegalArgumentException.class, () -> LucasSeries.lucasSeriesIteration(-5)); + } } diff --git a/src/test/java/com/thealgorithms/maths/MathBuilderTest.java b/src/test/java/com/thealgorithms/maths/MathBuilderTest.java new file mode 100644 index 000000000000..dc381bfca5d3 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/MathBuilderTest.java @@ -0,0 +1,52 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class MathBuilderTest { + + @Test + void simpleMath() { + double result = new MathBuilder.Builder(100).add(200).multiply(10).print().divideIf(20, (a, b) -> a % 2 == 0).sqrt().print().ceil().build().get(); + assertEquals(13, result); + } + + @Test + void memoryTest() { + long result = new MathBuilder.Builder().set(100).sqrt().remember().add(21).recallIf(a -> a < 50, true).mod(2).build().toLong(); + assertEquals(0, result); + } + + @Test + void freeFallDistance() { + long distance = new MathBuilder.Builder(9.81).multiply(0.5).multiply(5 * 5).round().build().toLong(); + assertEquals(123, distance); // Expected result: 0.5 * 9.81 * 25 = 122.625 โ‰ˆ 123 + } + + @Test + void batchSalaryProcessing() { + double[] salaries = {2000, 3000, 4000, 5000}; + long[] processedSalaries = new long[salaries.length]; + for (int i = 0; i < salaries.length; i++) { + processedSalaries[i] = new MathBuilder.Builder(salaries[i]).addIf(salaries[i] * 0.1, (sal, bonus) -> sal > 2500).multiply(0.92).round().build().toLong(); + } + long[] expectedSalaries = {1840, 3036, 4048, 5060}; + assertArrayEquals(expectedSalaries, processedSalaries); + } + + @Test + void parenthesis() { + // 10 + (20*5) - 40 + (100 / 10) = 80 + double result = new MathBuilder.Builder(10).openParenthesis(20).multiply(5).closeParenthesisAndPlus().minus(40).openParenthesis(100).divide(10).closeParenthesisAndPlus().build().get(); + assertEquals(80, result); + } + + @Test + void areaOfCircle() { + // Radius is 4 + double area = new MathBuilder.Builder().pi().openParenthesis(4).multiply(4).closeParenthesisAndMultiply().build().get(); + assertEquals(Math.PI * 4 * 4, area); + } +} diff --git a/src/test/java/com/thealgorithms/maths/MeansTest.java b/src/test/java/com/thealgorithms/maths/MeansTest.java index 4b3a5df44b34..deee0a931910 100644 --- a/src/test/java/com/thealgorithms/maths/MeansTest.java +++ b/src/test/java/com/thealgorithms/maths/MeansTest.java @@ -2,70 +2,217 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.TreeSet; import java.util.Vector; -import org.assertj.core.util.Lists; -import org.assertj.core.util.Sets; import org.junit.jupiter.api.Test; +/** + * Test class for {@link Means}. + *

+ * This class provides comprehensive test coverage for all mean calculation + * methods, + * including edge cases, various collection types, and error conditions. + *

+ */ class MeansTest { + private static final double EPSILON = 1e-9; + + // ========== Arithmetic Mean Tests ========== + @Test - void arithmeticMeanZeroNumbers() throws IllegalArgumentException { + void testArithmeticMeanThrowsExceptionForEmptyList() { List numbers = new ArrayList<>(); - assertThrows(IllegalArgumentException.class, () -> Means.arithmetic(numbers)); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.arithmetic(numbers)); + assertTrue(exception.getMessage().contains("Empty list")); + } + + @Test + void testArithmeticMeanSingleNumber() { + List numbers = Arrays.asList(2.5); + assertEquals(2.5, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanTwoNumbers() { + List numbers = Arrays.asList(2.0, 4.0); + assertEquals(3.0, Means.arithmetic(numbers), EPSILON); } @Test - void geometricMeanZeroNumbers() throws IllegalArgumentException { + void testArithmeticMeanMultipleNumbers() { + List numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0); + assertEquals(3.0, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanWithTreeSet() { + Set numbers = new TreeSet<>(Arrays.asList(1.0, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5)); + assertEquals(44.0, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanWithNegativeNumbers() { + List numbers = Arrays.asList(-5.0, -3.0, -1.0, 1.0, 3.0, 5.0); + assertEquals(0.0, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanWithDecimalNumbers() { + List numbers = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5); + assertEquals(3.3, Means.arithmetic(numbers), EPSILON); + } + + @Test + void testArithmeticMeanWithVector() { + Vector numbers = new Vector<>(Arrays.asList(10.0, 20.0, 30.0)); + assertEquals(20.0, Means.arithmetic(numbers), EPSILON); + } + + // ========== Geometric Mean Tests ========== + + @Test + void testGeometricMeanThrowsExceptionForEmptyList() { List numbers = new ArrayList<>(); - assertThrows(IllegalArgumentException.class, () -> Means.geometric(numbers)); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.geometric(numbers)); + assertTrue(exception.getMessage().contains("Empty list")); } @Test - void harmonicMeanZeroNumbers() throws IllegalArgumentException { + void testGeometricMeanSingleNumber() { + Set numbers = new LinkedHashSet<>(Arrays.asList(2.5)); + assertEquals(2.5, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanTwoNumbers() { + List numbers = Arrays.asList(2.0, 8.0); + assertEquals(4.0, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanMultipleNumbers() { + LinkedList numbers = new LinkedList<>(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 1.25)); + assertEquals(2.6426195539300585, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanPerfectSquares() { + List numbers = Arrays.asList(1.0, 4.0, 9.0, 16.0); + double expected = Math.pow(1.0 * 4.0 * 9.0 * 16.0, 1.0 / 4.0); + assertEquals(expected, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanIdenticalNumbers() { + List numbers = Arrays.asList(5.0, 5.0, 5.0, 5.0); + assertEquals(5.0, Means.geometric(numbers), EPSILON); + } + + @Test + void testGeometricMeanWithLinkedHashSet() { + LinkedHashSet numbers = new LinkedHashSet<>(Arrays.asList(2.0, 4.0, 8.0)); + double expected = Math.pow(2.0 * 4.0 * 8.0, 1.0 / 3.0); + assertEquals(expected, Means.geometric(numbers), EPSILON); + } + + // ========== Harmonic Mean Tests ========== + + @Test + void testHarmonicMeanThrowsExceptionForEmptyList() { List numbers = new ArrayList<>(); - assertThrows(IllegalArgumentException.class, () -> Means.harmonic(numbers)); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.harmonic(numbers)); + assertTrue(exception.getMessage().contains("Empty list")); + } + + @Test + void testHarmonicMeanSingleNumber() { + LinkedHashSet numbers = new LinkedHashSet<>(Arrays.asList(2.5)); + assertEquals(2.5, Means.harmonic(numbers), EPSILON); } @Test - void arithmeticMeanSingleNumber() { - List numbers = Lists.newArrayList(2.5); - assertEquals(2.5, Means.arithmetic(numbers)); + void testHarmonicMeanTwoNumbers() { + List numbers = Arrays.asList(2.0, 4.0); + double expected = 2.0 / (1.0 / 2.0 + 1.0 / 4.0); + assertEquals(expected, Means.harmonic(numbers), EPSILON); } @Test - void geometricMeanSingleNumber() { - Set numbers = Sets.newHashSet(Lists.newArrayList(2.5)); - assertEquals(2.5, Means.geometric(numbers)); + void testHarmonicMeanMultipleNumbers() { + Vector numbers = new Vector<>(Arrays.asList(1.0, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5)); + assertEquals(4.6697322801074135, Means.harmonic(numbers), EPSILON); } @Test - void harmonicMeanSingleNumber() { - LinkedHashSet numbers = Sets.newLinkedHashSet(2.5); - assertEquals(2.5, Means.harmonic(numbers)); + void testHarmonicMeanThreeNumbers() { + List numbers = Arrays.asList(1.0, 2.0, 4.0); + double expected = 3.0 / (1.0 / 1.0 + 1.0 / 2.0 + 1.0 / 4.0); + assertEquals(expected, Means.harmonic(numbers), EPSILON); } @Test - void arithmeticMeanMultipleNumbers() { - Set numbers = Sets.newTreeSet(1d, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5); - assertEquals(44, Means.arithmetic(numbers)); + void testHarmonicMeanIdenticalNumbers() { + List numbers = Arrays.asList(6.0, 6.0, 6.0); + assertEquals(6.0, Means.harmonic(numbers), EPSILON); } @Test - void geometricMeanMultipleNumbers() { - LinkedList numbers = new LinkedList<>(Lists.newArrayList(1d, 2d, 3d, 4d, 5d, 6d, 1.25)); - assertEquals(2.6426195539300585, Means.geometric(numbers)); + void testHarmonicMeanWithLinkedList() { + LinkedList numbers = new LinkedList<>(Arrays.asList(3.0, 6.0, 9.0)); + double expected = 3.0 / (1.0 / 3.0 + 1.0 / 6.0 + 1.0 / 9.0); + assertEquals(expected, Means.harmonic(numbers), EPSILON); + } + + // ========== Additional Edge Case Tests ========== + + @Test + void testArithmeticMeanWithVeryLargeNumbers() { + List numbers = Arrays.asList(1e100, 2e100, 3e100); + assertEquals(2e100, Means.arithmetic(numbers), 1e90); } @Test - void harmonicMeanMultipleNumbers() { - Vector numbers = new Vector<>(Lists.newArrayList(1d, 2.5, 83.3, 25.9999, 46.0001, 74.7, 74.5)); - assertEquals(4.6697322801074135, Means.harmonic(numbers)); + void testArithmeticMeanWithVerySmallNumbers() { + List numbers = Arrays.asList(1e-100, 2e-100, 3e-100); + assertEquals(2e-100, Means.arithmetic(numbers), 1e-110); + } + + @Test + void testGeometricMeanWithOnes() { + List numbers = Arrays.asList(1.0, 1.0, 1.0, 1.0); + assertEquals(1.0, Means.geometric(numbers), EPSILON); + } + + @Test + void testAllMeansConsistencyForIdenticalValues() { + List numbers = Arrays.asList(7.5, 7.5, 7.5, 7.5); + double arithmetic = Means.arithmetic(numbers); + double geometric = Means.geometric(numbers); + double harmonic = Means.harmonic(numbers); + + assertEquals(7.5, arithmetic, EPSILON); + assertEquals(7.5, geometric, EPSILON); + assertEquals(7.5, harmonic, EPSILON); + } + + @Test + void testMeansRelationship() { + // For positive numbers, harmonic mean โ‰ค geometric mean โ‰ค arithmetic mean + List numbers = Arrays.asList(2.0, 4.0, 8.0); + double arithmetic = Means.arithmetic(numbers); + double geometric = Means.geometric(numbers); + double harmonic = Means.harmonic(numbers); + + assertTrue(harmonic <= geometric, "Harmonic mean should be โ‰ค geometric mean"); + assertTrue(geometric <= arithmetic, "Geometric mean should be โ‰ค arithmetic mean"); } } diff --git a/src/test/java/com/thealgorithms/maths/MedianTest.java b/src/test/java/com/thealgorithms/maths/MedianTest.java index d2b637abd3cb..f42fe8bb17ee 100644 --- a/src/test/java/com/thealgorithms/maths/MedianTest.java +++ b/src/test/java/com/thealgorithms/maths/MedianTest.java @@ -1,37 +1,157 @@ package com.thealgorithms.maths; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; -public class MedianTest { +/** + * Test class for {@link Median}. + * Tests various scenarios including edge cases, odd/even length arrays, + * negative values, and unsorted inputs. + */ +class MedianTest { + @Test - void medianSingleValue() { + void testMedianSingleValue() { int[] arr = {0}; assertEquals(0, Median.median(arr)); } @Test - void medianTwoValues() { + void testMedianSinglePositiveValue() { + int[] arr = {42}; + assertEquals(42, Median.median(arr)); + } + + @Test + void testMedianSingleNegativeValue() { + int[] arr = {-15}; + assertEquals(-15, Median.median(arr)); + } + + @Test + void testMedianTwoValues() { int[] arr = {1, 2}; assertEquals(1.5, Median.median(arr)); } @Test - void medianThreeValues() { + void testMedianTwoIdenticalValues() { + int[] arr = {5, 5}; + assertEquals(5.0, Median.median(arr)); + } + + @Test + void testMedianThreeValues() { int[] arr = {1, 2, 3}; assertEquals(2, Median.median(arr)); } @Test - void medianDecimalValueReturn() { + void testMedianThreeUnsortedValues() { + int[] arr = {3, 1, 2}; + assertEquals(2, Median.median(arr)); + } + + @Test + void testMedianDecimalValueReturn() { int[] arr = {1, 2, 3, 4, 5, 6, 8, 9}; assertEquals(4.5, Median.median(arr)); } @Test - void medianNegativeValues() { + void testMedianNegativeValues() { int[] arr = {-27, -16, -7, -4, -2, -1}; assertEquals(-5.5, Median.median(arr)); } + + @Test + void testMedianMixedPositiveAndNegativeValues() { + int[] arr = {-10, -5, 0, 5, 10}; + assertEquals(0, Median.median(arr)); + } + + @Test + void testMedianMixedUnsortedValues() { + int[] arr = {10, -5, 0, 5, -10}; + assertEquals(0, Median.median(arr)); + } + + @Test + void testMedianLargeOddArray() { + int[] arr = {9, 7, 5, 3, 1, 2, 4, 6, 8}; + assertEquals(5, Median.median(arr)); + } + + @Test + void testMedianLargeEvenArray() { + int[] arr = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; + assertEquals(55.0, Median.median(arr)); + } + + @Test + void testMedianAllSameValues() { + int[] arr = {7, 7, 7, 7, 7}; + assertEquals(7.0, Median.median(arr)); + } + + @Test + void testMedianWithZeros() { + int[] arr = {0, 0, 0, 0, 0}; + assertEquals(0.0, Median.median(arr)); + } + + @Test + void testMedianAlreadySorted() { + int[] arr = {1, 2, 3, 4, 5}; + assertEquals(3, Median.median(arr)); + } + + @Test + void testMedianReverseSorted() { + int[] arr = {5, 4, 3, 2, 1}; + assertEquals(3, Median.median(arr)); + } + + @Test + void testMedianWithDuplicates() { + int[] arr = {1, 2, 2, 3, 3, 3, 4}; + assertEquals(3, Median.median(arr)); + } + + @Test + void testMedianEmptyArrayThrows() { + int[] arr = {}; + assertThrows(IllegalArgumentException.class, () -> Median.median(arr)); + } + + @Test + void testMedianNullArrayThrows() { + assertThrows(IllegalArgumentException.class, () -> Median.median(null)); + } + + @Test + void testMedianExtremeValues() { + int[] arr = {Integer.MAX_VALUE, Integer.MIN_VALUE}; + assertEquals(-0.5, Median.median(arr)); + } + + @Test + void testMedianTwoNegativeValues() { + int[] arr = {-10, -20}; + assertEquals(-15.0, Median.median(arr)); + } + + @Test + void testMedianFourValuesEven() { + int[] arr = {1, 2, 3, 4}; + assertEquals(2.5, Median.median(arr)); + } + + @Test + void testMedianFiveValuesOdd() { + int[] arr = {10, 20, 30, 40, 50}; + assertEquals(30.0, Median.median(arr)); + } } diff --git a/src/test/java/com/thealgorithms/maths/NumberOfDigitsTest.java b/src/test/java/com/thealgorithms/maths/NumberOfDigitsTest.java index 799052b22d83..153ab3347f1e 100644 --- a/src/test/java/com/thealgorithms/maths/NumberOfDigitsTest.java +++ b/src/test/java/com/thealgorithms/maths/NumberOfDigitsTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +@SuppressWarnings({"rawtypes", "unchecked"}) public class NumberOfDigitsTest { @ParameterizedTest diff --git a/src/test/java/com/thealgorithms/maths/NumberPersistenceTest.java b/src/test/java/com/thealgorithms/maths/NumberPersistenceTest.java new file mode 100644 index 000000000000..3f7e70167fc2 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/NumberPersistenceTest.java @@ -0,0 +1,42 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +class NumberPersistenceTest { + + @ParameterizedTest(name = "multiplicativePersistence({0}) = {1}") + @CsvSource({"0, 0", "7, 0", "217, 2", "39, 3", "999, 4"}) + @DisplayName("Test multiplicative persistence with valid inputs") + void testMultiplicativePersistenceValid(int input, int expected) { + assertEquals(expected, NumberPersistence.multiplicativePersistence(input)); + } + + @ParameterizedTest(name = "multiplicativePersistence({0}) throws IllegalArgumentException") + @ValueSource(ints = {-1, -100, -9999}) + @DisplayName("Test multiplicative persistence with negative numbers") + void testMultiplicativePersistenceNegative(int input) { + Exception exception = assertThrows(IllegalArgumentException.class, () -> NumberPersistence.multiplicativePersistence(input)); + assertEquals("multiplicativePersistence() does not accept negative values", exception.getMessage()); + } + + @ParameterizedTest(name = "additivePersistence({0}) = {1}") + @CsvSource({"0, 0", "5, 0", "199, 3", "999, 2", "1234, 2"}) + @DisplayName("Test additive persistence with valid inputs") + void testAdditivePersistenceValid(int input, int expected) { + assertEquals(expected, NumberPersistence.additivePersistence(input)); + } + + @ParameterizedTest(name = "additivePersistence({0}) throws IllegalArgumentException") + @ValueSource(ints = {-1, -100, -9999}) + @DisplayName("Test additive persistence with negative numbers") + void testAdditivePersistenceNegative(int input) { + Exception exception = assertThrows(IllegalArgumentException.class, () -> NumberPersistence.additivePersistence(input)); + assertEquals("additivePersistence() does not accept negative values", exception.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/maths/PiApproximationTest.java b/src/test/java/com/thealgorithms/maths/PiApproximationTest.java new file mode 100644 index 000000000000..47da3b46a336 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/PiApproximationTest.java @@ -0,0 +1,169 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class PiApproximationTest { + + private static final double DELTA = 0.5; // Tolerance for Pi approximation + private static final double TIGHT_DELTA = 0.1; // Tighter tolerance for large samples + + /** + * Test with known points that are all inside the quarter circle. + */ + @Test + public void testAllPointsInside() { + List points = new ArrayList<>(); + points.add(new PiApproximation.Point(0.0, 0.0)); // Origin + points.add(new PiApproximation.Point(0.5, 0.5)); // Inside + points.add(new PiApproximation.Point(0.3, 0.3)); // Inside + + double result = PiApproximation.approximatePi(points); + // All points inside, so result should be 4.0 + assertEquals(4.0, result, 0.001); + } + + /** + * Test with known points that are all outside the quarter circle. + */ + @Test + public void testAllPointsOutside() { + List points = new ArrayList<>(); + points.add(new PiApproximation.Point(1.0, 1.0)); // Corner - outside + points.add(new PiApproximation.Point(0.9, 0.9)); // Outside + + double result = PiApproximation.approximatePi(points); + // No points inside, so result should be 0.0 + assertEquals(0.0, result, 0.001); + } + + /** + * Test with mixed points (some inside, some outside). + */ + @Test + public void testMixedPoints() { + List points = new ArrayList<>(); + // Inside points + points.add(new PiApproximation.Point(0.0, 0.0)); + points.add(new PiApproximation.Point(0.5, 0.5)); + // Outside points + points.add(new PiApproximation.Point(1.0, 1.0)); + points.add(new PiApproximation.Point(0.9, 0.9)); + + double result = PiApproximation.approximatePi(points); + // 2 out of 4 points inside: 4 * 2/4 = 2.0 + assertEquals(2.0, result, 0.001); + } + + /** + * Test with boundary point (on the circle). + */ + @Test + public void testBoundaryPoint() { + List points = new ArrayList<>(); + points.add(new PiApproximation.Point(1.0, 0.0)); // On circle: xยฒ + yยฒ = 1 + points.add(new PiApproximation.Point(0.0, 1.0)); // On circle + + double result = PiApproximation.approximatePi(points); + // Boundary points should be counted as inside (โ‰ค 1) + assertEquals(4.0, result, 0.001); + } + + /** + * Test with small random sample (moderate accuracy expected). + */ + @Test + public void testSmallRandomSample() { + List points = PiApproximation.generateRandomPoints(1000); + double result = PiApproximation.approximatePi(points); + + // With 1000 points, result should be reasonably close to ฯ€ + assertEquals(Math.PI, result, DELTA); + } + + /** + * Test with large random sample (better accuracy expected). + */ + @Test + public void testLargeRandomSample() { + List points = PiApproximation.generateRandomPoints(100000); + double result = PiApproximation.approximatePi(points); + + // With 100000 points, result should be very close to ฯ€ + assertEquals(Math.PI, result, TIGHT_DELTA); + } + + /** + * Test that result is always positive. + */ + @Test + public void testResultIsPositive() { + List points = PiApproximation.generateRandomPoints(1000); + double result = PiApproximation.approximatePi(points); + + assertTrue(result >= 0, "Pi approximation should be positive"); + } + + /** + * Test that result is bounded (0 โ‰ค result โ‰ค 4). + */ + @Test + public void testResultIsBounded() { + List points = PiApproximation.generateRandomPoints(1000); + double result = PiApproximation.approximatePi(points); + + assertTrue(result >= 0 && result <= 4, "Pi approximation should be between 0 and 4"); + } + + /** + * Test with single point inside. + */ + @Test + public void testSinglePointInside() { + List points = new ArrayList<>(); + points.add(new PiApproximation.Point(0.0, 0.0)); + + double result = PiApproximation.approximatePi(points); + assertEquals(4.0, result, 0.001); + } + + /** + * Test with single point outside. + */ + @Test + public void testSinglePointOutside() { + List points = new ArrayList<>(); + points.add(new PiApproximation.Point(1.0, 1.0)); + + double result = PiApproximation.approximatePi(points); + assertEquals(0.0, result, 0.001); + } + + /** + * Test that generated points are within valid range [0, 1]. + */ + @Test + public void testGeneratedPointsInRange() { + List points = PiApproximation.generateRandomPoints(100); + + for (PiApproximation.Point p : points) { + assertTrue(p.x >= 0 && p.x <= 1, "X coordinate should be between 0 and 1"); + assertTrue(p.y >= 0 && p.y <= 1, "Y coordinate should be between 0 and 1"); + } + } + + /** + * Test that the correct number of points are generated. + */ + @Test + public void testCorrectNumberOfPointsGenerated() { + int expectedSize = 500; + List points = PiApproximation.generateRandomPoints(expectedSize); + + assertEquals(expectedSize, points.size()); + } +} diff --git a/src/test/java/com/thealgorithms/maths/PythagoreanTripleTest.java b/src/test/java/com/thealgorithms/maths/PythagoreanTripleTest.java index 13ea58155dec..75219dc47b47 100644 --- a/src/test/java/com/thealgorithms/maths/PythagoreanTripleTest.java +++ b/src/test/java/com/thealgorithms/maths/PythagoreanTripleTest.java @@ -1,22 +1,24 @@ package com.thealgorithms.maths; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class PythagoreanTripleTest { + @ParameterizedTest + @CsvSource({"3, 4, 5, true", "6, 8, 10, true", "9, 12, 15, true", "12, 16, 20, true", "15, 20, 25, true", "18, 24, 30, true", "5, 20, 30, false", "6, 8, 100, false", "-2, -2, 2, false", "0, 0, 0, false", "5, 5, 5, false"}) + void testIsPythagoreanTriple(int a, int b, int c, boolean expected) { + assertEquals(expected, PythagoreanTriple.isPythagTriple(a, b, c)); + } + @Test - public void testPythagoreanTriple() { - assertTrue(PythagoreanTriple.isPythagTriple(3, 4, 5)); - assertTrue(PythagoreanTriple.isPythagTriple(6, 8, 10)); - assertTrue(PythagoreanTriple.isPythagTriple(9, 12, 15)); - assertTrue(PythagoreanTriple.isPythagTriple(12, 16, 20)); - assertTrue(PythagoreanTriple.isPythagTriple(15, 20, 25)); - assertTrue(PythagoreanTriple.isPythagTriple(18, 24, 30)); - assertFalse(PythagoreanTriple.isPythagTriple(5, 20, 30)); - assertFalse(PythagoreanTriple.isPythagTriple(6, 8, 100)); - assertFalse(PythagoreanTriple.isPythagTriple(-2, -2, 2)); + void testUnorderedInputStillValid() { + // Should still detect Pythagorean triples regardless of argument order + assertTrue(PythagoreanTriple.isPythagTriple(5, 3, 4)); + assertTrue(PythagoreanTriple.isPythagTriple(13, 12, 5)); } } diff --git a/src/test/java/com/thealgorithms/maths/SieveOfAtkinTest.java b/src/test/java/com/thealgorithms/maths/SieveOfAtkinTest.java new file mode 100644 index 000000000000..8eca45c9abb8 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SieveOfAtkinTest.java @@ -0,0 +1,47 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the {@code SieveOfAtkin} class. + */ +class SieveOfAtkinTest { + + @Test + void testGeneratePrimesLimit10() { + List primes = SieveOfAtkin.generatePrimes(10); + // Assert the full expected list of primes + List expected = List.of(2, 3, 5, 7); + assertEquals(expected, primes, "Primes up to 10 should match expected list"); + } + + @Test + void testGeneratePrimesLimit2() { + List primes = SieveOfAtkin.generatePrimes(2); + List expected = List.of(2); + assertEquals(expected, primes, "Primes up to 2 should include 2"); + } + + @Test + void testGeneratePrimesLimit1() { + List primes = SieveOfAtkin.generatePrimes(1); + assertTrue(primes.isEmpty(), "Primes list should be empty when limit < 2"); + } + + @Test + void testGeneratePrimesLimit50() { + List primes = SieveOfAtkin.generatePrimes(50); + List expected = List.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47); + assertEquals(expected, primes, "Primes up to 50 should match expected list"); + } + + @Test + void testGeneratePrimesNegativeLimit() { + List primes = SieveOfAtkin.generatePrimes(-10); + assertTrue(primes.isEmpty(), "Primes list should be empty for negative limit"); + } +} diff --git a/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java b/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java new file mode 100644 index 000000000000..834fe61a049e --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java @@ -0,0 +1,68 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Test class for SumOfSquares + * + * @author BEASTSHRIRAM + */ +class SumOfSquaresTest { + + @Test + void testPerfectSquares() { + // Perfect squares should return 1 + assertEquals(1, SumOfSquares.minSquares(1)); // 1^2 + assertEquals(1, SumOfSquares.minSquares(4)); // 2^2 + assertEquals(1, SumOfSquares.minSquares(9)); // 3^2 + assertEquals(1, SumOfSquares.minSquares(16)); // 4^2 + assertEquals(1, SumOfSquares.minSquares(25)); // 5^2 + } + + @Test + void testTwoSquares() { + // Numbers that can be expressed as sum of two squares + assertEquals(2, SumOfSquares.minSquares(2)); // 1^2 + 1^2 + assertEquals(2, SumOfSquares.minSquares(5)); // 1^2 + 2^2 + assertEquals(2, SumOfSquares.minSquares(8)); // 2^2 + 2^2 + assertEquals(2, SumOfSquares.minSquares(10)); // 1^2 + 3^2 + assertEquals(2, SumOfSquares.minSquares(13)); // 2^2 + 3^2 + } + + @Test + void testThreeSquares() { + // Numbers that require exactly three squares + assertEquals(3, SumOfSquares.minSquares(3)); // 1^2 + 1^2 + 1^2 + assertEquals(3, SumOfSquares.minSquares(6)); // 1^2 + 1^2 + 2^2 + assertEquals(3, SumOfSquares.minSquares(11)); // 1^2 + 1^2 + 3^2 + assertEquals(3, SumOfSquares.minSquares(12)); // 2^2 + 2^2 + 2^2 + assertEquals(3, SumOfSquares.minSquares(14)); // 1^2 + 2^2 + 3^2 + } + + @Test + void testFourSquares() { + // Numbers that require exactly four squares (form 4^a * (8b + 7)) + assertEquals(4, SumOfSquares.minSquares(7)); // 1^2 + 1^2 + 1^2 + 2^2 + assertEquals(4, SumOfSquares.minSquares(15)); // 1^2 + 1^2 + 2^2 + 3^2 + assertEquals(4, SumOfSquares.minSquares(23)); // 1^2 + 1^2 + 3^2 + 3^2 + assertEquals(4, SumOfSquares.minSquares(28)); // 4 * 7, so needs 4 squares + assertEquals(4, SumOfSquares.minSquares(31)); // 1^2 + 2^2 + 3^2 + 3^2 + } + + @Test + void testLargerNumbers() { + // Test some larger numbers + assertEquals(1, SumOfSquares.minSquares(100)); // 10^2 + assertEquals(2, SumOfSquares.minSquares(65)); // 1^2 + 8^2 + assertEquals(3, SumOfSquares.minSquares(19)); // 1^2 + 3^2 + 3^2 + assertEquals(4, SumOfSquares.minSquares(60)); // 4 * 15, and 15 = 8*1 + 7 + } + + @Test + void testEdgeCases() { + // Test edge case + assertEquals(1, SumOfSquares.minSquares(0)); // 0^2 + } +} diff --git a/src/test/java/com/thealgorithms/maths/ZellersCongruenceTest.java b/src/test/java/com/thealgorithms/maths/ZellersCongruenceTest.java new file mode 100644 index 000000000000..71bcbdc5ed9f --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ZellersCongruenceTest.java @@ -0,0 +1,36 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class ZellersCongruenceTest { + + static Stream validDates() { + return Stream.of(Arguments.of("01-01-2000", "Saturday"), Arguments.of("12-25-2021", "Saturday"), Arguments.of("07-04-1776", "Thursday"), Arguments.of("02-29-2020", "Saturday"), Arguments.of("03-01-1900", "Thursday"), Arguments.of("03/01/1900", "Thursday")); + } + + static Stream invalidDates() { + return Stream.of(Arguments.of("13-01-2000", "Month must be between 1 and 12."), Arguments.of("02-30-2020", "Invalid date."), Arguments.of("00-15-2020", "Month must be between 1 and 12."), Arguments.of("01-01-0000", "Year must be between 46 and 8499."), + Arguments.of("01/01/200", "Input date must be 10 characters long in the format MM-DD-YYYY or MM/DD/YYYY."), Arguments.of("01@01>2000", "Date separator must be '-' or '/'."), Arguments.of("aa-01-1900", "Invalid numeric part: aa"), + Arguments.of(null, "Input date must be 10 characters long in the format MM-DD-YYYY or MM/DD/YYYY.")); + } + + @ParameterizedTest + @MethodSource("validDates") + void testValidDates(String inputDate, String expectedDay) { + String result = ZellersCongruence.calculateDay(inputDate); + assertEquals("The date " + inputDate + " falls on a " + expectedDay + ".", result); + } + + @ParameterizedTest + @MethodSource("invalidDates") + void testInvalidDates(String inputDate, String expectedErrorMessage) { + Exception exception = assertThrows(IllegalArgumentException.class, () -> ZellersCongruence.calculateDay(inputDate)); + assertEquals(expectedErrorMessage, exception.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java b/src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java new file mode 100644 index 000000000000..9463d33a18cb --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/MatrixMultiplicationTest.java @@ -0,0 +1,101 @@ +package com.thealgorithms.matrix; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class MatrixMultiplicationTest { + + private static final double EPSILON = 1e-9; // for floating point comparison + + @Test + void testMultiply1by1() { + double[][] matrixA = {{1.0}}; + double[][] matrixB = {{2.0}}; + double[][] expected = {{2.0}}; + + double[][] result = MatrixMultiplication.multiply(matrixA, matrixB); + assertMatrixEquals(expected, result); + } + + @Test + void testMultiply2by2() { + double[][] matrixA = {{1.0, 2.0}, {3.0, 4.0}}; + double[][] matrixB = {{5.0, 6.0}, {7.0, 8.0}}; + double[][] expected = {{19.0, 22.0}, {43.0, 50.0}}; + + double[][] result = MatrixMultiplication.multiply(matrixA, matrixB); + assertMatrixEquals(expected, result); // Use custom method due to floating point issues + } + + @Test + void testMultiply3by2and2by1() { + double[][] matrixA = {{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}}; + double[][] matrixB = {{7.0}, {8.0}}; + double[][] expected = {{23.0}, {53.0}, {83.0}}; + + double[][] result = MatrixMultiplication.multiply(matrixA, matrixB); + assertMatrixEquals(expected, result); + } + + @Test + void testMultiplyNonRectangularMatrices() { + double[][] matrixA = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}}; + double[][] matrixB = {{7.0, 8.0}, {9.0, 10.0}, {11.0, 12.0}}; + double[][] expected = {{58.0, 64.0}, {139.0, 154.0}}; + + double[][] result = MatrixMultiplication.multiply(matrixA, matrixB); + assertMatrixEquals(expected, result); + } + + @Test + void testNullMatrixA() { + double[][] b = {{1, 2}, {3, 4}}; + assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(null, b)); + } + + @Test + void testNullMatrixB() { + double[][] a = {{1, 2}, {3, 4}}; + assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(a, null)); + } + + @Test + void testMultiplyNull() { + double[][] matrixA = {{1.0, 2.0}, {3.0, 4.0}}; + double[][] matrixB = null; + + Exception exception = assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(matrixA, matrixB)); + + String expectedMessage = "Input matrices cannot be null"; + String actualMessage = exception.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + } + + @Test + void testIncompatibleDimensions() { + double[][] a = {{1.0, 2.0}}; + double[][] b = {{1.0, 2.0}}; + assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(a, b)); + } + + @Test + void testEmptyMatrices() { + double[][] a = new double[0][0]; + double[][] b = new double[0][0]; + assertThrows(IllegalArgumentException.class, () -> MatrixMultiplication.multiply(a, b)); + } + + private void assertMatrixEquals(double[][] expected, double[][] actual) { + assertEquals(expected.length, actual.length, "Row count mismatch"); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i].length, actual[i].length, "Column count mismatch at row " + i); + for (int j = 0; j < expected[i].length; j++) { + assertEquals(expected[i][j], actual[i][j], EPSILON, "Mismatch at (" + i + "," + j + ")"); + } + } + } +} diff --git a/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java b/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java index db66bb2d187b..b9b97014f3fc 100644 --- a/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java +++ b/src/test/java/com/thealgorithms/matrix/MedianOfMatrixTest.java @@ -1,9 +1,11 @@ package com.thealgorithms.matrix; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; @@ -31,4 +33,18 @@ public void testMedianWithEvenNumberOfElements() { assertEquals(2, result); } + + @Test + public void testMedianSingleElement() { + List> matrix = new ArrayList<>(); + matrix.add(List.of(1)); + + assertEquals(1, MedianOfMatrix.median(matrix)); + } + + @Test + void testEmptyMatrixThrowsException() { + Iterable> emptyMatrix = Collections.emptyList(); + assertThrows(IllegalArgumentException.class, () -> MedianOfMatrix.median(emptyMatrix), "Expected median() to throw, but it didn't"); + } } diff --git a/src/test/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrderTest.java b/src/test/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrderTest.java new file mode 100644 index 000000000000..745fdf98f193 --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrderTest.java @@ -0,0 +1,83 @@ +package com.thealgorithms.matrix; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +class PrintAMatrixInSpiralOrderTest { + + private final PrintAMatrixInSpiralOrder spiralPrinter = new PrintAMatrixInSpiralOrder(); + + @Test + void testSquareMatrix() { + int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + List expected = Arrays.asList(1, 2, 3, 6, 9, 8, 7, 4, 5); + assertEquals(expected, spiralPrinter.print(matrix, 3, 3)); + } + + @Test + void testRectangularMatrixMoreRows() { + int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; + List expected = Arrays.asList(1, 2, 3, 6, 9, 12, 11, 10, 7, 4, 5, 8); + assertEquals(expected, spiralPrinter.print(matrix, 4, 3)); + } + + @Test + void testRectangularMatrixMoreCols() { + int[][] matrix = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; + List expected = Arrays.asList(1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7); + assertEquals(expected, spiralPrinter.print(matrix, 3, 4)); + } + + @Test + void testSingleRow() { + int[][] matrix = {{1, 2, 3, 4}}; + List expected = Arrays.asList(1, 2, 3, 4); + assertEquals(expected, spiralPrinter.print(matrix, 1, 4)); + } + + @Test + void testSingleColumn() { + int[][] matrix = {{1}, {2}, {3}}; + List expected = Arrays.asList(1, 2, 3); + assertEquals(expected, spiralPrinter.print(matrix, 3, 1)); + } + + @Test + void testEmptyMatrix() { + int[][] matrix = new int[0][0]; + List expected = Collections.emptyList(); + assertEquals(expected, spiralPrinter.print(matrix, 0, 0)); + } + + @Test + void testOneElementMatrix() { + int[][] matrix = {{42}}; + List expected = Collections.singletonList(42); + assertEquals(expected, spiralPrinter.print(matrix, 1, 1)); + } + + @Test + void testMatrixWithNegativeNumbers() { + int[][] matrix = {{-1, -2}, {-3, -4}}; + List expected = Arrays.asList(-1, -2, -4, -3); + assertEquals(expected, spiralPrinter.print(matrix, 2, 2)); + } + + @Test + void testLargeSquareMatrix() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + List expected = Arrays.asList(3, 4, 5, 6, 7, 12, 18, 27, 34, 33, 32, 31, 30, 23, 14, 8, 9, 10, 11, 17, 26, 25, 24, 15, 16); + assertEquals(expected, spiralPrinter.print(matrix, 5, 5)); + } + + @Test + void testSingleRowWithTwoElements() { + int[][] matrix = {{2, 2}}; + List expected = Arrays.asList(2, 2); + assertEquals(expected, spiralPrinter.print(matrix, 1, 2)); + } +} diff --git a/src/test/java/com/thealgorithms/matrix/SolveSystemTest.java b/src/test/java/com/thealgorithms/matrix/SolveSystemTest.java new file mode 100644 index 000000000000..c8d289bd8339 --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/SolveSystemTest.java @@ -0,0 +1,21 @@ +package com.thealgorithms.matrix; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SolveSystemTest { + + @ParameterizedTest + @MethodSource({"matrixGenerator"}) + void solveSystem(double[][] matrix, double[] constants, double[] solution) { + double[] expected = SolveSystem.solveSystem(matrix, constants); + assertArrayEquals(expected, solution, 1.0E-10, "Solution does not match expected"); + } + private static Stream matrixGenerator() { + return Stream.of(Arguments.of(new double[][] {{-5, 8, -4}, {0, 6, 3}, {0, 0, -4}}, new double[] {38, -9, 20}, new double[] {-2, 1, -5}), Arguments.of(new double[][] {{-2, -1, -1}, {3, 4, 1}, {3, 6, 5}}, new double[] {-11, 19, 43}, new double[] {2, 2, 5})); + } +} diff --git a/src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java b/src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java deleted file mode 100644 index bb415a5861a8..000000000000 --- a/src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.thealgorithms.matrix; - -import static org.junit.jupiter.api.Assertions.assertIterableEquals; - -import java.util.List; -import org.junit.jupiter.api.Test; - -public class TestPrintMatrixInSpiralOrder { - @Test - public void testOne() { - int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; - var printClass = new PrintAMatrixInSpiralOrder(); - List res = printClass.print(matrix, matrix.length, matrix[0].length); - List list = List.of(3, 4, 5, 6, 7, 12, 18, 27, 34, 33, 32, 31, 30, 23, 14, 8, 9, 10, 11, 17, 26, 25, 24, 15, 16); - assertIterableEquals(res, list); - } - - @Test - public void testTwo() { - int[][] matrix = {{2, 2}}; - var printClass = new PrintAMatrixInSpiralOrder(); - List res = printClass.print(matrix, matrix.length, matrix[0].length); - List list = List.of(2, 2); - assertIterableEquals(res, list); - } -} diff --git a/src/test/java/com/thealgorithms/misc/MapReduceTest.java b/src/test/java/com/thealgorithms/misc/MapReduceTest.java index c79c40701cc1..748dd0a563cf 100644 --- a/src/test/java/com/thealgorithms/misc/MapReduceTest.java +++ b/src/test/java/com/thealgorithms/misc/MapReduceTest.java @@ -2,22 +2,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class MapReduceTest { - @Test - public void testMapReduceWithSingleWordSentence() { - String oneWordSentence = "Hactober"; - String result = MapReduce.mapreduce(oneWordSentence); - - assertEquals("Hactober: 1", result); - } - - @Test - public void testMapReduceWithMultipleWordSentence() { - String multipleWordSentence = "I Love Love HactoberFest"; - String result = MapReduce.mapreduce(multipleWordSentence); - - assertEquals("I: 1,Love: 2,HactoberFest: 1", result); + @ParameterizedTest + @CsvSource({"'hello world', 'hello: 1,world: 1'", "'one one two', 'one: 2,two: 1'", "'a a a a', 'a: 4'", "' spaced out ', 'spaced: 1,out: 1'"}) + void testCountWordFrequencies(String input, String expected) { + String result = MapReduce.countWordFrequencies(input); + assertEquals(expected, result); } } diff --git a/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java b/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java index e64ae1b741b6..f41953035846 100644 --- a/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java +++ b/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java @@ -11,12 +11,12 @@ */ public class MedianOfRunningArrayTest { - private static final String EXCEPTION_MESSAGE = "Enter at least 1 element, Median of empty list is not defined!"; + private static final String EXCEPTION_MESSAGE = "Median is undefined for an empty data set."; @Test public void testWhenInvalidInoutProvidedShouldThrowException() { var stream = new MedianOfRunningArrayInteger(); - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> stream.median()); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, stream::getMedian); assertEquals(exception.getMessage(), EXCEPTION_MESSAGE); } @@ -24,68 +24,68 @@ public void testWhenInvalidInoutProvidedShouldThrowException() { public void testWithNegativeValues() { var stream = new MedianOfRunningArrayInteger(); stream.insert(-1); - assertEquals(-1, stream.median()); + assertEquals(-1, stream.getMedian()); stream.insert(-2); - assertEquals(-1, stream.median()); + assertEquals(-1, stream.getMedian()); stream.insert(-3); - assertEquals(-2, stream.median()); + assertEquals(-2, stream.getMedian()); } @Test public void testWithSingleValues() { var stream = new MedianOfRunningArrayInteger(); stream.insert(-1); - assertEquals(-1, stream.median()); + assertEquals(-1, stream.getMedian()); } @Test public void testWithRandomValues() { var stream = new MedianOfRunningArrayInteger(); stream.insert(10); - assertEquals(10, stream.median()); + assertEquals(10, stream.getMedian()); stream.insert(5); - assertEquals(7, stream.median()); + assertEquals(7, stream.getMedian()); stream.insert(20); - assertEquals(10, stream.median()); + assertEquals(10, stream.getMedian()); stream.insert(15); - assertEquals(12, stream.median()); + assertEquals(12, stream.getMedian()); stream.insert(25); - assertEquals(15, stream.median()); + assertEquals(15, stream.getMedian()); stream.insert(30); - assertEquals(17, stream.median()); + assertEquals(17, stream.getMedian()); stream.insert(35); - assertEquals(20, stream.median()); + assertEquals(20, stream.getMedian()); stream.insert(1); - assertEquals(17, stream.median()); + assertEquals(17, stream.getMedian()); } @Test public void testWithNegativeAndPositiveValues() { var stream = new MedianOfRunningArrayInteger(); stream.insert(-1); - assertEquals(-1, stream.median()); + assertEquals(-1, stream.getMedian()); stream.insert(2); - assertEquals(0, stream.median()); + assertEquals(0, stream.getMedian()); stream.insert(-3); - assertEquals(-1, stream.median()); + assertEquals(-1, stream.getMedian()); } @Test public void testWithDuplicateValues() { var stream = new MedianOfRunningArrayInteger(); stream.insert(-1); - assertEquals(-1, stream.median()); + assertEquals(-1, stream.getMedian()); stream.insert(-1); - assertEquals(-1, stream.median()); + assertEquals(-1, stream.getMedian()); stream.insert(-1); - assertEquals(-1, stream.median()); + assertEquals(-1, stream.getMedian()); } @Test @@ -98,20 +98,20 @@ public void testWithDuplicateValuesB() { stream.insert(20); stream.insert(0); stream.insert(50); - assertEquals(10, stream.median()); + assertEquals(10, stream.getMedian()); } @Test public void testWithLargeValues() { var stream = new MedianOfRunningArrayInteger(); stream.insert(1000000); - assertEquals(1000000, stream.median()); + assertEquals(1000000, stream.getMedian()); stream.insert(12000); - assertEquals(506000, stream.median()); + assertEquals(506000, stream.getMedian()); stream.insert(15000000); - assertEquals(1000000, stream.median()); + assertEquals(1000000, stream.getMedian()); stream.insert(2300000); - assertEquals(1650000, stream.median()); + assertEquals(1650000, stream.getMedian()); } @Test @@ -120,7 +120,7 @@ public void testWithLargeCountOfValues() { for (int i = 1; i <= 1000; i++) { stream.insert(i); } - assertEquals(500, stream.median()); + assertEquals(500, stream.getMedian()); } @Test @@ -129,7 +129,7 @@ public void testWithThreeValuesInDescendingOrder() { stream.insert(30); stream.insert(20); stream.insert(10); - assertEquals(20, stream.median()); + assertEquals(20, stream.getMedian()); } @Test @@ -138,7 +138,7 @@ public void testWithThreeValuesInOrder() { stream.insert(10); stream.insert(20); stream.insert(30); - assertEquals(20, stream.median()); + assertEquals(20, stream.getMedian()); } @Test @@ -147,7 +147,7 @@ public void testWithThreeValuesNotInOrderA() { stream.insert(30); stream.insert(10); stream.insert(20); - assertEquals(20, stream.median()); + assertEquals(20, stream.getMedian()); } @Test @@ -156,46 +156,46 @@ public void testWithThreeValuesNotInOrderB() { stream.insert(20); stream.insert(10); stream.insert(30); - assertEquals(20, stream.median()); + assertEquals(20, stream.getMedian()); } @Test public void testWithFloatValues() { var stream = new MedianOfRunningArrayFloat(); stream.insert(20.0f); - assertEquals(20.0f, stream.median()); + assertEquals(20.0f, stream.getMedian()); stream.insert(10.5f); - assertEquals(15.25f, stream.median()); + assertEquals(15.25f, stream.getMedian()); stream.insert(30.0f); - assertEquals(20.0f, stream.median()); + assertEquals(20.0f, stream.getMedian()); } @Test public void testWithByteValues() { var stream = new MedianOfRunningArrayByte(); stream.insert((byte) 120); - assertEquals((byte) 120, stream.median()); + assertEquals((byte) 120, stream.getMedian()); stream.insert((byte) -120); - assertEquals((byte) 0, stream.median()); + assertEquals((byte) 0, stream.getMedian()); stream.insert((byte) 127); - assertEquals((byte) 120, stream.median()); + assertEquals((byte) 120, stream.getMedian()); } @Test public void testWithLongValues() { var stream = new MedianOfRunningArrayLong(); stream.insert(120000000L); - assertEquals(120000000L, stream.median()); + assertEquals(120000000L, stream.getMedian()); stream.insert(92233720368547757L); - assertEquals(46116860244273878L, stream.median()); + assertEquals(46116860244273878L, stream.getMedian()); } @Test public void testWithDoubleValues() { var stream = new MedianOfRunningArrayDouble(); stream.insert(12345.67891); - assertEquals(12345.67891, stream.median()); + assertEquals(12345.67891, stream.getMedian()); stream.insert(23456789.98); - assertEquals(11734567.83, stream.median(), .01); + assertEquals(11734567.83, stream.getMedian(), .01); } } diff --git a/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java b/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java index 7630d3e78dc7..543c66130449 100644 --- a/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java +++ b/src/test/java/com/thealgorithms/misc/RangeInSortedArrayTest.java @@ -32,4 +32,26 @@ private static Stream provideGetCountLessThanTestCases() { return Stream.of(Arguments.of(new int[] {1, 2, 3, 3, 4, 5}, 3, 4, "Count of elements less than existing key"), Arguments.of(new int[] {1, 2, 3, 3, 4, 5}, 4, 5, "Count of elements less than non-existing key"), Arguments.of(new int[] {1, 2, 2, 3}, 5, 4, "Count with all smaller elements"), Arguments.of(new int[] {2, 3, 4, 5}, 1, 0, "Count with no smaller elements"), Arguments.of(new int[] {}, 1, 0, "Count in empty array")); } + + @ParameterizedTest(name = "Edge case {index}: {3}") + @MethodSource("provideEdgeCasesForSortedRange") + void testSortedRangeEdgeCases(int[] nums, int key, int[] expectedRange, String description) { + assertArrayEquals(expectedRange, RangeInSortedArray.sortedRange(nums, key), description); + } + + private static Stream provideEdgeCasesForSortedRange() { + return Stream.of(Arguments.of(new int[] {5, 5, 5, 5, 5}, 5, new int[] {0, 4}, "All elements same as key"), Arguments.of(new int[] {1, 2, 3, 4, 5}, 1, new int[] {0, 0}, "Key is first element"), Arguments.of(new int[] {1, 2, 3, 4, 5}, 5, new int[] {4, 4}, "Key is last element"), + Arguments.of(new int[] {1, 2, 3, 4, 5}, 0, new int[] {-1, -1}, "Key less than all elements"), Arguments.of(new int[] {1, 2, 3, 4, 5}, 6, new int[] {-1, -1}, "Key greater than all elements"), + Arguments.of(new int[] {1, 2, 2, 2, 3, 3, 3, 4}, 3, new int[] {4, 6}, "Multiple occurrences spread"), Arguments.of(new int[] {2}, 2, new int[] {0, 0}, "Single element array key exists"), Arguments.of(new int[] {2}, 3, new int[] {-1, -1}, "Single element array key missing")); + } + + @ParameterizedTest(name = "Edge case {index}: {3}") + @MethodSource("provideEdgeCasesForGetCountLessThan") + void testGetCountLessThanEdgeCases(int[] nums, int key, int expectedCount, String description) { + assertEquals(expectedCount, RangeInSortedArray.getCountLessThan(nums, key), description); + } + + private static Stream provideEdgeCasesForGetCountLessThan() { + return Stream.of(Arguments.of(new int[] {1, 2, 3, 4, 5}, 0, 0, "Key less than all elements"), Arguments.of(new int[] {1, 2, 3, 4, 5}, 6, 5, "Key greater than all elements"), Arguments.of(new int[] {1}, 0, 0, "Single element greater than key")); + } } diff --git a/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java b/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java index a581a35bf963..3dc61f2c6569 100644 --- a/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/others/CRCAlgorithmTest.java @@ -1,29 +1,163 @@ - package com.thealgorithms.others; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; public class CRCAlgorithmTest { @Test - void test1() { + void testNoErrorsWithZeroBER() { CRCAlgorithm c = new CRCAlgorithm("10010101010100101010010000001010010101010", 10, 0.0); + c.generateRandomMess(); + c.divideMessageWithP(false); + c.changeMess(); + c.divideMessageWithP(true); + assertEquals(0, c.getWrongMess(), "BER=0 should produce no wrong messages"); + assertEquals(0, c.getWrongMessCaught(), "No errors, so no caught wrong messages"); + assertEquals(0, c.getWrongMessNotCaught(), "No errors, so no uncaught wrong messages"); + assertTrue(c.getCorrectMess() > 0, "Should have some correct messages"); + } + + @Test + void testAllErrorsWithBEROne() { + CRCAlgorithm c = new CRCAlgorithm("10010101010100101010010000001010010101010", 10, 1.0); + c.generateRandomMess(); + c.divideMessageWithP(false); + c.changeMess(); + c.divideMessageWithP(true); + assertTrue(c.getWrongMess() > 0, "BER=1 should produce wrong messages"); + assertEquals(0, c.getCorrectMess(), "BER=1 should produce no correct messages"); + } + + @Test + void testIntermediateBER() { + CRCAlgorithm c = new CRCAlgorithm("1101", 4, 0.5); + c.generateRandomMess(); + for (int i = 0; i < 1000; i++) { + c.refactor(); + c.generateRandomMess(); + c.divideMessageWithP(false); + c.changeMess(); + c.divideMessageWithP(true); + } + assertTrue(c.getWrongMess() > 0, "Some wrong messages expected with BER=0.5"); + assertTrue(c.getWrongMessCaught() >= 0, "Wrong messages caught counter >= 0"); + assertTrue(c.getWrongMessNotCaught() >= 0, "Wrong messages not caught counter >= 0"); + assertTrue(c.getCorrectMess() >= 0, "Correct messages counter >= 0"); + assertEquals(c.getWrongMess(), c.getWrongMessCaught() + c.getWrongMessNotCaught(), "Sum of caught and not caught wrong messages should equal total wrong messages"); + } - // A bit-error rate of 0.0 should not provide any wrong messages + @Test + void testMessageChangedFlag() { + CRCAlgorithm c = new CRCAlgorithm("1010", 4, 1.0); + c.generateRandomMess(); + c.divideMessageWithP(false); c.changeMess(); + assertTrue(c.getWrongMess() > 0, "Message should be marked as changed with BER=1"); + } + + @Test + void testSmallMessageSize() { + CRCAlgorithm c = new CRCAlgorithm("11", 2, 0.0); + c.generateRandomMess(); c.divideMessageWithP(false); - assertEquals(c.getWrongMess(), 0); + c.changeMess(); + c.divideMessageWithP(true); + assertEquals(0, c.getWrongMess(), "No errors expected for BER=0 with small message"); } @Test - void test2() { - CRCAlgorithm c = new CRCAlgorithm("10010101010100101010010000001010010101010", 10, 1.0); + void testLargeMessageSize() { + CRCAlgorithm c = new CRCAlgorithm("1101", 1000, 0.01); + c.generateRandomMess(); + c.divideMessageWithP(false); + c.changeMess(); + c.divideMessageWithP(true); + // Just ensure counters are updated, no exceptions + assertTrue(c.getWrongMess() >= 0); + assertTrue(c.getCorrectMess() >= 0); + } + + @Test + void testSingleBitMessage() { + CRCAlgorithm c = new CRCAlgorithm("11", 1, 0.0); + c.generateRandomMess(); + c.divideMessageWithP(false); + c.changeMess(); + c.divideMessageWithP(true); + + assertTrue(c.getCorrectMess() >= 0, "Single bit message should be handled"); + } - // A bit error rate of 1.0 should not provide any correct messages + @Test + void testPolynomialLongerThanMessage() { + CRCAlgorithm c = new CRCAlgorithm("11010101", 3, 0.0); + c.generateRandomMess(); + c.divideMessageWithP(false); c.changeMess(); + c.divideMessageWithP(true); + + // Should not crash, counters should be valid + assertTrue(c.getCorrectMess() + c.getWrongMess() >= 0); + } + + @Test + void testPolynomialWithOnlyOnes() { + CRCAlgorithm c = new CRCAlgorithm("1111", 5, 0.1); + c.generateRandomMess(); c.divideMessageWithP(false); - assertEquals(c.getCorrectMess(), 0); + c.changeMess(); + c.divideMessageWithP(true); + + assertTrue(c.getCorrectMess() + c.getWrongMess() >= 0); + } + + @Test + void testMultipleRefactorCalls() { + CRCAlgorithm c = new CRCAlgorithm("1101", 5, 0.2); + + for (int i = 0; i < 5; i++) { + c.refactor(); + c.generateRandomMess(); + c.divideMessageWithP(false); + c.changeMess(); + c.divideMessageWithP(true); + } + + // Counters should accumulate across multiple runs + assertTrue(c.getCorrectMess() + c.getWrongMess() > 0); + } + + @Test + void testCounterConsistency() { + CRCAlgorithm c = new CRCAlgorithm("1101", 10, 0.3); + + for (int i = 0; i < 100; i++) { + c.refactor(); + c.generateRandomMess(); + c.divideMessageWithP(false); + c.changeMess(); + c.divideMessageWithP(true); + } + + // Total messages processed should equal correct + wrong + int totalProcessed = c.getCorrectMess() + c.getWrongMess(); + assertEquals(100, totalProcessed, "Total processed messages should equal iterations"); + + // Wrong messages should equal caught + not caught + assertEquals(c.getWrongMess(), c.getWrongMessCaught() + c.getWrongMessNotCaught(), "Wrong messages should equal sum of caught and not caught"); + } + + @Test + void testGetterMethodsInitialState() { + CRCAlgorithm c = new CRCAlgorithm("1101", 10, 0.1); + + // Check initial state + assertEquals(0, c.getCorrectMess(), "Initial correct messages should be 0"); + assertEquals(0, c.getWrongMess(), "Initial wrong messages should be 0"); + assertEquals(0, c.getWrongMessCaught(), "Initial caught wrong messages should be 0"); + assertEquals(0, c.getWrongMessNotCaught(), "Initial not caught wrong messages should be 0"); } } diff --git a/src/test/java/com/thealgorithms/others/HuffmanTest.java b/src/test/java/com/thealgorithms/others/HuffmanTest.java new file mode 100644 index 000000000000..aa16f6493506 --- /dev/null +++ b/src/test/java/com/thealgorithms/others/HuffmanTest.java @@ -0,0 +1,223 @@ +package com.thealgorithms.others; + +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test class for Huffman coding algorithm. + * Tests various scenarios including normal cases, edge cases, and error + * conditions. + */ +class HuffmanTest { + + @Test + void testBuildHuffmanTreeWithBasicInput() { + char[] charArray = {'a', 'b', 'c', 'd', 'e', 'f'}; + int[] charFreq = {5, 9, 12, 13, 16, 45}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + + Assertions.assertNotNull(root); + Assertions.assertEquals(100, root.data); // Total frequency + } + + @Test + void testGenerateCodesWithBasicInput() { + char[] charArray = {'a', 'b', 'c', 'd', 'e', 'f'}; + int[] charFreq = {5, 9, 12, 13, 16, 45}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + Assertions.assertNotNull(codes); + Assertions.assertEquals(6, codes.size()); + + // Verify that all characters have codes + for (char c : charArray) { + Assertions.assertTrue(codes.containsKey(c), "Missing code for character: " + c); + Assertions.assertNotNull(codes.get(c), "Null code for character: " + c); + } + + // Verify that higher frequency characters have shorter codes + // 'f' has the highest frequency (45), so it should have one of the shortest + // codes + Assertions.assertTrue(codes.get('f').length() <= codes.get('a').length()); + } + + @Test + void testSingleCharacter() { + char[] charArray = {'a'}; + int[] charFreq = {10}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + Assertions.assertNotNull(codes); + Assertions.assertEquals(1, codes.size()); + Assertions.assertEquals("0", codes.get('a')); // Single character gets code "0" + } + + @Test + void testTwoCharacters() { + char[] charArray = {'a', 'b'}; + int[] charFreq = {3, 7}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + Assertions.assertNotNull(codes); + Assertions.assertEquals(2, codes.size()); + + // Verify both characters have codes + Assertions.assertTrue(codes.containsKey('a')); + Assertions.assertTrue(codes.containsKey('b')); + + // Verify codes are different + Assertions.assertNotEquals(codes.get('a'), codes.get('b')); + } + + @Test + void testEqualFrequencies() { + char[] charArray = {'a', 'b', 'c'}; + int[] charFreq = {5, 5, 5}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + Assertions.assertNotNull(codes); + Assertions.assertEquals(3, codes.size()); + + // Verify all characters have codes + for (char c : charArray) { + Assertions.assertTrue(codes.containsKey(c)); + } + } + + @Test + void testLargeFrequencyDifference() { + char[] charArray = {'a', 'b', 'c'}; + int[] charFreq = {1, 10, 100}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + Assertions.assertNotNull(codes); + Assertions.assertEquals(3, codes.size()); + + // Character 'c' with highest frequency should have shortest code + Assertions.assertTrue(codes.get('c').length() <= codes.get('b').length()); + Assertions.assertTrue(codes.get('c').length() <= codes.get('a').length()); + } + + @Test + void testNullCharacterArray() { + int[] charFreq = {5, 9, 12}; + + Assertions.assertThrows(IllegalArgumentException.class, () -> { Huffman.buildHuffmanTree(null, charFreq); }); + } + + @Test + void testNullFrequencyArray() { + char[] charArray = {'a', 'b', 'c'}; + + Assertions.assertThrows(IllegalArgumentException.class, () -> { Huffman.buildHuffmanTree(charArray, null); }); + } + + @Test + void testEmptyArrays() { + char[] charArray = {}; + int[] charFreq = {}; + + Assertions.assertThrows(IllegalArgumentException.class, () -> { Huffman.buildHuffmanTree(charArray, charFreq); }); + } + + @Test + void testMismatchedArrayLengths() { + char[] charArray = {'a', 'b', 'c'}; + int[] charFreq = {5, 9}; + + Assertions.assertThrows(IllegalArgumentException.class, () -> { Huffman.buildHuffmanTree(charArray, charFreq); }); + } + + @Test + void testNegativeFrequency() { + char[] charArray = {'a', 'b', 'c'}; + int[] charFreq = {5, -9, 12}; + + Assertions.assertThrows(IllegalArgumentException.class, () -> { Huffman.buildHuffmanTree(charArray, charFreq); }); + } + + @Test + void testZeroFrequency() { + char[] charArray = {'a', 'b', 'c'}; + int[] charFreq = {0, 5, 10}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + Assertions.assertNotNull(codes); + Assertions.assertEquals(3, codes.size()); + Assertions.assertTrue(codes.containsKey('a')); // Even with 0 frequency, character should have a code + } + + @Test + void testGenerateCodesWithNullRoot() { + Map codes = Huffman.generateCodes(null); + + Assertions.assertNotNull(codes); + Assertions.assertTrue(codes.isEmpty()); + } + + @Test + void testPrefixProperty() { + // Verify that no code is a prefix of another (Huffman property) + char[] charArray = {'a', 'b', 'c', 'd', 'e'}; + int[] charFreq = {5, 9, 12, 13, 16}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + // Check that no code is a prefix of another + for (Map.Entry entry1 : codes.entrySet()) { + for (Map.Entry entry2 : codes.entrySet()) { + if (!entry1.getKey().equals(entry2.getKey())) { + String code1 = entry1.getValue(); + String code2 = entry2.getValue(); + Assertions.assertTrue(!code1.startsWith(code2) && !code2.startsWith(code1), "Code " + code1 + " is a prefix of " + code2); + } + } + } + } + + @Test + void testBinaryCodesOnly() { + // Verify that all codes contain only '0' and '1' + char[] charArray = {'a', 'b', 'c', 'd'}; + int[] charFreq = {1, 2, 3, 4}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + for (String code : codes.values()) { + Assertions.assertTrue(code.matches("[01]+"), "Code contains non-binary characters: " + code); + } + } + + @Test + void testMultipleCharactersWithLargeAlphabet() { + char[] charArray = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}; + int[] charFreq = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; + + HuffmanNode root = Huffman.buildHuffmanTree(charArray, charFreq); + Map codes = Huffman.generateCodes(root); + + Assertions.assertNotNull(codes); + Assertions.assertEquals(10, codes.size()); + + // Verify all characters have codes + for (char c : charArray) { + Assertions.assertTrue(codes.containsKey(c)); + } + } +} diff --git a/src/test/java/com/thealgorithms/others/InsertDeleteInArrayTest.java b/src/test/java/com/thealgorithms/others/InsertDeleteInArrayTest.java new file mode 100644 index 000000000000..e8e49980de13 --- /dev/null +++ b/src/test/java/com/thealgorithms/others/InsertDeleteInArrayTest.java @@ -0,0 +1,220 @@ +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Test cases for {@link InsertDeleteInArray} class. + *

+ * Tests cover: + *

    + *
  • Insert operations at various positions
  • + *
  • Delete operations at various positions
  • + *
  • Edge cases (empty arrays, single element, boundary positions)
  • + *
  • Error conditions (null arrays, invalid positions)
  • + *
+ *

+ */ +class InsertDeleteInArrayTest { + + // ========== Insert Element Tests ========== + + @Test + void testInsertAtBeginning() { + int[] array = {2, 3, 4, 5}; + int[] result = InsertDeleteInArray.insertElement(array, 1, 0); + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, result); + } + + @Test + void testInsertAtEnd() { + int[] array = {1, 2, 3, 4}; + int[] result = InsertDeleteInArray.insertElement(array, 5, 4); + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, result); + } + + @Test + void testInsertInMiddle() { + int[] array = {1, 2, 4, 5}; + int[] result = InsertDeleteInArray.insertElement(array, 3, 2); + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, result); + } + + @Test + void testInsertIntoEmptyArray() { + int[] array = {}; + int[] result = InsertDeleteInArray.insertElement(array, 42, 0); + assertArrayEquals(new int[] {42}, result); + } + + @Test + void testInsertIntoSingleElementArray() { + int[] array = {5}; + int[] result = InsertDeleteInArray.insertElement(array, 3, 0); + assertArrayEquals(new int[] {3, 5}, result); + } + + @Test + void testInsertNegativeNumber() { + int[] array = {1, 2, 3}; + int[] result = InsertDeleteInArray.insertElement(array, -10, 1); + assertArrayEquals(new int[] {1, -10, 2, 3}, result); + } + + @Test + void testInsertZero() { + int[] array = {1, 2, 3}; + int[] result = InsertDeleteInArray.insertElement(array, 0, 1); + assertArrayEquals(new int[] {1, 0, 2, 3}, result); + } + + @Test + void testInsertWithNullArray() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> InsertDeleteInArray.insertElement(null, 5, 0)); + assertEquals("Array cannot be null", exception.getMessage()); + } + + @Test + void testInsertWithNegativePosition() { + int[] array = {1, 2, 3}; + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> InsertDeleteInArray.insertElement(array, 5, -1)); + assertEquals("Position must be between 0 and 3", exception.getMessage()); + } + + @Test + void testInsertWithPositionTooLarge() { + int[] array = {1, 2, 3}; + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> InsertDeleteInArray.insertElement(array, 5, 4)); + assertEquals("Position must be between 0 and 3", exception.getMessage()); + } + + // ========== Delete Element Tests ========== + + @Test + void testDeleteFromBeginning() { + int[] array = {1, 2, 3, 4, 5}; + int[] result = InsertDeleteInArray.deleteElement(array, 0); + assertArrayEquals(new int[] {2, 3, 4, 5}, result); + } + + @Test + void testDeleteFromEnd() { + int[] array = {1, 2, 3, 4, 5}; + int[] result = InsertDeleteInArray.deleteElement(array, 4); + assertArrayEquals(new int[] {1, 2, 3, 4}, result); + } + + @Test + void testDeleteFromMiddle() { + int[] array = {1, 2, 3, 4, 5}; + int[] result = InsertDeleteInArray.deleteElement(array, 2); + assertArrayEquals(new int[] {1, 2, 4, 5}, result); + } + + @Test + void testDeleteFromSingleElementArray() { + int[] array = {42}; + int[] result = InsertDeleteInArray.deleteElement(array, 0); + assertArrayEquals(new int[] {}, result); + } + + @Test + void testDeleteFromTwoElementArray() { + int[] array = {10, 20}; + int[] result = InsertDeleteInArray.deleteElement(array, 1); + assertArrayEquals(new int[] {10}, result); + } + + @Test + void testDeleteWithNullArray() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> InsertDeleteInArray.deleteElement(null, 0)); + assertEquals("Array cannot be null", exception.getMessage()); + } + + @Test + void testDeleteFromEmptyArray() { + int[] array = {}; + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> InsertDeleteInArray.deleteElement(array, 0)); + assertEquals("Array is empty", exception.getMessage()); + } + + @Test + void testDeleteWithNegativePosition() { + int[] array = {1, 2, 3}; + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> InsertDeleteInArray.deleteElement(array, -1)); + assertEquals("Position must be between 0 and 2", exception.getMessage()); + } + + @Test + void testDeleteWithPositionTooLarge() { + int[] array = {1, 2, 3}; + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> InsertDeleteInArray.deleteElement(array, 3)); + assertEquals("Position must be between 0 and 2", exception.getMessage()); + } + + @Test + void testDeleteWithPositionEqualToLength() { + int[] array = {1, 2, 3, 4, 5}; + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> InsertDeleteInArray.deleteElement(array, 5)); + assertEquals("Position must be between 0 and 4", exception.getMessage()); + } + + // ========== Combined Operations Tests ========== + + @Test + void testInsertThenDelete() { + int[] array = {1, 2, 3}; + int[] afterInsert = InsertDeleteInArray.insertElement(array, 99, 1); + assertArrayEquals(new int[] {1, 99, 2, 3}, afterInsert); + int[] afterDelete = InsertDeleteInArray.deleteElement(afterInsert, 1); + assertArrayEquals(new int[] {1, 2, 3}, afterDelete); + } + + @Test + void testMultipleInsertions() { + int[] array = {1, 3, 5}; + array = InsertDeleteInArray.insertElement(array, 2, 1); + assertArrayEquals(new int[] {1, 2, 3, 5}, array); + array = InsertDeleteInArray.insertElement(array, 4, 3); + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, array); + } + + @Test + void testMultipleDeletions() { + int[] array = {1, 2, 3, 4, 5}; + array = InsertDeleteInArray.deleteElement(array, 2); + assertArrayEquals(new int[] {1, 2, 4, 5}, array); + array = InsertDeleteInArray.deleteElement(array, 0); + assertArrayEquals(new int[] {2, 4, 5}, array); + } + + @Test + void testLargeArray() { + int[] array = new int[100]; + for (int i = 0; i < 100; i++) { + array[i] = i; + } + int[] result = InsertDeleteInArray.insertElement(array, 999, 50); + assertEquals(101, result.length); + assertEquals(999, result[50]); + assertEquals(49, result[49]); + assertEquals(50, result[51]); + } + + @Test + void testArrayWithDuplicates() { + int[] array = {1, 2, 2, 3, 2}; + int[] result = InsertDeleteInArray.deleteElement(array, 1); + assertArrayEquals(new int[] {1, 2, 3, 2}, result); + } + + @Test + void testNegativeNumbers() { + int[] array = {-5, -3, -1, 0, 1}; + int[] result = InsertDeleteInArray.insertElement(array, -2, 2); + assertArrayEquals(new int[] {-5, -3, -2, -1, 0, 1}, result); + } +} diff --git a/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java b/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java new file mode 100644 index 000000000000..560f1df68e81 --- /dev/null +++ b/src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java @@ -0,0 +1,163 @@ +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class IterativeFloodFillTest { + + @Test + void testForEmptyImage() { + int[][] image = {}; + int[][] expected = {}; + + IterativeFloodFill.floodFill(image, 4, 5, 3, 2); + assertArrayEquals(expected, image); + } + + @Test + void testForSingleElementImage() { + int[][] image = {{1}}; + int[][] expected = {{3}}; + + IterativeFloodFill.floodFill(image, 0, 0, 3, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForEmptyRow() { + int[][] image = {{}}; + int[][] expected = {{}}; + + IterativeFloodFill.floodFill(image, 4, 5, 3, 2); + assertArrayEquals(expected, image); + } + + @Test + void testForImageOne() { + int[][] image = { + {0, 0, 0, 0, 0, 0, 0}, + {0, 3, 3, 3, 3, 0, 0}, + {0, 3, 1, 1, 5, 0, 0}, + {0, 3, 1, 1, 5, 5, 3}, + {0, 3, 5, 5, 1, 1, 3}, + {0, 0, 0, 5, 1, 1, 3}, + {0, 0, 0, 3, 3, 3, 3}, + }; + + int[][] expected = { + {0, 0, 0, 0, 0, 0, 0}, + {0, 3, 3, 3, 3, 0, 0}, + {0, 3, 2, 2, 5, 0, 0}, + {0, 3, 2, 2, 5, 5, 3}, + {0, 3, 5, 5, 2, 2, 3}, + {0, 0, 0, 5, 2, 2, 3}, + {0, 0, 0, 3, 3, 3, 3}, + }; + + IterativeFloodFill.floodFill(image, 2, 2, 2, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForImageTwo() { + int[][] image = { + {0, 0, 1, 1, 0, 0, 0}, + {1, 1, 3, 3, 3, 0, 0}, + {1, 3, 1, 1, 5, 0, 0}, + {0, 3, 1, 1, 5, 5, 3}, + {0, 3, 5, 5, 1, 1, 3}, + {0, 0, 0, 5, 1, 1, 3}, + {0, 0, 0, 1, 3, 1, 3}, + }; + + int[][] expected = { + {0, 0, 2, 2, 0, 0, 0}, + {2, 2, 3, 3, 3, 0, 0}, + {2, 3, 2, 2, 5, 0, 0}, + {0, 3, 2, 2, 5, 5, 3}, + {0, 3, 5, 5, 2, 2, 3}, + {0, 0, 0, 5, 2, 2, 3}, + {0, 0, 0, 2, 3, 2, 3}, + }; + + IterativeFloodFill.floodFill(image, 2, 2, 2, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForImageThree() { + int[][] image = { + {1, 1, 2, 3, 1, 1, 1}, + {1, 0, 0, 1, 0, 0, 1}, + {1, 1, 1, 0, 3, 1, 2}, + }; + + int[][] expected = { + {4, 4, 2, 3, 4, 4, 4}, + {4, 0, 0, 4, 0, 0, 4}, + {4, 4, 4, 0, 3, 4, 2}, + }; + + IterativeFloodFill.floodFill(image, 0, 1, 4, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForSameNewAndOldColor() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 0, 1, 1, 1); + assertArrayEquals(expected, image); + } + + @Test + void testForBigImage() { + int[][] image = new int[100][100]; + + assertDoesNotThrow(() -> IterativeFloodFill.floodFill(image, 0, 0, 1, 0)); + } + + @Test + void testForBelowZeroX() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, -1, 1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForBelowZeroY() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 1, -1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForAboveBoundaryX() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 100, 1, 1, 0); + assertArrayEquals(expected, image); + } + + @Test + void testForAboveBoundaryY() { + int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}}; + + IterativeFloodFill.floodFill(image, 1, 100, 1, 0); + assertArrayEquals(expected, image); + } +} diff --git a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java b/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java index 1014f39a26bc..7c3ce6635aa0 100644 --- a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java +++ b/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java @@ -1,53 +1,80 @@ package com.thealgorithms.others; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +/** + * Comprehensive test suite for {@link LowestBasePalindrome}. + * Tests all public methods including edge cases and exception handling. + * + * @author TheAlgorithms Contributors + */ public class LowestBasePalindromeTest { @ParameterizedTest @MethodSource("provideListsForIsPalindromicPositive") public void testIsPalindromicPositive(List list) { - assertTrue(LowestBasePalindrome.isPalindromic(list)); + Assertions.assertTrue(LowestBasePalindrome.isPalindromic(list)); } @ParameterizedTest @MethodSource("provideListsForIsPalindromicNegative") public void testIsPalindromicNegative(List list) { - assertFalse(LowestBasePalindrome.isPalindromic(list)); + Assertions.assertFalse(LowestBasePalindrome.isPalindromic(list)); } @ParameterizedTest @MethodSource("provideNumbersAndBasesForIsPalindromicInBasePositive") public void testIsPalindromicInBasePositive(int number, int base) { - assertTrue(LowestBasePalindrome.isPalindromicInBase(number, base)); + Assertions.assertTrue(LowestBasePalindrome.isPalindromicInBase(number, base)); } @ParameterizedTest @MethodSource("provideNumbersAndBasesForIsPalindromicInBaseNegative") public void testIsPalindromicInBaseNegative(int number, int base) { - assertFalse(LowestBasePalindrome.isPalindromicInBase(number, base)); + Assertions.assertFalse(LowestBasePalindrome.isPalindromicInBase(number, base)); } @ParameterizedTest @MethodSource("provideNumbersAndBasesForExceptions") public void testIsPalindromicInBaseThrowsException(int number, int base) { - org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> LowestBasePalindrome.isPalindromicInBase(number, base)); + Assertions.assertThrows(IllegalArgumentException.class, () -> LowestBasePalindrome.isPalindromicInBase(number, base)); } @ParameterizedTest @MethodSource("provideNumbersForLowestBasePalindrome") public void testLowestBasePalindrome(int number, int expectedBase) { - assertEquals(expectedBase, LowestBasePalindrome.lowestBasePalindrome(number)); + Assertions.assertEquals(expectedBase, LowestBasePalindrome.lowestBasePalindrome(number)); + } + + @ParameterizedTest + @MethodSource("provideNumbersForComputeDigitsInBase") + public void testComputeDigitsInBase(int number, int base, List expectedDigits) { + Assertions.assertEquals(expectedDigits, LowestBasePalindrome.computeDigitsInBase(number, base)); + } + + @ParameterizedTest + @MethodSource("provideInvalidNumbersForComputeDigits") + public void testComputeDigitsInBaseThrowsExceptionForNegativeNumber(int number, int base) { + Assertions.assertThrows(IllegalArgumentException.class, () -> LowestBasePalindrome.computeDigitsInBase(number, base)); + } + + @ParameterizedTest + @MethodSource("provideInvalidBasesForComputeDigits") + public void testComputeDigitsInBaseThrowsExceptionForInvalidBase(int number, int base) { + Assertions.assertThrows(IllegalArgumentException.class, () -> LowestBasePalindrome.computeDigitsInBase(number, base)); + } + + @ParameterizedTest + @MethodSource("provideNegativeNumbersForLowestBasePalindrome") + public void testLowestBasePalindromeThrowsExceptionForNegativeNumber(int number) { + Assertions.assertThrows(IllegalArgumentException.class, () -> LowestBasePalindrome.lowestBasePalindrome(number)); } private static Stream provideListsForIsPalindromicPositive() { @@ -74,4 +101,21 @@ private static Stream provideNumbersForLowestBasePalindrome() { return Stream.of(Arguments.of(0, 2), Arguments.of(1, 2), Arguments.of(2, 3), Arguments.of(3, 2), Arguments.of(10, 3), Arguments.of(11, 10), Arguments.of(15, 2), Arguments.of(39, 12), Arguments.of(44, 10), Arguments.of(58, 28), Arguments.of(69, 22), Arguments.of(79, 78), Arguments.of(87, 28), Arguments.of(90, 14), Arguments.of(5591, 37), Arguments.of(5895, 130), Arguments.of(9950, 198), Arguments.of(9974, 4986)); } + + private static Stream provideNumbersForComputeDigitsInBase() { + return Stream.of(Arguments.of(0, 2, new ArrayList<>()), Arguments.of(5, 2, Arrays.asList(1, 0, 1)), Arguments.of(13, 2, Arrays.asList(1, 0, 1, 1)), Arguments.of(10, 3, Arrays.asList(1, 0, 1)), Arguments.of(15, 2, Arrays.asList(1, 1, 1, 1)), Arguments.of(101, 10, Arrays.asList(1, 0, 1)), + Arguments.of(255, 16, Arrays.asList(15, 15)), Arguments.of(100, 10, Arrays.asList(0, 0, 1))); + } + + private static Stream provideInvalidNumbersForComputeDigits() { + return Stream.of(Arguments.of(-1, 2), Arguments.of(-10, 10), Arguments.of(-100, 5)); + } + + private static Stream provideInvalidBasesForComputeDigits() { + return Stream.of(Arguments.of(10, 1), Arguments.of(5, 0), Arguments.of(100, -1)); + } + + private static Stream provideNegativeNumbersForLowestBasePalindrome() { + return Stream.of(Arguments.of(-1), Arguments.of(-10), Arguments.of(-100)); + } } diff --git a/src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java b/src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java index f360e3f53546..1a42f1815a96 100644 --- a/src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java +++ b/src/test/java/com/thealgorithms/others/MaximumSumOfDistinctSubarraysWithLengthKTest.java @@ -3,20 +3,157 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -public class MaximumSumOfDistinctSubarraysWithLengthKTest { +/** + * Test class for {@link MaximumSumOfDistinctSubarraysWithLengthK}. + * + * This class contains comprehensive test cases to verify the correctness of the + * maximum subarray sum algorithm with distinct elements constraint. + */ +class MaximumSumOfDistinctSubarraysWithLengthKTest { + /** + * Parameterized test for various input scenarios. + * + * @param expected the expected maximum sum + * @param k the subarray size + * @param arr the input array + */ @ParameterizedTest @MethodSource("inputStream") - void testMaximumSubarraySum(int expected, int k, int[] arr) { + void testMaximumSubarraySum(long expected, int k, int[] arr) { assertEquals(expected, MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(k, arr)); } + /** + * Provides test cases for the parameterized test. + * + * Test cases cover: + * - Normal cases with distinct and duplicate elements + * - Edge cases (empty array, k = 0, k > array length) + * - Single element arrays + * - Arrays with all duplicates + * - Negative numbers + * - Large sums + * + * @return stream of test arguments + */ private static Stream inputStream() { - return Stream.of(Arguments.of(15, 3, new int[] {1, 5, 4, 2, 9, 9, 9}), Arguments.of(0, 3, new int[] {4, 4, 4}), Arguments.of(12, 3, new int[] {9, 9, 9, 1, 2, 3}), Arguments.of(0, 0, new int[] {9, 9, 9}), Arguments.of(0, 5, new int[] {9, 9, 9}), Arguments.of(9, 1, new int[] {9, 2, 3, 7}), - Arguments.of(15, 5, new int[] {1, 2, 3, 4, 5}), Arguments.of(6, 3, new int[] {-1, 2, 3, 1, -2, 4}), Arguments.of(10, 1, new int[] {10}), Arguments.of(0, 2, new int[] {7, 7, 7, 7}), Arguments.of(0, 3, new int[] {}), Arguments.of(0, 10, new int[] {1, 2, 3})); + return Stream.of( + // Normal case: [5, 4, 2] has distinct elements with sum 11, but [4, 2, 9] also + // distinct with sum 15 + Arguments.of(15L, 3, new int[] {1, 5, 4, 2, 9, 9, 9}), + // All elements are same, no distinct subarray of size 3 + Arguments.of(0L, 3, new int[] {4, 4, 4}), + // First three have duplicates, but [1, 2, 3] are distinct with sum 6, wait + // [9,1,2] has sum 12 + Arguments.of(12L, 3, new int[] {9, 9, 9, 1, 2, 3}), + // k = 0, should return 0 + Arguments.of(0L, 0, new int[] {9, 9, 9}), + // k > array length, should return 0 + Arguments.of(0L, 5, new int[] {9, 9, 9}), + // k = 1, single element (always distinct) + Arguments.of(9L, 1, new int[] {9, 2, 3, 7}), + // All distinct elements, size matches array + Arguments.of(15L, 5, new int[] {1, 2, 3, 4, 5}), + // Array with negative numbers + Arguments.of(6L, 3, new int[] {-1, 2, 3, 1, -2, 4}), + // Single element array + Arguments.of(10L, 1, new int[] {10}), + // All duplicates with k = 2 + Arguments.of(0L, 2, new int[] {7, 7, 7, 7}), + // Empty array + Arguments.of(0L, 3, new int[] {}), + // k much larger than array length + Arguments.of(0L, 10, new int[] {1, 2, 3})); + } + + /** + * Test with a larger array and larger k value. + */ + @Test + void testLargerArray() { + int[] arr = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(5, arr); + // Maximum sum with 5 distinct elements: [6,7,8,9,10] = 40 + assertEquals(40L, result); + } + + /** + * Test with negative k value. + */ + @Test + void testNegativeK() { + int[] arr = new int[] {1, 2, 3, 4, 5}; + long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(-1, arr); + assertEquals(0L, result); + } + + /** + * Test with null array. + */ + @Test + void testNullArray() { + int[] nullArray = null; + long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, new int[][] {nullArray}[0]); + assertEquals(0L, result); + } + + /** + * Test with array containing duplicates at boundaries. + */ + @Test + void testDuplicatesAtBoundaries() { + int[] arr = new int[] {1, 1, 2, 3, 4, 4}; + // [2, 3, 4] is the only valid window with sum 9 + long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr); + assertEquals(9L, result); + } + + /** + * Test with large numbers to verify long return type. + */ + @Test + void testLargeNumbers() { + int[] arr = new int[] {1000000, 2000000, 3000000, 4000000}; + // All elements are distinct, max sum with k=3 is [2000000, 3000000, 4000000] = + // 9000000 + long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr); + assertEquals(9000000L, result); + } + + /** + * Test where multiple windows have the same maximum sum. + */ + @Test + void testMultipleMaxWindows() { + int[] arr = new int[] {1, 2, 3, 4, 3, 2, 1}; + // Windows [1,2,3], [2,3,4], [4,3,2], [3,2,1] - max is [2,3,4] = 9 + long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(3, arr); + assertEquals(9L, result); + } + + /** + * Test with only two elements and k=2. + */ + @Test + void testTwoElementsDistinct() { + int[] arr = new int[] {5, 10}; + long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(2, arr); + assertEquals(15L, result); + } + + /** + * Test with only two elements (duplicates) and k=2. + */ + @Test + void testTwoElementsDuplicate() { + int[] arr = new int[] {5, 5}; + long result = MaximumSumOfDistinctSubarraysWithLengthK.maximumSubarraySum(2, arr); + assertEquals(0L, result); } } diff --git a/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java new file mode 100644 index 000000000000..4e81c8b7e34f --- /dev/null +++ b/src/test/java/com/thealgorithms/others/MiniMaxAlgorithmTest.java @@ -0,0 +1,344 @@ +package com.thealgorithms.others; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Test class for MiniMaxAlgorithm + * Tests the minimax algorithm implementation for game tree evaluation + */ +class MiniMaxAlgorithmTest { + + private MiniMaxAlgorithm miniMax; + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + void setUp() { + miniMax = new MiniMaxAlgorithm(); + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + void tearDown() { + System.setOut(originalOut); + } + + @Test + void testConstructorCreatesValidScores() { + // The default constructor should create scores array of length 8 (2^3) + Assertions.assertEquals(8, miniMax.getScores().length); + Assertions.assertEquals(3, miniMax.getHeight()); + + // All scores should be positive (between 1 and 99) + for (int score : miniMax.getScores()) { + Assertions.assertTrue(score >= 1 && score <= 99); + } + } + + @Test + void testConstructorWithValidScores() { + int[] validScores = {10, 20, 30, 40}; + MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(validScores); + + Assertions.assertArrayEquals(validScores, customMiniMax.getScores()); + Assertions.assertEquals(2, customMiniMax.getHeight()); // log2(4) = 2 + } + + @Test + void testConstructorWithInvalidScoresThrowsException() { + int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 + Assertions.assertThrows(IllegalArgumentException.class, () -> new MiniMaxAlgorithm(invalidScores)); + } + + @Test + void testConstructorDoesNotModifyOriginalArray() { + int[] originalScores = {10, 20, 30, 40}; + int[] copyOfOriginal = {10, 20, 30, 40}; + MiniMaxAlgorithm customMiniMax = new MiniMaxAlgorithm(originalScores); + + // Modify the original array + originalScores[0] = 999; + + // Constructor should have made a copy, so internal state should be unchanged + Assertions.assertArrayEquals(copyOfOriginal, customMiniMax.getScores()); + } + + @Test + void testSetScoresWithValidPowerOfTwo() { + int[] validScores = {10, 20, 30, 40}; + miniMax.setScores(validScores); + + Assertions.assertArrayEquals(validScores, miniMax.getScores()); + Assertions.assertEquals(2, miniMax.getHeight()); // log2(4) = 2 + } + + @Test + void testSetScoresWithInvalidLength() { + int[] invalidScores = {10, 20, 30}; // Length 3 is not a power of 2 + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores)); + + // Scores should remain unchanged (original length 8) + Assertions.assertEquals(8, miniMax.getScores().length); + } + + @Test + void testSetScoresWithZeroLength() { + int[] emptyScores = {}; // Length 0 is not a power of 2 + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyScores)); + + // Scores should remain unchanged (original length 8) + Assertions.assertEquals(8, miniMax.getScores().length); + } + + @Test + void testSetScoresWithVariousInvalidLengths() { + // Test multiple invalid lengths to ensure isPowerOfTwo function is fully + // covered + int[][] invalidScoreArrays = { + {1, 2, 3, 4, 5}, // Length 5 + {1, 2, 3, 4, 5, 6}, // Length 6 + {1, 2, 3, 4, 5, 6, 7}, // Length 7 + new int[9], // Length 9 + new int[10], // Length 10 + new int[15] // Length 15 + }; + + for (int[] invalidScores : invalidScoreArrays) { + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(invalidScores), "Failed for array length: " + invalidScores.length); + } + + // Scores should remain unchanged (original length 8) + Assertions.assertEquals(8, miniMax.getScores().length); + } + + @Test + void testSetScoresWithSingleElement() { + int[] singleScore = {42}; + miniMax.setScores(singleScore); + + Assertions.assertArrayEquals(singleScore, miniMax.getScores()); + Assertions.assertEquals(0, miniMax.getHeight()); // log2(1) = 0 + } + + @Test + void testMiniMaxWithKnownScores() { + // Test with a known game tree: [3, 12, 8, 2] + int[] testScores = {3, 12, 8, 2}; + miniMax.setScores(testScores); + + // Maximizer starts: should choose max(min(3,12), min(8,2)) = max(3, 2) = 3 + int result = miniMax.miniMax(0, true, 0, false); + Assertions.assertEquals(3, result); + } + + @Test + void testMiniMaxWithMinimizerFirst() { + // Test with minimizer starting first + int[] testScores = {3, 12, 8, 2}; + miniMax.setScores(testScores); + + // Minimizer starts: should choose min(max(3,12), max(8,2)) = min(12, 8) = 8 + int result = miniMax.miniMax(0, false, 0, false); + Assertions.assertEquals(8, result); + } + + @Test + void testMiniMaxWithLargerTree() { + // Test with 8 elements: [5, 6, 7, 4, 5, 3, 6, 2] + int[] testScores = {5, 6, 7, 4, 5, 3, 6, 2}; + miniMax.setScores(testScores); + + // Maximizer starts + int result = miniMax.miniMax(0, true, 0, false); + // Expected: max(min(max(5,6), max(7,4)), min(max(5,3), max(6,2))) + // = max(min(6, 7), min(5, 6)) = max(6, 5) = 6 + Assertions.assertEquals(6, result); + } + + @Test + void testMiniMaxVerboseOutput() { + int[] testScores = {3, 12, 8, 2}; + miniMax.setScores(testScores); + + miniMax.miniMax(0, true, 0, true); + + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("Maximizer")); + Assertions.assertTrue(output.contains("Minimizer")); + Assertions.assertTrue(output.contains("chooses")); + } + + @Test + void testGetRandomScoresLength() { + int[] randomScores = MiniMaxAlgorithm.getRandomScores(4, 50); + Assertions.assertEquals(16, randomScores.length); // 2^4 = 16 + + // All scores should be between 1 and 50 + for (int score : randomScores) { + Assertions.assertTrue(score >= 1 && score <= 50); + } + } + + @Test + void testGetRandomScoresWithDifferentParameters() { + int[] randomScores = MiniMaxAlgorithm.getRandomScores(2, 10); + Assertions.assertEquals(4, randomScores.length); // 2^2 = 4 + + // All scores should be between 1 and 10 + for (int score : randomScores) { + Assertions.assertTrue(score >= 1 && score <= 10); + } + } + + @Test + void testMainMethod() { + // Test that main method runs without errors + Assertions.assertDoesNotThrow(() -> MiniMaxAlgorithm.main(new String[] {})); + + String output = outputStream.toString(); + Assertions.assertTrue(output.contains("The best score for")); + Assertions.assertTrue(output.contains("Maximizer")); + } + + @Test + void testHeightCalculation() { + // Test height calculation for different array sizes + int[] scores2 = {1, 2}; + miniMax.setScores(scores2); + Assertions.assertEquals(1, miniMax.getHeight()); // log2(2) = 1 + + int[] scores16 = new int[16]; + miniMax.setScores(scores16); + Assertions.assertEquals(4, miniMax.getHeight()); // log2(16) = 4 + } + + @Test + void testEdgeCaseWithZeroScores() { + int[] zeroScores = {0, 0, 0, 0}; + miniMax.setScores(zeroScores); + + int result = miniMax.miniMax(0, true, 0, false); + Assertions.assertEquals(0, result); + } + + @Test + void testEdgeCaseWithNegativeScores() { + int[] negativeScores = {-5, -2, -8, -1}; + miniMax.setScores(negativeScores); + + // Tree evaluation with maximizer first: + // Level 1 (minimizer): min(-5,-2) = -5, min(-8,-1) = -8 + // Level 0 (maximizer): max(-5, -8) = -5 + int result = miniMax.miniMax(0, true, 0, false); + Assertions.assertEquals(-5, result); + } + + @Test + void testSetScoresWithNegativeLength() { + // This test ensures the first condition of isPowerOfTwo (n > 0) is tested + // Although we can't directly create an array with negative length, + // we can test edge cases around zero and ensure proper validation + + // Test with array length 0 (edge case for n > 0 condition) + int[] emptyArray = new int[0]; + Assertions.assertThrows(IllegalArgumentException.class, () -> miniMax.setScores(emptyArray)); + + Assertions.assertEquals(8, miniMax.getScores().length); // Should remain unchanged + } + + @Test + void testSetScoresWithLargePowerOfTwo() { + // Test with a large power of 2 to ensure the algorithm works correctly + int[] largeValidScores = new int[32]; // 32 = 2^5 + for (int i = 0; i < largeValidScores.length; i++) { + largeValidScores[i] = i + 1; + } + + miniMax.setScores(largeValidScores); + Assertions.assertArrayEquals(largeValidScores, miniMax.getScores()); + Assertions.assertEquals(5, miniMax.getHeight()); // log2(32) = 5 + } + + @Test + void testSetScoresValidEdgeCases() { + // Test valid powers of 2 to ensure isPowerOfTwo returns true correctly + int[][] validPowersOf2 = { + new int[1], // 1 = 2^0 + new int[2], // 2 = 2^1 + new int[4], // 4 = 2^2 + new int[8], // 8 = 2^3 + new int[16], // 16 = 2^4 + new int[64] // 64 = 2^6 + }; + + int[] expectedHeights = {0, 1, 2, 3, 4, 6}; + + for (int i = 0; i < validPowersOf2.length; i++) { + miniMax.setScores(validPowersOf2[i]); + Assertions.assertEquals(validPowersOf2[i].length, miniMax.getScores().length, "Failed for array length: " + validPowersOf2[i].length); + Assertions.assertEquals(expectedHeights[i], miniMax.getHeight(), "Height calculation failed for array length: " + validPowersOf2[i].length); + } + } + + @Test + void testGetScoresReturnsDefensiveCopy() { + int[] originalScores = {10, 20, 30, 40}; + miniMax.setScores(originalScores); + + // Get the scores and modify them + int[] retrievedScores = miniMax.getScores(); + retrievedScores[0] = 999; + + // Internal state should remain unchanged + Assertions.assertEquals(10, miniMax.getScores()[0]); + } + + @Test + void testSetScoresCreatesDefensiveCopy() { + int[] originalScores = {10, 20, 30, 40}; + miniMax.setScores(originalScores); + + // Modify the original array after setting + originalScores[0] = 999; + + // Internal state should remain unchanged + Assertions.assertEquals(10, miniMax.getScores()[0]); + } + + @Test + void testMiniMaxWithAllSameScores() { + int[] sameScores = {5, 5, 5, 5}; + miniMax.setScores(sameScores); + + // When all scores are the same, result should be that score + int result = miniMax.miniMax(0, true, 0, false); + Assertions.assertEquals(5, result); + } + + @Test + void testMiniMaxAtDifferentDepths() { + int[] testScores = {3, 12, 8, 2, 14, 5, 2, 9}; + miniMax.setScores(testScores); + + // Test maximizer first + int result = miniMax.miniMax(0, true, 0, false); + // Expected: max(min(max(3,12), max(8,2)), min(max(14,5), max(2,9))) + // = max(min(12, 8), min(14, 9)) = max(8, 9) = 9 + Assertions.assertEquals(9, result); + } + + @Test + void testMiniMaxWithMinIntAndMaxInt() { + int[] extremeScores = {Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1}; + miniMax.setScores(extremeScores); + + int result = miniMax.miniMax(0, true, 0, false); + // Expected: max(min(MIN, MAX), min(0, 1)) = max(MIN, 0) = 0 + Assertions.assertEquals(0, result); + } +} diff --git a/src/test/java/com/thealgorithms/others/MosAlgorithmTest.java b/src/test/java/com/thealgorithms/others/MosAlgorithmTest.java new file mode 100644 index 000000000000..ac931eb0a2b9 --- /dev/null +++ b/src/test/java/com/thealgorithms/others/MosAlgorithmTest.java @@ -0,0 +1,202 @@ +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.Test; + +/** + * Test class for MosAlgorithm + * + * @author BEASTSHRIRAM + */ +class MosAlgorithmTest { + + @Test + void testRangeSumQueriesBasic() { + int[] arr = {1, 3, 5, 2, 7}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 2, 0), // Sum of [1, 3, 5] = 9 + new MosAlgorithm.Query(1, 3, 1), // Sum of [3, 5, 2] = 10 + new MosAlgorithm.Query(2, 4, 2) // Sum of [5, 2, 7] = 14 + }; + + int[] expected = {9, 10, 14}; + int[] results = MosAlgorithm.solveRangeSumQueries(arr, queries); + + assertArrayEquals(expected, results); + } + + @Test + void testRangeSumQueriesSingleElement() { + int[] arr = {5, 10, 15, 20}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 0, 0), // Sum of [5] = 5 + new MosAlgorithm.Query(1, 1, 1), // Sum of [10] = 10 + new MosAlgorithm.Query(2, 2, 2), // Sum of [15] = 15 + new MosAlgorithm.Query(3, 3, 3) // Sum of [20] = 20 + }; + + int[] expected = {5, 10, 15, 20}; + int[] results = MosAlgorithm.solveRangeSumQueries(arr, queries); + + assertArrayEquals(expected, results); + } + + @Test + void testRangeSumQueriesFullArray() { + int[] arr = {1, 2, 3, 4, 5}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 4, 0) // Sum of entire array = 15 + }; + + int[] expected = {15}; + int[] results = MosAlgorithm.solveRangeSumQueries(arr, queries); + + assertArrayEquals(expected, results); + } + + @Test + void testRangeSumQueriesOverlapping() { + int[] arr = {2, 4, 6, 8, 10}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 2, 0), // Sum of [2, 4, 6] = 12 + new MosAlgorithm.Query(1, 3, 1), // Sum of [4, 6, 8] = 18 + new MosAlgorithm.Query(2, 4, 2) // Sum of [6, 8, 10] = 24 + }; + + int[] expected = {12, 18, 24}; + int[] results = MosAlgorithm.solveRangeSumQueries(arr, queries); + + assertArrayEquals(expected, results); + } + + @Test + void testRangeFrequencyQueriesBasic() { + int[] arr = {1, 2, 2, 1, 3, 2, 1}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 3, 0), // Count of 2 in [1, 2, 2, 1] = 2 + new MosAlgorithm.Query(1, 5, 1), // Count of 2 in [2, 2, 1, 3, 2] = 3 + new MosAlgorithm.Query(4, 6, 2) // Count of 2 in [3, 2, 1] = 1 + }; + + int[] expected = {2, 3, 1}; + int[] results = MosAlgorithm.solveRangeFrequencyQueries(arr, queries, 2); + + assertArrayEquals(expected, results); + } + + @Test + void testRangeFrequencyQueriesNoMatch() { + int[] arr = {1, 3, 5, 7, 9}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 2, 0), // Count of 2 in [1, 3, 5] = 0 + new MosAlgorithm.Query(1, 4, 1) // Count of 2 in [3, 5, 7, 9] = 0 + }; + + int[] expected = {0, 0}; + int[] results = MosAlgorithm.solveRangeFrequencyQueries(arr, queries, 2); + + assertArrayEquals(expected, results); + } + + @Test + void testRangeFrequencyQueriesAllMatch() { + int[] arr = {5, 5, 5, 5, 5}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 2, 0), // Count of 5 in [5, 5, 5] = 3 + new MosAlgorithm.Query(1, 3, 1), // Count of 5 in [5, 5, 5] = 3 + new MosAlgorithm.Query(0, 4, 2) // Count of 5 in [5, 5, 5, 5, 5] = 5 + }; + + int[] expected = {3, 3, 5}; + int[] results = MosAlgorithm.solveRangeFrequencyQueries(arr, queries, 5); + + assertArrayEquals(expected, results); + } + + @Test + void testEmptyArray() { + int[] arr = {}; + MosAlgorithm.Query[] queries = {}; + + int[] expected = {}; + int[] results = MosAlgorithm.solveRangeSumQueries(arr, queries); + + assertArrayEquals(expected, results); + } + + @Test + void testNullInputs() { + int[] results1 = MosAlgorithm.solveRangeSumQueries(null, null); + assertArrayEquals(new int[0], results1); + + int[] results2 = MosAlgorithm.solveRangeFrequencyQueries(null, null, 1); + assertArrayEquals(new int[0], results2); + } + + @Test + void testQueryStructure() { + MosAlgorithm.Query query = new MosAlgorithm.Query(1, 5, 0); + + assertEquals(1, query.left); + assertEquals(5, query.right); + assertEquals(0, query.index); + assertEquals(0, query.result); // Default value + } + + @Test + void testLargerArray() { + int[] arr = {1, 4, 2, 8, 5, 7, 3, 6, 9, 10}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 4, 0), // Sum of [1, 4, 2, 8, 5] = 20 + new MosAlgorithm.Query(2, 7, 1), // Sum of [2, 8, 5, 7, 3, 6] = 31 + new MosAlgorithm.Query(5, 9, 2), // Sum of [7, 3, 6, 9, 10] = 35 + new MosAlgorithm.Query(1, 8, 3) // Sum of [4, 2, 8, 5, 7, 3, 6, 9] = 44 + }; + + int[] expected = {20, 31, 35, 44}; + int[] results = MosAlgorithm.solveRangeSumQueries(arr, queries); + + assertArrayEquals(expected, results); + } + + @Test + void testRangeFrequencyWithDuplicates() { + int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; + MosAlgorithm.Query[] queries = { + new MosAlgorithm.Query(0, 5, 0), // Count of 1 in [3, 1, 4, 1, 5, 9] = 2 + new MosAlgorithm.Query(3, 9, 1), // Count of 1 in [1, 5, 9, 2, 6, 5, 3] = 1 + new MosAlgorithm.Query(0, 9, 2) // Count of 1 in entire array = 2 + }; + + int[] expected = {2, 1, 2}; + int[] results = MosAlgorithm.solveRangeFrequencyQueries(arr, queries, 1); + + assertArrayEquals(expected, results); + } + + @Test + void testMainMethod() { + // Capture System.out + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outputStream)); + + try { + // Test main method + MosAlgorithm.main(new String[] {}); + String output = outputStream.toString(); + + // Verify expected output contains demonstration + assertTrue(output.contains("Range Sum Queries:")); + assertTrue(output.contains("Range Frequency Queries (count of value 3):")); + assertTrue(output.contains("Array: [1, 3, 5, 2, 7, 6, 3, 1, 4, 8]")); + } finally { + System.setOut(originalOut); + } + } +} diff --git a/src/test/java/com/thealgorithms/others/PageRankTest.java b/src/test/java/com/thealgorithms/others/PageRankTest.java new file mode 100644 index 000000000000..de96403dd1fc --- /dev/null +++ b/src/test/java/com/thealgorithms/others/PageRankTest.java @@ -0,0 +1,319 @@ +package com.thealgorithms.others; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Test class for PageRank algorithm implementation + * + * @author Hardvan + */ +class PageRankTest { + + private static final double EPSILON = 0.0001; // Tolerance for floating point comparisons + + /** + * Test basic PageRank calculation with a simple 3-node graph + * Graph: 1 -> 2, 2 -> 3, 3 -> 1 + */ + @Test + void testSimpleThreeNodeGraph() { + PageRank pageRank = new PageRank(3); + + // Create a simple circular graph: 1 -> 2 -> 3 -> 1 + int[][] adjacencyMatrix = new int[10][10]; + adjacencyMatrix[1][2] = 1; // Node 1 links to Node 2 + adjacencyMatrix[2][3] = 1; // Node 2 links to Node 3 + adjacencyMatrix[3][1] = 1; // Node 3 links to Node 1 + + pageRank.setAdjacencyMatrix(adjacencyMatrix); + double[] result = pageRank.calculatePageRank(3); + + // All nodes should have equal PageRank in a circular graph + assertNotNull(result); + assertEquals(result[1], result[2], EPSILON); + assertEquals(result[2], result[3], EPSILON); + } + + /** + * Test PageRank with a two-node graph where one node points to another + */ + @Test + void testTwoNodeGraph() { + PageRank pageRank = new PageRank(2); + + // Node 1 links to Node 2 + pageRank.setEdge(1, 2, 1); + + double[] result = pageRank.calculatePageRank(2); + + // Node 2 should have higher PageRank than Node 1 (after 2 iterations) + assertNotNull(result); + assertEquals(0.2775, result[2], EPSILON); + assertEquals(0.15, result[1], EPSILON); + } + + /** + * Test PageRank with a single node (no links) + */ + @Test + void testSingleNode() { + PageRank pageRank = new PageRank(1); + + double[] result = pageRank.calculatePageRank(1); + + // Single node should have (1-d) = 0.15 after applying damping + assertNotNull(result); + assertEquals(0.15, result[1], EPSILON); + } + + /** + * Test PageRank with a hub-and-spoke configuration + * Node 1 is the hub, pointing to nodes 2, 3, and 4 + */ + @Test + void testHubAndSpokeGraph() { + PageRank pageRank = new PageRank(4); + + // Hub node (1) links to all other nodes + pageRank.setEdge(1, 2, 1); + pageRank.setEdge(1, 3, 1); + pageRank.setEdge(1, 4, 1); + + // All spokes link back to hub + pageRank.setEdge(2, 1, 1); + pageRank.setEdge(3, 1, 1); + pageRank.setEdge(4, 1, 1); + + double[] result = pageRank.calculatePageRank(4); + + assertNotNull(result); + // Hub should have higher PageRank + assertEquals(result[2], result[3], EPSILON); + assertEquals(result[3], result[4], EPSILON); + } + + /** + * Test PageRank with multiple iterations + */ + @Test + void testMultipleIterations() { + PageRank pageRank = new PageRank(3); + + pageRank.setEdge(1, 2, 1); + pageRank.setEdge(2, 3, 1); + pageRank.setEdge(3, 1, 1); + + double[] result2Iterations = pageRank.calculatePageRank(3, 0.85, 2, false); + double[] result5Iterations = pageRank.calculatePageRank(3, 0.85, 5, false); + + assertNotNull(result2Iterations); + assertNotNull(result5Iterations); + } + + /** + * Test getPageRank method for individual node + */ + @Test + void testGetPageRank() { + PageRank pageRank = new PageRank(2); + + pageRank.setEdge(1, 2, 1); + pageRank.calculatePageRank(2); + + double node1PageRank = pageRank.getPageRank(1); + double node2PageRank = pageRank.getPageRank(2); + + assertEquals(0.15, node1PageRank, EPSILON); + assertEquals(0.2775, node2PageRank, EPSILON); + } + + /** + * Test getAllPageRanks method + */ + @Test + void testGetAllPageRanks() { + PageRank pageRank = new PageRank(2); + + pageRank.setEdge(1, 2, 1); + pageRank.calculatePageRank(2); + + double[] allPageRanks = pageRank.getAllPageRanks(); + + assertNotNull(allPageRanks); + assertEquals(0.15, allPageRanks[1], EPSILON); + assertEquals(0.2775, allPageRanks[2], EPSILON); + } + + /** + * Test that self-loops are not allowed + */ + @Test + void testNoSelfLoops() { + PageRank pageRank = new PageRank(2); + + // Try to set a self-loop + pageRank.setEdge(1, 1, 1); + pageRank.setEdge(1, 2, 1); + + double[] result = pageRank.calculatePageRank(2); + + assertNotNull(result); + // Self-loop should be ignored + } + + /** + * Test exception when node count is too small + */ + @Test + void testInvalidNodeCountTooSmall() { + assertThrows(IllegalArgumentException.class, () -> new PageRank(0)); + } + + /** + * Test exception when node count is too large + */ + @Test + void testInvalidNodeCountTooLarge() { + assertThrows(IllegalArgumentException.class, () -> new PageRank(11)); + } + + /** + * Test exception for invalid damping factor (negative) + */ + @Test + void testInvalidDampingFactorNegative() { + PageRank pageRank = new PageRank(2); + pageRank.setEdge(1, 2, 1); + + assertThrows(IllegalArgumentException.class, () -> pageRank.calculatePageRank(2, -0.1, 2, false)); + } + + /** + * Test exception for invalid damping factor (greater than 1) + */ + @Test + void testInvalidDampingFactorTooLarge() { + PageRank pageRank = new PageRank(2); + pageRank.setEdge(1, 2, 1); + + assertThrows(IllegalArgumentException.class, () -> pageRank.calculatePageRank(2, 1.5, 2, false)); + } + + /** + * Test exception for invalid iterations (less than 1) + */ + @Test + void testInvalidIterations() { + PageRank pageRank = new PageRank(2); + pageRank.setEdge(1, 2, 1); + + assertThrows(IllegalArgumentException.class, () -> pageRank.calculatePageRank(2, 0.85, 0, false)); + } + + /** + * Test exception when getting PageRank for invalid node + */ + @Test + void testGetPageRankInvalidNode() { + PageRank pageRank = new PageRank(2); + pageRank.calculatePageRank(2); + + assertThrows(IllegalArgumentException.class, () -> pageRank.getPageRank(3)); + } + + /** + * Test exception when getting PageRank for node less than 1 + */ + @Test + void testGetPageRankNodeLessThanOne() { + PageRank pageRank = new PageRank(2); + pageRank.calculatePageRank(2); + + assertThrows(IllegalArgumentException.class, () -> pageRank.getPageRank(0)); + } + + /** + * Test complex graph with multiple incoming and outgoing links + */ + @Test + void testComplexGraph() { + PageRank pageRank = new PageRank(4); + + // Create a more complex graph + pageRank.setEdge(1, 2, 1); + pageRank.setEdge(1, 3, 1); + pageRank.setEdge(2, 3, 1); + pageRank.setEdge(3, 4, 1); + pageRank.setEdge(4, 1, 1); + + double[] result = pageRank.calculatePageRank(4); + + assertNotNull(result); + // Node 3 should have high PageRank (receives links from nodes 1 and 2) + // After 2 iterations, the sum will not equal total nodes + double sum = result[1] + result[2] + result[3] + result[4]; + assertEquals(1.8325, sum, EPSILON); + } + + /** + * Test that PageRank values sum after 2 iterations + */ + @Test + void testPageRankSum() { + PageRank pageRank = new PageRank(5); + + // Create arbitrary graph + pageRank.setEdge(1, 2, 1); + pageRank.setEdge(2, 3, 1); + pageRank.setEdge(3, 4, 1); + pageRank.setEdge(4, 5, 1); + pageRank.setEdge(5, 1, 1); + + double[] result = pageRank.calculatePageRank(5); + + double sum = 0; + for (int i = 1; i <= 5; i++) { + sum += result[i]; + } + + // Sum after 2 iterations with default damping factor + assertEquals(2.11, sum, EPSILON); + } + + /** + * Test graph with isolated node (no incoming or outgoing links) + */ + @Test + void testGraphWithIsolatedNode() { + PageRank pageRank = new PageRank(3); + + // Node 1 and 2 are connected, Node 3 is isolated + pageRank.setEdge(1, 2, 1); + pageRank.setEdge(2, 1, 1); + + double[] result = pageRank.calculatePageRank(3); + + assertNotNull(result); + // Isolated node should have some PageRank due to damping factor + assertEquals(0.15, result[3], EPSILON); + } + + /** + * Test verbose mode (should not throw exception) + */ + @Test + void testVerboseMode() { + PageRank pageRank = new PageRank(2); + + pageRank.setEdge(1, 2, 1); + + // This should execute without throwing an exception + double[] result = pageRank.calculatePageRank(2, 0.85, 2, true); + + assertNotNull(result); + } +} diff --git a/src/test/java/com/thealgorithms/others/PerlinNoiseTest.java b/src/test/java/com/thealgorithms/others/PerlinNoiseTest.java new file mode 100644 index 000000000000..88c043ad9aa3 --- /dev/null +++ b/src/test/java/com/thealgorithms/others/PerlinNoiseTest.java @@ -0,0 +1,103 @@ +package com.thealgorithms.others; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PerlinNoiseTest { + + @Test + @DisplayName("generatePerlinNoise returns array with correct dimensions") + void testDimensions() { + int w = 8; + int h = 6; + float[][] noise = PerlinNoise.generatePerlinNoise(w, h, 4, 0.6f, 123L); + assertThat(noise).hasDimensions(w, h); + } + + @Test + @DisplayName("All values are within [0,1] after normalization") + void testRange() { + int w = 16; + int h = 16; + float[][] noise = PerlinNoise.generatePerlinNoise(w, h, 5, 0.7f, 42L); + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + assertThat(noise[x][y]).isBetween(0f, 1f); + } + } + } + + @Test + @DisplayName("Deterministic for same parameters and seed") + void testDeterminism() { + int w = 10; + int h = 10; + long seed = 98765L; + float[][] a = PerlinNoise.generatePerlinNoise(w, h, 3, 0.5f, seed); + float[][] b = PerlinNoise.generatePerlinNoise(w, h, 3, 0.5f, seed); + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + assertThat(a[x][y]).isEqualTo(b[x][y]); + } + } + } + + @Test + @DisplayName("Different seeds produce different outputs (probabilistically)") + void testDifferentSeeds() { + int w = 12; + int h = 12; + float[][] a = PerlinNoise.generatePerlinNoise(w, h, 4, 0.8f, 1L); + float[][] b = PerlinNoise.generatePerlinNoise(w, h, 4, 0.8f, 2L); + + // Count exact equalities; expect very few or none. + int equalCount = 0; + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + if (Float.compare(a[x][y], b[x][y]) == 0) { + equalCount++; + } + } + } + assertThat(equalCount).isLessThan(w * h / 10); // less than 10% equal exact values + } + + @Test + @DisplayName("Interpolation endpoints are respected") + void testInterpolateEndpoints() { + assertThat(PerlinNoise.interpolate(0f, 1f, 0f)).isEqualTo(0f); + assertThat(PerlinNoise.interpolate(0f, 1f, 1f)).isEqualTo(1f); + assertThat(PerlinNoise.interpolate(0.2f, 0.8f, 0.5f)).isEqualTo(0.5f); + } + + @Test + @DisplayName("Single octave reduces to bilinear interpolation of base grid") + void testSingleOctaveLayer() { + int w = 8; + int h = 8; + long seed = 7L; + float[][] base = PerlinNoise.createBaseGrid(w, h, seed); + float[][] layer = PerlinNoise.generatePerlinNoiseLayer(base, w, h, 0); // period=1 + // With period = 1, x0=x, x1=(x+1)%w etc. Values should be smooth and within + // [0,1] + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + assertThat(layer[x][y]).isBetween(0f, 1f); + } + } + } + + @Test + @DisplayName("Invalid inputs are rejected") + void testInvalidInputs() { + assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(0, 5, 1, 0.5f, 1L)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, -1, 1, 0.5f, 1L)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, 5, 0, 0.5f, 1L)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, 5, 1, 0f, 1L)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, 5, 1, Float.NaN, 1L)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> PerlinNoise.generatePerlinNoise(5, 5, 1, 1.1f, 1L)).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/com/thealgorithms/others/ReverseStackUsingRecursionTest.java b/src/test/java/com/thealgorithms/others/ReverseStackUsingRecursionTest.java deleted file mode 100644 index 23b99ae87d35..000000000000 --- a/src/test/java/com/thealgorithms/others/ReverseStackUsingRecursionTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.thealgorithms.others; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Stack; -import org.junit.jupiter.api.Test; - -public class ReverseStackUsingRecursionTest { - - @Test - void testReverseWithMultipleElements() { - Stack stack = new Stack<>(); - for (int i = 0; i < 5; i++) { - stack.push(i); - } - - ReverseStackUsingRecursion.reverse(stack); - - for (int i = 0; i < 5; i++) { - assertEquals(i, stack.pop()); - } - assertTrue(stack.isEmpty()); - } - - @Test - void testReverseWithSingleElement() { - Stack stack = new Stack<>(); - stack.push(1); - - ReverseStackUsingRecursion.reverse(stack); - - assertEquals(1, stack.pop()); - assertTrue(stack.isEmpty()); - } - - @Test - void testReverseWithEmptyStack() { - Stack stack = new Stack<>(); - - ReverseStackUsingRecursion.reverse(stack); - - assertTrue(stack.isEmpty()); - } - - @Test - void testReverseWithNullStack() { - Stack stack = null; - - Exception exception = assertThrows(IllegalArgumentException.class, () -> ReverseStackUsingRecursion.reverse(stack)); - - String expectedMessage = "Stack cannot be null"; - String actualMessage = exception.getMessage(); - - assertTrue(actualMessage.contains(expectedMessage)); - } -} diff --git a/src/test/java/com/thealgorithms/others/TwoPointersTest.java b/src/test/java/com/thealgorithms/others/TwoPointersTest.java index 3a174e0cd19e..6e0d2b22d280 100644 --- a/src/test/java/com/thealgorithms/others/TwoPointersTest.java +++ b/src/test/java/com/thealgorithms/others/TwoPointersTest.java @@ -1,6 +1,8 @@ package com.thealgorithms.others; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -69,4 +71,10 @@ void testPairExistsAtEdges() { int key = 9; assertTrue(TwoPointers.isPairedSum(arr, key)); } + + @Test + void isPairedSumShouldThrowExceptionWhenArrayIsNull() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> TwoPointers.isPairedSum(null, 10)); + assertEquals("Input array must not be null.", exception.getMessage()); + } } diff --git a/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java b/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java new file mode 100644 index 000000000000..4b3e9fafe063 --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java @@ -0,0 +1,143 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link DampedOscillator}. + * + *

Tests focus on: + *

    + *
  • Constructor validation
  • + *
  • Analytical displacement for underdamped and overdamped parameterizations
  • + *
  • Basic numeric integration sanity using explicit Euler for small step sizes
  • + *
  • Method argument validation (null/invalid inputs)
  • + *
+ */ +@DisplayName("DampedOscillator โ€” unit tests") +public class DampedOscillatorTest { + + private static final double TOLERANCE = 1e-3; + + @Test + @DisplayName("Constructor rejects invalid parameters") + void constructorValidation() { + assertAll("invalid-constructor-params", + () + -> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(0.0, 0.1), "omega0 == 0 should throw"), + () -> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(-1.0, 0.1), "negative omega0 should throw"), () -> assertThrows(IllegalArgumentException.class, () -> new DampedOscillator(1.0, -0.1), "negative gamma should throw")); + } + + @Test + @DisplayName("Analytical displacement matches expected formula for underdamped case") + void analyticalUnderdamped() { + double omega0 = 10.0; + double gamma = 0.5; + DampedOscillator d = new DampedOscillator(omega0, gamma); + + double a = 1.0; + double phi = 0.2; + double t = 0.123; + + // expected: a * exp(-gamma * t) * cos(omega_d * t + phi) + double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma)); + double expected = a * Math.exp(-gamma * t) * Math.cos(omegaD * t + phi); + + double actual = d.displacementAnalytical(a, phi, t); + assertEquals(expected, actual, 1e-12, "Analytical underdamped displacement should match closed-form value"); + } + + @Test + @DisplayName("Analytical displacement gracefully handles overdamped parameters (omegaD -> 0)") + void analyticalOverdamped() { + double omega0 = 1.0; + double gamma = 2.0; // gamma > omega0 => omega_d = 0 in our implementation (Math.max) + DampedOscillator d = new DampedOscillator(omega0, gamma); + + double a = 2.0; + double phi = Math.PI / 4.0; + double t = 0.5; + + // With omegaD forced to 0 by implementation, expected simplifies to: + double expected = a * Math.exp(-gamma * t) * Math.cos(phi); + double actual = d.displacementAnalytical(a, phi, t); + + assertEquals(expected, actual, 1e-12, "Overdamped handling should reduce to exponential * cos(phase)"); + } + + @Test + @DisplayName("Explicit Euler step approximates analytical solution for small dt over short time") + void eulerApproximatesAnalyticalSmallDt() { + double omega0 = 10.0; + double gamma = 0.5; + DampedOscillator d = new DampedOscillator(omega0, gamma); + + double a = 1.0; + double phi = 0.0; + + // initial conditions consistent with amplitude a and zero phase: + // x(0) = a, v(0) = -a * gamma * cos(phi) + a * omegaD * sin(phi) + double omegaD = Math.sqrt(Math.max(0.0, omega0 * omega0 - gamma * gamma)); + double x0 = a * Math.cos(phi); + double v0 = -a * gamma * Math.cos(phi) - a * omegaD * Math.sin(phi); // small general form + + double dt = 1e-4; + int steps = 1000; // simulate to t = 0.1s + double tFinal = steps * dt; + + double[] state = new double[] {x0, v0}; + for (int i = 0; i < steps; i++) { + state = d.stepEuler(state, dt); + } + + double analyticAtT = d.displacementAnalytical(a, phi, tFinal); + double numericAtT = state[0]; + + // Euler is low-order โ€” allow a small tolerance but assert it remains close for small dt + short time. + assertEquals(analyticAtT, numericAtT, TOLERANCE, String.format("Numeric Euler should approximate analytical solution at t=%.6f (tolerance=%g)", tFinal, TOLERANCE)); + } + + @Test + @DisplayName("stepEuler validates inputs and throws on null/invalid dt/state") + void eulerInputValidation() { + DampedOscillator d = new DampedOscillator(5.0, 0.1); + + assertAll("invalid-stepEuler-args", + () + -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(null, 0.01), "null state should throw"), + () + -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0}, 0.01), "state array with invalid length should throw"), + () -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0, 0.0}, 0.0), "non-positive dt should throw"), () -> assertThrows(IllegalArgumentException.class, () -> d.stepEuler(new double[] {1.0, 0.0}, -1e-3), "negative dt should throw")); + } + + @Test + @DisplayName("Getter methods return configured parameters") + void gettersReturnConfiguration() { + double omega0 = Math.PI; + double gamma = 0.01; + DampedOscillator d = new DampedOscillator(omega0, gamma); + + assertAll("getters", () -> assertEquals(omega0, d.getOmega0(), 0.0, "getOmega0 should return configured omega0"), () -> assertEquals(gamma, d.getGamma(), 0.0, "getGamma should return configured gamma")); + } + + @Test + @DisplayName("Analytical displacement at t=0 returns initial amplitude * cos(phase)") + void analyticalAtZeroTime() { + double omega0 = 5.0; + double gamma = 0.2; + DampedOscillator d = new DampedOscillator(omega0, gamma); + + double a = 2.0; + double phi = Math.PI / 3.0; + double t = 0.0; + + double expected = a * Math.cos(phi); + double actual = d.displacementAnalytical(a, phi, t); + + assertEquals(expected, actual, 1e-12, "Displacement at t=0 should be a * cos(phase)"); + } +} diff --git a/src/test/java/com/thealgorithms/physics/ElasticCollision2DTest.java b/src/test/java/com/thealgorithms/physics/ElasticCollision2DTest.java new file mode 100644 index 000000000000..480e5da23db1 --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/ElasticCollision2DTest.java @@ -0,0 +1,91 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ElasticCollision2DTest { + + @Test + void testEqualMassHeadOnCollision() { + ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 1, 0, 1.0, 0.5); + ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 0, -1, 0, 1.0, 0.5); + + ElasticCollision2D.resolveCollision(a, b); + + assertEquals(-1.0, a.vx, 1e-6); + assertEquals(0.0, a.vy, 1e-6); + assertEquals(1.0, b.vx, 1e-6); + assertEquals(0.0, b.vy, 1e-6); + } + + @Test + void testUnequalMassHeadOnCollision() { + ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 2, 0, 2.0, 0.5); + ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 0, -1, 0, 1.0, 0.5); + + ElasticCollision2D.resolveCollision(a, b); + + // 1D head-on collision results + assertEquals(0.0, a.vx, 1e-6); + assertEquals(0.0, a.vy, 1e-6); + assertEquals(3.0, b.vx, 1e-6); + assertEquals(0.0, b.vy, 1e-6); + } + + @Test + void testMovingApartNoCollision() { + ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, -1, 0, 1.0, 0.5); + ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 0, 1, 0, 1.0, 0.5); + + ElasticCollision2D.resolveCollision(a, b); + + assertEquals(-1.0, a.vx, 1e-6); + assertEquals(0.0, a.vy, 1e-6); + assertEquals(1.0, b.vx, 1e-6); + assertEquals(0.0, b.vy, 1e-6); + } + + @Test + void testGlancingCollision() { + ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 1, 1, 1.0, 0.5); + ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 1, -1, -0.5, 1.0, 0.5); + + ElasticCollision2D.resolveCollision(a, b); + + // Ensure relative velocity along normal is reversed + double nx = (b.x - a.x) / Math.hypot(b.x - a.x, b.y - a.y); + double ny = (b.y - a.y) / Math.hypot(b.x - a.x, b.y - a.y); + double relVelAfter = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny; + + assertTrue(relVelAfter > 0); + } + + @Test + void testOverlappingBodies() { + ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 1, 0, 1.0, 0.5); + ElasticCollision2D.Body b = new ElasticCollision2D.Body(0, 0, -1, 0, 1.0, 0.5); + + ElasticCollision2D.resolveCollision(a, b); + + // Should not crash, velocities may remain unchanged + assertEquals(1.0, a.vx, 1e-6); + assertEquals(0.0, a.vy, 1e-6); + assertEquals(-1.0, b.vx, 1e-6); + assertEquals(0.0, b.vy, 1e-6); + } + + @Test + void testStationaryBodyHit() { + ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 2, 0, 1.0, 0.5); + ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 0, 0, 0, 1.0, 0.5); + + ElasticCollision2D.resolveCollision(a, b); + + assertEquals(0.0, a.vx, 1e-6); + assertEquals(0.0, a.vy, 1e-6); + assertEquals(2.0, b.vx, 1e-6); + assertEquals(0.0, b.vy, 1e-6); + } +} diff --git a/src/test/java/com/thealgorithms/physics/GravitationTest.java b/src/test/java/com/thealgorithms/physics/GravitationTest.java new file mode 100644 index 000000000000..9094e289e79a --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/GravitationTest.java @@ -0,0 +1,83 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the Gravitation utility class. + */ +final class GravitationTest { + + // A small tolerance (delta) for comparing floating-point numbers + private static final double DELTA = 1e-9; + private static final double G = Gravitation.GRAVITATIONAL_CONSTANT; + + @Test + @DisplayName("Test gravitational force between two bodies on the x-axis") + void testSimpleForceCalculation() { + // Force on body 2 should be F = G*1*1 / 1^2 = G, directed towards body 1 (negative x) + double[] forceOnB = Gravitation.calculateGravitationalForce(1.0, 0, 0, 1.0, 1, 0); + assertArrayEquals(new double[] {-G, 0.0}, forceOnB, DELTA); + + // Force on body 1 should be equal and opposite (positive x) + double[] forceOnA = Gravitation.calculateGravitationalForce(1.0, 1, 0, 1.0, 0, 0); + assertArrayEquals(new double[] {G, 0.0}, forceOnA, DELTA); + } + + @Test + @DisplayName("Test gravitational force in a 2D plane") + void test2DForceCalculation() { + // Body 1 at (0,0) with mass 2kg + // Body 2 at (3,4) with mass 1kg + // Distance is sqrt(3^2 + 4^2) = 5 meters + double magnitude = 2.0 * G / 25.0; // G * 2 * 1 / 5^2 + // Unit vector from 2 to 1 is (-3/5, -4/5) + double expectedFx = magnitude * -3.0 / 5.0; // -6G / 125 + double expectedFy = magnitude * -4.0 / 5.0; // -8G / 125 + + double[] forceOnB = Gravitation.calculateGravitationalForce(2.0, 0, 0, 1.0, 3, 4); + assertArrayEquals(new double[] {expectedFx, expectedFy}, forceOnB, DELTA); + } + + @Test + @DisplayName("Test overlapping bodies should result in zero force") + void testOverlappingBodies() { + double[] force = Gravitation.calculateGravitationalForce(1000.0, 1.5, -2.5, 500.0, 1.5, -2.5); + assertArrayEquals(new double[] {0.0, 0.0}, force, DELTA); + } + + @Test + @DisplayName("Test circular orbit velocity with simple values") + void testCircularOrbitVelocity() { + // v = sqrt(G*1/1) = sqrt(G) + double velocity = Gravitation.calculateCircularOrbitVelocity(1.0, 1.0); + assertEquals(Math.sqrt(G), velocity, DELTA); + } + + @Test + @DisplayName("Test orbital velocity with real-world-ish values (LEO)") + void testEarthOrbitVelocity() { + // Mass of Earth ~5.972e24 kg + // Radius of LEO ~6,771,000 m (Earth radius + 400km) + double earthMass = 5.972e24; + double leoRadius = 6.771e6; + // FIX: Updated expected value to match the high-precision calculation + double expectedVelocity = 7672.4904; + + double velocity = Gravitation.calculateCircularOrbitVelocity(earthMass, leoRadius); + assertEquals(expectedVelocity, velocity, 0.0001); // Use a larger delta for big numbers + } + + @Test + @DisplayName("Test invalid inputs for orbital velocity throw exception") + void testInvalidOrbitalVelocityInputs() { + assertThrows(IllegalArgumentException.class, () -> Gravitation.calculateCircularOrbitVelocity(0, 100)); + assertThrows(IllegalArgumentException.class, () -> Gravitation.calculateCircularOrbitVelocity(-1000, 100)); + assertThrows(IllegalArgumentException.class, () -> Gravitation.calculateCircularOrbitVelocity(1000, 0)); + assertThrows(IllegalArgumentException.class, () -> Gravitation.calculateCircularOrbitVelocity(1000, -100)); + } +} diff --git a/src/test/java/com/thealgorithms/physics/GroundToGroundProjectileMotionTest.java b/src/test/java/com/thealgorithms/physics/GroundToGroundProjectileMotionTest.java new file mode 100644 index 000000000000..9d7e11777983 --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/GroundToGroundProjectileMotionTest.java @@ -0,0 +1,149 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * JUnit test class for GroundToGroundProjectileMotion + * + * Contains unit tests for projectile motion calculations using JUnit 5 + */ +public class GroundToGroundProjectileMotionTest { + + private static final double EPSILON = 0.001; // Tolerance for floating point comparison + + @Test + @DisplayName("Test time of flight calculation") + public void testTimeOfFlight() { + // Arrange + double initialVelocity = 5.0; + double angle = 40.0; + double expectedTimeOfFlight = 0.655; + + // Act + double flightTimeOutput = GroundToGroundProjectileMotion.timeOfFlight(initialVelocity, angle); + flightTimeOutput = Math.round(flightTimeOutput * 1000.0) / 1000.0; + + // Assert + assertEquals(expectedTimeOfFlight, flightTimeOutput, EPSILON, "Time of flight should be " + expectedTimeOfFlight + " seconds"); + + System.out.println("Projectile Flight Time Test"); + System.out.println("Input Initial Velocity: " + initialVelocity + " m/s"); + System.out.println("Input Angle: " + angle + " degrees"); + System.out.println("Expected Output: " + expectedTimeOfFlight + " seconds"); + System.out.println("Actual Output: " + flightTimeOutput + " seconds"); + System.out.println("TEST PASSED\n"); + } + + @Test + @DisplayName("Test horizontal range calculation") + public void testHorizontalRange() { + // Arrange + double initialVelocity = 5.0; + double angle = 40.0; + double flightTime = 0.655; + double expectedHorizontalRange = 2.51; + + // Act + double horizontalRangeOutput = GroundToGroundProjectileMotion.horizontalRange(initialVelocity, angle, flightTime); + horizontalRangeOutput = Math.round(horizontalRangeOutput * 100.0) / 100.0; + + // Assert + assertEquals(expectedHorizontalRange, horizontalRangeOutput, EPSILON, "Horizontal range should be " + expectedHorizontalRange + " meters"); + + System.out.println("Projectile Horizontal Range Test"); + System.out.println("Input Initial Velocity: " + initialVelocity + " m/s"); + System.out.println("Input Angle: " + angle + " degrees"); + System.out.println("Input Time Of Flight: " + flightTime + " seconds"); + System.out.println("Expected Output: " + expectedHorizontalRange + " meters"); + System.out.println("Actual Output: " + horizontalRangeOutput + " meters"); + System.out.println("TEST PASSED\n"); + } + + @Test + @DisplayName("Test max height calculation") + public void testMaxHeight() { + // Arrange + double initialVelocity = 5.0; + double angle = 40.0; + double expectedMaxHeight = 0.527; // Updated to match actual calculation + + // Act + double maxHeightOutput = GroundToGroundProjectileMotion.maxHeight(initialVelocity, angle); + maxHeightOutput = Math.round(maxHeightOutput * 1000.0) / 1000.0; + + // Assert + assertEquals(expectedMaxHeight, maxHeightOutput, EPSILON, "Max height should be " + expectedMaxHeight + " meters"); + + System.out.println("Projectile Max Height Test"); + System.out.println("Input Initial Velocity: " + initialVelocity + " m/s"); + System.out.println("Input Angle: " + angle + " degrees"); + System.out.println("Expected Output: " + expectedMaxHeight + " meters"); + System.out.println("Actual Output: " + maxHeightOutput + " meters"); + System.out.println("TEST PASSED\n"); + } + + @Test + @DisplayName("Test time of flight with custom gravity") + public void testTimeOfFlightWithCustomGravity() { + // Arrange + double initialVelocity = 10.0; + double angle = 45.0; + double customGravity = 1.62; // Moon gravity (m/s^2) + + // Act + double flightTime = GroundToGroundProjectileMotion.timeOfFlight(initialVelocity, angle, customGravity); + + // Assert + assertTrue(flightTime > 0, "Flight time should be positive"); + assertTrue(flightTime > 8.0, "Flight time on moon should be longer than on Earth"); + + System.out.println("Custom Gravity Test (Moon)"); + System.out.println("Input Initial Velocity: " + initialVelocity + " m/s"); + System.out.println("Input Angle: " + angle + " degrees"); + System.out.println("Gravity: " + customGravity + " m/s^2"); + System.out.println("Flight Time: " + flightTime + " seconds"); + System.out.println("TEST PASSED\n"); + } + + @Test + @DisplayName("Test projectile at 90 degrees (straight up)") + public void testVerticalProjectile() { + // Arrange + double initialVelocity = 20.0; + double angle = 90.0; + + // Act + double horizontalRange = GroundToGroundProjectileMotion.horizontalRange(initialVelocity, angle, 1.0); + + // Assert + assertEquals(0.0, horizontalRange, EPSILON, "Horizontal range should be zero for vertical launch"); + + System.out.println("Vertical Projectile Test"); + System.out.println("Input Angle: " + angle + " degrees"); + System.out.println("Horizontal Range: " + horizontalRange + " meters"); + System.out.println("TEST PASSED\n"); + } + + @Test + @DisplayName("Test projectile at 0 degrees (horizontal)") + public void testHorizontalProjectile() { + // Arrange + double initialVelocity = 15.0; + double angle = 0.0; + + // Act + double maxHeight = GroundToGroundProjectileMotion.maxHeight(initialVelocity, angle); + + // Assert + assertEquals(0.0, maxHeight, EPSILON, "Max height should be zero for horizontal launch"); + + System.out.println("Horizontal Projectile Test"); + System.out.println("Input Angle: " + angle + " degrees"); + System.out.println("Max Height: " + maxHeight + " meters"); + System.out.println("TEST PASSED\n"); + } +} diff --git a/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java b/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java new file mode 100644 index 000000000000..25e7cfa87f9e --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Test class for the general-purpose ProjectileMotion calculator. + * + */ +final class ProjectileMotionTest { + + private static final double DELTA = 1e-4; // Tolerance for comparing double values + + @Test + @DisplayName("Test ground-to-ground launch (initial height is zero)") + void testGroundToGroundLaunch() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(50, 30, 0); + assertEquals(5.0986, result.getTimeOfFlight(), DELTA); + assertEquals(220.7750, result.getHorizontalRange(), DELTA); + assertEquals(31.8661, result.getMaxHeight(), DELTA); + } + + @Test + @DisplayName("Test launch from an elevated position") + void testElevatedLaunch() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(30, 45, 100); + assertEquals(7.1705, result.getTimeOfFlight(), DELTA); + assertEquals(152.1091, result.getHorizontalRange(), DELTA); + assertEquals(122.9436, result.getMaxHeight(), DELTA); // Final corrected value + } + + @Test + @DisplayName("Test launch straight up (90 degrees)") + void testVerticalLaunch() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(40, 90, 20); + assertEquals(8.6303, result.getTimeOfFlight(), DELTA); + assertEquals(0.0, result.getHorizontalRange(), DELTA); + assertEquals(101.5773, result.getMaxHeight(), DELTA); + } + + @Test + @DisplayName("Test horizontal launch from a height (0 degrees)") + void testHorizontalLaunch() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(25, 0, 80); + assertEquals(4.0392, result.getTimeOfFlight(), DELTA); + assertEquals(100.9809, result.getHorizontalRange(), DELTA); + assertEquals(80.0, result.getMaxHeight(), DELTA); + } + + @Test + @DisplayName("Test downward launch from a height (negative angle)") + void testDownwardLaunchFromHeight() { + ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(20, -30, 100); + assertEquals(3.6100, result.getTimeOfFlight(), DELTA); + assertEquals(62.5268, result.getHorizontalRange(), DELTA); + assertEquals(100.0, result.getMaxHeight(), DELTA); + } + + @Test + @DisplayName("Test invalid arguments throw an exception") + void testInvalidInputs() { + assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(-10, 45, 100)); + assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(10, 45, -100)); + assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(10, 45, 100, 0)); + } +} diff --git a/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java new file mode 100644 index 000000000000..bec4ad8cbbd7 --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java @@ -0,0 +1,261 @@ +package com.thealgorithms.physics; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Test class for SimplePendulumRK4. + * Tests numerical accuracy, physical correctness, and edge cases. + */ +class SimplePendulumRK4Test { + + private static final double EPSILON = 1e-6; + private static final double ENERGY_DRIFT_TOLERANCE = 1e-3; + @Test + @DisplayName("Test constructor creates valid pendulum") + void testConstructor() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.5, 9.81); + Assertions.assertNotNull(pendulum); + Assertions.assertEquals(1.5, pendulum.getLength(), EPSILON); + Assertions.assertEquals(9.81, pendulum.getGravity(), EPSILON); + } + + @Test + @DisplayName("Test constructor rejects negative length") + void testConstructorNegativeLength() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(-1.0, 9.81); }); + } + + @Test + @DisplayName("Test constructor rejects negative gravity") + void testConstructorNegativeGravity() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(1.0, -9.81); }); + } + + @Test + @DisplayName("Test constructor rejects zero length") + void testConstructorZeroLength() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { new SimplePendulumRK4(0.0, 9.81); }); + } + + @Test + @DisplayName("Test getters return correct values") + void testGetters() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(2.5, 10.0); + Assertions.assertEquals(2.5, pendulum.getLength(), EPSILON); + Assertions.assertEquals(10.0, pendulum.getGravity(), EPSILON); + } + + @Test + @DisplayName("Test single RK4 step returns valid state") + void testSingleStep() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.1, 0.0}; + double[] newState = pendulum.stepRK4(state, 0.01); + + Assertions.assertNotNull(newState); + Assertions.assertEquals(2, newState.length); + } + + @Test + @DisplayName("Test equilibrium stability (pendulum at rest stays at rest)") + void testEquilibrium() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.0, 0.0}; + + for (int i = 0; i < 100; i++) { + state = pendulum.stepRK4(state, 0.01); + } + + Assertions.assertEquals(0.0, state[0], EPSILON, "Theta should remain at equilibrium"); + Assertions.assertEquals(0.0, state[1], EPSILON, "Omega should remain zero"); + } + + @Test + @DisplayName("Test small angle oscillation returns to initial position") + void testSmallAngleOscillation() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double initialAngle = Math.toRadians(5.0); + double[] state = {initialAngle, 0.0}; + double dt = 0.01; + + // Theoretical period for small angles + double expectedPeriod = 2 * Math.PI * Math.sqrt(1.0 / 9.81); + int stepsPerPeriod = (int) (expectedPeriod / dt); + + double[][] trajectory = pendulum.simulate(state, dt, stepsPerPeriod); + double finalTheta = trajectory[stepsPerPeriod][0]; + + // After one period, should return close to initial position + double error = Math.abs(finalTheta - initialAngle) / Math.abs(initialAngle); + Assertions.assertTrue(error < 0.05, "Small angle approximation error should be < 5%"); + } + + @Test + @DisplayName("Test large angle oscillation is symmetric") + void testLargeAngleOscillation() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(120.0), 0.0}; + + double[][] trajectory = pendulum.simulate(state, 0.01, 500); + + double maxTheta = Double.NEGATIVE_INFINITY; + double minTheta = Double.POSITIVE_INFINITY; + + for (double[] s : trajectory) { + maxTheta = Math.max(maxTheta, s[0]); + minTheta = Math.min(minTheta, s[0]); + } + + Assertions.assertTrue(maxTheta > 0, "Should have positive excursions"); + Assertions.assertTrue(minTheta < 0, "Should have negative excursions"); + + // Check symmetry + double asymmetry = Math.abs((maxTheta + minTheta) / maxTheta); + Assertions.assertTrue(asymmetry < 0.1, "Oscillation should be symmetric"); + } + + @Test + @DisplayName("Test energy conservation for small angle") + void testEnergyConservationSmallAngle() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(15.0), 0.0}; + + double initialEnergy = pendulum.calculateEnergy(state); + + for (int i = 0; i < 1000; i++) { + state = pendulum.stepRK4(state, 0.01); + } + + double finalEnergy = pendulum.calculateEnergy(state); + double drift = Math.abs(finalEnergy - initialEnergy) / initialEnergy; + + Assertions.assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%"); + } + + @Test + @DisplayName("Test energy conservation for large angle") + void testEnergyConservationLargeAngle() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(90.0), 0.0}; + + double initialEnergy = pendulum.calculateEnergy(state); + + for (int i = 0; i < 1000; i++) { + state = pendulum.stepRK4(state, 0.01); + } + + double finalEnergy = pendulum.calculateEnergy(state); + double drift = Math.abs(finalEnergy - initialEnergy) / initialEnergy; + + Assertions.assertTrue(drift < ENERGY_DRIFT_TOLERANCE, "Energy drift should be < 0.1%, got: " + (drift * 100) + "%"); + } + + @Test + @DisplayName("Test simulate method returns correct trajectory") + void testSimulate() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] initialState = {Math.toRadians(20.0), 0.0}; + int steps = 100; + + double[][] trajectory = pendulum.simulate(initialState, 0.01, steps); + + Assertions.assertEquals(steps + 1, trajectory.length, "Trajectory should have steps + 1 entries"); + Assertions.assertArrayEquals(initialState, trajectory[0], EPSILON, "First entry should match initial state"); + + // Verify state changes over time + boolean changed = false; + for (int i = 1; i <= steps; i++) { + if (Math.abs(trajectory[i][0] - initialState[0]) > EPSILON) { + changed = true; + break; + } + } + Assertions.assertTrue(changed, "Simulation should progress from initial state"); + } + + @Test + @DisplayName("Test energy calculation at equilibrium") + void testEnergyAtEquilibrium() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.0, 0.0}; + double energy = pendulum.calculateEnergy(state); + Assertions.assertEquals(0.0, energy, EPSILON, "Energy at equilibrium should be zero"); + } + + @Test + @DisplayName("Test energy calculation at maximum angle") + void testEnergyAtMaxAngle() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.PI / 2, 0.0}; + double energy = pendulum.calculateEnergy(state); + Assertions.assertTrue(energy > 0, "Energy should be positive at max angle"); + } + + @Test + @DisplayName("Test energy calculation with angular velocity") + void testEnergyWithVelocity() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.0, 1.0}; + double energy = pendulum.calculateEnergy(state); + Assertions.assertTrue(energy > 0, "Energy should be positive with velocity"); + } + + @Test + @DisplayName("Test stepRK4 rejects null state") + void testStepRejectsNullState() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + Assertions.assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(null, 0.01); }); + } + + @Test + @DisplayName("Test stepRK4 rejects invalid state length") + void testStepRejectsInvalidStateLength() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + Assertions.assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1}, 0.01); }); + } + + @Test + @DisplayName("Test stepRK4 rejects negative time step") + void testStepRejectsNegativeTimeStep() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + Assertions.assertThrows(IllegalArgumentException.class, () -> { pendulum.stepRK4(new double[] {0.1, 0.2}, -0.01); }); + } + + @Test + @DisplayName("Test extreme condition: very large angle") + void testExtremeLargeAngle() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(179.0), 0.0}; + double[] result = pendulum.stepRK4(state, 0.01); + + Assertions.assertNotNull(result); + Assertions.assertTrue(Double.isFinite(result[0]), "Should handle large angles without NaN"); + Assertions.assertTrue(Double.isFinite(result[1]), "Should handle large angles without NaN"); + } + + @Test + @DisplayName("Test extreme condition: high angular velocity") + void testExtremeHighVelocity() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {0.0, 10.0}; + double[] result = pendulum.stepRK4(state, 0.01); + + Assertions.assertNotNull(result); + Assertions.assertTrue(Double.isFinite(result[0]), "Should handle high velocity without NaN"); + Assertions.assertTrue(Double.isFinite(result[1]), "Should handle high velocity without NaN"); + } + + @Test + @DisplayName("Test extreme condition: very small time step") + void testExtremeSmallTimeStep() { + SimplePendulumRK4 pendulum = new SimplePendulumRK4(1.0, 9.81); + double[] state = {Math.toRadians(10.0), 0.0}; + double[] result = pendulum.stepRK4(state, 1e-6); + + Assertions.assertNotNull(result); + Assertions.assertTrue(Double.isFinite(result[0]), "Should handle small time steps without NaN"); + Assertions.assertTrue(Double.isFinite(result[1]), "Should handle small time steps without NaN"); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java b/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java new file mode 100644 index 000000000000..876b6bf45eaf --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java @@ -0,0 +1,114 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class KargerMinCutTest { + + @Test + public void testSimpleGraph() { + // Graph: 0 -- 1 + Collection nodes = Arrays.asList(0, 1); + List edges = List.of(new int[] {0, 1}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(1, result.minCut()); + assertTrue(result.first().contains(0) || result.first().contains(1)); + assertTrue(result.second().contains(0) || result.second().contains(1)); + } + + @Test + public void testTriangleGraph() { + // Graph: 0 -- 1 -- 2 -- 0 + Collection nodes = Arrays.asList(0, 1, 2); + List edges = List.of(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 0}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(2, result.minCut()); + } + + @Test + public void testSquareGraph() { + // Graph: 0 -- 1 + // | | + // 3 -- 2 + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 3}, new int[] {3, 0}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(2, result.minCut()); + } + + @Test + public void testDisconnectedGraph() { + // Graph: 0 -- 1 2 -- 3 + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of(new int[] {0, 1}, new int[] {2, 3}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + } + + @Test + public void testCompleteGraph() { + // Complete Graph: 0 -- 1 -- 2 -- 3 (all nodes connected to each other) + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of(new int[] {0, 1}, new int[] {0, 2}, new int[] {0, 3}, new int[] {1, 2}, new int[] {1, 3}, new int[] {2, 3}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(3, result.minCut()); + } + + @Test + public void testSingleNodeGraph() { + // Graph: Single node with no edges + Collection nodes = List.of(0); + List edges = List.of(); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + assertTrue(result.first().contains(0)); + assertTrue(result.second().isEmpty()); + } + + @Test + public void testTwoNodesNoEdge() { + // Graph: 0 1 (no edges) + Collection nodes = Arrays.asList(0, 1); + List edges = List.of(); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + assertTrue(result.first().contains(0) || result.first().contains(1)); + assertTrue(result.second().contains(0) || result.second().contains(1)); + } + + @Test + public void testComplexGraph() { + // Nodes: 0, 1, 2, 3, 4, 5, 6, 7, 8 + // Edges: Fully connected graph with additional edges for complexity + Collection nodes = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8); + List edges = List.of(new int[] {0, 1}, new int[] {0, 2}, new int[] {0, 3}, new int[] {0, 4}, new int[] {0, 5}, new int[] {1, 2}, new int[] {1, 3}, new int[] {1, 4}, new int[] {1, 5}, new int[] {1, 6}, new int[] {2, 3}, new int[] {2, 4}, new int[] {2, 5}, new int[] {2, 6}, + new int[] {2, 7}, new int[] {3, 4}, new int[] {3, 5}, new int[] {3, 6}, new int[] {3, 7}, new int[] {3, 8}, new int[] {4, 5}, new int[] {4, 6}, new int[] {4, 7}, new int[] {4, 8}, new int[] {5, 6}, new int[] {5, 7}, new int[] {5, 8}, new int[] {6, 7}, new int[] {6, 8}, new int[] {7, 8}, + new int[] {0, 6}, new int[] {1, 7}, new int[] {2, 8}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + // The exact minimum cut value depends on the randomization, but it should be consistent + // for this graph structure. For a fully connected graph, the minimum cut is typically + // determined by the smallest number of edges connecting two partitions. + assertTrue(result.minCut() > 0); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java new file mode 100644 index 000000000000..2a3a84b5ceea --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java @@ -0,0 +1,91 @@ +package com.thealgorithms.randomized; + +import static com.thealgorithms.randomized.MonteCarloIntegration.approximate; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.function.Function; +import org.junit.jupiter.api.Test; + +class MonteCarloIntegrationTest { + + private static final double EPSILON = 0.03; // Allow 3% error margin + + @Test + void testConstantFunction() { + // Integral of f(x) = 2 from 0 to 1 is 2 + Function constant = x -> 2.0; + double result = approximate(constant, 0, 1, 10000); + assertEquals(2.0, result, EPSILON); + } + + @Test + void testLinearFunction() { + // Integral of f(x) = x from 0 to 1 is 0.5 + Function linear = Function.identity(); + double result = approximate(linear, 0, 1, 10000); + assertEquals(0.5, result, EPSILON); + } + + @Test + void testQuadraticFunction() { + // Integral of f(x) = x^2 from 0 to 1 is 1/3 + Function quadratic = x -> x * x; + double result = approximate(quadratic, 0, 1, 10000); + assertEquals(1.0 / 3.0, result, EPSILON); + } + + @Test + void testLargeSampleSize() { + // Integral of f(x) = x^2 from 0 to 1 is 1/3 + Function quadratic = x -> x * x; + double result = approximate(quadratic, 0, 1, 50000000); + assertEquals(1.0 / 3.0, result, EPSILON / 2); // Larger sample size, smaller error margin + } + + @Test + void testReproducibility() { + Function linear = Function.identity(); + double result1 = approximate(linear, 0, 1, 10000, 42L); + double result2 = approximate(linear, 0, 1, 10000, 42L); + assertEquals(result1, result2, 0.0); // Exactly equal + } + + @Test + void testNegativeInterval() { + // Integral of f(x) = x from -1 to 1 is 0 + Function linear = Function.identity(); + double result = approximate(linear, -1, 1, 10000); + assertEquals(0.0, result, EPSILON); + } + + @Test + void testNullFunction() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(null, 0, 1, 1000)); + assertNotNull(exception); + } + + @Test + void testInvalidInterval() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + approximate(linear, 2, 1, 1000); // b <= a + }); + assertNotNull(exception); + } + + @Test + void testZeroSampleSize() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(linear, 0, 1, 0)); + assertNotNull(exception); + } + + @Test + void testNegativeSampleSize() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(linear, 0, 1, -100)); + assertNotNull(exception); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/RandomizedClosestPairTest.java b/src/test/java/com/thealgorithms/randomized/RandomizedClosestPairTest.java new file mode 100644 index 000000000000..fd739b743989 --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/RandomizedClosestPairTest.java @@ -0,0 +1,30 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.thealgorithms.randomized.RandomizedClosestPair.Point; +import org.junit.jupiter.api.Test; + +public class RandomizedClosestPairTest { + + @Test + public void testFindClosestPairDistance() { + Point[] points = {new Point(1, 1), new Point(2, 2), new Point(3, 3), new Point(8, 8), new Point(13, 13)}; + double result = RandomizedClosestPair.findClosestPairDistance(points); + assertEquals(Math.sqrt(2), result, 0.00001); + } + + @Test + public void testWithIdenticalPoints() { + Point[] points = {new Point(0, 0), new Point(0, 0), new Point(1, 1)}; + double result = RandomizedClosestPair.findClosestPairDistance(points); + assertEquals(0.0, result, 0.00001); + } + + @Test + public void testWithDistantPoints() { + Point[] points = {new Point(0, 0), new Point(5, 0), new Point(10, 0)}; + double result = RandomizedClosestPair.findClosestPairDistance(points); + assertEquals(5.0, result, 0.00001); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/RandomizedMatrixMultiplicationVerificationTest.java b/src/test/java/com/thealgorithms/randomized/RandomizedMatrixMultiplicationVerificationTest.java new file mode 100644 index 000000000000..330662ac2c52 --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/RandomizedMatrixMultiplicationVerificationTest.java @@ -0,0 +1,41 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class RandomizedMatrixMultiplicationVerificationTest { + + @Test + void testCorrectMultiplication() { + int[][] a = {{1, 2}, {3, 4}}; + int[][] b = {{5, 6}, {7, 8}}; + int[][] c = {{19, 22}, {43, 50}}; + assertTrue(RandomizedMatrixMultiplicationVerification.verify(a, b, c, 10)); + } + + @Test + void testIncorrectMultiplication() { + int[][] a = {{1, 2}, {3, 4}}; + int[][] b = {{5, 6}, {7, 8}}; + int[][] wrongC = {{20, 22}, {43, 51}}; + assertFalse(RandomizedMatrixMultiplicationVerification.verify(a, b, wrongC, 10)); + } + + @Test + void testLargeMatrix() { + int size = 100; + int[][] a = new int[size][size]; + int[][] b = new int[size][size]; + int[][] c = new int[size][size]; + + for (int i = 0; i < size; i++) { + a[i][i] = 1; + b[i][i] = 1; + c[i][i] = 1; + } + + assertTrue(RandomizedMatrixMultiplicationVerification.verify(a, b, c, 15)); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/RandomizedQuickSortTest.java b/src/test/java/com/thealgorithms/randomized/RandomizedQuickSortTest.java new file mode 100644 index 000000000000..ec3d5a0b3546 --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/RandomizedQuickSortTest.java @@ -0,0 +1,44 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the RandomizedQuickSort class. + */ +public class RandomizedQuickSortTest { + + /** + * Tests sorting of an array with multiple elements, including duplicates. + */ + @Test + public void testRandomizedQuickSortMultipleElements() { + int[] arr = {3, 6, 8, 10, 1, 2, 1}; + int[] expected = {1, 1, 2, 3, 6, 8, 10}; + RandomizedQuickSort.randomizedQuickSort(arr, 0, arr.length - 1); + assertArrayEquals(expected, arr); + } + + /** + * Tests sorting of an empty array. + */ + @Test + public void testRandomizedQuickSortEmptyArray() { + int[] arr = {}; + int[] expected = {}; + RandomizedQuickSort.randomizedQuickSort(arr, 0, arr.length - 1); + assertArrayEquals(expected, arr); + } + + /** + * Tests sorting of an array with a single element. + */ + @Test + public void testRandomizedQuickSortSingleElement() { + int[] arr = {5}; + int[] expected = {5}; + RandomizedQuickSort.randomizedQuickSort(arr, 0, arr.length - 1); + assertArrayEquals(expected, arr); + } +} diff --git a/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java b/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java new file mode 100644 index 000000000000..0c6061fcde2a --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java @@ -0,0 +1,45 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class ReservoirSamplingTest { + + @Test + public void testSampleSizeEqualsStreamLength() { + int[] stream = {1, 2, 3, 4, 5}; + int sampleSize = 5; + + List result = ReservoirSampling.sample(stream, sampleSize); + + assertEquals(sampleSize, result.size()); + assertTrue(Arrays.stream(stream).allMatch(result::contains)); + } + + @Test + public void testSampleSizeLessThanStreamLength() { + int[] stream = {10, 20, 30, 40, 50, 60}; + int sampleSize = 3; + + List result = ReservoirSampling.sample(stream, sampleSize); + + assertEquals(sampleSize, result.size()); + for (int value : result) { + assertTrue(Arrays.stream(stream).anyMatch(x -> x == value)); + } + } + + @Test + public void testSampleSizeGreaterThanStreamLengthThrowsException() { + int[] stream = {1, 2, 3}; + + Exception exception = assertThrows(IllegalArgumentException.class, () -> ReservoirSampling.sample(stream, 5)); + + assertEquals("Sample size cannot exceed stream size.", exception.getMessage()); + } +} diff --git a/src/test/java/com/thealgorithms/recursion/DiceThrowerTest.java b/src/test/java/com/thealgorithms/recursion/DiceThrowerTest.java new file mode 100644 index 000000000000..0804e9bfff25 --- /dev/null +++ b/src/test/java/com/thealgorithms/recursion/DiceThrowerTest.java @@ -0,0 +1,223 @@ +package com.thealgorithms.recursion; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Test class for DiceThrower + * + * @author BEASTSHRIRAM + */ +class DiceThrowerTest { + + @Test + void testTargetZero() { + List result = DiceThrower.getDiceCombinations(0); + assertEquals(1, result.size()); + assertEquals("", result.get(0)); + } + + @Test + void testTargetOne() { + List result = DiceThrower.getDiceCombinations(1); + assertEquals(1, result.size()); + assertEquals("1", result.get(0)); + } + + @Test + void testTargetTwo() { + List result = DiceThrower.getDiceCombinations(2); + assertEquals(2, result.size()); + assertTrue(result.contains("11")); + assertTrue(result.contains("2")); + } + + @Test + void testTargetThree() { + List result = DiceThrower.getDiceCombinations(3); + assertEquals(4, result.size()); + assertTrue(result.contains("111")); + assertTrue(result.contains("12")); + assertTrue(result.contains("21")); + assertTrue(result.contains("3")); + } + + @Test + void testTargetFour() { + List result = DiceThrower.getDiceCombinations(4); + assertEquals(8, result.size()); + assertTrue(result.contains("1111")); + assertTrue(result.contains("112")); + assertTrue(result.contains("121")); + assertTrue(result.contains("13")); + assertTrue(result.contains("211")); + assertTrue(result.contains("22")); + assertTrue(result.contains("31")); + assertTrue(result.contains("4")); + } + + @Test + void testTargetSix() { + List result = DiceThrower.getDiceCombinations(6); + assertEquals(32, result.size()); + assertTrue(result.contains("6")); + assertTrue(result.contains("33")); + assertTrue(result.contains("222")); + assertTrue(result.contains("111111")); + } + + @Test + void testTargetSeven() { + List result = DiceThrower.getDiceCombinations(7); + // Should include combinations like 61, 52, 43, 331, 322, 2221, etc. + assertTrue(result.size() > 0); + assertTrue(result.contains("61")); + assertTrue(result.contains("16")); + assertTrue(result.contains("52")); + assertTrue(result.contains("43")); + } + + @Test + void testLargerTarget() { + List result = DiceThrower.getDiceCombinations(10); + assertTrue(result.size() > 0); + // All results should sum to 10 + for (String combination : result) { + int sum = 0; + for (char c : combination.toCharArray()) { + sum += Character.getNumericValue(c); + } + assertEquals(10, sum); + } + } + + @Test + void testNegativeTarget() { + assertThrows(IllegalArgumentException.class, () -> { DiceThrower.getDiceCombinations(-1); }); + } + + @Test + void testNegativeTargetPrint() { + assertThrows(IllegalArgumentException.class, () -> { DiceThrower.printDiceCombinations(-1); }); + } + + @Test + void testAllCombinationsValid() { + List result = DiceThrower.getDiceCombinations(5); + + for (String combination : result) { + // Check that each character is a valid dice face (1-6) + for (char c : combination.toCharArray()) { + int face = Character.getNumericValue(c); + assertTrue(face >= 1 && face <= 6, "Invalid dice face: " + face); + } + + // Check that the sum equals the target + int sum = 0; + for (char c : combination.toCharArray()) { + sum += Character.getNumericValue(c); + } + assertEquals(5, sum, "Combination " + combination + " does not sum to 5"); + } + } + + @Test + void testPrintDiceCombinations() { + // Capture System.out + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outputStream)); + + try { + // Test printing combinations for target 3 + DiceThrower.printDiceCombinations(3); + String output = outputStream.toString(); + + // Verify all expected combinations are printed + assertTrue(output.contains("111")); + assertTrue(output.contains("12")); + assertTrue(output.contains("21")); + assertTrue(output.contains("3")); + + // Count number of lines (combinations) + String[] lines = output.trim().split("\n"); + assertEquals(4, lines.length); + } finally { + // Restore System.out + System.setOut(originalOut); + } + } + + @Test + void testPrintDiceCombinationsZero() { + // Capture System.out + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outputStream)); + + try { + DiceThrower.printDiceCombinations(0); + String output = outputStream.toString(); + + // Should print empty string (one line) + assertEquals("", output.trim()); + } finally { + System.setOut(originalOut); + } + } + + @Test + void testMainMethod() { + // Capture System.out + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outputStream)); + + try { + // Test main method + DiceThrower.main(new String[] {}); + String output = outputStream.toString(); + + // Verify expected output contains header and combinations + assertTrue(output.contains("All dice combinations that sum to 4:")); + assertTrue(output.contains("Total combinations: 8")); + assertTrue(output.contains("1111")); + assertTrue(output.contains("22")); + assertTrue(output.contains("4")); + } finally { + System.setOut(originalOut); + } + } + + @Test + void testEdgeCaseTargetFive() { + List result = DiceThrower.getDiceCombinations(5); + assertEquals(16, result.size()); + + // Test specific combinations exist + assertTrue(result.contains("11111")); + assertTrue(result.contains("1112")); + assertTrue(result.contains("122")); + assertTrue(result.contains("14")); + assertTrue(result.contains("23")); + assertTrue(result.contains("5")); + } + + @Test + void testTargetGreaterThanSix() { + List result = DiceThrower.getDiceCombinations(8); + assertTrue(result.size() > 0); + + // Verify some expected combinations + assertTrue(result.contains("62")); + assertTrue(result.contains("53")); + assertTrue(result.contains("44")); + assertTrue(result.contains("2222")); + } +} diff --git a/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java b/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java index b92d1406b0a7..983552722781 100644 --- a/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java +++ b/src/test/java/com/thealgorithms/recursion/GenerateSubsetsTest.java @@ -1,36 +1,40 @@ package com.thealgorithms.recursion; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import java.util.Arrays; import java.util.List; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; public final class GenerateSubsetsTest { @Test - void subsetRecursionTestOne() { - String str = "abc"; - String[] expected = new String[] {"abc", "ab", "ac", "a", "bc", "b", "c", ""}; - - List ans = GenerateSubsets.subsetRecursion(str); - assertArrayEquals(ans.toArray(), expected); + @DisplayName("Subsets of 'abc'") + void testSubsetsOfABC() { + assertSubsets("abc", Arrays.asList("abc", "ab", "ac", "a", "bc", "b", "c", "")); } @Test - void subsetRecursionTestTwo() { - String str = "cbf"; - String[] expected = new String[] {"cbf", "cb", "cf", "c", "bf", "b", "f", ""}; + @DisplayName("Subsets of 'cbf'") + void testSubsetsOfCBF() { + assertSubsets("cbf", Arrays.asList("cbf", "cb", "cf", "c", "bf", "b", "f", "")); + } - List ans = GenerateSubsets.subsetRecursion(str); - assertArrayEquals(ans.toArray(), expected); + @Test + @DisplayName("Subsets of 'aba' with duplicates") + void testSubsetsWithDuplicateChars() { + assertSubsets("aba", Arrays.asList("aba", "ab", "aa", "a", "ba", "b", "a", "")); } @Test - void subsetRecursionTestThree() { - String str = "aba"; - String[] expected = new String[] {"aba", "ab", "aa", "a", "ba", "b", "a", ""}; + @DisplayName("Subsets of empty string") + void testEmptyInput() { + assertSubsets("", List.of("")); + } - List ans = GenerateSubsets.subsetRecursion(str); - assertArrayEquals(ans.toArray(), expected); + private void assertSubsets(String input, Iterable expected) { + List actual = GenerateSubsets.subsetRecursion(input); + assertIterableEquals(expected, actual, "Subsets do not match for input: " + input); } } diff --git a/src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java b/src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java new file mode 100644 index 000000000000..3ea5641ac484 --- /dev/null +++ b/src/test/java/com/thealgorithms/recursion/SylvesterSequenceTest.java @@ -0,0 +1,51 @@ +package com.thealgorithms.recursion; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.math.BigInteger; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class SylvesterSequenceTest { + + /** + * Provides test cases for valid Sylvester sequence numbers. + * Format: { n, expectedValue } + */ + static Stream validSylvesterNumbers() { + return Stream.of(new Object[] {1, BigInteger.valueOf(2)}, new Object[] {2, BigInteger.valueOf(3)}, new Object[] {3, BigInteger.valueOf(7)}, new Object[] {4, BigInteger.valueOf(43)}, new Object[] {5, BigInteger.valueOf(1807)}, new Object[] {6, new BigInteger("3263443")}, + new Object[] {7, new BigInteger("10650056950807")}, new Object[] {8, new BigInteger("113423713055421844361000443")}); + } + + @ParameterizedTest + @MethodSource("validSylvesterNumbers") + void testSylvesterValidNumbers(int n, BigInteger expected) { + assertEquals(expected, SylvesterSequence.sylvester(n), "Sylvester sequence value mismatch for n=" + n); + } + + /** + * Test edge case for n <= 0 which should throw IllegalArgumentException + */ + @ParameterizedTest + @ValueSource(ints = {0, -1, -10, -100}) + void testSylvesterInvalidZero(int n) { + assertThrows(IllegalArgumentException.class, () -> SylvesterSequence.sylvester(n)); + } + + /** + * Test a larger number to ensure no overflow occurs. + */ + @Test + void testSylvesterLargeNumber() { + int n = 10; + BigInteger result = SylvesterSequence.sylvester(n); + assertNotNull(result); + assertTrue(result.compareTo(BigInteger.ZERO) > 0, "Result should be positive"); + } +} diff --git a/src/test/java/com/thealgorithms/scheduling/SJFSchedulingTest.java b/src/test/java/com/thealgorithms/scheduling/SJFSchedulingTest.java index aab5c64c847f..660a53299ab0 100644 --- a/src/test/java/com/thealgorithms/scheduling/SJFSchedulingTest.java +++ b/src/test/java/com/thealgorithms/scheduling/SJFSchedulingTest.java @@ -4,107 +4,46 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.thealgorithms.devutils.entities.ProcessDetails; -import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class SJFSchedulingTest { - private ArrayList process; - void initialisation0() { - process = new ArrayList<>(); - process.add(new ProcessDetails("1", 0, 6)); - process.add(new ProcessDetails("2", 1, 2)); + private static Stream schedulingTestData() { + return Stream.of(Arguments.of(List.of(new ProcessDetails("1", 0, 6), new ProcessDetails("2", 1, 2)), List.of("1", "2")), + Arguments.of(List.of(new ProcessDetails("1", 0, 6), new ProcessDetails("2", 1, 2), new ProcessDetails("3", 4, 3), new ProcessDetails("4", 3, 1), new ProcessDetails("5", 6, 4), new ProcessDetails("6", 5, 5)), List.of("1", "4", "2", "3", "5", "6")), + Arguments.of(List.of(new ProcessDetails("1", 0, 3), new ProcessDetails("2", 1, 2), new ProcessDetails("3", 2, 1)), List.of("1", "3", "2")), Arguments.of(List.of(new ProcessDetails("1", 0, 3), new ProcessDetails("2", 5, 2), new ProcessDetails("3", 9, 1)), List.of("1", "2", "3")), + Arguments.of(Collections.emptyList(), List.of())); } - void initialisation1() { - process = new ArrayList<>(); - process.add(new ProcessDetails("1", 0, 6)); - process.add(new ProcessDetails("2", 1, 2)); - process.add(new ProcessDetails("3", 4, 3)); - process.add(new ProcessDetails("4", 3, 1)); - process.add(new ProcessDetails("5", 6, 4)); - process.add(new ProcessDetails("6", 5, 5)); - } - - void initialisation2() { - - process = new ArrayList<>(); - process.add(new ProcessDetails("1", 0, 3)); - process.add(new ProcessDetails("2", 1, 2)); - process.add(new ProcessDetails("3", 2, 1)); - } - void initialisation3() { - process = new ArrayList<>(); - process.add(new ProcessDetails("1", 0, 3)); - process.add(new ProcessDetails("2", 5, 2)); - process.add(new ProcessDetails("3", 9, 1)); - } - @Test - void constructor() { - initialisation0(); - SJFScheduling a = new SJFScheduling(process); - assertEquals(6, a.processes.get(0).getBurstTime()); - assertEquals(2, a.processes.get(1).getBurstTime()); + @ParameterizedTest(name = "Test SJF schedule: {index}") + @MethodSource("schedulingTestData") + void testSJFScheduling(List inputProcesses, List expectedSchedule) { + SJFScheduling scheduler = new SJFScheduling(inputProcesses); + scheduler.scheduleProcesses(); + assertEquals(expectedSchedule, scheduler.getSchedule()); } @Test - void sort() { - initialisation1(); - SJFScheduling a = new SJFScheduling(process); - a.sortByArrivalTime(); - assertEquals("1", a.processes.get(0).getProcessId()); - assertEquals("2", a.processes.get(1).getProcessId()); - assertEquals("3", a.processes.get(3).getProcessId()); - assertEquals("4", a.processes.get(2).getProcessId()); - assertEquals("5", a.processes.get(5).getProcessId()); - assertEquals("6", a.processes.get(4).getProcessId()); - } + @DisplayName("Test sorting by arrival order") + void testProcessArrivalOrderIsSorted() { + List processes = List.of(new ProcessDetails("1", 0, 6), new ProcessDetails("2", 1, 2), new ProcessDetails("4", 3, 1), new ProcessDetails("3", 4, 3), new ProcessDetails("6", 5, 5), new ProcessDetails("5", 6, 4)); + SJFScheduling scheduler = new SJFScheduling(processes); + List actualOrder = scheduler.getProcesses().stream().map(ProcessDetails::getProcessId).toList(); - @Test - void scheduling() { - initialisation1(); - SJFScheduling a = new SJFScheduling(process); - a.scheduleProcesses(); - assertEquals("1", a.schedule.get(0)); - assertEquals("4", a.schedule.get(1)); - assertEquals("2", a.schedule.get(2)); - assertEquals("3", a.schedule.get(3)); - assertEquals("5", a.schedule.get(4)); - assertEquals("6", a.schedule.get(5)); + assertEquals(List.of("1", "2", "4", "3", "6", "5"), actualOrder); } @Test - void schedulingOfTwoProcesses() { - initialisation0(); - SJFScheduling a = new SJFScheduling(process); - a.scheduleProcesses(); - assertEquals("1", a.schedule.get(0)); - assertEquals("2", a.schedule.get(1)); - } - - @Test - void schedulingOfAShortestJobArrivingLast() { - initialisation2(); - SJFScheduling a = new SJFScheduling(process); - a.scheduleProcesses(); - assertEquals("1", a.schedule.get(0)); - assertEquals("3", a.schedule.get(1)); - assertEquals("2", a.schedule.get(2)); - } - @Test - void schedulingWithProcessesNotComingBackToBack() { - initialisation3(); - SJFScheduling a = new SJFScheduling(process); - a.scheduleProcesses(); - assertEquals("1", a.schedule.get(0)); - assertEquals("2", a.schedule.get(1)); - assertEquals("3", a.schedule.get(2)); - } - @Test - void schedulingOfNothing() { - process = new ArrayList<>(); - SJFScheduling a = new SJFScheduling(process); - a.scheduleProcesses(); - assertTrue(a.schedule.isEmpty()); + void testSchedulingEmptyList() { + SJFScheduling scheduler = new SJFScheduling(Collections.emptyList()); + scheduler.scheduleProcesses(); + assertTrue(scheduler.getSchedule().isEmpty()); } } diff --git a/src/test/java/com/thealgorithms/searches/BoyerMooreTest.java b/src/test/java/com/thealgorithms/searches/BoyerMooreTest.java new file mode 100644 index 000000000000..9021eacbb8ee --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/BoyerMooreTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class BoyerMooreTest { + + @Test + public void testPatternFound() { + BoyerMoore bm = new BoyerMoore("ABCDABD"); + String text = "ABC ABCDAB ABCDABCDABDE"; + int index = bm.search(text); + assertEquals(15, index); + } + + @Test + public void testPatternNotFound() { + BoyerMoore bm = new BoyerMoore("XYZ"); + String text = "ABC ABCDAB ABCDABCDABDE"; + int index = bm.search(text); + assertEquals(-1, index); + } + + @Test + public void testPatternAtBeginning() { + BoyerMoore bm = new BoyerMoore("ABC"); + String text = "ABCDEF"; + int index = bm.search(text); + assertEquals(0, index); + } + + @Test + public void testPatternAtEnd() { + BoyerMoore bm = new BoyerMoore("CDE"); + String text = "ABCDEFGCDE"; + int index = bm.search(text); + assertEquals(2, index); // Primera ocurrencia de "CDE" + } + + @Test + public void testEmptyPattern() { + BoyerMoore bm = new BoyerMoore(""); + String text = "Hello world"; + int index = bm.search(text); + assertEquals(0, index); + } + + @Test + public void testStaticSearchMethod() { + String text = "ABCDEFGCDE"; + int index = BoyerMoore.staticSearch(text, "CDE"); + assertEquals(2, index); // Primera ocurrencia de "CDE" + } +} diff --git a/src/test/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearchTest.java index 39ac5bf037ea..8d1423cbeeb0 100644 --- a/src/test/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearchTest.java +++ b/src/test/java/com/thealgorithms/searches/RowColumnWiseSorted2dArrayBinarySearchTest.java @@ -110,4 +110,131 @@ public void rowColumnSorted2dArrayBinarySearchTestNotFound() { assertEquals(expected[0], ans[0]); assertEquals(expected[1], ans[1]); } + + /** + * Tests for a WIDE rectangular matrix (3 rows, 4 columns) + */ + private static final Integer[][] WIDE_RECTANGULAR_MATRIX = { + {10, 20, 30, 40}, + {15, 25, 35, 45}, + {18, 28, 38, 48}, + }; + + @Test + public void rowColumnSorted2dArrayBinarySearchTestWideMatrixMiddle() { + Integer target = 25; // A value in the middle + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(WIDE_RECTANGULAR_MATRIX, target); + int[] expected = {1, 1}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestWideMatrixTopRightCorner() { + Integer target = 40; // The top-right corner element + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(WIDE_RECTANGULAR_MATRIX, target); + int[] expected = {0, 3}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestWideMatrixBottomLeftCorner() { + Integer target = 18; // The bottom-left corner element + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(WIDE_RECTANGULAR_MATRIX, target); + int[] expected = {2, 0}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestWideMatrixTopLeftCorner() { + Integer target = 10; // The top-left corner element + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(WIDE_RECTANGULAR_MATRIX, target); + int[] expected = {0, 0}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestWideMatrixBottomRightCorner() { + Integer target = 48; // The bottom-right corner element + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(WIDE_RECTANGULAR_MATRIX, target); + int[] expected = {2, 3}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestWideMatrixNotFound() { + Integer target = 99; // A value that does not exist + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(WIDE_RECTANGULAR_MATRIX, target); + int[] expected = {-1, -1}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + /** + * Tests for a TALL rectangular matrix (4 rows, 3 columns) + */ + private static final Integer[][] TALL_RECTANGULAR_MATRIX = { + {10, 20, 30}, + {15, 25, 35}, + {18, 28, 38}, + {21, 31, 41}, + }; + + @Test + public void rowColumnSorted2dArrayBinarySearchTestTallMatrixMiddle() { + Integer target = 28; // A value in the middle + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(TALL_RECTANGULAR_MATRIX, target); + int[] expected = {2, 1}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestTallMatrixTopRightCorner() { + Integer target = 30; // The top-right corner element + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(TALL_RECTANGULAR_MATRIX, target); + int[] expected = {0, 2}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestTallMatrixBottomLeftCorner() { + Integer target = 21; // The bottom-left corner element + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(TALL_RECTANGULAR_MATRIX, target); + int[] expected = {3, 0}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestTallMatrixTopLeftCorner() { + Integer target = 10; // The top-left corner element + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(TALL_RECTANGULAR_MATRIX, target); + int[] expected = {0, 0}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestTallMatrixBottomRightCorner() { + Integer target = 41; // The bottom-right corner element + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(TALL_RECTANGULAR_MATRIX, target); + int[] expected = {3, 2}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } + + @Test + public void rowColumnSorted2dArrayBinarySearchTestTallMatrixNotFound() { + Integer target = 5; // A value that does not exist + int[] ans = RowColumnWiseSorted2dArrayBinarySearch.search(TALL_RECTANGULAR_MATRIX, target); + int[] expected = {-1, -1}; + assertEquals(expected[0], ans[0]); + assertEquals(expected[1], ans[1]); + } } diff --git a/src/test/java/com/thealgorithms/searches/SentinelLinearSearchTest.java b/src/test/java/com/thealgorithms/searches/SentinelLinearSearchTest.java new file mode 100644 index 000000000000..0da8e1c83997 --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/SentinelLinearSearchTest.java @@ -0,0 +1,225 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Random; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the SentinelLinearSearch class. + */ +class SentinelLinearSearchTest { + + /** + * Test for finding an element present in the array. + */ + @Test + void testSentinelLinearSearchFound() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 5; // Element to find + assertEquals(5, sentinelLinearSearch.find(array, key), "The index of the found element should be 5."); + } + + /** + * Test for finding the first element in the array. + */ + @Test + void testSentinelLinearSearchFirstElement() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 0; // First element + assertEquals(0, sentinelLinearSearch.find(array, key), "The index of the first element should be 0."); + } + + /** + * Test for finding the last element in the array. + */ + @Test + void testSentinelLinearSearchLastElement() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = 10; // Last element + assertEquals(10, sentinelLinearSearch.find(array, key), "The index of the last element should be 10."); + } + + /** + * Test for finding an element not present in the array. + */ + @Test + void testSentinelLinearSearchNotFound() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Integer key = -1; // Element not in the array + assertEquals(-1, sentinelLinearSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for finding an element in an empty array. + */ + @Test + void testSentinelLinearSearchEmptyArray() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {}; // Empty array + Integer key = 1; // Key not present + assertEquals(-1, sentinelLinearSearch.find(array, key), "The element should not be found in an empty array."); + } + + /** + * Test for finding an element in a single-element array when present. + */ + @Test + void testSentinelLinearSearchSingleElementFound() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {42}; // Single element array + Integer key = 42; // Element present + assertEquals(0, sentinelLinearSearch.find(array, key), "The element should be found at index 0."); + } + + /** + * Test for finding an element in a single-element array when not present. + */ + @Test + void testSentinelLinearSearchSingleElementNotFound() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {42}; // Single element array + Integer key = 24; // Element not present + assertEquals(-1, sentinelLinearSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for finding multiple occurrences of the same element in the array. + */ + @Test + void testSentinelLinearSearchMultipleOccurrences() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {1, 2, 3, 4, 5, 3, 6, 7, 3}; // 3 occurs multiple times + Integer key = 3; // Key to find + assertEquals(2, sentinelLinearSearch.find(array, key), "The index of the first occurrence of the element should be 2."); + } + + /** + * Test for finding an element in a large array. + */ + @Test + void testSentinelLinearSearchLargeArray() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = new Integer[1000]; + for (int i = 0; i < array.length; i++) { + array[i] = i; // Fill the array with integers 0 to 999 + } + Integer key = 256; // Present in the array + assertEquals(256, sentinelLinearSearch.find(array, key), "The index of the found element should be 256."); + } + + /** + * Test for finding an element in a large array when it is not present. + */ + @Test + void testSentinelLinearSearchLargeArrayNotFound() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = new Integer[1000]; + for (int i = 0; i < array.length; i++) { + array[i] = i; // Fill the array with integers 0 to 999 + } + Integer key = 1001; // Key not present + assertEquals(-1, sentinelLinearSearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for performance with random large array. + */ + @Test + void testSentinelLinearSearchRandomArray() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Random random = new Random(); + Integer[] array = random.ints(0, 1000).distinct().limit(1000).boxed().toArray(Integer[] ::new); + Integer key = array[random.nextInt(array.length)]; // Key should be in the array + assertEquals(java.util.Arrays.asList(array).indexOf(key), sentinelLinearSearch.find(array, key), "The index of the found element should match."); + } + + /** + * Test for handling null array. + */ + @Test + void testSentinelLinearSearchNullArray() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = null; // Null array + Integer key = 1; // Any key + assertThrows(IllegalArgumentException.class, () -> sentinelLinearSearch.find(array, key), "Should throw IllegalArgumentException for null array."); + } + + /** + * Test for handling null key in array with null elements. + */ + @Test + void testSentinelLinearSearchNullKey() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {1, null, 3, 4, null}; // Array with null elements + Integer key = null; // Null key + assertEquals(1, sentinelLinearSearch.find(array, key), "The index of the first null element should be 1."); + } + + /** + * Test for handling null key when not present in array. + */ + @Test + void testSentinelLinearSearchNullKeyNotFound() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {1, 2, 3, 4, 5}; // Array without null elements + Integer key = null; // Null key + assertEquals(-1, sentinelLinearSearch.find(array, key), "Null key should not be found in array without null elements."); + } + + /** + * Test with String array to verify generic functionality. + */ + @Test + void testSentinelLinearSearchStringArray() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + String[] array = {"apple", "banana", "cherry", "date", "elderberry"}; + String key = "cherry"; // Element to find + assertEquals(2, sentinelLinearSearch.find(array, key), "The index of 'cherry' should be 2."); + } + + /** + * Test with String array when element not found. + */ + @Test + void testSentinelLinearSearchStringArrayNotFound() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + String[] array = {"apple", "banana", "cherry", "date", "elderberry"}; + String key = "grape"; // Element not in array + assertEquals(-1, sentinelLinearSearch.find(array, key), "The element 'grape' should not be found in the array."); + } + + /** + * Test that the original array is not modified after search. + */ + @Test + void testSentinelLinearSearchArrayIntegrity() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {1, 2, 3, 4, 5}; + Integer[] originalArray = array.clone(); // Keep a copy of the original + Integer key = 3; // Element to find + + sentinelLinearSearch.find(array, key); + + // Verify array is unchanged + for (int i = 0; i < array.length; i++) { + assertEquals(originalArray[i], array[i], "Array should remain unchanged after search."); + } + } + + /** + * Test edge case where the key is the same as the last element. + */ + @Test + void testSentinelLinearSearchKeyEqualsLastElement() { + SentinelLinearSearch sentinelLinearSearch = new SentinelLinearSearch(); + Integer[] array = {1, 2, 3, 4, 5, 3}; // Last element is 3, and 3 also appears earlier + Integer key = 3; // Key equals last element + assertEquals(2, sentinelLinearSearch.find(array, key), "Should find the first occurrence at index 2, not the last."); + } +} diff --git a/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java b/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java index 014fb4bd24af..a56f79670cf3 100644 --- a/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java +++ b/src/test/java/com/thealgorithms/searches/TestSearchInARowAndColWiseSortedMatrix.java @@ -1,27 +1,25 @@ -package com.thealgorithms.searches; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -import org.junit.jupiter.api.Test; - -public class TestSearchInARowAndColWiseSortedMatrix { - @Test - public void searchItem() { - int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; - - var test = new SearchInARowAndColWiseSortedMatrix(); - int[] res = test.search(matrix, 16); - int[] expectedResult = {2, 2}; - assertArrayEquals(expectedResult, res); - } - - @Test - public void notFound() { - int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; - - var test = new SearchInARowAndColWiseSortedMatrix(); - int[] res = test.search(matrix, 96); - int[] expectedResult = {-1, -1}; - assertArrayEquals(expectedResult, res); - } -} +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +public class TestSearchInARowAndColWiseSortedMatrix { + @Test + public void searchItem() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + var test = new SearchInARowAndColWiseSortedMatrix(); + int[] res = test.search(matrix, 16); + int[] expectedResult = {2, 2}; + assertArrayEquals(expectedResult, res); + } + + @Test + public void notFound() { + int[][] matrix = {{3, 4, 5, 6, 7}, {8, 9, 10, 11, 12}, {14, 15, 16, 17, 18}, {23, 24, 25, 26, 27}, {30, 31, 32, 33, 34}}; + var test = new SearchInARowAndColWiseSortedMatrix(); + int[] res = test.search(matrix, 96); + int[] expectedResult = {-1, -1}; + assertArrayEquals(expectedResult, res); + } +} diff --git a/src/test/java/com/thealgorithms/others/MaximumSlidingWindowTest.java b/src/test/java/com/thealgorithms/slidingwindow/MaximumSlidingWindowTest.java similarity index 97% rename from src/test/java/com/thealgorithms/others/MaximumSlidingWindowTest.java rename to src/test/java/com/thealgorithms/slidingwindow/MaximumSlidingWindowTest.java index 9209136a5af3..a2d233b6063b 100644 --- a/src/test/java/com/thealgorithms/others/MaximumSlidingWindowTest.java +++ b/src/test/java/com/thealgorithms/slidingwindow/MaximumSlidingWindowTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.slidingwindow; import static org.junit.jupiter.api.Assertions.assertArrayEquals; diff --git a/src/test/java/com/thealgorithms/slidingwindow/MinimumWindowSubstringTest.java b/src/test/java/com/thealgorithms/slidingwindow/MinimumWindowSubstringTest.java new file mode 100644 index 000000000000..2c1534f4bfe6 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/MinimumWindowSubstringTest.java @@ -0,0 +1,35 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Finds the minimum window substring in {@code s} that contains all characters of {@code t}. + * + * @param s The input string to search within + * @param t The string with required characters + * @return The minimum window substring, or empty string if not found + * @author (https://github.com/Chiefpatwal) + */ +public class MinimumWindowSubstringTest { + + /** + * Tests for MinimumWindowSubstring.minWindow. + */ + @Test + public void testMinimumWindowSubstring() { + assertEquals("BANC", MinimumWindowSubstring.minWindow("ADOBECODEBANC", "ABC")); + assertEquals("a", MinimumWindowSubstring.minWindow("a", "a")); + assertEquals("", MinimumWindowSubstring.minWindow("a", "aa")); + assertEquals("", MinimumWindowSubstring.minWindow("ADOBECODEBANC", "XYZ")); + assertEquals("BC", MinimumWindowSubstring.minWindow("ABCDEF", "BC")); + assertEquals("q", MinimumWindowSubstring.minWindow("abcdefghijklmnopqrstuvwxyz", "q")); + assertEquals("", MinimumWindowSubstring.minWindow("zzzzzzzzz", "zzzzzzzzzz")); + assertEquals("abbbbbcdd", MinimumWindowSubstring.minWindow("aaaaaaaaaaaabbbbbcdd", "abcdd")); + assertEquals("ABCDEFG", MinimumWindowSubstring.minWindow("ABCDEFG", "ABCDEFG")); + assertEquals("", MinimumWindowSubstring.minWindow("abc", "A")); + assertEquals("A", MinimumWindowSubstring.minWindow("aAbBcC", "A")); + assertEquals("AABBC", MinimumWindowSubstring.minWindow("AAABBC", "AABC")); + } +} diff --git a/src/test/java/com/thealgorithms/slidingwindow/ShortestCoprimeSegmentTest.java b/src/test/java/com/thealgorithms/slidingwindow/ShortestCoprimeSegmentTest.java new file mode 100644 index 000000000000..acb9e1e30ac7 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/ShortestCoprimeSegmentTest.java @@ -0,0 +1,65 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for ShortestCoprimeSegment algorithm + * + * @author DomTr (...) + */ +public class ShortestCoprimeSegmentTest { + @Test + public void testShortestCoprimeSegment() { + assertArrayEquals(new long[] {4, 6, 9}, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {4, 6, 9, 3, 6})); + assertArrayEquals(new long[] {4, 5}, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {4, 5, 9, 3, 6})); + assertArrayEquals(new long[] {3, 2}, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {3, 2})); + assertArrayEquals(new long[] {9, 10}, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {3, 9, 9, 9, 10})); + + long[] test5 = new long[] {3 * 11, 11 * 7, 11 * 7 * 3, 11 * 7 * 3 * 5, 11 * 7 * 3 * 5 * 13, 7 * 13, 11 * 7 * 3 * 5 * 13}; + long[] answer5 = Arrays.copyOfRange(test5, 0, test5.length - 1); + assertArrayEquals(answer5, ShortestCoprimeSegment.shortestCoprimeSegment(test5)); + + // Test suite, when the entire array needs to be taken + long[] test6 = new long[] {3 * 7, 7 * 5, 5 * 7 * 3, 3 * 5}; + assertArrayEquals(test6, ShortestCoprimeSegment.shortestCoprimeSegment(test6)); + + long[] test7 = new long[] {3 * 11, 11 * 7, 11 * 7 * 3, 3 * 7}; + assertArrayEquals(test7, ShortestCoprimeSegment.shortestCoprimeSegment(test7)); + + long[] test8 = new long[] {3 * 11, 11 * 7, 11 * 7 * 3, 11 * 7 * 3 * 5, 5 * 7}; + assertArrayEquals(test8, ShortestCoprimeSegment.shortestCoprimeSegment(test8)); + + long[] test9 = new long[] {3 * 11, 11 * 7, 11 * 7 * 3, 11 * 7 * 3 * 5, 11 * 7 * 3 * 5 * 13, 7 * 13}; + assertArrayEquals(test9, ShortestCoprimeSegment.shortestCoprimeSegment(test9)); + + long[] test10 = new long[] {3 * 11, 7 * 11, 3 * 7 * 11, 3 * 5 * 7 * 11, 3 * 5 * 7 * 11 * 13, 2 * 3 * 5 * 7 * 11 * 13, 2 * 3 * 5 * 7 * 11 * 13 * 17, 2 * 3 * 5 * 7 * 11 * 13 * 17 * 19, 2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23, 7 * 13}; + assertArrayEquals(test10, ShortestCoprimeSegment.shortestCoprimeSegment(test10)); + + // Segment can consist of one element + long[] test11 = new long[] {1}; + assertArrayEquals(test11, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {4, 6, 1, 3, 6})); + long[] test12 = new long[] {1}; + assertArrayEquals(test12, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {1})); + } + @Test + public void testShortestCoprimeSegment2() { + assertArrayEquals(new long[] {2 * 3, 2 * 3 * 5, 2 * 3 * 5 * 7, 5 * 7}, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {2 * 3, 2 * 3 * 5, 2 * 3 * 5 * 7, 5 * 7, 2 * 3 * 5 * 7})); + assertArrayEquals(new long[] {5 * 7, 2}, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {2 * 3, 2 * 3 * 5, 2 * 3 * 5 * 7, 5 * 7, 2})); + assertArrayEquals(new long[] {5 * 7, 2 * 5 * 7, 2 * 11}, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {2 * 3, 2 * 3 * 5, 2 * 3 * 5 * 7, 5 * 7, 2 * 5 * 7, 2 * 11})); + assertArrayEquals(new long[] {3 * 5 * 7, 2 * 3, 2}, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {2, 2 * 3, 2 * 3 * 5, 3 * 5 * 7, 2 * 3, 2})); + } + @Test + public void testNoCoprimeSegment() { + // There may not be a coprime segment + long[] empty = new long[] {}; + assertArrayEquals(empty, ShortestCoprimeSegment.shortestCoprimeSegment(null)); + assertArrayEquals(empty, ShortestCoprimeSegment.shortestCoprimeSegment(empty)); + assertArrayEquals(empty, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {4, 6, 8, 12, 8})); + assertArrayEquals(empty, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {4, 4, 4, 4, 10, 4, 6, 8, 12, 8})); + assertArrayEquals(empty, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {100})); + assertArrayEquals(empty, ShortestCoprimeSegment.shortestCoprimeSegment(new long[] {2, 2, 2})); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java b/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java index 9d94b165d81b..8cb401802c4b 100644 --- a/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/AdaptiveMergeSortTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import java.util.Objects; import org.junit.jupiter.api.Test; public class AdaptiveMergeSortTest { @@ -50,4 +51,92 @@ public void testSortSingleElement() { Integer[] result = adaptiveMergeSort.sort(input); assertArrayEquals(expected, result); } + + @Test + public void testSortAlreadySortedArray() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] inputArray = {-12, -6, -3, 0, 2, 2, 13, 46}; + Integer[] outputArray = adaptiveMergeSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortReversedSortedArray() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] inputArray = {46, 13, 2, 2, 0, -3, -6, -12}; + Integer[] outputArray = adaptiveMergeSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortAllEqualArray() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Integer[] inputArray = {2, 2, 2, 2, 2}; + Integer[] outputArray = adaptiveMergeSort.sort(inputArray); + Integer[] expectedOutput = {2, 2, 2, 2, 2}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortMixedCaseStrings() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + String[] inputArray = {"banana", "Apple", "apple", "Banana"}; + String[] expectedOutput = {"Apple", "Banana", "apple", "banana"}; + String[] outputArray = adaptiveMergeSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } + + /** + * Custom Comparable class for testing. + **/ + static class Person implements Comparable { + String name; + int age; + + Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public int compareTo(Person o) { + return Integer.compare(this.age, o.age); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return age == person.age && Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } + } + + @Test + public void testSortCustomObjects() { + AdaptiveMergeSort adaptiveMergeSort = new AdaptiveMergeSort(); + Person[] inputArray = { + new Person("Alice", 32), + new Person("Bob", 25), + new Person("Charlie", 28), + }; + Person[] expectedOutput = { + new Person("Bob", 25), + new Person("Charlie", 28), + new Person("Alice", 32), + }; + Person[] outputArray = adaptiveMergeSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } } diff --git a/src/test/java/com/thealgorithms/sorts/BogoSortTest.java b/src/test/java/com/thealgorithms/sorts/BogoSortTest.java index 3ebfb7a305b0..dc4f9e1c25bb 100644 --- a/src/test/java/com/thealgorithms/sorts/BogoSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/BogoSortTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import java.util.Objects; import org.junit.jupiter.api.Test; public class BogoSortTest { @@ -63,4 +64,87 @@ public void bogoSortDuplicateStringArray() { String[] expectedOutput = {"a", "b", "b", "c", "d", "d", "h", "s"}; assertArrayEquals(outputArray, expectedOutput); } + + @Test + public void bogoSortAlreadySortedArray() { + Integer[] inputArray = {-12, -6, -3, 0, 2, 2, 13, 46}; + Integer[] outputArray = bogoSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void bogoSortReversedSortedArray() { + Integer[] inputArray = {46, 13, 2, 2, 0, -3, -6, -12}; + Integer[] outputArray = bogoSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void bogoSortAllEqualArray() { + Integer[] inputArray = {2, 2, 2, 2, 2}; + Integer[] outputArray = bogoSort.sort(inputArray); + Integer[] expectedOutput = {2, 2, 2, 2, 2}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void bogoSortMixedCaseStrings() { + String[] inputArray = {"banana", "Apple", "apple", "Banana"}; + String[] expectedOutput = {"Apple", "Banana", "apple", "banana"}; + String[] outputArray = bogoSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } + + /** + * Custom Comparable class for testing. + **/ + static class Person implements Comparable { + String name; + int age; + + Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public int compareTo(Person o) { + return Integer.compare(this.age, o.age); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return age == person.age && Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } + } + + @Test + public void bogoSortCustomObjects() { + Person[] inputArray = { + new Person("Alice", 32), + new Person("Bob", 25), + new Person("Charlie", 28), + }; + Person[] expectedOutput = { + new Person("Bob", 25), + new Person("Charlie", 28), + new Person("Alice", 32), + }; + Person[] outputArray = bogoSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } } diff --git a/src/test/java/com/thealgorithms/sorts/BubbleSortTest.java b/src/test/java/com/thealgorithms/sorts/BubbleSortTest.java index 8690a3f5435c..3b99dca13b06 100644 --- a/src/test/java/com/thealgorithms/sorts/BubbleSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/BubbleSortTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import java.util.Objects; import org.junit.jupiter.api.Test; /** @@ -91,4 +92,87 @@ public void bubbleSortStringArray() { }; assertArrayEquals(outputArray, expectedOutput); } + + @Test + public void bubbleSortAlreadySortedArray() { + Integer[] inputArray = {-12, -6, -3, 0, 2, 2, 13, 46}; + Integer[] outputArray = bubbleSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void bubbleSortReversedSortedArray() { + Integer[] inputArray = {46, 13, 2, 2, 0, -3, -6, -12}; + Integer[] outputArray = bubbleSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void bubbleSortAllEqualArray() { + Integer[] inputArray = {2, 2, 2, 2, 2}; + Integer[] outputArray = bubbleSort.sort(inputArray); + Integer[] expectedOutput = {2, 2, 2, 2, 2}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void bubbleSortMixedCaseStrings() { + String[] inputArray = {"banana", "Apple", "apple", "Banana"}; + String[] expectedOutput = {"Apple", "Banana", "apple", "banana"}; + String[] outputArray = bubbleSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } + + /** + * Custom Comparable class for testing. + **/ + static class Person implements Comparable { + String name; + int age; + + Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public int compareTo(Person o) { + return Integer.compare(this.age, o.age); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return age == person.age && Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } + } + + @Test + public void bubbleSortCustomObjects() { + Person[] inputArray = { + new Person("Alice", 32), + new Person("Bob", 25), + new Person("Charlie", 28), + }; + Person[] expectedOutput = { + new Person("Bob", 25), + new Person("Charlie", 28), + new Person("Alice", 32), + }; + Person[] outputArray = bubbleSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } } diff --git a/src/test/java/com/thealgorithms/sorts/FlashSortTest.java b/src/test/java/com/thealgorithms/sorts/FlashSortTest.java index 6b1a74403a59..5b27975d3bea 100644 --- a/src/test/java/com/thealgorithms/sorts/FlashSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/FlashSortTest.java @@ -5,7 +5,6 @@ import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; @@ -47,7 +46,7 @@ public void testCustomConstructorInvalidRatio(double ratio) { } @TestFactory - public Collection dynamicTestsForSorting() { + public List dynamicTestsForSorting() { List dynamicTests = new ArrayList<>(); double[] ratios = {0.1, 0.2, 0.5, 0.9}; @@ -60,7 +59,7 @@ public Collection dynamicTestsForSorting() { return dynamicTests; } - private Collection createDynamicTestsForRatio(double ratio) { + private List createDynamicTestsForRatio(double ratio) { List dynamicTests = new ArrayList<>(); for (TestMethod testMethod : getTestMethodsFromSuperClass()) { dynamicTests.add(DynamicTest.dynamicTest("Ratio: " + ratio + " - Test: " + testMethod.name(), testMethod.executable())); diff --git a/src/test/java/com/thealgorithms/sorts/GnomeSortTest.java b/src/test/java/com/thealgorithms/sorts/GnomeSortTest.java index d86546472580..1d875d1fad0d 100644 --- a/src/test/java/com/thealgorithms/sorts/GnomeSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/GnomeSortTest.java @@ -1,7 +1,9 @@ package com.thealgorithms.sorts; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import java.util.Objects; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -79,4 +81,92 @@ public void gnomeSortDuplicateStringArray() { gnomeSort.sort(inputArray); assertThat(inputArray).isEqualTo(expectedOutput); } + + @Test + @DisplayName("GnomeSort for sorted Array") + public void testSortAlreadySortedArray() { + Integer[] inputArray = {-12, -6, -3, 0, 2, 2, 13, 46}; + Integer[] outputArray = gnomeSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + @DisplayName("GnomeSort for reversed sorted Array") + public void testSortReversedSortedArray() { + Integer[] inputArray = {46, 13, 2, 2, 0, -3, -6, -12}; + Integer[] outputArray = gnomeSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + @DisplayName("GnomeSort for All equal Array") + public void testSortAllEqualArray() { + Integer[] inputArray = {2, 2, 2, 2, 2}; + Integer[] outputArray = gnomeSort.sort(inputArray); + Integer[] expectedOutput = {2, 2, 2, 2, 2}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + @DisplayName("GnomeSort String Array with mixed cases") + public void testSortMixedCaseStrings() { + String[] inputArray = {"banana", "Apple", "apple", "Banana"}; + String[] expectedOutput = {"Apple", "Banana", "apple", "banana"}; + String[] outputArray = gnomeSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } + + /** + * Custom Comparable class for testing. + **/ + static class Person implements Comparable { + String name; + int age; + + Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public int compareTo(Person o) { + return Integer.compare(this.age, o.age); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return age == person.age && Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } + } + + @Test + @DisplayName("GnomeSort Custom Object Array") + public void testSortCustomObjects() { + Person[] inputArray = { + new Person("Alice", 32), + new Person("Bob", 25), + new Person("Charlie", 28), + }; + Person[] expectedOutput = { + new Person("Bob", 25), + new Person("Charlie", 28), + new Person("Alice", 32), + }; + Person[] outputArray = gnomeSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } } diff --git a/src/test/java/com/thealgorithms/sorts/InsertionSortTest.java b/src/test/java/com/thealgorithms/sorts/InsertionSortTest.java index 78744973355d..32a2a807295b 100644 --- a/src/test/java/com/thealgorithms/sorts/InsertionSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/InsertionSortTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Objects; import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -111,4 +112,87 @@ private void testWithRandomArray(Function sortAlgorithm) { Double[] sorted = sortAlgorithm.apply(array); assertTrue(SortUtils.isSorted(sorted)); } + + @Test + public void testSortAlreadySortedArray() { + Integer[] inputArray = {-12, -6, -3, 0, 2, 2, 13, 46}; + Integer[] outputArray = insertionSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortReversedSortedArray() { + Integer[] inputArray = {46, 13, 2, 2, 0, -3, -6, -12}; + Integer[] outputArray = insertionSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortAllEqualArray() { + Integer[] inputArray = {2, 2, 2, 2, 2}; + Integer[] outputArray = insertionSort.sort(inputArray); + Integer[] expectedOutput = {2, 2, 2, 2, 2}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortMixedCaseStrings() { + String[] inputArray = {"banana", "Apple", "apple", "Banana"}; + String[] expectedOutput = {"Apple", "Banana", "apple", "banana"}; + String[] outputArray = insertionSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } + + /** + * Custom Comparable class for testing. + **/ + static class Person implements Comparable { + String name; + int age; + + Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public int compareTo(Person o) { + return Integer.compare(this.age, o.age); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return age == person.age && Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } + } + + @Test + public void testSortCustomObjects() { + Person[] inputArray = { + new Person("Alice", 32), + new Person("Bob", 25), + new Person("Charlie", 28), + }; + Person[] expectedOutput = { + new Person("Bob", 25), + new Person("Charlie", 28), + new Person("Alice", 32), + }; + Person[] outputArray = insertionSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } } diff --git a/src/test/java/com/thealgorithms/sorts/PriorityQueueSortTest.java b/src/test/java/com/thealgorithms/sorts/PriorityQueueSortTest.java new file mode 100644 index 000000000000..215431293227 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/PriorityQueueSortTest.java @@ -0,0 +1,56 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +class PriorityQueueSortTest { + + @Test + void testNullArray() { + int[] input = null; + assertArrayEquals(null, PriorityQueueSort.sort(input)); + } + + @Test + void testSingleElementArray() { + int[] input = {5}; + int[] expected = {5}; + assertArrayEquals(expected, PriorityQueueSort.sort(input)); + } + + @Test + void testSortNormalArray() { + int[] input = {7, 2, 9, 4, 1}; + int[] expected = {1, 2, 4, 7, 9}; + assertArrayEquals(expected, PriorityQueueSort.sort(input)); + } + + @Test + void testEmptyArray() { + int[] input = {}; + int[] expected = {}; + assertArrayEquals(expected, PriorityQueueSort.sort(input)); + } + + @Test + void testNegativeNumbers() { + int[] input = {3, -1, 2, -5, 0}; + int[] expected = {-5, -1, 0, 2, 3}; + assertArrayEquals(expected, PriorityQueueSort.sort(input)); + } + + @Test + void testAlreadySortedArray() { + int[] input = {1, 2, 3, 4, 5}; + int[] expected = {1, 2, 3, 4, 5}; + assertArrayEquals(expected, PriorityQueueSort.sort(input)); + } + + @Test + void testArrayWithDuplicates() { + int[] input = {5, 1, 3, 3, 2, 5}; + int[] expected = {1, 2, 3, 3, 5, 5}; + assertArrayEquals(expected, PriorityQueueSort.sort(input)); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/SimpleSortTest.java b/src/test/java/com/thealgorithms/sorts/SimpleSortTest.java deleted file mode 100644 index 887b314e46ff..000000000000 --- a/src/test/java/com/thealgorithms/sorts/SimpleSortTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.thealgorithms.sorts; - -public class SimpleSortTest extends SortingAlgorithmTest { - @Override - SortAlgorithm getSortAlgorithm() { - return new SimpleSort(); - } -} diff --git a/src/test/java/com/thealgorithms/sorts/SlowSortTest.java b/src/test/java/com/thealgorithms/sorts/SlowSortTest.java index d4d9eaa1c275..5fbdf8477092 100644 --- a/src/test/java/com/thealgorithms/sorts/SlowSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/SlowSortTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import java.util.Objects; import org.junit.jupiter.api.Test; /** @@ -76,4 +77,87 @@ public void slowSortStringSymbolArray() { String[] expectedOutput = {"(a", "(b", ")", "a", "au", "auk", "auk", "bhy", "cba", "cba", "cbf", "รก", "รณ"}; assertArrayEquals(outputArray, expectedOutput); } + + @Test + public void testSortAlreadySortedArray() { + Integer[] inputArray = {-12, -6, -3, 0, 2, 2, 13, 46}; + Integer[] outputArray = slowSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortReversedSortedArray() { + Integer[] inputArray = {46, 13, 2, 2, 0, -3, -6, -12}; + Integer[] outputArray = slowSort.sort(inputArray); + Integer[] expectedOutput = {-12, -6, -3, 0, 2, 2, 13, 46}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortAllEqualArray() { + Integer[] inputArray = {2, 2, 2, 2, 2}; + Integer[] outputArray = slowSort.sort(inputArray); + Integer[] expectedOutput = {2, 2, 2, 2, 2}; + assertArrayEquals(outputArray, expectedOutput); + } + + @Test + public void testSortMixedCaseStrings() { + String[] inputArray = {"banana", "Apple", "apple", "Banana"}; + String[] expectedOutput = {"Apple", "Banana", "apple", "banana"}; + String[] outputArray = slowSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } + + /** + * Custom Comparable class for testing. + **/ + static class Person implements Comparable { + String name; + int age; + + Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public int compareTo(Person o) { + return Integer.compare(this.age, o.age); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return age == person.age && Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } + } + + @Test + public void testSortCustomObjects() { + Person[] inputArray = { + new Person("Alice", 32), + new Person("Bob", 25), + new Person("Charlie", 28), + }; + Person[] expectedOutput = { + new Person("Bob", 25), + new Person("Charlie", 28), + new Person("Alice", 32), + }; + Person[] outputArray = slowSort.sort(inputArray); + assertArrayEquals(expectedOutput, outputArray); + } } diff --git a/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java b/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java index a4992a02abfa..896aee8ba4ab 100644 --- a/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/SpreadSortTest.java @@ -6,8 +6,7 @@ import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; public class SpreadSortTest extends SortingAlgorithmTest { @@ -20,16 +19,13 @@ SortAlgorithm getSortAlgorithm() { return new SpreadSort(); } - static class ConstructorArgumentsProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(org.junit.jupiter.api.extension.ExtensionContext context) { - return Stream.of(Arguments.of(0, 16, 2, IllegalArgumentException.class), Arguments.of(16, 0, 2, IllegalArgumentException.class), Arguments.of(16, 16, 0, IllegalArgumentException.class), Arguments.of(1001, 16, 2, IllegalArgumentException.class), - Arguments.of(16, 1001, 2, IllegalArgumentException.class), Arguments.of(16, 16, 101, IllegalArgumentException.class)); - } + private static Stream wrongConstructorInputs() { + return Stream.of(Arguments.of(0, 16, 2, IllegalArgumentException.class), Arguments.of(16, 0, 2, IllegalArgumentException.class), Arguments.of(16, 16, 0, IllegalArgumentException.class), Arguments.of(1001, 16, 2, IllegalArgumentException.class), + Arguments.of(16, 1001, 2, IllegalArgumentException.class), Arguments.of(16, 16, 101, IllegalArgumentException.class)); } @ParameterizedTest - @ArgumentsSource(ConstructorArgumentsProvider.class) + @MethodSource("wrongConstructorInputs") void testConstructor(int insertionSortThreshold, int initialBucketCapacity, int minBuckets, Class expectedException) { Executable executable = () -> new SpreadSort(insertionSortThreshold, initialBucketCapacity, minBuckets); assertThrows(expectedException, executable); diff --git a/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java b/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java index de115b458fe7..d5588b2b968e 100644 --- a/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.thealgorithms.sorts.TopologicalSort.Graph; import java.util.LinkedList; @@ -59,4 +60,18 @@ public void failureTest() { + "Back edge: 6 -> 2"; assertEquals(exception.getMessage(), expected); } + @Test + void testEmptyGraph() { + Graph graph = new Graph(); + LinkedList sorted = TopologicalSort.sort(graph); + assertTrue(sorted.isEmpty()); + } + @Test + void testSingleNode() { + Graph graph = new Graph(); + graph.addEdge("A", ""); + LinkedList sorted = TopologicalSort.sort(graph); + assertEquals(1, sorted.size()); + assertEquals("A", sorted.getFirst()); + } } diff --git a/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java b/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java index e2cc6acb8112..bc7b3266d98e 100644 --- a/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java +++ b/src/test/java/com/thealgorithms/stacks/DuplicateBracketsTest.java @@ -4,20 +4,23 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; class DuplicateBracketsTest { @ParameterizedTest - @CsvSource({"'((a + b) + (c + d))'", "'(a + b)'", "'a + b'", "'('", "''"}) + @CsvSource({"'((a + b) + (c + d))'", "'(a + b)'", "'a + b'", "'('", "''", "'a + (b * c) - d'", "'(x + y) * (z)'", "'(a + (b - c))'"}) void testInputReturnsFalse(String input) { assertFalse(DuplicateBrackets.check(input)); } @ParameterizedTest - @CsvSource({"'(a + b) + ((c + d))'", "'((a + b))'", "'((((a + b)))))'"}) + @CsvSource({"'(a + b) + ((c + d))'", "'((a + b))'", "'((((a + b)))))'", "'((x))'", "'((a + (b)))'", "'(a + ((b)))'", "'(((a)))'", "'(((())))'"}) void testInputReturnsTrue(String input) { assertTrue(DuplicateBrackets.check(input)); } @@ -26,4 +29,27 @@ void testInputReturnsTrue(String input) { void testInvalidInput() { assertThrows(IllegalArgumentException.class, () -> DuplicateBrackets.check(null)); } + + @ParameterizedTest(name = "Should be true: \"{0}\"") + @MethodSource("provideInputsThatShouldReturnTrue") + void testDuplicateBracketsTrueCases(String input) { + assertTrue(DuplicateBrackets.check(input)); + } + + static Stream provideInputsThatShouldReturnTrue() { + return Stream.of(Arguments.of("()"), Arguments.of("(( ))")); + } + + @ParameterizedTest(name = "Should be false: \"{0}\"") + @MethodSource("provideInputsThatShouldReturnFalse") + void testDuplicateBracketsFalseCases(String input) { + assertFalse(DuplicateBrackets.check(input)); + } + + static Stream provideInputsThatShouldReturnFalse() { + return Stream.of(Arguments.of("( )"), // whitespace inside brackets + Arguments.of("abc + def"), // no brackets + Arguments.of("(a + (b * c)) - (d / e)") // complex, but no duplicates + ); + } } diff --git a/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java b/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java index 02a08e393a00..bd63c1ac28f2 100644 --- a/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java +++ b/src/test/java/com/thealgorithms/stacks/InfixToPostfixTest.java @@ -12,7 +12,7 @@ class InfixToPostfixTest { @ParameterizedTest @MethodSource("provideValidExpressions") - void testValidExpressions(String infix, String expectedPostfix) throws Exception { + void testValidExpressions(String infix, String expectedPostfix) { assertEquals(expectedPostfix, InfixToPostfix.infix2PostFix(infix)); } @@ -28,6 +28,6 @@ void testInvalidExpressions(String infix, String expectedMessage) { } private static Stream provideInvalidExpressions() { - return Stream.of(Arguments.of("((a+b)*c-d", "invalid expression")); + return Stream.of(Arguments.of("((a+b)*c-d", "Invalid expression: unbalanced brackets.")); } } diff --git a/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java b/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java index 91be8a63da62..0ea948307336 100644 --- a/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java +++ b/src/test/java/com/thealgorithms/stacks/InfixToPrefixTest.java @@ -13,7 +13,7 @@ public class InfixToPrefixTest { @ParameterizedTest @MethodSource("provideValidExpressions") - void testValidExpressions(String infix, String expectedPrefix) throws Exception { + void testValidExpressions(String infix, String expectedPrefix) { assertEquals(expectedPrefix, InfixToPrefix.infix2Prefix(infix)); } diff --git a/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java b/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java index a54372adda0e..fec5d371c106 100644 --- a/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java +++ b/src/test/java/com/thealgorithms/stacks/LargestRectangleTest.java @@ -2,76 +2,27 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class LargestRectangleTest { - @Test - void testLargestRectangleHistogramWithTypicalCases() { - // Typical case with mixed heights - int[] heights = {2, 1, 5, 6, 2, 3}; - String expected = "10"; - String result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); - - // Another typical case with increasing heights - heights = new int[] {2, 4}; - expected = "4"; - result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); - - // Case with multiple bars of the same height - heights = new int[] {4, 4, 4, 4}; - expected = "16"; - result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); + @ParameterizedTest(name = "Histogram: {0} โ†’ Expected area: {1}") + @MethodSource("histogramProvider") + void testLargestRectangleHistogram(int[] heights, String expected) { + assertEquals(expected, LargestRectangle.largestRectangleHistogram(heights)); } - @Test - void testLargestRectangleHistogramWithEdgeCases() { - // Edge case with an empty array - int[] heights = {}; - String expected = "0"; - String result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); - - // Edge case with a single bar - heights = new int[] {5}; - expected = "5"; - result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); - - // Edge case with all bars of height 0 - heights = new int[] {0, 0, 0}; - expected = "0"; - result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); + static Stream histogramProvider() { + return Stream.of(Arguments.of(new int[] {2, 1, 5, 6, 2, 3}, "10"), Arguments.of(new int[] {2, 4}, "4"), Arguments.of(new int[] {4, 4, 4, 4}, "16"), Arguments.of(new int[] {}, "0"), Arguments.of(new int[] {5}, "5"), Arguments.of(new int[] {0, 0, 0}, "0"), + Arguments.of(new int[] {6, 2, 5, 4, 5, 1, 6}, "12"), Arguments.of(new int[] {2, 1, 5, 6, 2, 3, 1}, "10"), Arguments.of(createLargeArray(10000, 1), "10000")); } - @Test - void testLargestRectangleHistogramWithLargeInput() { - // Large input case - int[] heights = new int[10000]; - for (int i = 0; i < heights.length; i++) { - heights[i] = 1; - } - String expected = "10000"; - String result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); - } - - @Test - void testLargestRectangleHistogramWithComplexCases() { - // Complex case with a mix of heights - int[] heights = {6, 2, 5, 4, 5, 1, 6}; - String expected = "12"; - String result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); - - // Case with a peak in the middle - heights = new int[] {2, 1, 5, 6, 2, 3, 1}; - expected = "10"; - result = LargestRectangle.largestRectangleHistogram(heights); - assertEquals(expected, result); + private static int[] createLargeArray(int size, int value) { + int[] arr = new int[size]; + java.util.Arrays.fill(arr, value); + return arr; } } diff --git a/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java b/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java index e5deb17e9a8f..36bdde49b235 100644 --- a/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java +++ b/src/test/java/com/thealgorithms/stacks/MinStackUsingTwoStacksTest.java @@ -1,38 +1,112 @@ package com.thealgorithms.stacks; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.EmptyStackException; import org.junit.jupiter.api.Test; public class MinStackUsingTwoStacksTest { @Test - public void testMinStackOperations() { + public void testBasicOperations() { MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); minStack.push(3); minStack.push(5); - assertEquals(3, minStack.getMin()); + assertEquals(3, minStack.getMin(), "Min should be 3"); minStack.push(2); minStack.push(1); - assertEquals(1, minStack.getMin()); + assertEquals(1, minStack.getMin(), "Min should be 1"); minStack.pop(); - assertEquals(2, minStack.getMin()); + assertEquals(2, minStack.getMin(), "Min should be 2 after popping 1"); + + assertEquals(2, minStack.top(), "Top should be 2"); + } + + @Test + public void testPushDuplicateMins() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + minStack.push(2); + minStack.push(2); + minStack.push(1); + minStack.push(1); + assertEquals(1, minStack.getMin(), "Min should be 1"); + + minStack.pop(); + assertEquals(1, minStack.getMin(), "Min should still be 1 after popping one 1"); + + minStack.pop(); + assertEquals(2, minStack.getMin(), "Min should be 2 after popping both 1s"); + + minStack.pop(); + assertEquals(2, minStack.getMin(), "Min should still be 2 after popping one 2"); + + minStack.pop(); + // Now stack is empty, expect exception on getMin + assertThrows(EmptyStackException.class, minStack::getMin); } @Test - public void testMinStackOperations2() { + public void testPopOnEmptyStack() { MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + assertThrows(EmptyStackException.class, minStack::pop); + } + + @Test + public void testTopOnEmptyStack() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + assertThrows(EmptyStackException.class, minStack::top); + } + + @Test + public void testGetMinOnEmptyStack() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + assertThrows(EmptyStackException.class, minStack::getMin); + } + + @Test + public void testSingleElementStack() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + minStack.push(10); + assertEquals(10, minStack.getMin()); + assertEquals(10, minStack.top()); + + minStack.pop(); + assertThrows(EmptyStackException.class, minStack::getMin); + } + + @Test + public void testIncreasingSequence() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + minStack.push(1); + minStack.push(2); minStack.push(3); - minStack.push(5); - assertEquals(3, minStack.getMin()); + minStack.push(4); + + assertEquals(1, minStack.getMin()); + assertEquals(4, minStack.top()); + + minStack.pop(); + minStack.pop(); + assertEquals(1, minStack.getMin()); + assertEquals(2, minStack.top()); + } + @Test + public void testDecreasingSequence() { + MinStackUsingTwoStacks minStack = new MinStackUsingTwoStacks(); + minStack.push(4); + minStack.push(3); minStack.push(2); minStack.push(1); + assertEquals(1, minStack.getMin()); + assertEquals(1, minStack.top()); minStack.pop(); assertEquals(2, minStack.getMin()); + assertEquals(2, minStack.top()); } } diff --git a/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java b/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java index 882fe644ccd5..682240acd752 100644 --- a/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java +++ b/src/test/java/com/thealgorithms/stacks/PostfixEvaluatorTest.java @@ -4,24 +4,41 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.EmptyStackException; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class PostfixEvaluatorTest { - @Test - public void testValidExpressions() { - assertEquals(22, PostfixEvaluator.evaluatePostfix("5 6 + 2 *")); - assertEquals(27, PostfixEvaluator.evaluatePostfix("7 2 + 3 *")); - assertEquals(3, PostfixEvaluator.evaluatePostfix("10 5 / 1 +")); + @ParameterizedTest(name = "Expression: \"{0}\" โ†’ Result: {1}") + @CsvSource({"'5 6 + 2 *', 22", "'7 2 + 3 *', 27", "'10 5 / 1 +', 3", "'8', 8", "'3 4 +', 7"}) + @DisplayName("Valid postfix expressions") + void testValidExpressions(String expression, int expected) { + assertEquals(expected, PostfixEvaluator.evaluatePostfix(expression)); } @Test - public void testInvalidExpression() { + @DisplayName("Should throw EmptyStackException for incomplete expression") + void testInvalidExpression() { assertThrows(EmptyStackException.class, () -> PostfixEvaluator.evaluatePostfix("5 +")); } @Test - public void testMoreThanOneStackSizeAfterEvaluation() { + @DisplayName("Should throw IllegalArgumentException for extra operands") + void testExtraOperands() { assertThrows(IllegalArgumentException.class, () -> PostfixEvaluator.evaluatePostfix("5 6 + 2 * 3")); } + + @Test + @DisplayName("Should throw ArithmeticException for division by zero") + void testDivisionByZero() { + assertThrows(ArithmeticException.class, () -> PostfixEvaluator.evaluatePostfix("1 0 /")); + } + + @Test + @DisplayName("Should throw IllegalArgumentException for invalid characters") + void testInvalidToken() { + assertThrows(IllegalArgumentException.class, () -> PostfixEvaluator.evaluatePostfix("1 a +")); + } } diff --git a/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java b/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java index e2faa61955b3..ba67163fd49e 100644 --- a/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java +++ b/src/test/java/com/thealgorithms/stacks/PrefixEvaluatorTest.java @@ -4,24 +4,28 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.EmptyStackException; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class PrefixEvaluatorTest { - @Test - public void testValidExpressions() { - assertEquals(10, PrefixEvaluator.evaluatePrefix("+ * 2 3 4")); - assertEquals(5, PrefixEvaluator.evaluatePrefix("- + 7 3 5")); - assertEquals(6, PrefixEvaluator.evaluatePrefix("/ * 3 2 1")); + @ParameterizedTest(name = "Expression: \"{0}\" โ†’ Result: {1}") + @CsvSource({"'+ * 2 3 4', 10", "'- + 7 3 5', 5", "'/ * 3 2 1', 6"}) + void testValidExpressions(String expression, int expected) { + assertEquals(expected, PrefixEvaluator.evaluatePrefix(expression)); } @Test - public void testInvalidExpression() { + @DisplayName("Should throw EmptyStackException for incomplete expression") + void testInvalidExpression() { assertThrows(EmptyStackException.class, () -> PrefixEvaluator.evaluatePrefix("+ 3")); } @Test - public void testMoreThanOneStackSizeAfterEvaluation() { + @DisplayName("Should throw IllegalArgumentException if stack not reduced to one result") + void testMoreThanOneStackSizeAfterEvaluation() { assertThrows(IllegalArgumentException.class, () -> PrefixEvaluator.evaluatePrefix("+ 3 4 5")); } } diff --git a/src/test/java/com/thealgorithms/stacks/SortStackTest.java b/src/test/java/com/thealgorithms/stacks/SortStackTest.java index b9f2f1b6f106..9747af5337e8 100644 --- a/src/test/java/com/thealgorithms/stacks/SortStackTest.java +++ b/src/test/java/com/thealgorithms/stacks/SortStackTest.java @@ -74,4 +74,187 @@ public void testSortWithDuplicateElements() { assertEquals(2, (int) stack.pop()); assertEquals(1, (int) stack.pop()); } + + @Test + public void testSortReverseSortedStack() { + // Test worst case scenario - completely reverse sorted + stack.push(5); + stack.push(4); + stack.push(3); + stack.push(2); + stack.push(1); + SortStack.sortStack(stack); + + assertEquals(5, stack.size()); + assertEquals(5, (int) stack.pop()); + assertEquals(4, (int) stack.pop()); + assertEquals(3, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + } + + @Test + public void testSortWithAllSameElements() { + // Test stack with all identical elements + stack.push(7); + stack.push(7); + stack.push(7); + stack.push(7); + SortStack.sortStack(stack); + + assertEquals(4, stack.size()); + assertEquals(7, (int) stack.pop()); + assertEquals(7, (int) stack.pop()); + assertEquals(7, (int) stack.pop()); + assertEquals(7, (int) stack.pop()); + } + + @Test + public void testSortWithNegativeNumbers() { + // Test with negative numbers + stack.push(-3); + stack.push(1); + stack.push(-5); + stack.push(2); + stack.push(-1); + SortStack.sortStack(stack); + + assertEquals(5, stack.size()); + assertEquals(2, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + assertEquals(-1, (int) stack.pop()); + assertEquals(-3, (int) stack.pop()); + assertEquals(-5, (int) stack.pop()); + } + + @Test + public void testSortWithAllNegativeNumbers() { + // Test with only negative numbers + stack.push(-10); + stack.push(-5); + stack.push(-15); + stack.push(-1); + SortStack.sortStack(stack); + + assertEquals(4, stack.size()); + assertEquals(-1, (int) stack.pop()); + assertEquals(-5, (int) stack.pop()); + assertEquals(-10, (int) stack.pop()); + assertEquals(-15, (int) stack.pop()); + } + + @Test + public void testSortWithZero() { + // Test with zero included + stack.push(3); + stack.push(0); + stack.push(-2); + stack.push(1); + SortStack.sortStack(stack); + + assertEquals(4, stack.size()); + assertEquals(3, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + assertEquals(0, (int) stack.pop()); + assertEquals(-2, (int) stack.pop()); + } + + @Test + public void testSortLargerStack() { + // Test with a larger number of elements + int[] values = {15, 3, 9, 1, 12, 6, 18, 4, 11, 8}; + for (int value : values) { + stack.push(value); + } + + SortStack.sortStack(stack); + + assertEquals(10, stack.size()); + + // Verify sorted order (largest to smallest when popping) + int[] expectedOrder = {18, 15, 12, 11, 9, 8, 6, 4, 3, 1}; + for (int expected : expectedOrder) { + assertEquals(expected, (int) stack.pop()); + } + } + + @Test + public void testSortTwoElements() { + // Test edge case with exactly two elements + stack.push(5); + stack.push(2); + SortStack.sortStack(stack); + + assertEquals(2, stack.size()); + assertEquals(5, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + } + + @Test + public void testSortTwoElementsAlreadySorted() { + // Test two elements already in correct order + stack.push(2); + stack.push(5); + SortStack.sortStack(stack); + + assertEquals(2, stack.size()); + assertEquals(5, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + } + + @Test + public void testSortStackWithMinAndMaxValues() { + // Test with Integer.MAX_VALUE and Integer.MIN_VALUE + stack.push(0); + stack.push(Integer.MAX_VALUE); + stack.push(Integer.MIN_VALUE); + stack.push(100); + SortStack.sortStack(stack); + + assertEquals(4, stack.size()); + assertEquals(Integer.MAX_VALUE, (int) stack.pop()); + assertEquals(100, (int) stack.pop()); + assertEquals(0, (int) stack.pop()); + assertEquals(Integer.MIN_VALUE, (int) stack.pop()); + } + + @Test + public void testSortWithManyDuplicates() { + // Test with multiple sets of duplicates + stack.push(3); + stack.push(1); + stack.push(3); + stack.push(1); + stack.push(2); + stack.push(2); + stack.push(3); + SortStack.sortStack(stack); + + assertEquals(7, stack.size()); + assertEquals(3, (int) stack.pop()); + assertEquals(3, (int) stack.pop()); + assertEquals(3, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + } + + @Test + public void testOriginalStackIsModified() { + // Verify that the original stack is modified, not a copy + Stack originalReference = stack; + stack.push(3); + stack.push(1); + stack.push(2); + + SortStack.sortStack(stack); + + // Verify it's the same object reference + assertTrue(stack == originalReference); + assertEquals(3, stack.size()); + assertEquals(3, (int) stack.pop()); + assertEquals(2, (int) stack.pop()); + assertEquals(1, (int) stack.pop()); + } } diff --git a/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java b/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java index 083239152ec2..7b41e11ef22f 100644 --- a/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java +++ b/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java @@ -1,30 +1,15 @@ package com.thealgorithms.strings; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class AlphabeticalTest { - @Test - public void isAlphabetical() { - // expected to be true - String input1 = "abcdefghijklmno"; - String input2 = "abcdxxxyzzzz"; - String input3 = "fpw"; - - // expected to be false - String input4 = "123a"; - String input5 = "abcABC"; - String input6 = "abcdefghikjlmno"; - - assertTrue(Alphabetical.isAlphabetical(input1)); - assertTrue(Alphabetical.isAlphabetical(input2)); - assertTrue(Alphabetical.isAlphabetical(input3)); - - assertFalse(Alphabetical.isAlphabetical(input4)); - assertFalse(Alphabetical.isAlphabetical(input5)); - assertFalse(Alphabetical.isAlphabetical(input6)); + @ParameterizedTest(name = "\"{0}\" โ†’ Expected: {1}") + @CsvSource({"'abcdefghijklmno', true", "'abcdxxxyzzzz', true", "'123a', false", "'abcABC', false", "'abcdefghikjlmno', false", "'aBC', true", "'abc', true", "'xyzabc', false", "'abcxyz', true", "'', false", "'1', false"}) + void testIsAlphabetical(String input, boolean expected) { + assertEquals(expected, Alphabetical.isAlphabetical(input)); } } diff --git a/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java new file mode 100644 index 000000000000..9e8ae9e9f153 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java @@ -0,0 +1,23 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class AlternativeStringArrangeTest { + + // Method to provide test data + private static Stream provideTestData() { + return Stream.of(new Object[] {"abc", "12345", "a1b2c345"}, new Object[] {"abcd", "12", "a1b2cd"}, new Object[] {"", "123", "123"}, new Object[] {"abc", "", "abc"}, new Object[] {"a", "1", "a1"}, new Object[] {"ab", "12", "a1b2"}, new Object[] {"abcdef", "123", "a1b2c3def"}, + new Object[] {"ab", "123456", "a1b23456"}); + } + + // Parameterized test using the provided test data + @ParameterizedTest(name = "{0} and {1} should return {2}") + @MethodSource("provideTestData") + void arrangeTest(String input1, String input2, String expected) { + assertEquals(expected, AlternativeStringArrange.arrange(input1, input2)); + } +} diff --git a/src/test/java/com/thealgorithms/strings/AnagramsTest.java b/src/test/java/com/thealgorithms/strings/AnagramsTest.java index 88f6e0bb72ec..fa8ea72f2b8c 100644 --- a/src/test/java/com/thealgorithms/strings/AnagramsTest.java +++ b/src/test/java/com/thealgorithms/strings/AnagramsTest.java @@ -13,36 +13,37 @@ record AnagramTestCase(String input1, String input2, boolean expected) { private static Stream anagramTestData() { return Stream.of(new AnagramTestCase("late", "tale", true), new AnagramTestCase("late", "teal", true), new AnagramTestCase("listen", "silent", true), new AnagramTestCase("hello", "olelh", true), new AnagramTestCase("hello", "world", false), new AnagramTestCase("deal", "lead", true), - new AnagramTestCase("binary", "brainy", true), new AnagramTestCase("adobe", "abode", true), new AnagramTestCase("cat", "act", true), new AnagramTestCase("cat", "cut", false)); + new AnagramTestCase("binary", "brainy", true), new AnagramTestCase("adobe", "abode", true), new AnagramTestCase("cat", "act", true), new AnagramTestCase("cat", "cut", false), new AnagramTestCase("Listen", "Silent", true), new AnagramTestCase("Dormitory", "DirtyRoom", true), + new AnagramTestCase("Schoolmaster", "TheClassroom", true), new AnagramTestCase("Astronomer", "MoonStarer", true), new AnagramTestCase("Conversation", "VoicesRantOn", true)); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach1(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach1(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsBySorting(testCase.input1(), testCase.input2())); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach2(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach2(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsByCountingChars(testCase.input1(), testCase.input2())); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach3(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach3(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsByCountingCharsSingleArray(testCase.input1(), testCase.input2())); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach4(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach4(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsUsingHashMap(testCase.input1(), testCase.input2())); } @ParameterizedTest @MethodSource("anagramTestData") void testApproach5(AnagramTestCase testCase) { - assertEquals(testCase.expected(), Anagrams.approach5(testCase.input1(), testCase.input2())); + assertEquals(testCase.expected(), Anagrams.areAnagramsBySingleFreqArray(testCase.input1(), testCase.input2())); } } diff --git a/src/test/java/com/thealgorithms/strings/CharacterSameTest.java b/src/test/java/com/thealgorithms/strings/CharactersSameTest.java similarity index 100% rename from src/test/java/com/thealgorithms/strings/CharacterSameTest.java rename to src/test/java/com/thealgorithms/strings/CharactersSameTest.java diff --git a/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java b/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java deleted file mode 100644 index 82a75a130ef0..000000000000 --- a/src/test/java/com/thealgorithms/strings/CheckAnagramsTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.thealgorithms.strings; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class CheckAnagramsTest { - private static final String MESSAGE = "Strings must contain only lowercase English letters!"; - - // CHECK METHOD isAnagrams() - @Test - public void testCheckAnagrams() { - String testString1 = "STUDY"; - String testString2 = "DUSTY"; - Assertions.assertTrue(CheckAnagrams.isAnagrams(testString1, testString2)); - } - - @Test - public void testCheckFalseAnagrams() { - String testString1 = "STUDY"; - String testString2 = "random"; - Assertions.assertFalse(CheckAnagrams.isAnagrams(testString1, testString2)); - } - - @Test - public void testCheckSameWordAnagrams() { - String testString1 = "STUDY"; - Assertions.assertTrue(CheckAnagrams.isAnagrams(testString1, testString1)); - } - - @Test - public void testCheckDifferentCasesAnagram() { - String testString1 = "STUDY"; - String testString2 = "dusty"; - Assertions.assertTrue(CheckAnagrams.isAnagrams(testString1, testString2)); - } - - // CHECK METHOD isAnagramsUnicode() - // Below tests work with strings which consist of Unicode symbols & the algorithm is case-sensitive. - @Test - public void testStringAreValidAnagramsCaseSensitive() { - Assertions.assertTrue(CheckAnagrams.isAnagramsUnicode("Silent", "liSten")); - Assertions.assertTrue(CheckAnagrams.isAnagramsUnicode("This is a string", "is This a string")); - } - - @Test - public void testStringAreNotAnagramsCaseSensitive() { - Assertions.assertFalse(CheckAnagrams.isAnagramsUnicode("Silent", "Listen")); - Assertions.assertFalse(CheckAnagrams.isAnagramsUnicode("This is a string", "Is this a string")); - } - - // CHECK METHOD isAnagramsOptimised() - // Below tests work with strings which consist of only lowercase English letters - @Test - public void testOptimisedAlgorithmStringsAreValidAnagrams() { - Assertions.assertTrue(CheckAnagrams.isAnagramsOptimised("silent", "listen")); - Assertions.assertTrue(CheckAnagrams.isAnagramsOptimised("mam", "amm")); - } - - @Test - public void testOptimisedAlgorithmShouldThrowExceptionWhenStringsContainUppercaseLetters() { - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> CheckAnagrams.isAnagramsOptimised("Silent", "Listen")); - Assertions.assertEquals(exception.getMessage(), MESSAGE); - - exception = assertThrows(IllegalArgumentException.class, () -> Assertions.assertFalse(CheckAnagrams.isAnagramsOptimised("This is a string", "Is this a string"))); - Assertions.assertEquals(exception.getMessage(), MESSAGE); - } -} diff --git a/src/test/java/com/thealgorithms/strings/IsogramTest.java b/src/test/java/com/thealgorithms/strings/IsogramTest.java new file mode 100644 index 000000000000..9dd844dc7cf9 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/IsogramTest.java @@ -0,0 +1,117 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class IsogramTest { + + record IsogramTestCase(String input, boolean expected) { + } + + private static Stream isAlphabeticIsogram() { + return Stream.of( + // Valid isograms (only checks letters) + new IsogramTestCase("uncopyrightable", true), new IsogramTestCase("dermatoglyphics", true), new IsogramTestCase("background", true), new IsogramTestCase("python", true), new IsogramTestCase("keyboard", true), new IsogramTestCase("clipboard", true), new IsogramTestCase("flowchart", true), + new IsogramTestCase("bankruptcy", true), new IsogramTestCase("computer", true), new IsogramTestCase("algorithms", true), + + // Not isograms - letters repeat + new IsogramTestCase("hello", false), new IsogramTestCase("programming", false), new IsogramTestCase("java", false), new IsogramTestCase("coffee", false), new IsogramTestCase("book", false), new IsogramTestCase("letter", false), new IsogramTestCase("mississippi", false), + new IsogramTestCase("google", false), + + // Edge cases + new IsogramTestCase("", true), new IsogramTestCase("a", true), new IsogramTestCase("ab", true), new IsogramTestCase("abc", true), new IsogramTestCase("aa", false), new IsogramTestCase("abcdefghijklmnopqrstuvwxyz", true), + + // Case insensitive + new IsogramTestCase("Python", true), new IsogramTestCase("BACKGROUND", true), new IsogramTestCase("Hello", false), new IsogramTestCase("PROGRAMMING", false)); + } + + private static Stream isFullIsogram() { + return Stream.of( + // Valid isograms (checks all characters) + new IsogramTestCase("uncopyrightable", true), new IsogramTestCase("dermatoglyphics", true), new IsogramTestCase("background", true), new IsogramTestCase("python", true), new IsogramTestCase("keyboard", true), new IsogramTestCase("clipboard", true), new IsogramTestCase("flowchart", true), + new IsogramTestCase("bankruptcy", true), new IsogramTestCase("computer", true), new IsogramTestCase("algorithms", true), + + // Not isograms - characters repeat + new IsogramTestCase("hello", false), new IsogramTestCase("programming", false), new IsogramTestCase("java", false), new IsogramTestCase("coffee", false), new IsogramTestCase("book", false), new IsogramTestCase("letter", false), new IsogramTestCase("mississippi", false), + new IsogramTestCase("google", false), + + // Edge cases + new IsogramTestCase("", true), new IsogramTestCase("a", true), new IsogramTestCase("ab", true), new IsogramTestCase("abc", true), new IsogramTestCase("aa", false), new IsogramTestCase("abcdefghijklmnopqrstuvwxyz", true), + + // Case insensitive + new IsogramTestCase("Python", true), new IsogramTestCase("BACKGROUND", true), new IsogramTestCase("Hello", false), new IsogramTestCase("PROGRAMMING", false), + + // Strings with symbols and numbers + new IsogramTestCase("abc@def", true), // all characters unique + new IsogramTestCase("test-case", false), // 't', 's', 'e' repeat + new IsogramTestCase("python123", true), // all characters unique + new IsogramTestCase("hello@123", false), // 'l' repeats + new IsogramTestCase("abc123!@#", true), // all characters unique + new IsogramTestCase("test123test", false), // 't', 'e', 's' repeat + new IsogramTestCase("1234567890", true), // all digits unique + new IsogramTestCase("12321", false), // '1' and '2' repeat + new IsogramTestCase("!@#$%^&*()", true) // all special characters unique + ); + } + + @ParameterizedTest + @MethodSource("isAlphabeticIsogram") + void testIsogramByArray(IsogramTestCase testCase) { + assertEquals(testCase.expected(), Isogram.isAlphabeticIsogram(testCase.input())); + } + + @ParameterizedTest + @MethodSource("isFullIsogram") + void testIsogramByLength(IsogramTestCase testCase) { + assertEquals(testCase.expected(), Isogram.isFullIsogram(testCase.input())); + } + + @Test + void testNullInputByArray() { + assertTrue(Isogram.isAlphabeticIsogram(null)); + } + + @Test + void testNullInputByLength() { + assertTrue(Isogram.isFullIsogram(null)); + } + + @Test + void testEmptyStringByArray() { + assertTrue(Isogram.isAlphabeticIsogram("")); + } + + @Test + void testEmptyStringByLength() { + assertTrue(Isogram.isFullIsogram("")); + } + + @Test + void testAlphabeticIsogramThrowsException() { + // Test that IllegalArgumentException is thrown for non-alphabetic characters + assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("1")); + assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("@")); + assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("python!")); + assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("123algorithm")); + assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("hello123")); + assertThrows(IllegalArgumentException.class, () -> Isogram.isAlphabeticIsogram("!@@#$%^&*()")); + } + + @Test + void testFullIsogramWithMixedCharacters() { + // Test that full isogram method handles all character types without exceptions + assertTrue(Isogram.isFullIsogram("abc123")); + assertFalse(Isogram.isFullIsogram("test@email")); // 'e' repeats + assertFalse(Isogram.isFullIsogram("hello123")); // 'l' repeats + assertTrue(Isogram.isFullIsogram("1234567890")); + assertFalse(Isogram.isFullIsogram("12321")); // '1' and '2' repeat + assertTrue(Isogram.isFullIsogram("!@#$%^&*()")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/IsomorphicTest.java b/src/test/java/com/thealgorithms/strings/IsomorphicTest.java index 0dac47551868..5c3ed89b65d3 100644 --- a/src/test/java/com/thealgorithms/strings/IsomorphicTest.java +++ b/src/test/java/com/thealgorithms/strings/IsomorphicTest.java @@ -1,31 +1,23 @@ package com.thealgorithms.strings; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public final class IsomorphicTest { - private IsomorphicTest() { - } - - @Test - public static void main(String[] args) { - String str1 = "abbbbaac"; - String str2 = "kffffkkd"; - - String str3 = "xyxyxy"; - String str4 = "bnbnbn"; - String str5 = "ghjknnmm"; - String str6 = "wertpopo"; - - String str7 = "aaammmnnn"; - String str8 = "ggghhhbbj"; + @ParameterizedTest + @MethodSource("inputs") + public void testCheckStrings(String str1, String str2, Boolean expected) { + assertEquals(expected, Isomorphic.areIsomorphic(str1, str2)); + assertEquals(expected, Isomorphic.areIsomorphic(str2, str1)); + } - assertTrue(Isomorphic.checkStrings(str1, str2)); - assertTrue(Isomorphic.checkStrings(str3, str4)); - assertFalse(Isomorphic.checkStrings(str5, str6)); - assertFalse(Isomorphic.checkStrings(str7, str8)); + private static Stream inputs() { + return Stream.of(Arguments.of("", "", Boolean.TRUE), Arguments.of("", "a", Boolean.FALSE), Arguments.of("aaa", "aa", Boolean.FALSE), Arguments.of("abbbbaac", "kffffkkd", Boolean.TRUE), Arguments.of("xyxyxy", "bnbnbn", Boolean.TRUE), Arguments.of("ghjknnmm", "wertpopo", Boolean.FALSE), + Arguments.of("aaammmnnn", "ggghhhbbj", Boolean.FALSE)); } } diff --git a/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java index 580a2726d285..84e54f75e8cb 100644 --- a/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java +++ b/src/test/java/com/thealgorithms/strings/LongestCommonPrefixTest.java @@ -2,72 +2,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class LongestCommonPrefixTest { - private final LongestCommonPrefix longestCommonPrefix = new LongestCommonPrefix(); - - @Test - public void testCommonPrefix() { - String[] input = {"flower", "flow", "flight"}; - String expected = "fl"; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); - } - - @Test - public void testNoCommonPrefix() { - String[] input = {"dog", "racecar", "car"}; - String expected = ""; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); - } - - @Test - public void testEmptyArray() { - String[] input = {}; - String expected = ""; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); - } - - @Test - public void testNullArray() { - String[] input = null; - String expected = ""; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); - } - - @Test - public void testSingleString() { - String[] input = {"single"}; - String expected = "single"; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); - } - - @Test - public void testCommonPrefixWithDifferentLengths() { - String[] input = {"ab", "a"}; - String expected = "a"; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); - } - - @Test - public void testAllSameStrings() { - String[] input = {"test", "test", "test"}; - String expected = "test"; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); - } - - @Test - public void testPrefixAtEnd() { - String[] input = {"abcde", "abcfgh", "abcmnop"}; - String expected = "abc"; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + @ParameterizedTest(name = "{index} => input={0}, expected=\"{1}\"") + @MethodSource("provideTestCases") + @DisplayName("Test Longest Common Prefix") + void testLongestCommonPrefix(String[] input, String expected) { + assertEquals(expected, LongestCommonPrefix.longestCommonPrefix(input)); } - @Test - public void testMixedCase() { - String[] input = {"Flower", "flow", "flight"}; - String expected = ""; - assertEquals(expected, longestCommonPrefix.longestCommonPrefix(input)); + private static Stream provideTestCases() { + return Stream.of(Arguments.of(new String[] {"flower", "flow", "flight"}, "fl"), Arguments.of(new String[] {"dog", "racecar", "car"}, ""), Arguments.of(new String[] {}, ""), Arguments.of(null, ""), Arguments.of(new String[] {"single"}, "single"), Arguments.of(new String[] {"ab", "a"}, "a"), + Arguments.of(new String[] {"test", "test", "test"}, "test"), Arguments.of(new String[] {"abcde", "abcfgh", "abcmnop"}, "abc"), Arguments.of(new String[] {"Flower", "flow", "flight"}, "")); } } diff --git a/src/test/java/com/thealgorithms/others/RemoveDuplicateFromStringTest.java b/src/test/java/com/thealgorithms/strings/RemoveDuplicateFromStringTest.java similarity index 96% rename from src/test/java/com/thealgorithms/others/RemoveDuplicateFromStringTest.java rename to src/test/java/com/thealgorithms/strings/RemoveDuplicateFromStringTest.java index 3401a51c56c9..de912bbfdb18 100644 --- a/src/test/java/com/thealgorithms/others/RemoveDuplicateFromStringTest.java +++ b/src/test/java/com/thealgorithms/strings/RemoveDuplicateFromStringTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.strings; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/src/test/java/com/thealgorithms/strings/ReverseStringRecursiveTest.java b/src/test/java/com/thealgorithms/strings/ReverseStringRecursiveTest.java deleted file mode 100644 index 19daa61f48ca..000000000000 --- a/src/test/java/com/thealgorithms/strings/ReverseStringRecursiveTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.thealgorithms.strings; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -public class ReverseStringRecursiveTest { - @ParameterizedTest - @CsvSource({"'Hello World', 'dlroW olleH'", "'helloworld', 'dlrowolleh'", "'123456789', '987654321'", "'', ''", "'A', 'A'", "'!123 ABC xyz!', '!zyx CBA 321!'", "'Abc 123 Xyz', 'zyX 321 cbA'", "'12.34,56;78:90', '09:87;65,43.21'", "'abcdEFGHiJKL', 'LKJiHGFEdcba'", - "'MixOf123AndText!', '!txeTdnA321fOxiM'"}) - public void - testReverseString(String input, String expectedOutput) { - assertEquals(expectedOutput, ReverseStringRecursive.reverse(input)); - } -} diff --git a/src/test/java/com/thealgorithms/strings/ReverseStringTest.java b/src/test/java/com/thealgorithms/strings/ReverseStringTest.java index 501f702976ec..c50f92c1a008 100644 --- a/src/test/java/com/thealgorithms/strings/ReverseStringTest.java +++ b/src/test/java/com/thealgorithms/strings/ReverseStringTest.java @@ -1,10 +1,13 @@ package com.thealgorithms.strings; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; public class ReverseStringTest { @@ -25,4 +28,29 @@ public void testReverseString(String input, String expectedOutput) { public void testReverseString2(String input, String expectedOutput) { assertEquals(expectedOutput, ReverseString.reverse2(input)); } + + @ParameterizedTest + @MethodSource("testCases") + public void testReverseString3(String input, String expectedOutput) { + assertEquals(expectedOutput, ReverseString.reverse3(input)); + } + + @ParameterizedTest + @MethodSource("testCases") + public void testReverseStringUsingStack(String input, String expectedOutput) { + assertEquals(expectedOutput, ReverseString.reverseStringUsingStack(input)); + } + + @Test + public void testReverseStringUsingStackWithNullInput() { + assertThrows(IllegalArgumentException.class, () -> ReverseString.reverseStringUsingStack(null)); + } + + @ParameterizedTest + @CsvSource({"'Hello World', 'dlroW olleH'", "'helloworld', 'dlrowolleh'", "'123456789', '987654321'", "'', ''", "'A', 'A'", "'!123 ABC xyz!', '!zyx CBA 321!'", "'Abc 123 Xyz', 'zyX 321 cbA'", "'12.34,56;78:90', '09:87;65,43.21'", "'abcdEFGHiJKL', 'LKJiHGFEdcba'", + "'MixOf123AndText!', '!txeTdnA321fOxiM'"}) + public void + testReverseStringUsingRecursion(String input, String expectedOutput) { + assertEquals(expectedOutput, ReverseString.reverseStringUsingRecursion(input)); + } } diff --git a/src/test/java/com/thealgorithms/strings/SuffixArrayTest.java b/src/test/java/com/thealgorithms/strings/SuffixArrayTest.java new file mode 100644 index 000000000000..9d4b3d582b75 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/SuffixArrayTest.java @@ -0,0 +1,44 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +class SuffixArrayTest { + + @Test + void testEmptyString() { + int[] result = SuffixArray.buildSuffixArray(""); + assertArrayEquals(new int[] {}, result, "Empty string should return empty suffix array"); + } + + @Test + void testSingleCharacter() { + int[] result = SuffixArray.buildSuffixArray("a"); + assertArrayEquals(new int[] {0}, result, "Single char string should return [0]"); + } + + @Test + void testDistinctCharacters() { + int[] result = SuffixArray.buildSuffixArray("abc"); + assertArrayEquals(new int[] {0, 1, 2}, result, "Suffixes already in order for distinct chars"); + } + + @Test + void testBananaExample() { + int[] result = SuffixArray.buildSuffixArray("banana"); + assertArrayEquals(new int[] {5, 3, 1, 0, 4, 2}, result, "Suffix array of 'banana' should be [5,3,1,0,4,2]"); + } + + @Test + void testStringWithDuplicates() { + int[] result = SuffixArray.buildSuffixArray("aaaa"); + assertArrayEquals(new int[] {3, 2, 1, 0}, result, "Suffix array should be descending indices for 'aaaa'"); + } + + @Test + void testRandomString() { + int[] result = SuffixArray.buildSuffixArray("mississippi"); + assertArrayEquals(new int[] {10, 7, 4, 1, 0, 9, 8, 6, 3, 5, 2}, result, "Suffix array for 'mississippi' should match expected"); + } +} diff --git a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java index 2b6884c91c8f..411b11e743b8 100644 --- a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java +++ b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java @@ -1,27 +1,33 @@ package com.thealgorithms.strings; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; public class ValidParenthesesTest { - @Test - void testOne() { - assertTrue(ValidParentheses.isValid("()")); - assertTrue(ValidParentheses.isValidParentheses("()")); + @ParameterizedTest(name = "Input: \"{0}\" โ†’ Expected: {1}") + @CsvSource({"'()', true", "'()[]{}', true", "'(]', false", "'{[]}', true", "'([{}])', true", "'([)]', false", "'', true", "'(', false", "')', false", "'{{{{}}}}', true", "'[({})]', true", "'[(])', false", "'[', false", "']', false", "'()()()()', true", "'(()', false", "'())', false", + "'{[()()]()}', true"}) + void + testIsValid(String input, boolean expected) { + assertEquals(expected, ValidParentheses.isValid(input)); } @Test - void testTwo() { - assertTrue(ValidParentheses.isValid("()[]{}")); - assertTrue(ValidParentheses.isValidParentheses("()[]{}")); + void testNullInputThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(null)); + assertEquals("Input string cannot be null", ex.getMessage()); } - @Test - void testThree() { - assertFalse(ValidParentheses.isValid("(]")); - assertFalse(ValidParentheses.isValidParentheses("(]")); + @ParameterizedTest(name = "Input: \"{0}\" โ†’ throws IllegalArgumentException") + @CsvSource({"'a'", "'()a'", "'[123]'", "'{hello}'", "'( )'", "'\t'", "'\n'", "'@#$%'"}) + void testInvalidCharactersThrow(String input) { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(input)); + assertTrue(ex.getMessage().startsWith("Unexpected character")); } } diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java new file mode 100644 index 000000000000..29189290e1d4 --- /dev/null +++ b/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java @@ -0,0 +1,69 @@ +package com.thealgorithms.tree; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class HeavyLightDecompositionTest { + + private HeavyLightDecomposition hld; + private final int[] values = {0, 10, 20, 30, 40, 50}; + + /** + * Initializes the test environment with a predefined tree structure and values. + */ + @BeforeEach + void setUp() { + hld = new HeavyLightDecomposition(5); + hld.addEdge(1, 2); + hld.addEdge(1, 3); + hld.addEdge(2, 4); + hld.addEdge(2, 5); + hld.initialize(1, values); + } + + /** + * Verifies that the tree initializes successfully without errors. + */ + @Test + void testBasicTreeInitialization() { + assertTrue(true, "Basic tree structure initialized successfully"); + } + + /** + * Tests the maximum value query in the path between nodes. + */ + @Test + void testQueryMaxInPath() { + assertEquals(50, hld.queryMaxInPath(4, 5), "Max value in path (4,5) should be 50"); + assertEquals(30, hld.queryMaxInPath(3, 2), "Max value in path (3,2) should be 30"); + } + + /** + * Tests updating a node's value and ensuring it is reflected in queries. + */ + @Test + void testUpdateNodeValue() { + hld.updateSegmentTree(1, 0, hld.getPositionIndex() - 1, hld.getPosition(4), 100); + assertEquals(100, hld.queryMaxInPath(4, 5), "Updated value should be reflected in query"); + } + + /** + * Tests the maximum value query in a skewed tree structure. + */ + @Test + void testSkewedTreeMaxQuery() { + assertEquals(40, hld.queryMaxInPath(1, 4), "Max value in skewed tree (1,4) should be 40"); + } + + /** + * Ensures query handles cases where u is a deeper node correctly. + */ + @Test + void testDepthSwapInPathQuery() { + assertEquals(50, hld.queryMaxInPath(5, 2), "Query should handle depth swap correctly"); + assertEquals(40, hld.queryMaxInPath(4, 1), "Query should handle swapped nodes correctly and return max value"); + } +}