diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2e5622f7b51d..1b91763b2d53 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,14 +5,20 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 - package-ecosystem: "github-actions" directory: "/.github/workflows/" schedule: interval: "daily" + cooldown: + default-days: 7 - package-ecosystem: "maven" directory: "/" schedule: interval: "daily" + cooldown: + default-days: 7 ... diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d9cc4c3c35c5..30b46bbf45b5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,4 +13,5 @@ In order to reduce the number of notifications sent to the maintainers, please: - [ ] All filenames are in PascalCase. - [ ] All functions and variable names follow Java naming conventions. - [ ] All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations. -- [ ] All new code is formatted with `clang-format -i --style=file path/to/your/file.java` \ No newline at end of file +- [ ] All new algorithms include a corresponding test class that validates their functionality. +- [ ] All new code is formatted with `clang-format -i --style=file path/to/your/file.java` diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39bde758d68e..a1395d87ec8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,9 +8,9 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.7.0 with: java-version: 21 distribution: 'temurin' @@ -20,7 +20,7 @@ jobs: if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 with: fail_ci_if_error: true - name: Upload coverage to codecov (with token) @@ -28,7 +28,7 @@ jobs: github.repository == 'TheAlgorithms/Java' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/.github/workflows/clang-format-lint.yml b/.github/workflows/clang-format-lint.yml index ca014e115282..622679f73842 100644 --- a/.github/workflows/clang-format-lint.yml +++ b/.github/workflows/clang-format-lint.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - uses: DoozyX/clang-format-lint-action@v0.20 with: source: './src' diff --git a/.github/workflows/close-failed-prs.yml b/.github/workflows/close-failed-prs.yml index 6deea88f0daf..4013e87b6569 100644 --- a/.github/workflows/close-failed-prs.yml +++ b/.github/workflows/close-failed-prs.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Close stale PRs - uses: actions/github-script@v8 + uses: actions/github-script@v9 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3a4cfd829b6b..4861e5df2b29 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,16 +21,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.7.0 with: java-version: 21 distribution: 'temurin' - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.37.6 with: languages: 'java-kotlin' @@ -38,7 +38,7 @@ jobs: run: mvn --batch-mode --update-snapshots verify - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.37.6 with: category: "/language:java-kotlin" @@ -52,15 +52,15 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v7 - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.37.6 with: languages: 'actions' - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@v4.37.6 with: category: "/language:actions" ... diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index 3df7c4b1fc9e..3bfc509ebf47 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -15,42 +15,16 @@ jobs: run_infer: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v5.7.0 with: java-version: 21 distribution: 'temurin' - - name: Set up OCaml - uses: ocaml/setup-ocaml@v3 - with: - ocaml-compiler: 5 - - - name: Get current year/weak - run: echo "year_week=$(date +'%Y_%U')" >> $GITHUB_ENV - - - name: Cache infer build - id: cache-infer - uses: actions/cache@v4 - with: - path: infer - key: ${{ runner.os }}-infer-${{ env.year_week }} - - - name: Build infer - if: steps.cache-infer.outputs.cache-hit != 'true' - run: | - cd .. - git clone https://github.com/facebook/infer.git - cd infer - git checkout 01aaa268f9d38723ba69c139e10f9e2a04b40b1c - ./build-infer.sh java - cp -r infer ../Java - - - name: Add infer to PATH - run: | - echo "infer/bin" >> $GITHUB_PATH + - name: Set up inferAdd commentMore actions + uses: srz-zumix/setup-infer@v1 - name: Display infer version run: | @@ -60,5 +34,5 @@ jobs: - name: Run infer run: | mvn clean - infer --fail-on-issue --print-logs --no-progress-bar -- mvn test + infer --java-version 21 --fail-on-issue --print-logs --no-progress-bar -- mvn test ... diff --git a/.github/workflows/project_structure.yml b/.github/workflows/project_structure.yml index f9fb82a2781c..a70c3240ce08 100644 --- a/.github/workflows/project_structure.yml +++ b/.github/workflows/project_structure.yml @@ -15,8 +15,8 @@ jobs: check_structure: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7.0.0 with: python-version: '3.13' diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index bb613daf8f1d..b961d15c240e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -11,7 +11,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@v11.0.0 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 index 6692a884a867..3977bfda86bf 100644 --- a/.github/workflows/update-directorymd.yml +++ b/.github/workflows/update-directorymd.yml @@ -14,7 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v5 + uses: actions/checkout@v7 + with: + persist-credentials: false - name: Run Directory Tree Generator uses: DenizAltunkapan/directory-tree-generator@v2 @@ -31,7 +33,7 @@ jobs: git diff --cached --quiet || git commit -m "Update DIRECTORY.md" - name: Create Pull Request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: token: ${{ secrets.REPO_SCOPED_TOKEN }} branch: update-directory diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index 4195f928d1bc..a8951da7de26 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -1,4 +1,4 @@ -FROM gitpod/workspace-java-21:2025-10-06-13-14-25 +FROM gitpod/workspace-java-21:2025-11-14-10-05-32 ENV LLVM_SCRIPT="tmp_llvm.sh" diff --git a/.inferconfig b/.inferconfig index 6af4f9e2e818..cf26212feac5 100644 --- a/.inferconfig +++ b/.inferconfig @@ -1,6 +1,8 @@ { "report-block-list-path-regex": [ "src/main/java/com/thealgorithms/ciphers/a5/CompositeLFSR.java", + "src/main/java/com/thealgorithms/compression/ArithmeticCoding.java", + "src/main/java/com/thealgorithms/datastructures/caches/FIFOCache.java", "src/main/java/com/thealgorithms/datastructures/crdt/GCounter.java", "src/main/java/com/thealgorithms/datastructures/crdt/PNCounter.java", "src/main/java/com/thealgorithms/datastructures/graphs/KahnsAlgorithm.java", @@ -8,15 +10,19 @@ "src/main/java/com/thealgorithms/datastructures/lists/DoublyLinkedList.java", "src/main/java/com/thealgorithms/datastructures/trees/CreateBinaryTreeFromInorderPreorder.java", "src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java", + "src/main/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistance.java", "src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java", "src/main/java/com/thealgorithms/maths/SimpsonIntegration.java", "src/main/java/com/thealgorithms/others/Dijkstra.java", "src/main/java/com/thealgorithms/sorts/TopologicalSort.java", "src/main/java/com/thealgorithms/strings/AhoCorasick.java", + "src/test/java/com/thealgorithms/compression/ShannonFanoTest.java", "src/test/java/com/thealgorithms/datastructures/caches/LRUCacheTest.java", "src/test/java/com/thealgorithms/datastructures/lists/SkipListTest.java", "src/test/java/com/thealgorithms/datastructures/trees/KDTreeTest.java", "src/test/java/com/thealgorithms/datastructures/trees/LazySegmentTreeTest.java", + "src/test/java/com/thealgorithms/dynamicprogramming/DamerauLevenshteinDistanceTest.java", + "src/test/java/com/thealgorithms/others/HuffmanTest.java", "src/test/java/com/thealgorithms/searches/QuickSelectTest.java", "src/test/java/com/thealgorithms/stacks/PostfixToInfixTest.java", "src/test/java/com/thealgorithms/strings/HorspoolSearchTest.java" diff --git a/DIRECTORY.md b/DIRECTORY.md index eca2453fad0f..37d9b3c295e2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -13,6 +13,7 @@ - 📄 [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) + - 📄 [CombinationSum](src/main/java/com/thealgorithms/backtracking/CombinationSum.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) @@ -23,11 +24,14 @@ - 📄 [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) + - 📄 [SudokuSolver](src/main/java/com/thealgorithms/backtracking/SudokuSolver.java) + - 📄 [UniquePermutation](src/main/java/com/thealgorithms/backtracking/UniquePermutation.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) + - 📄 [BitRotate](src/main/java/com/thealgorithms/bitmanipulation/BitRotate.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) @@ -76,6 +80,7 @@ - 📄 [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) + - 📄 [OneTimePadCipher](src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.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) @@ -93,6 +98,12 @@ - 📄 [LFSR](src/main/java/com/thealgorithms/ciphers/a5/LFSR.java) - 📄 [Utils](src/main/java/com/thealgorithms/ciphers/a5/Utils.java) - 📁 **compression** + - 📄 [ArithmeticCoding](src/main/java/com/thealgorithms/compression/ArithmeticCoding.java) + - 📄 [BurrowsWheelerTransform](src/main/java/com/thealgorithms/compression/BurrowsWheelerTransform.java) + - 📄 [LZ77](src/main/java/com/thealgorithms/compression/LZ77.java) + - 📄 [LZ78](src/main/java/com/thealgorithms/compression/LZ78.java) + - 📄 [LZW](src/main/java/com/thealgorithms/compression/LZW.java) + - 📄 [MoveToFront](src/main/java/com/thealgorithms/compression/MoveToFront.java) - 📄 [RunLengthEncoding](src/main/java/com/thealgorithms/compression/RunLengthEncoding.java) - 📄 [ShannonFano](src/main/java/com/thealgorithms/compression/ShannonFano.java) - 📁 **conversions** @@ -125,6 +136,7 @@ - 📄 [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) + - 📄 [TemperatureConverter](src/main/java/com/thealgorithms/conversions/TemperatureConverter.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) @@ -173,7 +185,6 @@ - 📄 [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) - - 📄 [HierholzerAlgorithm](src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.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) @@ -190,6 +201,7 @@ - 📄 [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) + - 📄 [ImmutableHashMap](src/main/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMap.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) @@ -201,6 +213,7 @@ - 📄 [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) + - 📄 [IndexedPriorityQueue](src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.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) @@ -255,8 +268,10 @@ - 📄 [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) + - 📄 [BinaryTreeToString](src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java) - 📄 [BoundaryTraversal](src/main/java/com/thealgorithms/datastructures/trees/BoundaryTraversal.java) - 📄 [CeilInBinarySearchTree](src/main/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTree.java) + - 📄 [CentroidDecomposition](src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.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) @@ -276,6 +291,7 @@ - 📄 [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) + - 📄 [ThreadedBinaryTree](src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.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) @@ -358,8 +374,10 @@ - 📄 [WildcardMatching](src/main/java/com/thealgorithms/dynamicprogramming/WildcardMatching.java) - 📄 [WineProblem](src/main/java/com/thealgorithms/dynamicprogramming/WineProblem.java) - 📁 **geometry** + - 📄 [BentleyOttmann](src/main/java/com/thealgorithms/geometry/BentleyOttmann.java) - 📄 [BresenhamLine](src/main/java/com/thealgorithms/geometry/BresenhamLine.java) - 📄 [ConvexHull](src/main/java/com/thealgorithms/geometry/ConvexHull.java) + - 📄 [DDALine](src/main/java/com/thealgorithms/geometry/DDALine.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) @@ -372,10 +390,14 @@ - 📄 [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) + - 📄 [GomoryHuTree](src/main/java/com/thealgorithms/graph/GomoryHuTree.java) + - 📄 [HierholzerAlgorithm](src/main/java/com/thealgorithms/graph/HierholzerAlgorithm.java) + - 📄 [HierholzerEulerianPath](src/main/java/com/thealgorithms/graph/HierholzerEulerianPath.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) + - 📄 [StoerWagner](src/main/java/com/thealgorithms/graph/StoerWagner.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) @@ -409,6 +431,7 @@ - 📄 [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) + - 📄 [AbundantNumber](src/main/java/com/thealgorithms/maths/AbundantNumber.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) @@ -420,6 +443,7 @@ - 📄 [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) + - 📄 [ChebyshevIteration](src/main/java/com/thealgorithms/maths/ChebyshevIteration.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) @@ -434,10 +458,11 @@ - 📄 [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) + - 📄 [EvilNumber](src/main/java/com/thealgorithms/maths/EvilNumber.java) + - 📄 [ExtendedEuclideanAlgorithm](src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.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) @@ -471,6 +496,7 @@ - 📄 [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) + - 📄 [LuckyNumber](src/main/java/com/thealgorithms/maths/LuckyNumber.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) @@ -478,6 +504,7 @@ - 📄 [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) + - 📄 [Neville](src/main/java/com/thealgorithms/maths/Neville.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) @@ -493,6 +520,7 @@ - 📄 [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) + - 📄 [PowerOfFour](src/main/java/com/thealgorithms/maths/PowerOfFour.java) - 📄 [PowerOfTwoOrNot](src/main/java/com/thealgorithms/maths/PowerOfTwoOrNot.java) - 📄 [PowerUsingRecursion](src/main/java/com/thealgorithms/maths/PowerUsingRecursion.java) - 📁 **Prime** @@ -511,6 +539,7 @@ - 📄 [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) + - 📄 [SmithNumber](src/main/java/com/thealgorithms/maths/SmithNumber.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) @@ -531,6 +560,7 @@ - 📄 [ZellersCongruence](src/main/java/com/thealgorithms/maths/ZellersCongruence.java) - 📁 **matrix** - 📄 [InverseOfMatrix](src/main/java/com/thealgorithms/matrix/InverseOfMatrix.java) + - 📄 [LUDecomposition](src/main/java/com/thealgorithms/matrix/LUDecomposition.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) @@ -539,6 +569,7 @@ - 📄 [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) + - 📄 [StochasticMatrix](src/main/java/com/thealgorithms/matrix/StochasticMatrix.java) - 📁 **matrixexponentiation** - 📄 [Fibonacci](src/main/java/com/thealgorithms/matrix/matrixexponentiation/Fibonacci.java) - 📁 **utils** @@ -591,17 +622,22 @@ - 📄 [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** + - 📄 [CoulombsLaw](src/main/java/com/thealgorithms/physics/CoulombsLaw.java) + - 📄 [DampedOscillator](src/main/java/com/thealgorithms/physics/DampedOscillator.java) + - 📄 [ElasticCollision2D](src/main/java/com/thealgorithms/physics/ElasticCollision2D.java) + - 📄 [Gravitation](src/main/java/com/thealgorithms/physics/Gravitation.java) - 📄 [GroundToGroundProjectileMotion](src/main/java/com/thealgorithms/physics/GroundToGroundProjectileMotion.java) + - 📄 [Kinematics](src/main/java/com/thealgorithms/physics/Kinematics.java) + - 📄 [ProjectileMotion](src/main/java/com/thealgorithms/physics/ProjectileMotion.java) + - 📄 [SimplePendulumRK4](src/main/java/com/thealgorithms/physics/SimplePendulumRK4.java) + - 📄 [SnellLaw](src/main/java/com/thealgorithms/physics/SnellLaw.java) + - 📄 [ThinLens](src/main/java/com/thealgorithms/physics/ThinLens.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** @@ -613,6 +649,7 @@ - 📄 [ReservoirSampling](src/main/java/com/thealgorithms/randomized/ReservoirSampling.java) - 📁 **recursion** - 📄 [DiceThrower](src/main/java/com/thealgorithms/recursion/DiceThrower.java) + - 📄 [FactorialRecursion](src/main/java/com/thealgorithms/recursion/FactorialRecursion.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) @@ -649,7 +686,7 @@ - 📄 [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) + - 📄 [ExponentialSearch](src/main/java/com/thealgorithms/searches/ExponentialSearch.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) @@ -662,7 +699,6 @@ - 📄 [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) @@ -671,7 +707,6 @@ - 📄 [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) @@ -758,6 +793,8 @@ - 📄 [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) + - 📄 [TrappingRainwater](src/main/java/com/thealgorithms/stacks/TrappingRainwater.java) + - 📄 [ValidParentheses](src/main/java/com/thealgorithms/stacks/ValidParentheses.java) - 📁 **strings** - 📄 [AhoCorasick](src/main/java/com/thealgorithms/strings/AhoCorasick.java) - 📄 [Alphabetical](src/main/java/com/thealgorithms/strings/Alphabetical.java) @@ -772,13 +809,14 @@ - 📄 [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) + - 📄 [LengthOfLastWord](src/main/java/com/thealgorithms/strings/LengthOfLastWord.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) + - 📄 [MoveHashToEnd](src/main/java/com/thealgorithms/strings/MoveHashToEnd.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) @@ -792,8 +830,8 @@ - 📄 [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) + - 📄 [ZAlgorithm](src/main/java/com/thealgorithms/strings/ZAlgorithm.java) - 📁 **zigZagPattern** - 📄 [ZigZagPattern](src/main/java/com/thealgorithms/strings/zigZagPattern/ZigZagPattern.java) - 📁 **tree** @@ -808,6 +846,7 @@ - 📁 **backtracking** - 📄 [AllPathsFromSourceToTargetTest](src/test/java/com/thealgorithms/backtracking/AllPathsFromSourceToTargetTest.java) - 📄 [ArrayCombinationTest](src/test/java/com/thealgorithms/backtracking/ArrayCombinationTest.java) + - 📄 [CombinationSumTest](src/test/java/com/thealgorithms/backtracking/CombinationSumTest.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) @@ -819,11 +858,14 @@ - 📄 [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) + - 📄 [SudokuSolverTest](src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java) + - 📄 [UniquePermutationTest](src/test/java/com/thealgorithms/backtracking/UniquePermutationTest.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) + - 📄 [BitRotateTest](src/test/java/com/thealgorithms/bitmanipulation/BitRotateTest.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) @@ -871,6 +913,7 @@ - 📄 [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) + - 📄 [OneTimePadCipherTest](src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.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) @@ -884,6 +927,12 @@ - 📄 [A5KeyStreamGeneratorTest](src/test/java/com/thealgorithms/ciphers/a5/A5KeyStreamGeneratorTest.java) - 📄 [LFSRTest](src/test/java/com/thealgorithms/ciphers/a5/LFSRTest.java) - 📁 **compression** + - 📄 [ArithmeticCodingTest](src/test/java/com/thealgorithms/compression/ArithmeticCodingTest.java) + - 📄 [BurrowsWheelerTransformTest](src/test/java/com/thealgorithms/compression/BurrowsWheelerTransformTest.java) + - 📄 [LZ77Test](src/test/java/com/thealgorithms/compression/LZ77Test.java) + - 📄 [LZ78Test](src/test/java/com/thealgorithms/compression/LZ78Test.java) + - 📄 [LZWTest](src/test/java/com/thealgorithms/compression/LZWTest.java) + - 📄 [MoveToFrontTest](src/test/java/com/thealgorithms/compression/MoveToFrontTest.java) - 📄 [RunLengthEncodingTest](src/test/java/com/thealgorithms/compression/RunLengthEncodingTest.java) - 📄 [ShannonFanoTest](src/test/java/com/thealgorithms/compression/ShannonFanoTest.java) - 📁 **conversions** @@ -914,6 +963,7 @@ - 📄 [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) + - 📄 [TemperatureConverterTest](src/test/java/com/thealgorithms/conversions/TemperatureConverterTest.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) @@ -947,8 +997,10 @@ - 📄 [DynamicArrayTest](src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java) - 📁 **graphs** - 📄 [AStarTest](src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java) + - 📄 [BellmanFordTest](src/test/java/com/thealgorithms/datastructures/graphs/BellmanFordTest.java) - 📄 [BipartiteGraphDFSTest](src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java) - 📄 [BoruvkaAlgorithmTest](src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java) + - 📄 [ConnectedComponentTest](src/test/java/com/thealgorithms/datastructures/graphs/ConnectedComponentTest.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) @@ -971,6 +1023,7 @@ - 📄 [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) + - 📄 [ImmutableHashMapTest](src/test/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMapTest.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) @@ -979,6 +1032,7 @@ - 📄 [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) + - 📄 [IndexedPriorityQueueTest](src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.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) @@ -1028,8 +1082,10 @@ - 📄 [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) + - 📄 [BinaryTreeToStringTest](src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java) - 📄 [BoundaryTraversalTest](src/test/java/com/thealgorithms/datastructures/trees/BoundaryTraversalTest.java) - 📄 [CeilInBinarySearchTreeTest](src/test/java/com/thealgorithms/datastructures/trees/CeilInBinarySearchTreeTest.java) + - 📄 [CentroidDecompositionTest](src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.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) @@ -1043,6 +1099,7 @@ - 📄 [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) + - 📄 [ThreadedBinaryTreeTest](src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.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) @@ -1114,8 +1171,10 @@ - 📄 [WildcardMatchingTest](src/test/java/com/thealgorithms/dynamicprogramming/WildcardMatchingTest.java) - 📄 [WineProblemTest](src/test/java/com/thealgorithms/dynamicprogramming/WineProblemTest.java) - 📁 **geometry** + - 📄 [BentleyOttmannTest](src/test/java/com/thealgorithms/geometry/BentleyOttmannTest.java) - 📄 [BresenhamLineTest](src/test/java/com/thealgorithms/geometry/BresenhamLineTest.java) - 📄 [ConvexHullTest](src/test/java/com/thealgorithms/geometry/ConvexHullTest.java) + - 📄 [DDALineTest](src/test/java/com/thealgorithms/geometry/DDALineTest.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) @@ -1128,9 +1187,14 @@ - 📄 [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) + - 📄 [GomoryHuTreeTest](src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java) + - 📄 [HierholzerAlgorithmTest](src/test/java/com/thealgorithms/graph/HierholzerAlgorithmTest.java) + - 📄 [HierholzerEulerianPathTest](src/test/java/com/thealgorithms/graph/HierholzerEulerianPathTest.java) - 📄 [HopcroftKarpTest](src/test/java/com/thealgorithms/graph/HopcroftKarpTest.java) + - 📄 [HungarianAlgorithmTest](src/test/java/com/thealgorithms/graph/HungarianAlgorithmTest.java) - 📄 [PredecessorConstrainedDfsTest](src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java) - 📄 [PushRelabelTest](src/test/java/com/thealgorithms/graph/PushRelabelTest.java) + - 📄 [StoerWagnerTest](src/test/java/com/thealgorithms/graph/StoerWagnerTest.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) @@ -1161,6 +1225,7 @@ - 📄 [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) + - 📄 [AbundantNumberTest](src/test/java/com/thealgorithms/maths/AbundantNumberTest.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) @@ -1172,6 +1237,7 @@ - 📄 [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) + - 📄 [ChebyshevIterationTest](src/test/java/com/thealgorithms/maths/ChebyshevIterationTest.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) @@ -1185,8 +1251,9 @@ - 📄 [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) + - 📄 [EvilNumberTest](src/test/java/com/thealgorithms/maths/EvilNumberTest.java) + - 📄 [ExtendedEuclideanAlgorithmTest](src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.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) @@ -1211,6 +1278,7 @@ - 📄 [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) + - 📄 [JugglerSequenceTest](src/test/java/com/thealgorithms/maths/JugglerSequenceTest.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) @@ -1220,12 +1288,14 @@ - 📄 [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) + - 📄 [LuckyNumberTest](src/test/java/com/thealgorithms/maths/LuckyNumberTest.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) + - 📄 [NevilleTest](src/test/java/com/thealgorithms/maths/NevilleTest.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) @@ -1240,6 +1310,7 @@ - 📄 [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) + - 📄 [PowerOfFourTest](src/test/java/com/thealgorithms/maths/PowerOfFourTest.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) @@ -1249,6 +1320,7 @@ - 📄 [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) + - 📄 [SmithNumberTest](src/test/java/com/thealgorithms/maths/SmithNumberTest.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) @@ -1275,14 +1347,16 @@ - 📄 [PrimeFactorizationTest](src/test/java/com/thealgorithms/maths/prime/PrimeFactorizationTest.java) - 📁 **matrix** - 📄 [InverseOfMatrixTest](src/test/java/com/thealgorithms/matrix/InverseOfMatrixTest.java) + - 📄 [LUDecompositionTest](src/test/java/com/thealgorithms/matrix/LUDecompositionTest.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) + - 📄 [PrintAMatrixInSpiralOrderTest](src/test/java/com/thealgorithms/matrix/PrintAMatrixInSpiralOrderTest.java) - 📄 [SolveSystemTest](src/test/java/com/thealgorithms/matrix/SolveSystemTest.java) - - 📄 [TestPrintMatrixInSpiralOrder](src/test/java/com/thealgorithms/matrix/TestPrintMatrixInSpiralOrder.java) + - 📄 [StochasticMatrixTest](src/test/java/com/thealgorithms/matrix/StochasticMatrixTest.java) - 📁 **misc** - 📄 [ColorContrastRatioTest](src/test/java/com/thealgorithms/misc/ColorContrastRatioTest.java) - 📄 [MapReduceTest](src/test/java/com/thealgorithms/misc/MapReduceTest.java) @@ -1316,22 +1390,26 @@ - 📄 [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** + - 📄 [CoulombsLawTest](src/test/java/com/thealgorithms/physics/CoulombsLawTest.java) + - 📄 [DampedOscillatorTest](src/test/java/com/thealgorithms/physics/DampedOscillatorTest.java) + - 📄 [ElasticCollision2DTest](src/test/java/com/thealgorithms/physics/ElasticCollision2DTest.java) + - 📄 [GravitationTest](src/test/java/com/thealgorithms/physics/GravitationTest.java) - 📄 [GroundToGroundProjectileMotionTest](src/test/java/com/thealgorithms/physics/GroundToGroundProjectileMotionTest.java) + - 📄 [KinematicsTest](src/test/java/com/thealgorithms/physics/KinematicsTest.java) + - 📄 [ProjectileMotionTest](src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java) + - 📄 [SimplePendulumRK4Test](src/test/java/com/thealgorithms/physics/SimplePendulumRK4Test.java) + - 📄 [SnellLawTest](src/test/java/com/thealgorithms/physics/SnellLawTest.java) + - 📄 [ThinLensTest](src/test/java/com/thealgorithms/physics/ThinLensTest.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** @@ -1343,6 +1421,7 @@ - 📄 [ReservoirSamplingTest](src/test/java/com/thealgorithms/randomized/ReservoirSamplingTest.java) - 📁 **recursion** - 📄 [DiceThrowerTest](src/test/java/com/thealgorithms/recursion/DiceThrowerTest.java) + - 📄 [FactorialRecursionTest](src/test/java/com/thealgorithms/recursion/FactorialRecursionTest.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) @@ -1392,7 +1471,6 @@ - 📄 [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) @@ -1401,7 +1479,6 @@ - 📄 [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) @@ -1487,6 +1564,8 @@ - 📄 [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) + - 📄 [TrappingRainwaterTest](src/test/java/com/thealgorithms/stacks/TrappingRainwaterTest.java) + - 📄 [ValidParenthesesTest](src/test/java/com/thealgorithms/stacks/ValidParenthesesTest.java) - 📁 **strings** - 📄 [AhoCorasickTest](src/test/java/com/thealgorithms/strings/AhoCorasickTest.java) - 📄 [AlphabeticalTest](src/test/java/com/thealgorithms/strings/AlphabeticalTest.java) @@ -1500,10 +1579,10 @@ - 📄 [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) + - 📄 [LengthOfLastWordTest](src/test/java/com/thealgorithms/strings/LengthOfLastWordTest.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) @@ -1519,8 +1598,8 @@ - 📄 [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) + - 📄 [ZAlgorithmTest](src/test/java/com/thealgorithms/strings/ZAlgorithmTest.java) - 📁 **zigZagPattern** - 📄 [ZigZagPatternTest](src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java) - 📁 **tree** diff --git a/README-ko.md b/README-ko.md deleted file mode 100644 index 4f8cab92fc42..000000000000 --- a/README-ko.md +++ /dev/null @@ -1,191 +0,0 @@ -# 알고리즘 - 자바 - -## 이 [개발브런치](https://github.com/TheAlgorithms/Java/tree/Development)는 기존 프로젝트를 Java 프로젝트 구조로 재개발하기 위해 작성되었다. 기여도를 위해 개발 지사로 전환할 수 있다. 자세한 내용은 이 문제를 참조하십시오. 컨트리뷰션을 위해 [개발브런치](https://github.com/TheAlgorithms/Java/tree/Development)로 전환할 수 있다. 자세한 내용은 [이 이슈](https://github.com/TheAlgorithms/Java/issues/474)를 참고하십시오. - -### 자바로 구현된 모든 알고리즘들 (교육용) - -이것들은 단지 시범을 위한 것이다. 표준 자바 라이브러리에는 성능상의 이유로 더 나은 것들이 구현되어있다 - -## 정렬 알고리즘 - -### Bubble(버블 정렬) - -![alt text][bubble-image] - -From [Wikipedia][bubble-wiki]: 버블 소트(sinking sor라고도 불리움)는 리스트를 반복적인 단계로 접근하여 정렬한다. 각각의 짝을 비교하며, 순서가 잘못된 경우 그접한 아이템들을 스왑하는 알고리즘이다. 더 이상 스왑할 것이 없을 때까지 반복하며, 반복이 끝남음 리스트가 정렬되었음을 의미한다. - -**속성** - -- 최악의 성능 O(n^2) -- 최고의 성능 O(n) -- 평균 성능 O(n^2) - -###### View the algorithm in [action][bubble-toptal] - -### Insertion(삽입 정렬) - -![alt text][insertion-image] - -From [Wikipedia][insertion-wiki]: 삽입 정렬은 최종 정렬된 배열(또는 리스트)을 한번에 하나씩 구축하는 알고리즘이다. 이것은 큰 리스트에서 더 나은 알고리즘인 퀵 소트, 힙 소트, 또는 머지 소트보다 훨씬 안좋은 효율을 가진다. 그림에서 각 막대는 정렬해야 하는 배열의 요소를 나타낸다. 상단과 두 번째 상단 막대의 첫 번째 교차점에서 발생하는 것은 두 번째 요소가 첫 번째 요소보다 더 높은 우선 순위를 가지기 때문에 막대로 표시되는 이러한 요소를 교환한 것이다. 이 방법을 반복하면 삽입 정렬이 완료된다. - -**속성** - -- 최악의 성능 O(n^2) -- 최고의 성능 O(n) -- 평균 O(n^2) - -###### View the algorithm in [action][insertion-toptal] - -### Merge(합병 정렬) - -![alt text][merge-image] - -From [Wikipedia][merge-wiki]: 컴퓨터 과학에서, 합병 정렬은 효율적인, 범용적인, 비교 기반 정렬 알고리즘이다. 대부분의 구현은 안정적인 분류를 이루는데, 이것은 구현이 정렬된 출력에 동일한 요소의 입력 순서를 유지한다는 것을 의미한다. 합병 정렬은 1945년에 John von Neumann이 발명한 분할 정복 알고리즘이다. - -**속성** - -- 최악의 성능 O(n log n) (일반적) -- 최고의 성능 O(n log n) -- 평균 O(n log n) - -###### View the algorithm in [action][merge-toptal] - -### Quick(퀵 정렬) - -![alt text][quick-image] - -From [Wikipedia][quick-wiki]: 퀵 정렬sometimes called partition-exchange sort)은 효율적인 정렬 알고리즘으로, 배열의 요소를 순서대로 정렬하는 체계적인 방법 역활을 한다. - -**속성** - -- 최악의 성능 O(n^2) -- 최고의 성능 O(n log n) or O(n) with three-way partition -- 평균 O(n log n) - -###### View the algorithm in [action][quick-toptal] - -### Selection(선택 정렬) - -![alt text][selection-image] - -From [Wikipedia][selection-wiki]: 알고리즘 입력 리스트를 두 부분으로 나눈다 : 첫 부분은 아이템들이 이미 왼쪽에서 오른쪽으로 정렬되었다. 그리고 남은 부분의 아이템들은 나머지 항목을 차지하는 리스트이다. 처음에는 정렬된 리스트는 공백이고 나머지가 전부이다. 오르차순(또는 내림차순) 알고리즘은 가장 작은 요소를 정렬되지 않은 리스트에서 찾고 정렬이 안된 가장 왼쪽(정렬된 리스트) 리스트와 바꾼다. 이렇게 오른쪽으로 나아간다. - -**속성** - -- 최악의 성능 O(n^2) -- 최고의 성능 O(n^2) -- 평균 O(n^2) - -###### View the algorithm in [action][selection-toptal] - -### Shell(쉘 정렬) - -![alt text][shell-image] - -From [Wikipedia][shell-wiki]: 쉘 정렬은 멀리 떨어져 있는 항목의 교환을 허용하는 삽입 종류의 일반화이다. 그 아이디어는 모든 n번째 요소가 정렬된 목록을 제공한다는 것을 고려하여 어느 곳에서든지 시작하도록 요소의 목록을 배열하는 것이다. 이러한 목록은 h-sorted로 알려져 있다. 마찬가지로, 각각 개별적으로 정렬된 h 인터리브 목록으로 간주할 수 있다. - -**속성** - -- 최악의 성능 O(nlog2 2n) -- 최고의 성능 O(n log n) -- Average case performance depends on gap sequence - -###### View the algorithm in [action][shell-toptal] - -### 시간 복잡성 그래프 - -정렬 알고리즘의 복잡성 비교 (버블 정렬, 삽입 정렬, 선택 정렬) - -[복잡성 그래프](https://github.com/prateekiiest/Python/blob/master/sorts/sortinggraphs.png) - ---- - -## 검색 알고리즘 - -### Linear (선형 탐색) - -![alt text][linear-image] - -From [Wikipedia][linear-wiki]: 선형 탐색 또는 순차 탐색은 목록 내에서 목표값을 찾는 방법이다. 일치 항목이 발견되거나 모든 요소가 탐색될 때까지 목록의 각 요소에 대해 목표값을 순차적으로 검사한다. -선형 검색은 최악의 선형 시간으로 실행되며 최대 n개의 비교에서 이루어진다. 여기서 n은 목록의 길이다. - -**속성** - -- 최악의 성능 O(n) -- 최고의 성능 O(1) -- 평균 O(n) -- 최악의 경우 공간 복잡성 O(1) iterative - -### Binary (이진 탐색) - -![alt text][binary-image] - -From [Wikipedia][binary-wiki]: 이진 탐색, (also known as half-interval search or logarithmic search), 은 정렬된 배열 내에서 목표값의 위치를 찾는 검색 알고리즘이다. 목표값을 배열의 중간 요소와 비교한다; 만약 목표값이 동일하지 않으면, 목표물의 절반이 제거되고 검색이 성공할 때까지 나머지 절반에서 속된다. - -**속성** - -- 최악의 성능 O(log n) -- 최고의 성능 O(1) -- 평균 O(log n) -- 최악의 경우 공간 복잡성 O(1) - -[bubble-toptal]: https://www.toptal.com/developers/sorting-algorithms/bubble-sort -[bubble-wiki]: https://en.wikipedia.org/wiki/Bubble_sort -[bubble-image]: https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Bubblesort-edited-color.svg/220px-Bubblesort-edited-color.svg.png "Bubble Sort" -[insertion-toptal]: https://www.toptal.com/developers/sorting-algorithms/insertion-sort -[insertion-wiki]: https://en.wikipedia.org/wiki/Insertion_sort -[insertion-image]: https://upload.wikimedia.org/wikipedia/commons/7/7e/Insertionsort-edited.png "Insertion Sort" -[quick-toptal]: https://www.toptal.com/developers/sorting-algorithms/quick-sort -[quick-wiki]: https://en.wikipedia.org/wiki/Quicksort -[quick-image]: https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif "Quick Sort" -[merge-toptal]: https://www.toptal.com/developers/sorting-algorithms/merge-sort -[merge-wiki]: https://en.wikipedia.org/wiki/Merge_sort -[merge-image]: https://upload.wikimedia.org/wikipedia/commons/c/cc/Merge-sort-example-300px.gif "Merge Sort" -[selection-toptal]: https://www.toptal.com/developers/sorting-algorithms/selection-sort -[selection-wiki]: https://en.wikipedia.org/wiki/Selection_sort -[selection-image]: https://upload.wikimedia.org/wikipedia/commons/thumb/b/b0/Selection_sort_animation.gif/250px-Selection_sort_animation.gif "Selection Sort Sort" -[shell-toptal]: https://www.toptal.com/developers/sorting-algorithms/shell-sort -[shell-wiki]: https://en.wikipedia.org/wiki/Shellsort -[shell-image]: https://upload.wikimedia.org/wikipedia/commons/d/d8/Sorting_shellsort_anim.gif "Shell Sort" -[linear-wiki]: https://en.wikipedia.org/wiki/Linear_search -[linear-image]: http://www.tutorialspoint.com/data_structures_algorithms/images/linear_search.gif -[binary-wiki]: https://en.wikipedia.org/wiki/Binary_search_algorithm -[binary-image]: https://upload.wikimedia.org/wikipedia/commons/f/f7/Binary_search_into_array.png - ---- - -## 나머지 알고리즘에 대한 링크 - -| 전환 | 다이나믹프로그래밍(DP) | 암호 | 그 외 것들 | -| --------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------ | -| [Any Base to Any Base](Conversions/AnyBaseToAnyBase.java) | [Coin Change](DynamicProgramming/CoinChange.java) | [Caesar](Ciphers/Caesar.java) | [Heap Sort](Sorts/HeapSort.java) | -| [Any Base to Decimal](Conversions/AnyBaseToDecimal.java) | [Egg Dropping](DynamicProgramming/EggDropping.java) | [Columnar Transposition Cipher](Ciphers/ColumnarTranspositionCipher.java) | [Palindromic Prime Checker](Misc/PalindromePrime.java) | -| [Binary to Decimal](Conversions/BinaryToDecimal.java) | [Fibonacci](DynamicProgramming/Fibonacci.java) | [RSA](Ciphers/RSA.java) | More soon... | -| [Binary to HexaDecimal](Conversions/BinaryToHexadecimal.java) | [Kadane Algorithm](DynamicProgramming/KadaneAlgorithm.java) | more coming soon... | -| [Binary to Octal](Conversions/BinaryToOctal.java) | [Knapsack](DynamicProgramming/Knapsack.java) | -| [Decimal To Any Base](Conversions/DecimalToAnyBase.java) | [Longest Common Subsequence](DynamicProgramming/LongestCommonSubsequence.java) | -| [Decimal To Binary](Conversions/DecimalToBinary.java) | [Longest Increasing Subsequence](DynamicProgramming/LongestIncreasingSubsequence.java) | -| [Decimal To Hexadecimal](Conversions/DecimalToHexaDecimal.java) | [Rod Cutting](DynamicProgramming/RodCutting.java) | -| and much more... | and more... | - -### 자료 구조 - -| 그래프 | 힙 | 리스트 | 큐 | -| ------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------- | -| | [빈 힙 예외처리](DataStructures/Heaps/EmptyHeapException.java) | [원형 연결리스트](DataStructures/Lists/CircleLinkedList.java) | [제너릭 어레이 리스트 큐](DataStructures/Queues/GenericArrayListQueue.java) | -| | [힙](DataStructures/Heaps/Heap.java) | [이중 연결리스트](DataStructures/Lists/DoublyLinkedList.java) | [큐](DataStructures/Queues/Queues.java) | -| [그래프](DataStructures/Graphs/Graphs.java) | [힙 요소](DataStructures/Heaps/HeapElement.java) | [단순 연결리스트](DataStructures/Lists/SinglyLinkedList.java) | -| [크루스칼 알고리즘](DataStructures/Graphs/Kruskal.java) | [최대힙](DataStructures/Heaps/MaxHeap.java) | -| [행렬 그래프](DataStructures/Graphs/MatrixGraphs.java) | [최소힙](DataStructures/Heaps/MinHeap.java) | -| [프림 최소신장트리](DataStructures/Graphs/PrimMST.java) | - -| 스택 | 트리 | -| --------------------------------------------------------------- | ------------------------------------------------- | -| [노드 스택](DataStructures/Stacks/NodeStack.java) | [AVL 트리](DataStructures/Trees/AVLTree.java) | -| [연결리스트 스택](DataStructures/Stacks/StackOfLinkedList.java) | [이진 트리](DataStructures/Trees/BinaryTree.java) | -| [스택](DataStructures/Stacks) | And much more... | - -- [Bags](DataStructures/Bags/Bag.java) -- [Buffer](DataStructures/Buffers/CircularBuffer.java) -- [HashMap](DataStructures/HashMap/Hashing/HashMap.java) -- diff --git a/pmd-exclude.properties b/pmd-exclude.properties index a3c95b12fa4b..26653d9dc17b 100644 --- a/pmd-exclude.properties +++ b/pmd-exclude.properties @@ -53,14 +53,11 @@ 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=UselessMainMethod,UselessParentheses -com.thealgorithms.maths.LeonardoNumber=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 @@ -71,7 +68,6 @@ 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.others.BankersAlgorithm=UselessMainMethod com.thealgorithms.others.BrianKernighanAlgorithm=UselessMainMethod com.thealgorithms.others.CRC16=UselessMainMethod,UselessParentheses @@ -79,11 +75,9 @@ 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 @@ -92,9 +86,8 @@ 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.Trieac=UselessMainMethod,UselessParentheses +com.thealgorithms.datastructures.trees.TrieAutocomplete=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 @@ -108,15 +101,11 @@ com.thealgorithms.sorts.MergeSortNoExtraSpace=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.KMP=UselessMainMethod com.thealgorithms.strings.Lower=UselessMainMethod com.thealgorithms.strings.Palindrome=UselessParentheses 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 7e936967208b..68b72189fdb3 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ UTF-8 21 21 - 3.27.6 + 3.27.7 @@ -20,7 +20,7 @@ org.junit junit-bom - 6.0.1 + 6.1.3 pom import @@ -42,18 +42,18 @@ org.mockito mockito-core - 5.20.0 + 5.23.0 test org.apache.commons commons-lang3 - 3.19.0 + 3.20.0 org.apache.commons commons-collections4 - 4.5.0 + 4.6.0 @@ -61,7 +61,7 @@ maven-surefire-plugin - 3.5.4 + 3.5.6 @@ -69,7 +69,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + 3.15.0 21 @@ -82,7 +82,7 @@ org.jacoco jacoco-maven-plugin - 0.8.14 + 0.8.15 @@ -112,14 +112,14 @@ com.puppycrawl.tools checkstyle - 12.1.1 + 13.9.0 com.github.spotbugs spotbugs-maven-plugin - 4.9.8.1 + 4.10.3.0 spotbugs-exclude.xml true @@ -127,7 +127,7 @@ com.mebigfatguy.fb-contrib fb-contrib - 7.6.15 + 7.7.4 com.h3xstream.findsecbugs diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index d2e094556d61..1c34e47fb651 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -8,18 +8,9 @@ - - - - - - - - - @@ -32,15 +23,9 @@ - - - - - - @@ -50,15 +35,9 @@ - - - - - - @@ -71,9 +50,6 @@ - - - @@ -84,7 +60,10 @@ - + + + + @@ -114,12 +93,6 @@ - - - - - - @@ -129,9 +102,6 @@ - - - @@ -150,9 +120,6 @@ - - - @@ -190,16 +157,19 @@ - + - + - + - + + + + diff --git a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java index 0dd23e937953..4a9e954bd202 100644 --- a/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java +++ b/src/main/java/com/thealgorithms/audiofilters/EMAFilter.java @@ -3,16 +3,19 @@ /** * Exponential Moving Average (EMA) Filter for smoothing audio signals. * - *

This filter applies an exponential moving average to a sequence of audio + *

+ * This filter applies an exponential moving average to a sequence of audio * signal values, making it useful for smoothing out rapid fluctuations. * The smoothing factor (alpha) controls the degree of smoothing. * - *

Based on the definition from + *

+ * Based on the definition from * Wikipedia link. */ public class EMAFilter { private final double alpha; private double emaValue; + /** * Constructs an EMA filter with a given smoothing factor. * @@ -26,14 +29,17 @@ public EMAFilter(double alpha) { this.alpha = alpha; this.emaValue = 0.0; } + /** * Applies the EMA filter to an audio signal array. + * EMA formula: + * EMA = alpha * currentSample + (1 - alpha) * previousEMA * * @param audioSignal Array of audio samples to process * @return Array of processed (smoothed) samples */ public double[] apply(double[] audioSignal) { - if (audioSignal.length == 0) { + if (audioSignal == null || audioSignal.length == 0) { return new double[0]; } double[] emaSignal = new double[audioSignal.length]; diff --git a/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java b/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java index c35a36d97a57..269880b8ddae 100644 --- a/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java +++ b/src/main/java/com/thealgorithms/backtracking/AllPathsFromSourceToTarget.java @@ -4,11 +4,36 @@ import java.util.List; /** - * Program description - To find all possible paths from source to destination - * Wikipedia + * Finds all possible simple paths from a given source vertex to a destination vertex + * in a directed graph using backtracking. * - * @author Siddhant Swarup Mallick + *

This algorithm performs a Depth First Search (DFS) traversal while keeping track + * of visited vertices to avoid cycles. Whenever the destination vertex is reached, + * the current path is stored as one valid path.

+ * + *

Key Characteristics:

+ *
    + *
  • Works on directed graphs
  • + *
  • Does not allow revisiting vertices in the same path
  • + *
  • Stores all possible paths from source to destination
  • + *
+ * + *

Time Complexity:

+ *
    + *
  • Worst Case: O(V!) — when the graph is fully connected
  • + *
+ * + *

Space Complexity:

+ *
    + *
  • O(V) for recursion stack and visited array
  • + *
  • Additional space for storing all valid paths
  • + *
+ * + *

This implementation is intended for educational purposes.

+ * + * @see Depth First Search */ + @SuppressWarnings({"rawtypes", "unchecked"}) public class AllPathsFromSourceToTarget { diff --git a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java index f8cd0c40c20e..d05e33a4242f 100644 --- a/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java +++ b/src/main/java/com/thealgorithms/backtracking/ArrayCombination.java @@ -20,8 +20,8 @@ private ArrayCombination() { * @throws IllegalArgumentException if n or k are negative, or if k is greater than n. */ public static List> combination(int n, int k) { - if (n < 0 || k < 0 || k > n) { - throw new IllegalArgumentException("Invalid input: n must be non-negative, k must be non-negative and less than or equal to n."); + if (k < 0 || k > n) { + throw new IllegalArgumentException("Invalid input: 0 ≤ k ≤ n is required."); } List> combinations = new ArrayList<>(); @@ -48,7 +48,7 @@ private static void combine(List> combinations, List curr for (int i = start; i < n; i++) { current.add(i); combine(combinations, current, i + 1, n, k); - current.remove(current.size() - 1); // Backtrack + current.removeLast(); // Backtrack } } } diff --git a/src/main/java/com/thealgorithms/backtracking/Combination.java b/src/main/java/com/thealgorithms/backtracking/Combination.java index ecaf7428f986..377d2c862d54 100644 --- a/src/main/java/com/thealgorithms/backtracking/Combination.java +++ b/src/main/java/com/thealgorithms/backtracking/Combination.java @@ -7,8 +7,7 @@ import java.util.TreeSet; /** - * Finds all permutations of given array - * @author Alan Piao (git-Alan Piao) + * Finds all combinations of a given array using backtracking algorithm * @author Alan Piao (git-Alan Piao) */ public final class Combination { private Combination() { diff --git a/src/main/java/com/thealgorithms/backtracking/NQueens.java b/src/main/java/com/thealgorithms/backtracking/NQueens.java index 1a8e453e34cb..404f677738a0 100644 --- a/src/main/java/com/thealgorithms/backtracking/NQueens.java +++ b/src/main/java/com/thealgorithms/backtracking/NQueens.java @@ -1,7 +1,9 @@ package com.thealgorithms.backtracking; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Problem statement: Given a N x N chess board. Return all arrangements in @@ -32,7 +34,22 @@ * queen is not placed safely. If there is no such way then return an empty list * as solution */ + +/* + * Time Complexity: O(N!) + * space Complexity: O(N) + */ public final class NQueens { + + // Store occupied rows for constant time safety check + private static final Set OCROWS = new HashSet<>(); + + // Store occupied main diagonals (row - column) + private static final Set OCDIAG = new HashSet<>(); + + // Store occupied anti-diagonals (row + columns) + private static final Set OCANTIDIAG = new HashSet<>(); + private NQueens() { } @@ -43,10 +60,10 @@ public static List> getNQueensArrangements(int queens) { } public static void placeQueens(final int queens) { - List> arrangements = new ArrayList>(); + List> arrangements = new ArrayList<>(); getSolution(queens, arrangements, new int[queens], 0); if (arrangements.isEmpty()) { - System.out.println("There is no way to place " + queens + " queens on board of size " + queens + "x" + queens); + System.out.println(" no way to place " + queens + " queens on board of size " + queens + "x" + queens); } else { System.out.println("Arrangement for placing " + queens + " queens"); } @@ -59,15 +76,15 @@ public static void placeQueens(final int queens) { /** * This is backtracking function which tries to place queen recursively * - * @param boardSize: size of chess board - * @param solutions: this holds all possible arrangements - * @param columns: columns[i] = rowId where queen is placed in ith column. + * @param boardSize: size of chess board + * @param solutions: this holds all possible arrangements + * @param columns: columns[i] = rowId where queen is placed in ith column. * @param columnIndex: This is the column in which queen is being placed */ private static void getSolution(int boardSize, List> solutions, int[] columns, int columnIndex) { if (columnIndex == boardSize) { // this means that all queens have been placed - List sol = new ArrayList(); + List sol = new ArrayList<>(); for (int i = 0; i < boardSize; i++) { StringBuilder sb = new StringBuilder(); for (int j = 0; j < boardSize; j++) { @@ -82,30 +99,29 @@ private static void getSolution(int boardSize, List> solutions, int // This loop tries to place queen in a row one by one for (int rowIndex = 0; rowIndex < boardSize; rowIndex++) { columns[columnIndex] = rowIndex; - if (isPlacedCorrectly(columns, rowIndex, columnIndex)) { - // If queen is placed successfully at rowIndex in column=columnIndex then try - // placing queen in next column - getSolution(boardSize, solutions, columns, columnIndex + 1); - } - } - } - /** - * This function checks if queen can be placed at row = rowIndex in column = - * columnIndex safely - * - * @param columns: columns[i] = rowId where queen is placed in ith column. - * @param rowIndex: row in which queen has to be placed - * @param columnIndex: column in which queen is being placed - * @return true: if queen can be placed safely false: otherwise - */ - private static boolean isPlacedCorrectly(int[] columns, int rowIndex, int columnIndex) { - for (int i = 0; i < columnIndex; i++) { - int diff = Math.abs(columns[i] - rowIndex); - if (diff == 0 || columnIndex - i == diff) { - return false; + // Skip current position if row or diagonal is already occupied + boolean isROp = OCROWS.contains(rowIndex); + + boolean isDOp = OCDIAG.contains(rowIndex - columnIndex) || OCANTIDIAG.contains(rowIndex + columnIndex); + + if (isROp || isDOp) { + continue; } + + // Mark current row and diagonal as occupied + OCROWS.add(rowIndex); + OCDIAG.add(rowIndex - columnIndex); + OCANTIDIAG.add(rowIndex + columnIndex); + + // Move to the next column after placing current queen + getSolution(boardSize, solutions, columns, columnIndex + 1); + + // Backtrack by removing current queen + + OCROWS.remove(rowIndex); + OCDIAG.remove(rowIndex - columnIndex); + OCANTIDIAG.remove(rowIndex + columnIndex); } - return true; } } diff --git a/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java new file mode 100644 index 000000000000..183b4bbd97f8 --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/RatInAMaze.java @@ -0,0 +1,119 @@ +package com.thealgorithms.backtracking; + +import java.util.ArrayList; +import java.util.List; + +/** + * Rat in a Maze Problem using Backtracking. + * + *

Given an {@code n x n} binary maze where {@code 1} represents an open cell + * and {@code 0} represents a blocked cell, find all paths for a rat starting at + * the top-left cell {@code (0, 0)} to reach the bottom-right cell {@code (n-1, n-1)}. + * + *

The rat can move in four directions: Up (U), Down (D), Left (L), Right (R). + * Each cell may be visited at most once per path. + * + *

Time Complexity: O(4^(n²)) in the worst case (four choices per cell). + * Space Complexity: O(n²) for the visited matrix and recursion stack. + * + *

Example: + *

+ *   maze = { {1, 0, 0, 0},
+ *            {1, 1, 0, 1},
+ *            {0, 1, 0, 0},
+ *            {0, 1, 1, 1} }
+ *   Output: ["DDRDRR", "DRDDRR"]  (two valid paths)
+ * 
+ * + * @see Maze solving algorithm + * @author the-Sunny-Sharma (GitHub) + */ +public final class RatInAMaze { + + private RatInAMaze() { + } + + /** + * Finds all paths from the top-left to the bottom-right of the given maze. + * + * @param maze an {@code n x n} binary matrix where {@code 1} = open, {@code 0} = blocked + * @return a sorted list of all valid path strings using directions D, L, R, U; + * an empty list if no path exists + * @throws IllegalArgumentException if the maze is null, empty, or not square + */ + public static List findPaths(final int[][] maze) { + if (maze == null || maze.length == 0) { + throw new IllegalArgumentException("Maze must not be null or empty."); + } + int n = maze.length; + for (int[] row : maze) { + if (row.length != n) { + throw new IllegalArgumentException("Maze must be a square (n x n) matrix."); + } + } + List results = new ArrayList<>(); + if (maze[0][0] == 0 || maze[n - 1][n - 1] == 0) { + return results; + } + boolean[][] visited = new boolean[n][n]; + solve(maze, 0, 0, n, "", visited, results); + return results; + } + + /** + * Recursive backtracking helper that explores all four directions. + * + * @param maze the binary maze + * @param row current row position + * @param col current column position + * @param n maze dimension + * @param path path string built so far + * @param visited tracks visited cells for the current path + * @param results accumulates complete paths + */ + private static void solve(final int[][] maze, final int row, final int col, final int n, final String path, final boolean[][] visited, final List results) { + // Base case: reached destination + if (row == n - 1 && col == n - 1) { + results.add(path); + return; + } + + // Mark current cell as visited + visited[row][col] = true; + + // Explore in alphabetical order: Down, Left, Right, Up + // Down + if (isSafe(maze, row + 1, col, n, visited)) { + solve(maze, row + 1, col, n, path + 'D', visited, results); + } + // Left + if (isSafe(maze, row, col - 1, n, visited)) { + solve(maze, row, col - 1, n, path + 'L', visited, results); + } + // Right + if (isSafe(maze, row, col + 1, n, visited)) { + solve(maze, row, col + 1, n, path + 'R', visited, results); + } + // Up + if (isSafe(maze, row - 1, col, n, visited)) { + solve(maze, row - 1, col, n, path + 'U', visited, results); + } + + // Backtrack: unmark current cell + visited[row][col] = false; + } + + /** + * Checks whether moving to {@code (row, col)} is valid. + * + * @param maze the binary maze + * @param row target row + * @param col target column + * @param n maze dimension + * @param visited tracks visited cells for the current path + * @return {@code true} if the cell is within bounds, open, and not yet visited + */ + private static boolean isSafe(final int[][] maze, final int row, final int col, final int n, final boolean[][] visited) { + return row >= 0 && row < n && col >= 0 && col < n && maze[row][col] == 1 && !visited[row][col]; + } +} diff --git a/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java b/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java new file mode 100644 index 000000000000..543fe2d02b50 --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/SudokuSolver.java @@ -0,0 +1,157 @@ +package com.thealgorithms.backtracking; + +/** + * Sudoku Solver using Backtracking Algorithm + * Solves a 9x9 Sudoku puzzle by filling empty cells with valid digits (1-9) + * + * @author Navadeep0007 + */ +public final class SudokuSolver { + + private static final int GRID_SIZE = 9; + private static final int SUBGRID_SIZE = 3; + private static final int EMPTY_CELL = 0; + + private SudokuSolver() { + // Utility class, prevent instantiation + } + + /** + * Solves the Sudoku puzzle using backtracking + * + * @param board 9x9 Sudoku board with 0 representing empty cells + * @return true if puzzle is solved, false otherwise + */ + public static boolean solveSudoku(int[][] board) { + if (board == null || board.length != GRID_SIZE) { + return false; + } + + for (int row = 0; row < GRID_SIZE; row++) { + if (board[row].length != GRID_SIZE) { + return false; + } + } + + return solve(board); + } + + /** + * Recursive helper method to solve the Sudoku puzzle + * + * @param board the Sudoku board + * @return true if solution is found, false otherwise + */ + private static boolean solve(int[][] board) { + for (int row = 0; row < GRID_SIZE; row++) { + for (int col = 0; col < GRID_SIZE; col++) { + if (board[row][col] == EMPTY_CELL) { + for (int number = 1; number <= GRID_SIZE; number++) { + if (isValidPlacement(board, row, col, number)) { + board[row][col] = number; + + if (solve(board)) { + return true; + } + + // Backtrack + board[row][col] = EMPTY_CELL; + } + } + return false; + } + } + } + return true; + } + + /** + * Checks if placing a number at given position is valid + * + * @param board the Sudoku board + * @param row row index + * @param col column index + * @param number number to place (1-9) + * @return true if placement is valid, false otherwise + */ + private static boolean isValidPlacement(int[][] board, int row, int col, int number) { + return !isNumberInRow(board, row, number) && !isNumberInColumn(board, col, number) && !isNumberInSubgrid(board, row, col, number); + } + + /** + * Checks if number exists in the given row + * + * @param board the Sudoku board + * @param row row index + * @param number number to check + * @return true if number exists in row, false otherwise + */ + private static boolean isNumberInRow(int[][] board, int row, int number) { + for (int col = 0; col < GRID_SIZE; col++) { + if (board[row][col] == number) { + return true; + } + } + return false; + } + + /** + * Checks if number exists in the given column + * + * @param board the Sudoku board + * @param col column index + * @param number number to check + * @return true if number exists in column, false otherwise + */ + private static boolean isNumberInColumn(int[][] board, int col, int number) { + for (int row = 0; row < GRID_SIZE; row++) { + if (board[row][col] == number) { + return true; + } + } + return false; + } + + /** + * Checks if number exists in the 3x3 subgrid + * + * @param board the Sudoku board + * @param row row index + * @param col column index + * @param number number to check + * @return true if number exists in subgrid, false otherwise + */ + private static boolean isNumberInSubgrid(int[][] board, int row, int col, int number) { + int subgridRowStart = row - row % SUBGRID_SIZE; + int subgridColStart = col - col % SUBGRID_SIZE; + + for (int i = subgridRowStart; i < subgridRowStart + SUBGRID_SIZE; i++) { + for (int j = subgridColStart; j < subgridColStart + SUBGRID_SIZE; j++) { + if (board[i][j] == number) { + return true; + } + } + } + return false; + } + + /** + * Prints the Sudoku board + * + * @param board the Sudoku board + */ + public static void printBoard(int[][] board) { + for (int row = 0; row < GRID_SIZE; row++) { + if (row % SUBGRID_SIZE == 0 && row != 0) { + System.out.println("-----------"); + } + for (int col = 0; col < GRID_SIZE; col++) { + if (col % SUBGRID_SIZE == 0 && col != 0) { + System.out.print("|"); + } + System.out.print(board[row][col]); + } + System.out.println(); + } + } +} diff --git a/src/main/java/com/thealgorithms/backtracking/UniquePermutation.java b/src/main/java/com/thealgorithms/backtracking/UniquePermutation.java new file mode 100644 index 000000000000..4804e247ab03 --- /dev/null +++ b/src/main/java/com/thealgorithms/backtracking/UniquePermutation.java @@ -0,0 +1,62 @@ +package com.thealgorithms.backtracking; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Generates all UNIQUE permutations of a string, even when duplicate characters exist. + * + * Example: + * Input: "AAB" + * Output: ["AAB", "ABA", "BAA"] + * + * Time Complexity: O(n! * n) + */ +public final class UniquePermutation { + + private UniquePermutation() { + // Prevent instantiation + throw new UnsupportedOperationException("Utility class"); + } + + public static List generateUniquePermutations(String input) { + List result = new ArrayList<>(); + if (input == null) { + return result; + } + + char[] chars = input.toCharArray(); + Arrays.sort(chars); // important: sort to detect duplicates + + backtrack(chars, new boolean[chars.length], new StringBuilder(), result); + return result; + } + + private static void backtrack(char[] chars, boolean[] used, StringBuilder current, List result) { + + if (current.length() == chars.length) { + result.add(current.toString()); + return; + } + + for (int i = 0; i < chars.length; i++) { + + // skip duplicates + if (i > 0 && chars[i] == chars[i - 1] && !used[i - 1]) { + continue; + } + + if (!used[i]) { + used[i] = true; + current.append(chars[i]); + + backtrack(chars, used, current, result); + + // undo changes + used[i] = false; + current.deleteCharAt(current.length() - 1); + } + } + } +} diff --git a/src/main/java/com/thealgorithms/backtracking/WordSearch.java b/src/main/java/com/thealgorithms/backtracking/WordSearch.java index 174ca90ccaab..452f17b6ace6 100644 --- a/src/main/java/com/thealgorithms/backtracking/WordSearch.java +++ b/src/main/java/com/thealgorithms/backtracking/WordSearch.java @@ -35,22 +35,6 @@ * - Stack space for the recursive DFS function, where L is the maximum depth of recursion (length of the word). */ public class WordSearch { - private final int[] dx = {0, 0, 1, -1}; - private final int[] dy = {1, -1, 0, 0}; - private boolean[][] visited; - private char[][] board; - private String word; - - /** - * Checks if the given (x, y) coordinates are valid positions in the board. - * - * @param x The row index. - * @param y The column index. - * @return True if the coordinates are within the bounds of the board; false otherwise. - */ - private boolean isValid(int x, int y) { - return x >= 0 && x < board.length && y >= 0 && y < board[0].length; - } /** * Performs Depth First Search (DFS) from the cell (x, y) @@ -58,28 +42,27 @@ private boolean isValid(int x, int y) { * * @param x The current row index. * @param y The current column index. - * @param nextIdx The index of the next character in the word to be matched. + * @param idx The index of the next character in the word to be matched. * @return True if a valid path is found to match the remaining characters of the word; false otherwise. */ - private boolean doDFS(int x, int y, int nextIdx) { - visited[x][y] = true; - if (nextIdx == word.length()) { + + private boolean dfs(char[][] board, int x, int y, String word, int idx) { + if (idx == word.length()) { return true; } - for (int i = 0; i < 4; ++i) { - int xi = x + dx[i]; - int yi = y + dy[i]; - if (isValid(xi, yi) && board[xi][yi] == word.charAt(nextIdx) && !visited[xi][yi]) { - boolean exists = doDFS(xi, yi, nextIdx + 1); - if (exists) { - return true; - } - } + if (x < 0 || y < 0 || x >= board.length || y >= board[0].length || board[x][y] != word.charAt(idx)) { + return false; } - visited[x][y] = false; // Backtrack - return false; + char temp = board[x][y]; + board[x][y] = '#'; + + boolean found = dfs(board, x + 1, y, word, idx + 1) || dfs(board, x - 1, y, word, idx + 1) || dfs(board, x, y + 1, word, idx + 1) || dfs(board, x, y - 1, word, idx + 1); + + board[x][y] = temp; + + return found; } /** @@ -90,20 +73,21 @@ private boolean doDFS(int x, int y, int nextIdx) { * @param word The target word to search for in the board. * @return True if the word exists in the board; false otherwise. */ + public boolean exist(char[][] board, String word) { - this.board = board; - this.word = word; - for (int i = 0; i < board.length; ++i) { - for (int j = 0; j < board[0].length; ++j) { - if (board[i][j] == word.charAt(0)) { - visited = new boolean[board.length][board[0].length]; - boolean exists = doDFS(i, j, 1); - if (exists) { - return true; - } + + int m = board.length; + int n = board[0].length; + + // DFS search + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (board[i][j] == word.charAt(0) && dfs(board, i, j, word, 0)) { + return true; } } } + return false; } } diff --git a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java index 0d6fd140c720..5038d44079ec 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/BinaryPalindromeCheck.java @@ -9,6 +9,12 @@ *

* * @author Hardvan + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber */ public final class BinaryPalindromeCheck { private BinaryPalindromeCheck() { diff --git a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java index 242f35fc35f2..7df522ca8f69 100644 --- a/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java +++ b/src/main/java/com/thealgorithms/bitmanipulation/CountSetBits.java @@ -1,79 +1,79 @@ package com.thealgorithms.bitmanipulation; -public class CountSetBits { +/** + * Utility class to count total set bits from 1 to N + * A set bit is a bit in binary representation that is 1 + * + * @author navadeep + */ +public final class CountSetBits { + + private CountSetBits() { + // Utility class, prevent instantiation + } /** - * The below algorithm is called as Brian Kernighan's algorithm - * We can use Brian Kernighan’s algorithm to improve the above naive algorithm’s performance. - The idea is to only consider the set bits of an integer by turning off its rightmost set bit - (after counting it), so the next iteration of the loop considers the next rightmost bit. - - The expression n & (n-1) can be used to turn off the rightmost set bit of a number n. This - works as the expression n-1 flips all the bits after the rightmost set bit of n, including the - rightmost set bit itself. Therefore, n & (n-1) results in the last bit flipped of n. - - For example, consider number 52, which is 00110100 in binary, and has a total 3 bits set. - - 1st iteration of the loop: n = 52 - - 00110100 & (n) - 00110011 (n-1) - ~~~~~~~~ - 00110000 + * Counts total number of set bits in all numbers from 1 to n + * Time Complexity: O(log n) + * + * @param n the upper limit (inclusive) + * @return total count of set bits from 1 to n + * @throws IllegalArgumentException if n is negative + */ + public static int countSetBits(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative"); + } + if (n == 0) { + return 0; + } - 2nd iteration of the loop: n = 48 + // Find the largest power of 2 <= n + int x = largestPowerOf2InNumber(n); - 00110000 & (n) - 00101111 (n-1) - ~~~~~~~~ - 00100000 + // Total bits at position x: x * 2^(x-1) + int bitsAtPositionX = x * (1 << (x - 1)); + // Remaining numbers after 2^x + int remainingNumbers = n - (1 << x) + 1; - 3rd iteration of the loop: n = 32 + // Recursively count for the rest + int rest = countSetBits(n - (1 << x)); - 00100000 & (n) - 00011111 (n-1) - ~~~~~~~~ - 00000000 (n = 0) + return bitsAtPositionX + remainingNumbers + rest; + } - * @param num takes Long number whose number of set bit is to be found - * @return the count of set bits in the binary equivalent - */ - public long countSetBits(long num) { - long cnt = 0; - while (num > 0) { - cnt++; - num &= (num - 1); + /** + * Finds the position of the most significant bit in n + * + * @param n the number + * @return position of MSB (0-indexed from right) + */ + private static int largestPowerOf2InNumber(int n) { + int position = 0; + while ((1 << position) <= n) { + position++; } - return cnt; + return position - 1; } /** - * This approach takes O(1) running time to count the set bits, but requires a pre-processing. + * Alternative naive approach - counts set bits by iterating through all numbers + * Time Complexity: O(n log n) * - * So, we divide our 32-bit input into 8-bit chunks, with four chunks. We have 8 bits in each chunk. - * - * Then the range is from 0-255 (0 to 2^7). - * So, we may need to count set bits from 0 to 255 in individual chunks. - * - * @param num takes a long number - * @return the count of set bits in the binary equivalent + * @param n the upper limit (inclusive) + * @return total count of set bits from 1 to n */ - public int lookupApproach(int num) { - int[] table = new int[256]; - table[0] = 0; - - for (int i = 1; i < 256; i++) { - table[i] = (i & 1) + table[i >> 1]; // i >> 1 equals to i/2 + public static int countSetBitsNaive(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative"); } - int res = 0; - for (int i = 0; i < 4; i++) { - res += table[num & 0xff]; - num >>= 8; + int count = 0; + for (int i = 1; i <= n; i++) { + count += Integer.bitCount(i); } - - return res; + return count; } } diff --git a/src/main/java/com/thealgorithms/ciphers/AESEncryption.java b/src/main/java/com/thealgorithms/ciphers/AESEncryption.java index 14582205442f..6f155e73f47d 100644 --- a/src/main/java/com/thealgorithms/ciphers/AESEncryption.java +++ b/src/main/java/com/thealgorithms/ciphers/AESEncryption.java @@ -38,7 +38,7 @@ public static void main(String[] args) throws Exception { System.out.println("Original Text:" + plainText); System.out.println("AES Key (Hex Form):" + bytesToHex(secKey.getEncoded())); System.out.println("Encrypted Text (Hex Form):" + bytesToHex(cipherText)); - System.out.println("Descrypted Text:" + decryptedText); + System.out.println("Decryption successful. Decrypted text matches original: " + decryptedText.equals(plainText)); } /** diff --git a/src/main/java/com/thealgorithms/ciphers/Caesar.java b/src/main/java/com/thealgorithms/ciphers/Caesar.java index 23535bc2b5d2..7a5e70e6eb78 100644 --- a/src/main/java/com/thealgorithms/ciphers/Caesar.java +++ b/src/main/java/com/thealgorithms/ciphers/Caesar.java @@ -9,8 +9,8 @@ * @author khalil2535 */ public class Caesar { - private static char normalizeShift(final int shift) { - return (char) (shift % 26); + private static int normalizeShift(final int shift) { + return ((shift % 26) + 26) % 26; } /** @@ -22,21 +22,18 @@ private static char normalizeShift(final int shift) { public String encode(String message, int shift) { StringBuilder encoded = new StringBuilder(); - final char shiftChar = normalizeShift(shift); + final int shiftChar = normalizeShift(shift); final int length = message.length(); for (int i = 0; i < length; i++) { - // int current = message.charAt(i); //using char to shift characters because - // ascii - // is in-order latin alphabet - char current = message.charAt(i); // Java law : char + int = char + final char current = message.charAt(i); if (isCapitalLatinLetter(current)) { - current += shiftChar; - encoded.append((char) (current > 'Z' ? current - 26 : current)); // 26 = number of latin letters + final int shifted = current + shiftChar; + encoded.append((char) (shifted > 'Z' ? shifted - 26 : shifted)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current += shiftChar; - encoded.append((char) (current > 'z' ? current - 26 : current)); // 26 = number of latin letters + final int shifted = current + shiftChar; + encoded.append((char) (shifted > 'z' ? shifted - 26 : shifted)); // 26 = number of latin letters } else { encoded.append(current); } @@ -53,17 +50,17 @@ public String encode(String message, int shift) { public String decode(String encryptedMessage, int shift) { StringBuilder decoded = new StringBuilder(); - final char shiftChar = normalizeShift(shift); + final int shiftChar = normalizeShift(shift); final int length = encryptedMessage.length(); for (int i = 0; i < length; i++) { - char current = encryptedMessage.charAt(i); + final char current = encryptedMessage.charAt(i); if (isCapitalLatinLetter(current)) { - current -= shiftChar; - decoded.append((char) (current < 'A' ? current + 26 : current)); // 26 = number of latin letters + final int shifted = current - shiftChar; + decoded.append((char) (shifted < 'A' ? shifted + 26 : shifted)); // 26 = number of latin letters } else if (isSmallLatinLetter(current)) { - current -= shiftChar; - decoded.append((char) (current < 'a' ? current + 26 : current)); // 26 = number of latin letters + final int shifted = current - shiftChar; + decoded.append((char) (shifted < 'a' ? shifted + 26 : shifted)); // 26 = number of latin letters } else { decoded.append(current); } diff --git a/src/main/java/com/thealgorithms/ciphers/ElGamalCipher.java b/src/main/java/com/thealgorithms/ciphers/ElGamalCipher.java new file mode 100644 index 000000000000..6383caa59b1f --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/ElGamalCipher.java @@ -0,0 +1,174 @@ +package com.thealgorithms.ciphers; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * ElGamal Encryption Algorithm Implementation. + * + *

+ * ElGamal is an asymmetric key encryption algorithm for public-key cryptography + * based on the Diffie–Hellman key exchange. It relies on the difficulty + * of computing discrete logarithms in a cyclic group. + *

+ * + *

+ * Key Features: + *

    + *
  • Uses Safe Primes (p = 2q + 1) to ensure group security.
  • + *
  • Verifies the generator is a primitive root modulo p.
  • + *
  • Stateless design using Java Records.
  • + *
  • SecureRandom for all cryptographic operations.
  • + *
+ *

+ * + * @author Chahat Sandhu, singhc7 + * @see ElGamal Encryption (Wikipedia) + * @see Safe Primes + */ +public final class ElGamalCipher { + + private static final SecureRandom RANDOM = new SecureRandom(); + private static final int PRIME_CERTAINTY = 40; + private static final int MIN_BIT_LENGTH = 256; + + private ElGamalCipher() { + } + + /** + * A container for the Public and Private keys. + * + * @param p The prime modulus. + * @param g The generator (primitive root). + * @param y The public key component (g^x mod p). + * @param x The private key. + */ + public record KeyPair(BigInteger p, BigInteger g, BigInteger y, BigInteger x) { + } + + /** + * Container for the encryption result. + * + * @param a The first component (g^k mod p). + * @param b The second component (y^k * m mod p). + */ + public record CipherText(BigInteger a, BigInteger b) { + } + + /** + * Generates a valid ElGamal KeyPair using a Safe Prime. + * + * @param bitLength The bit length of the prime modulus p. Must be at least 256. + * @return A valid KeyPair (p, g, y, x). + * @throws IllegalArgumentException if bitLength is too small. + */ + public static KeyPair generateKeys(int bitLength) { + if (bitLength < MIN_BIT_LENGTH) { + throw new IllegalArgumentException("Bit length must be at least " + MIN_BIT_LENGTH + " for security."); + } + + BigInteger p; + BigInteger q; + BigInteger g; + BigInteger x; + BigInteger y; + + // Generate Safe Prime p = 2q + 1 + do { + q = new BigInteger(bitLength - 1, PRIME_CERTAINTY, RANDOM); + p = q.multiply(BigInteger.TWO).add(BigInteger.ONE); + } while (!p.isProbablePrime(PRIME_CERTAINTY)); + + // Find a Generator g (Primitive Root modulo p) + do { + g = new BigInteger(bitLength, RANDOM).mod(p.subtract(BigInteger.TWO)).add(BigInteger.TWO); + } while (!isValidGenerator(g, p, q)); + + // Generate Private Key x in range [2, p-2] + do { + x = new BigInteger(bitLength, RANDOM); + } while (x.compareTo(BigInteger.TWO) < 0 || x.compareTo(p.subtract(BigInteger.TWO)) > 0); + + // Compute Public Key y = g^x mod p + y = g.modPow(x, p); + + return new KeyPair(p, g, y, x); + } + + /** + * Encrypts a message using the public key. + * + * @param message The message converted to BigInteger. + * @param p The prime modulus. + * @param g The generator. + * @param y The public key component. + * @return The CipherText pair (a, b). + * @throws IllegalArgumentException if inputs are null, negative, or message >= p. + */ + public static CipherText encrypt(BigInteger message, BigInteger p, BigInteger g, BigInteger y) { + if (message == null || p == null || g == null || y == null) { + throw new IllegalArgumentException("Inputs cannot be null."); + } + if (message.compareTo(BigInteger.ZERO) < 0) { + throw new IllegalArgumentException("Message must be non-negative."); + } + if (message.compareTo(p) >= 0) { + throw new IllegalArgumentException("Message must be smaller than the prime modulus p."); + } + + BigInteger k; + BigInteger pMinus1 = p.subtract(BigInteger.ONE); + + // Select ephemeral key k such that 1 < k < p-1 and gcd(k, p-1) = 1 + do { + k = new BigInteger(p.bitLength(), RANDOM); + } while (k.compareTo(BigInteger.ONE) <= 0 || k.compareTo(pMinus1) >= 0 || !k.gcd(pMinus1).equals(BigInteger.ONE)); + + BigInteger a = g.modPow(k, p); + BigInteger b = y.modPow(k, p).multiply(message).mod(p); + + return new CipherText(a, b); + } + + /** + * Decrypts a ciphertext using the private key. + * + * @param cipher The CipherText (a, b). + * @param x The private key. + * @param p The prime modulus. + * @return The decrypted message as BigInteger. + * @throws IllegalArgumentException if inputs are null. + */ + public static BigInteger decrypt(CipherText cipher, BigInteger x, BigInteger p) { + if (cipher == null || x == null || p == null) { + throw new IllegalArgumentException("Inputs cannot be null."); + } + + BigInteger a = cipher.a(); + BigInteger b = cipher.b(); + + BigInteger s = a.modPow(x, p); + BigInteger sInverse = s.modInverse(p); + + return b.multiply(sInverse).mod(p); + } + + /** + * Verifies if g is a valid generator for safe prime p = 2q + 1. + * + * @param g The candidate generator. + * @param p The safe prime. + * @param q The Sophie Germain prime (p-1)/2. + * @return True if g is a primitive root, False otherwise. + */ + private static boolean isValidGenerator(BigInteger g, BigInteger p, BigInteger q) { + // Fix: Must use braces {} for all if statements + if (g.equals(BigInteger.ONE)) { + return false; + } + if (g.modPow(BigInteger.TWO, p).equals(BigInteger.ONE)) { + return false; + } + return !g.modPow(q, p).equals(BigInteger.ONE); + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java b/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java new file mode 100644 index 000000000000..7733f5cb46f2 --- /dev/null +++ b/src/main/java/com/thealgorithms/ciphers/OneTimePadCipher.java @@ -0,0 +1,89 @@ +package com.thealgorithms.ciphers; + +import java.security.SecureRandom; +import java.util.Objects; + +/** + * One-Time Pad (OTP) cipher implementation. + * + *

The One-Time Pad is information-theoretically secure if: + *

    + *
  • The key is truly random.
  • + *
  • The key length is at least as long as the plaintext.
  • + *
  • The key is used only once and kept secret.
  • + *
+ * + *

This implementation is for educational purposes only and should not be + * used in production systems. + */ +public final class OneTimePadCipher { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private OneTimePadCipher() { + // utility class + } + + /** + * Generates a random key of the given length in bytes. + * + * @param length the length of the key in bytes, must be non-negative + * @return a new random key + * @throws IllegalArgumentException if length is negative + */ + public static byte[] generateKey(int length) { + if (length < 0) { + throw new IllegalArgumentException("length must be non-negative"); + } + byte[] key = new byte[length]; + RANDOM.nextBytes(key); + return key; + } + + /** + * Encrypts the given plaintext bytes using the provided key. + *

The key length must be exactly the same as the plaintext length. + * + * @param plaintext the plaintext bytes, must not be {@code null} + * @param key the one-time pad key bytes, must not be {@code null} + * @return the ciphertext bytes + * @throws IllegalArgumentException if the key length does not match plaintext length + * @throws NullPointerException if plaintext or key is {@code null} + */ + public static byte[] encrypt(byte[] plaintext, byte[] key) { + validateInputs(plaintext, key); + return xor(plaintext, key); + } + + /** + * Decrypts the given ciphertext bytes using the provided key. + *

For a One-Time Pad, decryption is identical to encryption: + * {@code plaintext = ciphertext XOR key}. + * + * @param ciphertext the ciphertext bytes, must not be {@code null} + * @param key the one-time pad key bytes, must not be {@code null} + * @return the decrypted plaintext bytes + * @throws IllegalArgumentException if the key length does not match ciphertext length + * @throws NullPointerException if ciphertext or key is {@code null} + */ + public static byte[] decrypt(byte[] ciphertext, byte[] key) { + validateInputs(ciphertext, key); + return xor(ciphertext, key); + } + + private static void validateInputs(byte[] input, byte[] key) { + Objects.requireNonNull(input, "input must not be null"); + Objects.requireNonNull(key, "key must not be null"); + if (input.length != key.length) { + throw new IllegalArgumentException("Key length must match input length"); + } + } + + private static byte[] xor(byte[] data, byte[] key) { + byte[] result = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + result[i] = (byte) (data[i] ^ key[i]); + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java index f81252980468..324dc88e4f19 100644 --- a/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java +++ b/src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java @@ -1,7 +1,5 @@ package com.thealgorithms.ciphers; -import java.util.Arrays; - /** * The rail fence cipher (also called a zigzag cipher) is a classical type of transposition cipher. * It derives its name from the manner in which encryption is performed, in analogy to a fence built with horizontal rails. @@ -14,28 +12,27 @@ public class RailFenceCipher { // Encrypts the input string using the rail fence cipher method with the given number of rails. public String encrypt(String str, int rails) { + checkInput(str, rails); + // Base case of single rail or rails are more than the number of characters in the string if (rails == 1 || rails >= str.length()) { return str; } - // Boolean flag to determine if the movement is downward or upward in the rail matrix. + // Boolean flag to determine if the movement is downward or upward in the rail pattern. boolean down = true; - // Create a 2D array to represent the rails (rows) and the length of the string (columns). - char[][] strRail = new char[rails][str.length()]; - - // Initialize all positions in the rail matrix with a placeholder character ('\n'). + // Collect the characters of every rail separately. Using one buffer per rail (instead of a + // rails x length matrix with a placeholder character) keeps every character of the input, + // including characters that would otherwise be indistinguishable from the placeholder. + StringBuilder[] railBuffers = new StringBuilder[rails]; for (int i = 0; i < rails; i++) { - Arrays.fill(strRail[i], '\n'); + railBuffers[i] = new StringBuilder(); } - int row = 0; // Start at the first row - int col = 0; // Start at the first column + int row = 0; // Start at the first rail - int i = 0; - - // Fill the rail matrix with characters from the string based on the rail pattern. - while (col < str.length()) { + // Distribute the characters of the string over the rails following the zigzag pattern. + for (int i = 0; i < str.length(); i++) { // Change direction to down when at the first row. if (row == 0) { down = true; @@ -45,33 +42,28 @@ else if (row == rails - 1) { down = false; } - // Place the character in the current position of the rail matrix. - strRail[row][col] = str.charAt(i); - col++; // Move to the next column. + // Append the character to the rail it belongs to. + railBuffers[row].append(str.charAt(i)); // Move to the next row based on the direction. if (down) { row++; } else { row--; } - - i++; } - // Construct the encrypted string by reading characters row by row. - StringBuilder encryptedString = new StringBuilder(); - for (char[] chRow : strRail) { - for (char ch : chRow) { - if (ch != '\n') { - encryptedString.append(ch); - } - } + // Construct the encrypted string by reading the rails top to bottom. + StringBuilder encryptedString = new StringBuilder(str.length()); + for (StringBuilder railBuffer : railBuffers) { + encryptedString.append(railBuffer); } return encryptedString.toString(); } // Decrypts the input string using the rail fence cipher method with the given number of rails. public String decrypt(String str, int rails) { + checkInput(str, rails); + // Base case of single rail or rails are more than the number of characters in the string if (rails == 1 || rails >= str.length()) { return str; @@ -144,4 +136,14 @@ else if (row == rails - 1) { return decryptedString.toString(); } + + // Rejects inputs the zigzag pattern is not defined for. + private static void checkInput(String str, int rails) { + if (str == null) { + throw new IllegalArgumentException("Input string must not be null"); + } + if (rails <= 0) { + throw new IllegalArgumentException("Number of rails must be positive, but was " + rails); + } + } } diff --git a/src/main/java/com/thealgorithms/compression/HuffmanCoding.java b/src/main/java/com/thealgorithms/compression/HuffmanCoding.java new file mode 100644 index 000000000000..d7f9d58d2429 --- /dev/null +++ b/src/main/java/com/thealgorithms/compression/HuffmanCoding.java @@ -0,0 +1,253 @@ +package com.thealgorithms.compression; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.PriorityQueue; + +/** + * Huffman Coding Compression Algorithm Implementation. + *

+ * Huffman Coding is a popular greedy algorithm used for lossless data compression. + * It reduces the overall size of data by assigning variable-length, prefix-free + * binary codes to input characters, ensuring that more frequent characters receive + * the shortest possible codes. + *

+ *

+ * Key Features: + *

    + *
  • Uses a PriorityQueue (min-heap) to efficiently construct the optimal prefix tree.
  • + *
  • Fail-fast design throws exceptions for unsupported characters and malformed binary payloads.
  • + *
  • Immutable internal dictionary state prevents external tampering with generated codes.
  • + *
  • Robust handling of edge cases, including single-character strings and incomplete sequences.
  • + *
+ *

+ * @author Chahat Sandhu, singhc7 + * @see Huffman Coding (Wikipedia) + */ +public class HuffmanCoding { + + private Node root; + private final Map huffmanCodes; + + /** + * Represents a node within the Huffman Tree. + * Implements {@link Comparable} to allow sorting by frequency in a PriorityQueue. + */ + private static class Node implements Comparable { + final char ch; + final int freq; + final Node left; + final Node right; + + /** + * Constructs a leaf node containing a specific character and its frequency. + * + * @param ch The character stored in this leaf. + * @param freq The frequency of occurrence of the character. + */ + Node(char ch, int freq) { + this.ch = ch; + this.freq = freq; + this.left = null; + this.right = null; + } + + /** + * Constructs an internal node that merges two child nodes. + * The character is defaulted to the null character ('\0'). + * + * @param freq The combined frequency of the left and right child nodes. + * @param left The left child node. + * @param right The right child node. + */ + Node(int freq, Node left, Node right) { + this.ch = '\0'; + this.freq = freq; + this.left = left; + this.right = right; + } + + /** + * Determines if the current node is a leaf (contains no children). + * + * @return {@code true} if both left and right children are null, {@code false} otherwise. + */ + boolean isLeaf() { + return left == null && right == null; + } + + /** + * Compares this node with another node based on their frequencies. + * Used by the PriorityQueue to maintain the min-heap property. + * + * @param other The other Node to compare against. + * @return A negative integer, zero, or a positive integer as this node's frequency + * is less than, equal to, or greater than the specified node's frequency. + */ + @Override + public int compareTo(Node other) { + return Integer.compare(this.freq, other.freq); + } + } + + /** + * Initializes the Huffman Tree and generates immutable prefix-free codes + * based on the character frequencies in the provided text. + * + * @param text The input string used to calculate frequencies and build the optimal tree. + * If null or empty, an empty tree and dictionary are created. + */ + public HuffmanCoding(String text) { + if (text == null || text.isEmpty()) { + this.huffmanCodes = Collections.emptyMap(); + return; + } + + Map tempCodes = new HashMap<>(); + buildTree(text); + generateCodes(root, "", tempCodes); + + if (tempCodes.size() == 1) { + tempCodes.put(root.ch, "0"); + } + + this.huffmanCodes = Collections.unmodifiableMap(tempCodes); + } + + /** + * Computes character frequencies and constructs the Huffman Tree using a min-heap. + * The optimal tree is built by repeatedly extracting the two lowest-frequency nodes + * and merging them until a single root node remains. + * + * @param text The input text to analyze. + */ + private void buildTree(String text) { + Map freqMap = new HashMap<>(); + for (char c : text.toCharArray()) { + freqMap.put(c, freqMap.getOrDefault(c, 0) + 1); + } + + PriorityQueue pq = new PriorityQueue<>(); + for (Map.Entry entry : freqMap.entrySet()) { + pq.add(new Node(entry.getKey(), entry.getValue())); + } + + while (pq.size() > 1) { + Node left = pq.poll(); + Node right = pq.poll(); + pq.add(new Node(left.freq + right.freq, left, right)); + } + + root = pq.poll(); + } + + /** + * Recursively traverses the Huffman Tree to generate prefix-free binary codes. + * Left traversals append a '0' to the code, while right traversals append a '1'. + * + * @param node The current node in the traversal. + * @param code The accumulated binary string for the current path. + * @param map The temporary dictionary to populate with the final character-to-code mappings. + */ + private void generateCodes(Node node, String code, Map map) { + if (node == null) { + return; + } + if (node.isLeaf()) { + map.put(node.ch, code); + return; + } + generateCodes(node.left, code + "0", map); + generateCodes(node.right, code + "1", map); + } + + /** + * Encodes the given plaintext string into a binary string using the generated Huffman dictionary. + * + * @param text The plaintext string to compress. + * @return A string of '0's and '1's representing the compressed data. + * Returns an empty string if the input is null or empty. + * @throws IllegalStateException If attempting to encode when the Huffman tree is empty. + * @throws IllegalArgumentException If the input text contains a character not present + * in the original text used to build the tree. + */ + public String encode(String text) { + if (text == null || text.isEmpty()) { + return ""; + } + if (root == null) { + throw new IllegalStateException("Huffman tree is empty."); + } + + StringBuilder sb = new StringBuilder(); + for (char c : text.toCharArray()) { + if (!huffmanCodes.containsKey(c)) { + throw new IllegalArgumentException(String.format("Character '%c' (U+%04X) not found in Huffman dictionary.", c, (int) c)); + } + sb.append(huffmanCodes.get(c)); + } + return sb.toString(); + } + + /** + * Decodes the given binary string back into the original plaintext using the Huffman Tree. + * Validates the integrity of the binary payload during traversal. + * + * @param encodedText The binary string of '0's and '1's to decompress. + * @return The reconstructed plaintext string. Returns an empty string if the input is null or empty. + * @throws IllegalStateException If attempting to decode when the Huffman tree is empty. + * @throws IllegalArgumentException If the binary string contains characters other than '0' or '1', + * or if the sequence ends abruptly without reaching a leaf node. + */ + public String decode(String encodedText) { + if (encodedText == null || encodedText.isEmpty()) { + return ""; + } + if (root == null) { + throw new IllegalStateException("Huffman tree is empty."); + } + + StringBuilder sb = new StringBuilder(); + + if (root.isLeaf()) { + for (char bit : encodedText.toCharArray()) { + if (bit != '0') { + throw new IllegalArgumentException("Invalid binary sequence for single-character tree."); + } + sb.append(root.ch); + } + return sb.toString(); + } + + Node current = root; + for (char bit : encodedText.toCharArray()) { + if (bit != '0' && bit != '1') { + throw new IllegalArgumentException("Encoded text contains invalid characters: " + bit); + } + + current = (bit == '0') ? current.left : current.right; + + if (current.isLeaf()) { + sb.append(current.ch); + current = root; + } + } + + if (current != root) { + throw new IllegalArgumentException("Malformed encoded string: incomplete sequence ending."); + } + + return sb.toString(); + } + + /** + * Retrieves the generated Huffman dictionary mapping characters to their binary codes. + * + * @return An unmodifiable map containing the character-to-binary-code mappings to prevent + * external mutation of the algorithm's state. + */ + public Map getHuffmanCodes() { + return huffmanCodes; + } +} diff --git a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java index 7a9448fd8fe7..314e7fba38a3 100644 --- a/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java +++ b/src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java @@ -1,3 +1,4 @@ + package com.thealgorithms.conversions; import java.util.Arrays; diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java index 5219c4ba7f4e..fb4411b399a3 100644 --- a/src/main/java/com/thealgorithms/conversions/Base64.java +++ b/src/main/java/com/thealgorithms/conversions/Base64.java @@ -119,8 +119,15 @@ public static byte[] decode(String input) { // 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)"); + if (firstPadding != -1) { + if (firstPadding < input.length() - 2) { + throw new IllegalArgumentException("Padding '=' can only appear at the end (last 1 or 2 characters)"); + } + for (int i = firstPadding; i < input.length(); i++) { + if (input.charAt(i) != '=') { + throw new IllegalArgumentException("A padding '=' must not be followed by a non-padding character"); + } + } } List result = new ArrayList<>(); diff --git a/src/main/java/com/thealgorithms/conversions/TemperatureConverter.java b/src/main/java/com/thealgorithms/conversions/TemperatureConverter.java new file mode 100644 index 000000000000..901db17c665d --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/TemperatureConverter.java @@ -0,0 +1,46 @@ +package com.thealgorithms.conversions; + +/** + * A utility class to convert between different temperature units. + * + *

This class supports conversions between the following units: + *

    + *
  • Celsius
  • + *
  • Fahrenheit
  • + *
  • Kelvin
  • + *
+ * + *

This class is final and cannot be instantiated. + * + * @author krishna-medapati (https://github.com/krishna-medapati) + * @see Wikipedia: Temperature Conversion + */ +public final class TemperatureConverter { + + private TemperatureConverter() { + } + + public static double celsiusToFahrenheit(double celsius) { + return celsius * 9.0 / 5.0 + 32.0; + } + + public static double celsiusToKelvin(double celsius) { + return celsius + 273.15; + } + + public static double fahrenheitToCelsius(double fahrenheit) { + return (fahrenheit - 32.0) * 5.0 / 9.0; + } + + public static double fahrenheitToKelvin(double fahrenheit) { + return (fahrenheit - 32.0) * 5.0 / 9.0 + 273.15; + } + + public static double kelvinToCelsius(double kelvin) { + return kelvin - 273.15; + } + + public static double kelvinToFahrenheit(double kelvin) { + return (kelvin - 273.15) * 9.0 / 5.0 + 32.0; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java index cd5dc580b694..dbdb2d806209 100644 --- a/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java +++ b/src/main/java/com/thealgorithms/datastructures/dynamicarray/DynamicArray.java @@ -63,7 +63,8 @@ public void add(final E element) { * * @param index the index at which the element is to be placed * @param element the element to be inserted at the specified index - * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the number of elements + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or + * equal to the number of elements */ public void put(final int index, E element) { if (index < 0) { @@ -82,7 +83,8 @@ public void put(final int index, E element) { * * @param index the index of the element to retrieve * @return the element at the specified index - * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or + * equal to the current size */ @SuppressWarnings("unchecked") public E get(final int index) { @@ -97,7 +99,8 @@ public E get(final int index) { * * @param index the index of the element to be removed * @return the element that was removed from the array - * @throws IndexOutOfBoundsException if index is less than 0 or greater than or equal to the current size + * @throws IndexOutOfBoundsException if index is less than 0 or greater than or + * equal to the current size */ public E remove(final int index) { if (index < 0 || index >= size) { @@ -127,6 +130,21 @@ public boolean isEmpty() { return size == 0; } + /** + * Checks whether the array contains the specified element. + * + * @param element the element to check for + * @return true if the array contains the specified element, false otherwise + */ + public boolean contains(final E element) { + for (int i = 0; i < size; i++) { + if (Objects.equals(elements[i], element)) { + return true; + } + } + return false; + } + /** * Returns a sequential stream with this collection as its source. * @@ -137,7 +155,8 @@ public Stream stream() { } /** - * Ensures that the array has enough capacity to hold the specified number of elements. + * Ensures that the array has enough capacity to hold the specified number of + * elements. * * @param minCapacity the minimum capacity required */ @@ -150,7 +169,8 @@ private void ensureCapacity(int minCapacity) { /** * Removes the element at the specified index without resizing the array. - * This method shifts any subsequent elements to the left and clears the last element. + * This method shifts any subsequent elements to the left and clears the last + * element. * * @param index the index of the element to remove */ @@ -163,7 +183,8 @@ private void fastRemove(int index) { } /** - * Returns a string representation of the array, including only the elements that are currently stored. + * Returns a string representation of the array, including only the elements + * that are currently stored. * * @return a string containing the elements in the array */ @@ -227,7 +248,9 @@ public E next() { /** * Removes the last element returned by this iterator. * - * @throws IllegalStateException if the next method has not yet been called, or the remove method has already been called after the last call to the next method + * @throws IllegalStateException if the next method has not yet been called, or + * the remove method has already been called after + * the last call to the next method */ @Override public void remove() { @@ -242,7 +265,8 @@ public void remove() { /** * Checks for concurrent modifications to the array during iteration. * - * @throws ConcurrentModificationException if the array has been modified structurally + * @throws ConcurrentModificationException if the array has been modified + * structurally */ private void checkForComodification() { if (modCount != expectedModCount) { @@ -251,7 +275,8 @@ private void checkForComodification() { } /** - * Performs the given action for each remaining element in the iterator until all elements have been processed. + * Performs the given action for each remaining element in the iterator until + * all elements have been processed. * * @param action the action to be performed for each element * @throws NullPointerException if the specified action is null diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java index 70699a9461f7..1b5f765843be 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithm.java @@ -1,6 +1,7 @@ package com.thealgorithms.datastructures.graphs; import java.util.Arrays; +import java.util.PriorityQueue; /** * Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph. @@ -18,6 +19,21 @@ public DijkstraAlgorithm(int vertexCount) { this.vertexCount = vertexCount; } + private static class Node implements Comparable { + int id; + int distance; + + Node(int id, int distance) { + this.id = id; + this.distance = distance; + } + + @Override + public int compareTo(Node other) { + return Integer.compare(this.distance, other.distance); + } + } + /** * Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices. * @@ -36,18 +52,25 @@ public int[] run(int[][] graph, int source) { int[] distances = new int[vertexCount]; boolean[] processed = new boolean[vertexCount]; + PriorityQueue unprocessed = new PriorityQueue<>(); Arrays.fill(distances, Integer.MAX_VALUE); - Arrays.fill(processed, false); distances[source] = 0; + unprocessed.add(new Node(source, 0)); + + while (!unprocessed.isEmpty()) { + Node current = unprocessed.poll(); + int u = current.id; - for (int count = 0; count < vertexCount - 1; count++) { - int u = getMinDistanceVertex(distances, processed); + if (processed[u]) { + continue; + } processed[u] = true; for (int v = 0; v < vertexCount; v++) { if (!processed[v] && graph[u][v] != 0 && distances[u] != Integer.MAX_VALUE && distances[u] + graph[u][v] < distances[v]) { distances[v] = distances[u] + graph[u][v]; + unprocessed.add(new Node(v, distances[v])); } } } @@ -56,27 +79,6 @@ public int[] run(int[][] graph, int source) { return distances; } - /** - * Finds the vertex with the minimum distance value from the set of vertices that have not yet been processed. - * - * @param distances The array of current shortest distances from the source vertex. - * @param processed The array indicating whether each vertex has been processed. - * @return The index of the vertex with the minimum distance value. - */ - private int getMinDistanceVertex(int[] distances, boolean[] processed) { - int min = Integer.MAX_VALUE; - int minIndex = -1; - - for (int v = 0; v < vertexCount; v++) { - if (!processed[v] && distances[v] <= min) { - min = distances[v]; - minIndex = v; - } - } - - return minIndex; - } - /** * Prints the shortest distances from the source vertex to all other vertices. * diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java b/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java deleted file mode 100644 index a686b808a970..000000000000 --- a/src/main/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithm.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.thealgorithms.datastructures.graphs; - -import java.util.Arrays; -import java.util.Set; -import java.util.TreeSet; -import org.apache.commons.lang3.tuple.Pair; - -/** - * Dijkstra's algorithm for finding the shortest path from a single source vertex to all other vertices in a graph. - */ -public class DijkstraOptimizedAlgorithm { - - private final int vertexCount; - - /** - * Constructs a Dijkstra object with the given number of vertices. - * - * @param vertexCount The number of vertices in the graph. - */ - public DijkstraOptimizedAlgorithm(int vertexCount) { - this.vertexCount = vertexCount; - } - - /** - * Executes Dijkstra's algorithm on the provided graph to find the shortest paths from the source vertex to all other vertices. - * - * The graph is represented as an adjacency matrix where {@code graph[i][j]} represents the weight of the edge from vertex {@code i} - * to vertex {@code j}. A value of 0 indicates no edge exists between the vertices. - * - * @param graph The graph represented as an adjacency matrix. - * @param source The source vertex. - * @return An array where the value at each index {@code i} represents the shortest distance from the source vertex to vertex {@code i}. - * @throws IllegalArgumentException if the source vertex is out of range. - */ - public int[] run(int[][] graph, int source) { - if (source < 0 || source >= vertexCount) { - throw new IllegalArgumentException("Incorrect source"); - } - - int[] distances = new int[vertexCount]; - boolean[] processed = new boolean[vertexCount]; - Set> unprocessed = new TreeSet<>(); - - Arrays.fill(distances, Integer.MAX_VALUE); - Arrays.fill(processed, false); - distances[source] = 0; - unprocessed.add(Pair.of(0, source)); - - while (!unprocessed.isEmpty()) { - Pair distanceAndU = unprocessed.iterator().next(); - unprocessed.remove(distanceAndU); - int u = distanceAndU.getRight(); - processed[u] = true; - - for (int v = 0; v < vertexCount; v++) { - if (!processed[v] && graph[u][v] != 0 && distances[u] != Integer.MAX_VALUE && distances[u] + graph[u][v] < distances[v]) { - unprocessed.remove(Pair.of(distances[v], v)); - distances[v] = distances[u] + graph[u][v]; - unprocessed.add(Pair.of(distances[v], v)); - } - } - } - - return distances; - } -} diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java b/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java new file mode 100644 index 000000000000..3ce8696f55ff --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/graphs/RottingOranges.java @@ -0,0 +1,114 @@ +package com.thealgorithms.datastructures.graphs; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * Multi-source Breadth-First Search (BFS) implementation for the Rotting Oranges problem. + * + *

Algorithm explanation: + * https://en.wikipedia.org/wiki/Breadth-first_search + * + *

Problem reference: + * https://leetcode.com/problems/rotting-oranges/ + * + *

Given a grid where: + *

    + *
  • 0 represents an empty cell
  • + *
  • 1 represents a fresh orange
  • + *
  • 2 represents a rotten orange
  • + *
+ * + *

Returns the minimum number of minutes required for all fresh oranges + * to become rotten. Returns {@code -1} if it is impossible. + * + *

Time Complexity: O(m × n) + *
Space Complexity: O(m × n) + */ +public class RottingOranges { + + private static final int[] DEL_ROW = {-1, 0, 1, 0}; + private static final int[] DEL_COL = {0, 1, 0, -1}; + + private static final class Cell { + private final int row; + private final int col; + private final int minute; + + Cell(int row, int col, int minute) { + this.row = row; + this.col = col; + this.minute = minute; + } + } + + /** + * Executes the Rotting Oranges algorithm. + * + * @param grid the input grid + * @return minimum minutes required to rot all fresh oranges, + * or -1 if impossible + */ + public int run(int[][] grid) { + + if (grid == null || grid.length == 0 || grid[0].length == 0) { + return 0; + } + + int rows = grid.length; + int cols = grid[0].length; + + // Create a copy so original input is not modified + int[][] copy = new int[rows][cols]; + + for (int i = 0; i < rows; i++) { + copy[i] = grid[i].clone(); + } + + Queue queue = new LinkedList<>(); + int freshOranges = 0; + + // Find all rotten oranges and count fresh oranges + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + + if (copy[row][col] == 2) { + queue.offer(new Cell(row, col, 0)); + } else if (copy[row][col] == 1) { + freshOranges++; + } + } + } + + if (freshOranges == 0) { + return 0; + } + + int rottedFresh = 0; + int minutes = 0; + + // Multi-source BFS + while (!queue.isEmpty()) { + + Cell current = queue.poll(); + + minutes = Math.max(minutes, current.minute); + + for (int i = 0; i < 4; i++) { + + int newRow = current.row + DEL_ROW[i]; + int newCol = current.col + DEL_COL[i]; + + if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && copy[newRow][newCol] == 1) { + + copy[newRow][newCol] = 2; + rottedFresh++; + + queue.offer(new Cell(newRow, newCol, current.minute + 1)); + } + } + } + + return rottedFresh == freshOranges ? minutes : -1; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md b/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md index 252b06ea59b0..4400a97d8128 100644 --- a/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/Readme.md @@ -2,6 +2,8 @@ A hash map organizes data so you can quickly look up values for a given key. +> Note: The term “hash map” refers to the data structure concept, while `HashMap` refers specifically to Java’s implementation. + ## Strengths: - **Fast lookups**: Lookups take O(1) time on average. - **Flexible keys**: Most data types can be used for keys, as long as they're hashable. diff --git a/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMap.java b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMap.java new file mode 100644 index 000000000000..f6e09ec623b6 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMap.java @@ -0,0 +1,115 @@ +package com.thealgorithms.datastructures.hashmap.hashing; + +/** + * Immutable HashMap implementation using separate chaining. + * + *

This HashMap does not allow modification of existing instances. + * Any update operation returns a new ImmutableHashMap. + * + * @param key type + * @param value type + */ +public final class ImmutableHashMap { + + private static final int DEFAULT_CAPACITY = 16; + + private final Node[] table; + private final int size; + + /** + * Private constructor to enforce immutability. + */ + private ImmutableHashMap(Node[] table, int size) { + this.table = table; + this.size = size; + } + + /** + * Creates an empty ImmutableHashMap. + * + * @param key type + * @param value type + * @return empty ImmutableHashMap + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static ImmutableHashMap empty() { + Node[] table = (Node[]) new Node[DEFAULT_CAPACITY]; + return new ImmutableHashMap<>(table, 0); + } + + /** + * Returns a new ImmutableHashMap with the given key-value pair added. + * + * @param key key to add + * @param value value to associate + * @return new ImmutableHashMap instance + */ + public ImmutableHashMap put(K key, V value) { + Node[] newTable = table.clone(); + int index = hash(key); + + newTable[index] = new Node<>(key, value, newTable[index]); + return new ImmutableHashMap<>(newTable, size + 1); + } + + /** + * Retrieves the value associated with the given key. + * + * @param key key to search + * @return value if found, otherwise null + */ + public V get(K key) { + int index = hash(key); + Node current = table[index]; + + while (current != null) { + if ((key == null && current.key == null) || (key != null && key.equals(current.key))) { + return current.value; + } + current = current.next; + } + return null; + } + + /** + * Checks whether the given key exists in the map. + * + * @param key key to check + * @return true if key exists, false otherwise + */ + public boolean containsKey(K key) { + return get(key) != null; + } + + /** + * Returns the number of key-value pairs. + * + * @return size of the map + */ + public int size() { + return size; + } + + /** + * Computes hash index for a given key. + */ + private int hash(K key) { + return key == null ? 0 : (key.hashCode() & Integer.MAX_VALUE) % table.length; + } + + /** + * Node class for separate chaining. + */ + private static final class Node { + + private final K key; + private final V value; + private final Node next; + + private Node(K key, V value, Node next) { + this.key = key; + this.value = value; + this.next = next; + } + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java b/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java new file mode 100644 index 000000000000..ad7229760fd0 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueue.java @@ -0,0 +1,327 @@ +package com.thealgorithms.datastructures.heaps; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * An addressable (indexed) min-priority queue with O(log n) updates. + * + *

Key features: + *

    + *
  • Each element E is tracked by a handle (its current heap index) via a map, + * enabling O(log n) {@code remove(e)} and O(log n) key updates + * ({@code changeKey/decreaseKey/increaseKey}).
  • + *
  • The queue order is determined by the provided {@link Comparator}. If the + * comparator is {@code null}, elements must implement {@link Comparable} + * (same contract as {@link java.util.PriorityQueue}).
  • + *
  • By default this implementation uses {@link IdentityHashMap} for the index + * mapping to avoid issues with duplicate-equals elements or mutable equals/hashCode. + * If you need value-based equality, switch to {@code HashMap} and read the caveats + * in the class-level Javadoc carefully.
  • + *
+ * + *

IMPORTANT contracts

+ *
    + *
  • Do not mutate comparator-relevant fields of an element directly while it is + * inside the queue. Always use {@code changeKey}/{@code decreaseKey}/{@code increaseKey} + * so the heap can be restored accordingly.
  • + *
  • If you replace {@link IdentityHashMap} with {@link HashMap}, you must ensure: + * (a) no two distinct elements are {@code equals()}-equal at the same time in the queue, and + * (b) {@code equals/hashCode} of elements remain stable while enqueued.
  • + *
  • {@code peek()} returns {@code null} when empty (matching {@link java.util.PriorityQueue}).
  • + *
  • Not thread-safe.
  • + *
+ * + *

Complexities: + * {@code offer, poll, remove(e), changeKey, decreaseKey, increaseKey} are O(log n); + * {@code peek, isEmpty, size, contains} are O(1). + */ +public class IndexedPriorityQueue { + + /** Binary heap storage (min-heap). */ + private Object[] heap; + + /** Number of elements in the heap. */ + private int size; + + /** Comparator used for ordering; if null, elements must be Comparable. */ + private final Comparator cmp; + + /** + * Index map: element -> current heap index. + *

We use IdentityHashMap by default to: + *

    + *
  • allow duplicate-equals elements;
  • + *
  • avoid corruption when equals/hashCode are mutable or not ID-based.
  • + *
+ * If you prefer value-based semantics, replace with HashMap and + * respect the warnings in the class Javadoc. + */ + private final IdentityHashMap index; + + private static final int DEFAULT_INITIAL_CAPACITY = 11; + + public IndexedPriorityQueue() { + this(DEFAULT_INITIAL_CAPACITY, null); + } + + public IndexedPriorityQueue(Comparator cmp) { + this(DEFAULT_INITIAL_CAPACITY, cmp); + } + + public IndexedPriorityQueue(int initialCapacity, Comparator cmp) { + if (initialCapacity < 1) { + throw new IllegalArgumentException("initialCapacity < 1"); + } + this.heap = new Object[initialCapacity]; + this.cmp = cmp; + this.index = new IdentityHashMap<>(); + } + + /** Returns current number of elements. */ + public int size() { + return size; + } + + /** Returns {@code true} if empty. */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns the minimum element without removing it, or {@code null} if empty. + * Matches {@link java.util.PriorityQueue#peek()} behavior. + */ + @SuppressWarnings("unchecked") + public E peek() { + return size == 0 ? null : (E) heap[0]; + } + + /** + * Inserts the specified element (O(log n)). + * @throws NullPointerException if {@code e} is null + * @throws ClassCastException if {@code cmp == null} and {@code e} is not Comparable, + * or if incompatible with other elements + */ + public boolean offer(E e) { + Objects.requireNonNull(e, "element is null"); + if (size >= heap.length) { + grow(size + 1); + } + // Insert at the end and bubble up. siftUp will maintain 'index' for all touched nodes. + siftUp(size, e); + size++; + return true; + } + + /** + * Removes and returns the minimum element (O(log n)), or {@code null} if empty. + */ + @SuppressWarnings("unchecked") + public E poll() { + if (size == 0) { + return null; + } + E min = (E) heap[0]; + removeAt(0); // updates map and heap structure + return min; + } + + /** + * Removes one occurrence of the specified element e (O(log n)) if present. + * Uses the index map for O(1) lookup. + */ + public boolean remove(Object o) { + Integer i = index.get(o); + if (i == null) { + return false; + } + removeAt(i); + return true; + } + + /** O(1): returns whether the queue currently contains the given element reference. */ + public boolean contains(Object o) { + return index.containsKey(o); + } + + /** Clears the heap and the index map. */ + public void clear() { + Arrays.fill(heap, 0, size, null); + index.clear(); + size = 0; + } + + // ------------------------------------------------------------------------------------ + // Key update API + // ------------------------------------------------------------------------------------ + + /** + * Changes comparator-relevant fields of {@code e} via the provided {@code mutator}, + * then restores the heap in O(log n) by bubbling in the correct direction. + * + *

IMPORTANT: The mutator must not change {@code equals/hashCode} of {@code e} + * if you migrate this implementation to value-based indexing (HashMap). + * + * @throws IllegalArgumentException if {@code e} is not in the queue + */ + public void changeKey(E e, Consumer mutator) { + Integer i = index.get(e); + if (i == null) { + throw new IllegalArgumentException("Element not in queue"); + } + // Mutate fields used by comparator (do NOT mutate equality/hash if using value-based map) + mutator.accept(e); + // Try bubbling up; if no movement occurred, bubble down. + if (!siftUp(i)) { + siftDown(i); + } + } + + /** + * Faster variant if the new key is strictly smaller (higher priority). + * Performs a single sift-up (O(log n)). + */ + public void decreaseKey(E e, Consumer mutator) { + Integer i = index.get(e); + if (i == null) { + throw new IllegalArgumentException("Element not in queue"); + } + mutator.accept(e); + siftUp(i); + } + + /** + * Faster variant if the new key is strictly larger (lower priority). + * Performs a single sift-down (O(log n)). + */ + public void increaseKey(E e, Consumer mutator) { + Integer i = index.get(e); + if (i == null) { + throw new IllegalArgumentException("Element not in queue"); + } + mutator.accept(e); + siftDown(i); + } + + // ------------------------------------------------------------------------------------ + // Internal utilities + // ------------------------------------------------------------------------------------ + + /** Grows the internal array to accommodate at least {@code minCapacity}. */ + private void grow(int minCapacity) { + int old = heap.length; + int pref = (old < 64) ? old + 2 : old + (old >> 1); // +2 if small, else +50% + int newCap = Math.max(minCapacity, pref); + heap = Arrays.copyOf(heap, newCap); + } + + @SuppressWarnings("unchecked") + private int compare(E a, E b) { + if (cmp != null) { + return cmp.compare(a, b); + } + return ((Comparable) a).compareTo(b); + } + + /** + * Inserts item {@code x} at position {@code k}, bubbling up while maintaining the heap. + * Also maintains the index map for all moved elements. + */ + @SuppressWarnings("unchecked") + private void siftUp(int k, E x) { + while (k > 0) { + int p = (k - 1) >>> 1; + E e = (E) heap[p]; + if (compare(x, e) >= 0) { + break; + } + heap[k] = e; + index.put(e, k); + k = p; + } + heap[k] = x; + index.put(x, k); + } + + /** + * Attempts to bubble up the element currently at {@code k}. + * @return true if it moved; false otherwise. + */ + @SuppressWarnings("unchecked") + private boolean siftUp(int k) { + int orig = k; + E x = (E) heap[k]; + while (k > 0) { + int p = (k - 1) >>> 1; + E e = (E) heap[p]; + if (compare(x, e) >= 0) { + break; + } + heap[k] = e; + index.put(e, k); + k = p; + } + if (k != orig) { + heap[k] = x; + index.put(x, k); + return true; + } + return false; + } + + /** Bubbles down the element currently at {@code k}. */ + @SuppressWarnings("unchecked") + private void siftDown(int k) { + int n = size; + E x = (E) heap[k]; + int half = n >>> 1; // loop while k has at least one child + while (k < half) { + int child = (k << 1) + 1; // assume left is smaller + E c = (E) heap[child]; + int r = child + 1; + if (r < n && compare(c, (E) heap[r]) > 0) { + child = r; + c = (E) heap[child]; + } + if (compare(x, c) <= 0) { + break; + } + heap[k] = c; + index.put(c, k); + k = child; + } + heap[k] = x; + index.put(x, k); + } + + /** + * Removes the element at heap index {@code i}, restoring the heap afterwards. + *

Returns nothing; the standard {@code PriorityQueue} returns a displaced + * element in a rare case to help its iterator. We don't need that here, so + * we keep the API simple. + */ + @SuppressWarnings("unchecked") + private void removeAt(int i) { + int n = --size; // last index after removal + E moved = (E) heap[n]; + E removed = (E) heap[i]; + heap[n] = null; // help GC + index.remove(removed); // drop mapping for removed element + + if (i == n) { + return; // removed last element; done + } + + heap[i] = moved; + index.put(moved, i); + + // Try sift-up first (cheap if key decreased); if no movement, sift-down. + if (!siftUp(i)) { + siftDown(i); + } + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java new file mode 100644 index 000000000000..0ee788db2ff9 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedList.java @@ -0,0 +1,46 @@ +package com.thealgorithms.datastructures.lists; + +/** + * Returns the middle node of a singly linked list using the two-pointer technique. + * + *

The {@code slow} pointer advances by one node per iteration while {@code fast} advances by two. + * When {@code fast == null} or {@code fast.next == null}, {@code slow} points to the middle node. + * For even-length lists, this returns the second middle node.

+ * + *

This method does not modify the input list.

+ * + *

Reference: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare

+ * + *

Complexity:

+ *
    + *
  • Time: {@code O(n)}
  • + *
  • Space: {@code O(1)}
  • + *
+ */ +public final class MiddleOfLinkedList { + + private MiddleOfLinkedList() { + } + + /** + * Returns the middle node of the list. + * + * @param head the head of the singly linked list; may be {@code null} + * @return the middle node (second middle for even-sized lists), or {@code null} if {@code head} is {@code null} + */ + public static SinglyLinkedListNode middleNode(final SinglyLinkedListNode head) { + if (head == null) { + return null; + } + + SinglyLinkedListNode slow = head; + SinglyLinkedListNode fast = head; + + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + } + + return slow; + } +} diff --git a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java b/src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java similarity index 84% rename from src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java rename to src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java index c81476eaec32..7bb16921b9ef 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromeSinglyLinkedList.java +++ b/src/main/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedList.java @@ -1,4 +1,4 @@ -package com.thealgorithms.misc; +package com.thealgorithms.datastructures.lists; import java.util.Stack; @@ -9,6 +9,13 @@ * * See more: * https://www.geeksforgeeks.org/function-to-check-if-a-singly-linked-list-is-palindrome/ + * + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber */ @SuppressWarnings("rawtypes") public final class PalindromeSinglyLinkedList { diff --git a/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java b/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java new file mode 100644 index 000000000000..79275dcefe20 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursion.java @@ -0,0 +1,28 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.Queue; + +/** + * Reverse a queue using recursion. + */ +public final class ReverseQueueRecursion { + private ReverseQueueRecursion() { + // private constructor to prevent instantiation + } + + /** + * Reverses the given queue recursively. + * + * @param queue the queue to reverse + * @param the type of elements in the queue + */ + public static void reverseQueue(final Queue queue) { + if (queue == null || queue.isEmpty()) { + return; + } + + final T front = queue.poll(); + reverseQueue(queue); + queue.add(front); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java b/src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java new file mode 100644 index 000000000000..a943b0028974 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/queues/ThreadSafeQueue.java @@ -0,0 +1,186 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @brief Thread-safe bounded queue implementation using ReentrantLock and Condition variables + * @details A blocking queue that supports multiple producers and consumers. + * Uses a circular buffer internally with lock-based synchronization to ensure + * thread safety. Producers block when the queue is full, and consumers block + * when the queue is empty. + * @see Producer-Consumer Problem + */ +public class ThreadSafeQueue { + + private final Object[] buffer; + private final int capacity; + private int head; + private int tail; + private int count; + private final ReentrantLock lock; + private final Condition notFull; + private final Condition notEmpty; + + /** + * @brief Constructs a ThreadSafeQueue with the specified capacity + * @param capacity the maximum number of elements the queue can hold + * @throws IllegalArgumentException if capacity is less than or equal to zero + */ + public ThreadSafeQueue(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be greater than zero."); + } + this.capacity = capacity; + this.buffer = new Object[capacity]; + this.head = 0; + this.tail = 0; + this.count = 0; + this.lock = new ReentrantLock(); + this.notFull = lock.newCondition(); + this.notEmpty = lock.newCondition(); + } + + /** + * @brief Adds an element to the tail of the queue, blocking if full + * @param item the element to add + * @throws InterruptedException if the thread is interrupted while waiting + * @throws IllegalArgumentException if the item is null + */ + public void enqueue(T item) throws InterruptedException { + if (item == null) { + throw new IllegalArgumentException("Cannot enqueue null item."); + } + + lock.lock(); + try { + while (count == capacity) { + notFull.await(); + } + buffer[tail] = item; + tail = (tail + 1) % capacity; + count++; + notEmpty.signalAll(); + } finally { + lock.unlock(); + } + } + + /** + * @brief Removes and returns the element at the head of the queue, blocking if empty + * @return the element at the head of the queue + * @throws InterruptedException if the thread is interrupted while waiting + */ + @SuppressWarnings("unchecked") + public T dequeue() throws InterruptedException { + lock.lock(); + try { + while (count == 0) { + notEmpty.await(); + } + T item = (T) buffer[head]; + buffer[head] = null; + head = (head + 1) % capacity; + count--; + notFull.signalAll(); + return item; + } finally { + lock.unlock(); + } + } + + /** + * @brief Adds an element to the tail of the queue without blocking + * @param item the element to add + * @return true if the element was added, false if the queue was full + * @throws IllegalArgumentException if the item is null + */ + public boolean offer(T item) { + if (item == null) { + throw new IllegalArgumentException("Cannot enqueue null item."); + } + + lock.lock(); + try { + if (count == capacity) { + return false; + } + buffer[tail] = item; + tail = (tail + 1) % capacity; + count++; + notEmpty.signalAll(); + return true; + } finally { + lock.unlock(); + } + } + + /** + * @brief Removes and returns the element at the head without blocking + * @return the element at the head, or null if the queue is empty + */ + @SuppressWarnings("unchecked") + public T poll() { + lock.lock(); + try { + if (count == 0) { + return null; + } + T item = (T) buffer[head]; + buffer[head] = null; + head = (head + 1) % capacity; + count--; + notFull.signalAll(); + return item; + } finally { + lock.unlock(); + } + } + + /** + * @brief Returns the number of elements in the queue + * @return the current size of the queue + */ + public int size() { + lock.lock(); + try { + return count; + } finally { + lock.unlock(); + } + } + + /** + * @brief Checks if the queue is empty + * @return true if the queue contains no elements + */ + public boolean isEmpty() { + lock.lock(); + try { + return count == 0; + } finally { + lock.unlock(); + } + } + + /** + * @brief Checks if the queue is full + * @return true if the queue has reached its capacity + */ + public boolean isFull() { + lock.lock(); + try { + return count == capacity; + } finally { + lock.unlock(); + } + } + + /** + * @brief Returns the maximum capacity of the queue + * @return the capacity + */ + public int capacity() { + return capacity; + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java new file mode 100644 index 000000000000..2f9b3b489d56 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/BinaryTreeToString.java @@ -0,0 +1,100 @@ +package com.thealgorithms.datastructures.trees; + +/** + * Leetcode 606: Construct String from Binary Tree: + * https://leetcode.com/problems/construct-string-from-binary-tree/ + * + * Utility class to convert a {@link BinaryTree} into its string representation. + *

+ * The conversion follows a preorder traversal pattern (root → left → right) + * and uses parentheses to denote the tree structure. + * Empty parentheses "()" are used to explicitly represent missing left children + * when a right child exists, ensuring the structure is unambiguous. + *

+ * + *

Rules:

+ *
    + *
  • Each node is represented as {@code (value)}.
  • + *
  • If a node has only a right child, include {@code ()} before the right + * child + * to indicate the missing left child.
  • + *
  • If a node has no children, it appears as just {@code (value)}.
  • + *
  • The outermost parentheses are removed from the final string.
  • + *
+ * + *

Example:

+ * + *
+ *     Input tree:
+ *           1
+ *          / \
+ *         2   3
+ *          \
+ *           4
+ *
+ *     Output string:
+ *     "1(2()(4))(3)"
+ * 
+ * + *

+ * This implementation matches the logic from LeetCode problem 606: + * Construct String from Binary Tree. + *

+ * + * @author Muhammad Junaid + * @see BinaryTree + */ +public class BinaryTreeToString { + + /** String builder used to accumulate the string representation. */ + private StringBuilder sb; + + /** + * Converts a binary tree (given its root node) to its string representation. + * + * @param root the root node of the binary tree + * @return the string representation of the binary tree, or an empty string if + * the tree is null + */ + public String tree2str(BinaryTree.Node root) { + if (root == null) { + return ""; + } + + sb = new StringBuilder(); + dfs(root); + + // Remove the leading and trailing parentheses added by the root call + return sb.substring(1, sb.length() - 1); + } + + /** + * Performs a recursive depth-first traversal to build the string. + * Each recursive call appends the node value and its children (if any) + * enclosed in parentheses. + * + * @param node the current node being processed + */ + private void dfs(BinaryTree.Node node) { + if (node == null) { + return; + } + + sb.append("(").append(node.data); + + // Recursively build left and right subtrees + if (node.left != null) { + dfs(node.left); + } + + // Handle the special case: right child exists but left child is null + if (node.right != null && node.left == null) { + sb.append("()"); + dfs(node.right); + } else if (node.right != null) { + dfs(node.right); + } + + sb.append(")"); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java b/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java new file mode 100644 index 000000000000..0b29dd6f5f5e --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/CentroidDecomposition.java @@ -0,0 +1,217 @@ +package com.thealgorithms.datastructures.trees; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Centroid Decomposition is a divide-and-conquer technique for trees. + * It recursively partitions a tree by finding centroids - nodes whose removal + * creates balanced subtrees (each with at most N/2 nodes). + * + *

+ * Time Complexity: O(N log N) for construction + * Space Complexity: O(N) + * + *

+ * Applications: + * - Distance queries on trees + * - Path counting problems + * - Nearest neighbor searches + * + * @see Centroid Decomposition + * @see Centroid Decomposition Tutorial + * @author lens161 + */ +public final class CentroidDecomposition { + + private CentroidDecomposition() { + } + + /** + * Represents the centroid tree structure. + */ + public static final class CentroidTree { + private final int n; + private final List> adj; + private final int[] parent; + private final int[] subtreeSize; + private final boolean[] removed; + private int root; + + /** + * Constructs a centroid tree from an adjacency list. + * + * @param adj adjacency list representation of the tree (0-indexed) + * @throws IllegalArgumentException if tree is empty or null + */ + public CentroidTree(List> adj) { + if (adj == null || adj.isEmpty()) { + throw new IllegalArgumentException("Tree cannot be empty or null"); + } + + this.n = adj.size(); + this.adj = adj; + this.parent = new int[n]; + this.subtreeSize = new int[n]; + this.removed = new boolean[n]; + Arrays.fill(parent, -1); + + // Build centroid tree starting from node 0 + this.root = decompose(0, -1); + } + + /** + * Recursively builds the centroid tree. + * + * @param u current node + * @param p parent in centroid tree + * @return centroid of current component + */ + private int decompose(int u, int p) { + int size = getSubtreeSize(u, -1); + int centroid = findCentroid(u, -1, size); + + removed[centroid] = true; + parent[centroid] = p; + + // Recursively decompose each subtree + for (int v : adj.get(centroid)) { + if (!removed[v]) { + decompose(v, centroid); + } + } + + return centroid; + } + + /** + * Calculates subtree size from node u. + * + * @param u current node + * @param p parent node (-1 for root) + * @return size of subtree rooted at u + */ + private int getSubtreeSize(int u, int p) { + subtreeSize[u] = 1; + for (int v : adj.get(u)) { + if (v != p && !removed[v]) { + subtreeSize[u] += getSubtreeSize(v, u); + } + } + return subtreeSize[u]; + } + + /** + * Finds the centroid of a subtree. + * A centroid is a node whose removal creates components with size <= totalSize/2. + * + * @param u current node + * @param p parent node + * @param totalSize total size of current component + * @return centroid node + */ + private int findCentroid(int u, int p, int totalSize) { + for (int v : adj.get(u)) { + if (v != p && !removed[v] && subtreeSize[v] > totalSize / 2) { + return findCentroid(v, u, totalSize); + } + } + return u; + } + + /** + * Gets the parent of a node in the centroid tree. + * + * @param node the node + * @return parent node in centroid tree, or -1 if root + */ + public int getParent(int node) { + if (node < 0 || node >= n) { + throw new IllegalArgumentException("Invalid node: " + node); + } + return parent[node]; + } + + /** + * Gets the root of the centroid tree. + * + * @return root node + */ + public int getRoot() { + return root; + } + + /** + * Gets the number of nodes in the tree. + * + * @return number of nodes + */ + public int size() { + return n; + } + + /** + * Returns the centroid tree structure as a string. + * Format: node -> parent (or ROOT for root node) + * + * @return string representation + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Centroid Tree:\n"); + for (int i = 0; i < n; i++) { + sb.append("Node ").append(i).append(" -> "); + if (parent[i] == -1) { + sb.append("ROOT"); + } else { + sb.append("Parent ").append(parent[i]); + } + sb.append("\n"); + } + return sb.toString(); + } + } + + /** + * Creates a centroid tree from an edge list. + * + * @param n number of nodes (0-indexed: 0 to n-1) + * @param edges list of edges where each edge is [u, v] + * @return CentroidTree object + * @throws IllegalArgumentException if n <= 0 or edges is invalid + */ + public static CentroidTree buildFromEdges(int n, int[][] edges) { + if (n <= 0) { + throw new IllegalArgumentException("Number of nodes must be positive"); + } + if (edges == null) { + throw new IllegalArgumentException("Edges cannot be null"); + } + if (edges.length != n - 1) { + throw new IllegalArgumentException("Tree must have exactly n-1 edges"); + } + + List> adj = new ArrayList<>(); + for (int i = 0; i < n; i++) { + adj.add(new ArrayList<>()); + } + + for (int[] edge : edges) { + if (edge.length != 2) { + throw new IllegalArgumentException("Each edge must have exactly 2 nodes"); + } + int u = edge[0]; + int v = edge[1]; + + if (u < 0 || u >= n || v < 0 || v >= n) { + throw new IllegalArgumentException("Invalid node in edge: [" + u + ", " + v + "]"); + } + + adj.get(u).add(v); + adj.get(v).add(u); + } + + return new CentroidTree(adj); + } +} diff --git a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java b/src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java similarity index 99% rename from src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java rename to src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java index 236a23205180..ed67f9ae3394 100644 --- a/src/main/java/com/thealgorithms/tree/HeavyLightDecomposition.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/HeavyLightDecomposition.java @@ -1,4 +1,4 @@ -package com.thealgorithms.tree; +package com.thealgorithms.datastructures.trees; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java new file mode 100644 index 000000000000..40b9e8a73533 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/SegmentTree2D.java @@ -0,0 +1,201 @@ +package com.thealgorithms.datastructures.trees; + +/** + * 2D Segment Tree (Tree of Trees) implementation. + * This data structure supports point updates and submatrix sum queries + * in a 2D grid. It achieves this by nesting 1D Segment Trees within a 1D Segment Tree. + * + * Time Complexity: + * - Build/Initialization: O(N * M) + * - Point Update: O(log N * log M) + * - Submatrix Query: O(log N * log M) + * + * @see 2D Segment Tree + */ +public class SegmentTree2D { + + /** + * Represents a 1D Segment Tree. + * This is equivalent to your 'Sagara' struct. It manages the columns (X-axis). + */ + public static class SegmentTree1D { + private int n; + private final int[] tree; + + /** + * Initializes the 1D Segment Tree with the nearest power of 2. + * + * @param size The expected number of elements (columns). + */ + public SegmentTree1D(int size) { + n = 1; + while (n < size) { + n *= 2; + } + tree = new int[n * 2]; + } + + /** + * Recursively updates a point in the 1D tree. + */ + private void update(int index, int val, int node, int lx, int rx) { + if (rx - lx == 1) { + tree[node] = val; + return; + } + + int mid = lx + (rx - lx) / 2; + int leftChild = node * 2 + 1; + int rightChild = node * 2 + 2; + + if (index < mid) { + update(index, val, leftChild, lx, mid); + } else { + update(index, val, rightChild, mid, rx); + } + + tree[node] = tree[leftChild] + tree[rightChild]; + } + + /** + * Public wrapper to update a specific index. + * + * @param index The column index to update. + * @param val The new value. + */ + public void update(int index, int val) { + update(index, val, 0, 0, n); + } + + /** + * Retrieves the exact value at a specific leaf node. + * + * @param index The column index. + * @return The value at the given index. + */ + public int get(int index) { + return query(index, index + 1, 0, 0, n); + } + + /** + * Recursively queries the sum in a 1D range. + */ + private int query(int l, int r, int node, int lx, int rx) { + if (lx >= r || rx <= l) { + return 0; // Out of bounds + } + if (lx >= l && rx <= r) { + return tree[node]; // Fully inside + } + + int mid = lx + (rx - lx) / 2; + int leftSum = query(l, r, node * 2 + 1, lx, mid); + int rightSum = query(l, r, node * 2 + 2, mid, rx); + + return leftSum + rightSum; + } + + /** + * Public wrapper to query the sum in the range [l, r). + * + * @param l Left boundary (inclusive). + * @param r Right boundary (exclusive). + * @return The sum of the range. + */ + public int query(int l, int r) { + return query(l, r, 0, 0, n); + } + } + + // --- Start of 2D Segment Tree (equivalent to 'Sagara2D') --- + + private int n; + private final SegmentTree1D[] tree; + + /** + * Initializes the 2D Segment Tree. + * + * @param rows The number of rows in the matrix. + * @param cols The number of columns in the matrix. + */ + public SegmentTree2D(int rows, int cols) { + n = 1; + while (n < rows) { + n *= 2; + } + tree = new SegmentTree1D[n * 2]; + for (int i = 0; i < n * 2; i++) { + // Every node in the outer tree is a full 1D tree! + tree[i] = new SegmentTree1D(cols); + } + } + + /** + * Recursively updates a point in the 2D grid. + */ + private void update(int row, int col, int val, int node, int lx, int rx) { + if (rx - lx == 1) { + tree[node].update(col, val); + return; + } + + int mid = lx + (rx - lx) / 2; + int leftChild = node * 2 + 1; + int rightChild = node * 2 + 2; + + if (row < mid) { + update(row, col, val, leftChild, lx, mid); + } else { + update(row, col, val, rightChild, mid, rx); + } + + // The value of the current node's column is the sum of its children's column values + int leftVal = tree[leftChild].get(col); + int rightVal = tree[rightChild].get(col); + tree[node].update(col, leftVal + rightVal); + } + + /** + * Public wrapper to update a specific point (row, col). + * + * @param row The row index. + * @param col The column index. + * @param val The new value. + */ + public void update(int row, int col, int val) { + update(row, col, val, 0, 0, n); + } + + /** + * Recursively queries the sum in a submatrix. + */ + private int query(int top, int bottom, int left, int right, int node, int lx, int rx) { + if (lx >= bottom || rx <= top) { + return 0; // Out of bounds + } + if (lx >= top && rx <= bottom) { + // Fully inside the row range, so delegate the column query to the 1D tree + return tree[node].query(left, right); + } + + int mid = lx + (rx - lx) / 2; + int leftSum = query(top, bottom, left, right, node * 2 + 1, lx, mid); + int rightSum = query(top, bottom, left, right, node * 2 + 2, mid, rx); + + return leftSum + rightSum; + } + + /** + * Public wrapper to query the sum of a submatrix. + * Note: boundaries are [top, bottom) and [left, right). + * + * @param top Top row index (inclusive). + * @param bottom Bottom row index (exclusive). + * @param left Left column index (inclusive). + * @param right Right column index (exclusive). + * @return The sum of the submatrix. + */ + public int query(int top, int bottom, int left, int right) { + return query(top, bottom, left, right, 0, 0, n); + } +} diff --git a/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java b/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java new file mode 100644 index 000000000000..fd8876cecb70 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTree.java @@ -0,0 +1,145 @@ +/* + * TheAlgorithms (https://github.com/TheAlgorithms/Java) + * Author: Shewale41 + * This file is licensed under the MIT License. + */ + +package com.thealgorithms.datastructures.trees; + +import java.util.ArrayList; +import java.util.List; + +/** + * Threaded binary tree implementation that supports insertion and + * in-order traversal without recursion or stack by using threads. + * + *

In this implementation, a node's null left/right pointers are used + * to point to the in-order predecessor/successor respectively. Two flags + * indicate whether left/right pointers are real children or threads. + * + * @see Wikipedia: + * Threaded binary tree + */ +public final class ThreadedBinaryTree { + + private Node root; + + private static final class Node { + int value; + Node left; + Node right; + boolean leftIsThread; + boolean rightIsThread; + + Node(int value) { + this.value = value; + this.left = null; + this.right = null; + this.leftIsThread = false; + this.rightIsThread = false; + } + } + + public ThreadedBinaryTree() { + this.root = null; + } + + /** + * Inserts a value into the threaded binary tree. Duplicate values are inserted + * to the right subtree (consistent deterministic rule). + * + * @param value the integer value to insert + */ + public void insert(int value) { + Node newNode = new Node(value); + if (root == null) { + root = newNode; + return; + } + + Node current = root; + Node parent = null; + + while (true) { + parent = current; + if (value < current.value) { + if (!current.leftIsThread && current.left != null) { + current = current.left; + } else { + break; + } + } else { // value >= current.value + if (!current.rightIsThread && current.right != null) { + current = current.right; + } else { + break; + } + } + } + + if (value < parent.value) { + // attach newNode as left child + newNode.left = parent.left; + newNode.leftIsThread = parent.leftIsThread; + newNode.right = parent; + newNode.rightIsThread = true; + + parent.left = newNode; + parent.leftIsThread = false; + } else { + // attach newNode as right child + newNode.right = parent.right; + newNode.rightIsThread = parent.rightIsThread; + newNode.left = parent; + newNode.leftIsThread = true; + + parent.right = newNode; + parent.rightIsThread = false; + } + } + + /** + * Returns the in-order traversal of the tree as a list of integers. + * Traversal is done without recursion or an explicit stack by following threads. + * + * @return list containing the in-order sequence of node values + */ + public List inorderTraversal() { + List result = new ArrayList<>(); + Node current = root; + if (current == null) { + return result; + } + + // Move to the leftmost node + while (current.left != null && !current.leftIsThread) { + current = current.left; + } + + while (current != null) { + result.add(current.value); + + // If right pointer is a thread, follow it + if (current.rightIsThread) { + current = current.right; + } else { + // Move to leftmost node in right subtree + current = current.right; + while (current != null && !current.leftIsThread && current.left != null) { + current = current.left; + } + } + } + + return result; + } + + /** + * Helper: checks whether the tree is empty. + * + * @return true if tree has no nodes + */ + public boolean isEmpty() { + return root == null; + } +} diff --git a/src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java b/src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java similarity index 98% rename from src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java rename to src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java index 7a1a7aadd805..624e3d65bfc1 100644 --- a/src/main/java/com/thealgorithms/others/Implementing_auto_completing_features_using_trie.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/TrieAutocomplete.java @@ -1,8 +1,8 @@ -package com.thealgorithms.others; +package com.thealgorithms.datastructures.trees; // Java Program to implement Auto-Complete // Feature using Trie -class Trieac { +class TrieAutocomplete { // Alphabet size (# of symbols) public static final int ALPHABET_SIZE = 26; diff --git a/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java b/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java new file mode 100644 index 000000000000..6feaa6f35048 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/trees/WaveletTree.java @@ -0,0 +1,235 @@ +package com.thealgorithms.datastructures.trees; + +import java.util.ArrayList; +import java.util.List; + +/** + * A Wavelet Tree is a highly efficient data structure used to store sequences + * and answer queries like rank, select, and quantile in O(log(max_val - min_val)) time. + * This structure is particularly useful in competitive programming and text compression. + */ +public class WaveletTree { + + private class Node { + int low; + int high; + Node left; + Node right; + List leftCount; // Prefix sums of elements going to the left child + + /** + * Recursively constructs the tree nodes by partitioning the array. + * + * @param arr the subarray for the current node + * @param low the minimum possible value in the current node + * @param high the maximum possible value in the current node + */ + Node(int[] arr, int low, int high) { + this.low = low; + this.high = high; + + if (low == high) { + return; + } + + int mid = low + (high - low) / 2; + leftCount = new ArrayList<>(arr.length + 1); + leftCount.add(0); + + List leftArr = new ArrayList<>(); + List rightArr = new ArrayList<>(); + + for (int x : arr) { + if (x <= mid) { + leftArr.add(x); + leftCount.add(leftCount.get(leftCount.size() - 1) + 1); + } else { + rightArr.add(x); + leftCount.add(leftCount.get(leftCount.size() - 1)); + } + } + + if (!leftArr.isEmpty()) { + this.left = new Node(leftArr.stream().mapToInt(i -> i).toArray(), low, mid); + } + if (!rightArr.isEmpty()) { + this.right = new Node(rightArr.stream().mapToInt(i -> i).toArray(), mid + 1, high); + } + } + } + + private Node root; + private final int n; + + /** + * Constructs a Wavelet Tree from the given array. + * The min and max values are determined dynamically from the array. + * + * @param arr the input array + */ + public WaveletTree(int[] arr) { + if (arr == null || arr.length == 0) { + this.n = 0; + return; + } + this.n = arr.length; + int min = arr[0]; + int max = arr[0]; + for (int x : arr) { + if (x < min) { + min = x; + } + if (x > max) { + max = x; + } + } + root = new Node(arr, min, max); + } + + /** + * Constructs a Wavelet Tree from the given array with specific min and max values. + * + * @param arr the input array + * @param minValue the minimum possible value + * @param maxValue the maximum possible value + */ + public WaveletTree(int[] arr, int minValue, int maxValue) { + if (arr == null || arr.length == 0) { + this.n = 0; + return; + } + this.n = arr.length; + root = new Node(arr, minValue, maxValue); + } + + /** + * How many times does the number x appear in the array from index 0 to i (inclusive)? + * + * @param x the number to search for + * @param i the end index (0-based, inclusive) + * @return the number of occurrences of x in arr[0...i] + */ + public int rank(int x, int i) { + if (root == null || x < root.low || x > root.high || i < 0) { + return 0; + } + // If i is out of bounds, cap it at n - 1 + int endIdx = Math.min(i, n - 1); + return rank(root, x, endIdx + 1); + } + + private int rank(Node node, int x, int count) { + if (node == null || count == 0) { + return 0; + } + if (node.low == node.high) { + return count; + } + int mid = node.low + (node.high - node.low) / 2; + int leftC = node.leftCount.get(count); + if (x <= mid) { + return rank(node.left, x, leftC); + } else { + return rank(node.right, x, count - leftC); + } + } + + /** + * What is the 0-based index of the k-th occurrence of the number x in the array? + * + * @param x the number to search for + * @param k the occurrence count (1-based) + * @return the 0-based index in the original array, or -1 if x occurs less than k times + */ + public int select(int x, int k) { + if (root == null || x < root.low || x > root.high || k <= 0) { + return -1; + } + if (rank(x, n - 1) < k) { + return -1; + } + return select(root, x, k); + } + + private int select(Node node, int x, int k) { + if (node.low == node.high) { + return k - 1; // 0-based index within the imaginary array at the leaf + } + int mid = node.low + (node.high - node.low) / 2; + if (x <= mid) { + int posInLeft = select(node.left, x, k); + return binarySearchLeft(node.leftCount, posInLeft + 1); + } else { + int posInRight = select(node.right, x, k); + return binarySearchRight(node.leftCount, posInRight + 1); + } + } + + private int binarySearchLeft(List prefixSums, int k) { + int l = 1; + int r = prefixSums.size() - 1; + int ans = -1; + while (l <= r) { + int mid = l + (r - l) / 2; + if (prefixSums.get(mid) >= k) { + ans = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return ans == -1 ? -1 : ans - 1; // Convert to 0-based index + } + + private int binarySearchRight(List prefixSums, int k) { + int l = 1; + int r = prefixSums.size() - 1; + int ans = -1; + while (l <= r) { + int mid = l + (r - l) / 2; + if (mid - prefixSums.get(mid) >= k) { + ans = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return ans == -1 ? -1 : ans - 1; // Convert to 0-based index + } + + /** + * If you sort the subarray from index left to right, what would be the k-th smallest element? + * This query is also commonly known as the quantile query. + * + * @param left the start index of the subarray (0-based, inclusive) + * @param right the end index of the subarray (0-based, inclusive) + * @param k the rank of the smallest element (1-based, e.g., k=1 is the minimum) + * @return the k-th smallest element in the subarray, or -1 if invalid parameters + */ + public int kthSmallest(int left, int right, int k) { + if (root == null || left > right || left < 0 || k < 1 || k > right - left + 1) { + return -1; + } + return kthSmallest(root, left, right, k); + } + + private int kthSmallest(Node node, int left, int right, int k) { + if (node.low == node.high) { + return node.low; + } + + int countLeftInLMinus1 = (left == 0) ? 0 : node.leftCount.get(left); + int countLeftInR = node.leftCount.get(right + 1); + int elementsToLeft = countLeftInR - countLeftInLMinus1; + + if (k <= elementsToLeft) { + int newL = countLeftInLMinus1; + int newR = countLeftInR - 1; + return kthSmallest(node.left, newL, newR, k); + } else { + int newL = left - countLeftInLMinus1; + int newR = right - countLeftInR; + return kthSmallest(node.right, newL, newR, k - elementsToLeft); + } + } +} diff --git a/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java b/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java index 4c9c40c83174..323098a99887 100644 --- a/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java +++ b/src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java @@ -66,10 +66,6 @@ public static class Location { } } - public Location[] createLocation(int numberValues) { - return new Location[numberValues]; - } - public Location buildLocation(double x, double y) { return new Location(x, y); } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java b/src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java new file mode 100644 index 000000000000..7dae7603fedc --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/DigitDP.java @@ -0,0 +1,111 @@ +package com.thealgorithms.dynamicprogramming; +import java.util.Arrays; + +/** + * A generalized template for the Digit Dynamic Programming (Digit DP) + * technique. + * Digit DP is used to count numbers within a range [L, R] that satisfy specific + * digit properties. + * This specific implementation demonstrates counting the numbers whose digit + * sum equals a target value. + * + *

+ * Example: + * countRangeWithDigitSum(1, 100, 5) returns 6 (numbers: 5, 14, 23, 32, 41, 50) + */ +public final class DigitDP { + + // Maximum theoretical digit sum for a 64-bit signed long integer (9 * 19 digits + // = 171) + private static final int MAX_DIGIT_SUM = 171; + + private DigitDP() { + // Prevent instantiation for utility/algorithm template class + } + + /** + * Counts how many numbers in the range [L, R] have a digit sum equal to the + * target. + * + * @param l The lower bound of the range (inclusive). + * @param r The upper bound of the range (inclusive). + * @param target The exact sum of digits required. + * @return The count of valid integers. + */ + public static long countRangeWithDigitSum(long l, long r, int target) { + if (l > r || target < 0 || target > MAX_DIGIT_SUM) { + return 0; + } + long countR = countWithDigitSum(r, target); + long countLMinus1 = countWithDigitSum(l - 1, target); + return countR - countLMinus1; + } + + private static long countWithDigitSum(long number, int target) { + if (number < 0) { + return 0; + } + String numStr = Long.toString(number); + int length = numStr.length(); + + // dp[index][current_sum][tight] + long[][][] dp = new long[length][MAX_DIGIT_SUM + 1][2]; + for (long[][] row : dp) { + for (long[] col : row) { + Arrays.fill(col, -1); + } + } + + return solve(0, 0, 1, numStr, target, dp); + } + + /** + * Recursive memoized function to explore digit placements. + * + * Time Complexity: O(number_of_digits * target_sum * 10) + * Space Complexity: O(number_of_digits * target_sum * 2) + * + * @param index Current digit position from left to right (most significant + * first). + * @param currentSum Cumulative sum of digits chosen so far. + * @param tight Flag indicating if current prefix matches the original + * number boundary. + * @param numStr String representation of the upper ceiling limit. + * @param target The exact required sum of digits. + * @param dp Memoization matrix cache table. + * @return Total valid combinations from the current state configuration. + */ + private static long solve(int index, int currentSum, int tight, String numStr, int target, long[][][] dp) { + // Base case: If we have processed all digits + if (index == numStr.length()) { + return currentSum == target ? 1 : 0; + } + + // Return memoized state if already evaluated + if (dp[index][currentSum][tight] != -1) { + return dp[index][currentSum][tight]; + } + + long ans = 0; + // Determine the maximum limit for the current position digit + int limit = (tight == 1) ? (numStr.charAt(index) - '0') : 9; + + // Iterate through all possible valid digits for this position + for (int digit = 0; digit <= limit; digit++) { + int nextSum = currentSum + digit; + + // Optimization: If the digit sum exceeds the target, prune branch + if (nextSum > target) { + continue; + } + + // Next state remains tight only if current state is tight and we place the + // exact limit digit + int nextTight = (tight == 1 && digit == limit) ? 1 : 0; + ans += solve(index + 1, nextSum, nextTight, numStr, target, dp); + } + + dp[index][currentSum][tight] = ans; + return ans; + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java index 0d6aff2bbef3..df158112d233 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/Fibonacci.java @@ -1,10 +1,22 @@ package com.thealgorithms.dynamicprogramming; - import java.util.HashMap; import java.util.Map; - /** - * @author Varun Upadhyay (https://github.com/varunu28) + * Collection of Dynamic Programming techniques to solve for the n-th Fibonacci number. + *

+ * This file showcases Top-Down Memoization ({@code fibMemo}), Bottom-Up Tabulation ({@code fibBotUp}), + * and Space-Optimized Iteration ({@code fibOptimized}). + *

+ * For alternative structural paradigms, mathematical formulas, or verification steps, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
+ * * @author Varun Upadhyay (https://github.com/varunu28) */ public final class Fibonacci { private Fibonacci() { diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java b/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java index 134561766830..0d4c8d501f9f 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/Knapsack.java @@ -3,53 +3,76 @@ import java.util.Arrays; /** - * A Dynamic Programming based solution for the 0-1 Knapsack problem. - * This class provides a method, `knapSack`, that calculates the maximum value that can be - * obtained from a given set of items with weights and values, while not exceeding a - * given weight capacity. + * 0/1 Knapsack Problem - Dynamic Programming solution. * - * @see 0-1 Knapsack Problem + * This algorithm solves the classic optimization problem where we have n items, + * each with a weight and a value. The goal is to maximize the total value + * without exceeding the knapsack's weight capacity. + * + * Time Complexity: O(n * W) + * Space Complexity: O(W) + * + * Example: + * values = {60, 100, 120} + * weights = {10, 20, 30} + * W = 50 + * Output: 220 + * + * @author Arpita + * @see Knapsack Problem */ public final class Knapsack { private Knapsack() { } + /** + * Validates the input to ensure correct constraints. + */ private static void throwIfInvalidInput(final int weightCapacity, final int[] weights, final int[] values) { if (weightCapacity < 0) { throw new IllegalArgumentException("Weight capacity should not be negative."); } if (weights == null || values == null || weights.length != values.length) { - throw new IllegalArgumentException("Input arrays must not be null and must have the same length."); + throw new IllegalArgumentException("Weights and values must be non-null and of the same length."); } if (Arrays.stream(weights).anyMatch(w -> w <= 0)) { - throw new IllegalArgumentException("Input array should not contain non-positive weight(s)."); + throw new IllegalArgumentException("Weights must be positive."); } } /** - * Solves the 0-1 Knapsack problem using Dynamic Programming. + * Solves the 0/1 Knapsack problem using Dynamic Programming (bottom-up approach). * * @param weightCapacity The maximum weight capacity of the knapsack. - * @param weights An array of item weights. - * @param values An array of item values. - * @return The maximum value that can be obtained without exceeding the weight capacity. - * @throws IllegalArgumentException If the input arrays are null or have different lengths. + * @param weights The array of item weights. + * @param values The array of item values. + * @return The maximum total value achievable without exceeding capacity. */ - public static int knapSack(final int weightCapacity, final int[] weights, final int[] values) throws IllegalArgumentException { + public static int knapSack(final int weightCapacity, final int[] weights, final int[] values) { throwIfInvalidInput(weightCapacity, weights, values); - // DP table to store the state of the maximum possible return for a given weight capacity. int[] dp = new int[weightCapacity + 1]; + // Fill dp[] array iteratively for (int i = 0; i < values.length; i++) { - for (int w = weightCapacity; w > 0; w--) { - if (weights[i] <= w) { - dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]); - } + for (int w = weightCapacity; w >= weights[i]; w--) { + dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]); } } return dp[weightCapacity]; } + + /* + // Example main method for local testing only. + public static void main(String[] args) { + int[] values = {60, 100, 120}; + int[] weights = {10, 20, 30}; + int weightCapacity = 50; + + int maxValue = knapSack(weightCapacity, weights, values); + System.out.println("Maximum value = " + maxValue); // Output: 220 + } + */ } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java b/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java index 0b40d4559341..5c8a6a953f83 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequence.java @@ -1,58 +1,42 @@ package com.thealgorithms.dynamicprogramming; /** - * Algorithm explanation - * https://www.educative.io/edpresso/longest-palindromic-subsequence-algorithm + * Longest Palindromic Subsequence algorithm. + * A palindromic subsequence is a subsequence that reads the same forwards and backwards. + * This implementation finds the longest such subsequence by computing the LCS of the + * original string and its reverse. + * + * @see Wikipedia */ public final class LongestPalindromicSubsequence { private LongestPalindromicSubsequence() { } - public static void main(String[] args) { - String a = "BBABCBCAB"; - String b = "BABCBAB"; - - String aLPS = lps(a); - String bLPS = lps(b); - - System.out.println(a + " => " + aLPS); - System.out.println(b + " => " + bLPS); - } - - public static String lps(String original) throws IllegalArgumentException { - StringBuilder reverse = new StringBuilder(original); - reverse = reverse.reverse(); - return recursiveLPS(original, reverse.toString()); + /** + * Returns the longest palindromic subsequence of the given string. + * + * @param original the input string + * @return the longest palindromic subsequence + * @throws IllegalArgumentException if the input string is null + */ + public static String lps(String original) { + if (original == null) { + throw new IllegalArgumentException("Input string must not be null"); + } + String reverse = new StringBuilder(original).reverse().toString(); + return recursiveLPS(original, reverse); } private static String recursiveLPS(String original, String reverse) { - String bestResult = ""; - - // no more chars, then return empty - if (original.length() == 0 || reverse.length() == 0) { - bestResult = ""; - } else { - // if the last chars match, then remove it from both strings and recur - if (original.charAt(original.length() - 1) == reverse.charAt(reverse.length() - 1)) { - String bestSubResult = recursiveLPS(original.substring(0, original.length() - 1), reverse.substring(0, reverse.length() - 1)); - - bestResult = reverse.charAt(reverse.length() - 1) + bestSubResult; - } else { - // otherwise (1) ignore the last character of reverse, and recur on original and - // updated reverse again (2) ignore the last character of original and recur on the - // updated original and reverse again then select the best result from these two - // subproblems. - - String bestSubResult1 = recursiveLPS(original, reverse.substring(0, reverse.length() - 1)); - String bestSubResult2 = recursiveLPS(original.substring(0, original.length() - 1), reverse); - if (bestSubResult1.length() > bestSubResult2.length()) { - bestResult = bestSubResult1; - } else { - bestResult = bestSubResult2; - } - } + if (original.isEmpty() || reverse.isEmpty()) { + return ""; } - - return bestResult; + if (original.charAt(original.length() - 1) == reverse.charAt(reverse.length() - 1)) { + String bestSubResult = recursiveLPS(original.substring(0, original.length() - 1), reverse.substring(0, reverse.length() - 1)); + return reverse.charAt(reverse.length() - 1) + bestSubResult; + } + String sub1 = recursiveLPS(original, reverse.substring(0, reverse.length() - 1)); + String sub2 = recursiveLPS(original.substring(0, original.length() - 1), reverse); + return sub1.length() >= sub2.length() ? sub1 : sub2; } } diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java b/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java new file mode 100644 index 000000000000..428176ea6c40 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTree.java @@ -0,0 +1,130 @@ +package com.thealgorithms.dynamicprogramming; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Computes the minimum search cost of an optimal binary search tree. + * + *

The algorithm sorts the keys, preserves the corresponding search frequencies, and uses + * dynamic programming with Knuth's optimization to compute the minimum weighted search cost. + * + *

Example: if keys = [10, 12] and frequencies = [34, 50], the best tree puts 12 at the root + * and 10 as its left child. The total cost is 50 * 1 + 34 * 2 = 118. + * + *

Reference: + * https://en.wikipedia.org/wiki/Optimal_binary_search_tree + */ +public final class OptimalBinarySearchTree { + private OptimalBinarySearchTree() { + } + + /** + * Computes the minimum weighted search cost for the given keys and search frequencies. + * + * @param keys the BST keys + * @param frequencies the search frequencies associated with the keys + * @return the minimum search cost + * @throws IllegalArgumentException if the input is invalid + */ + public static long findOptimalCost(int[] keys, int[] frequencies) { + validateInput(keys, frequencies); + if (keys.length == 0) { + return 0L; + } + + int[][] sortedNodes = sortNodes(keys, frequencies); + int nodeCount = sortedNodes.length; + long[] prefixSums = buildPrefixSums(sortedNodes); + long[][] optimalCost = new long[nodeCount][nodeCount]; + int[][] root = new int[nodeCount][nodeCount]; + + // Small example: + // keys = [10, 12] + // frequencies = [34, 50] + // Choosing 12 as the root gives cost 50 * 1 + 34 * 2 = 118, + // which is better than choosing 10 as the root. + + // Base case: a subtree containing one key has cost equal to its frequency, + // because that key becomes the root of the subtree and is searched at depth 1. + for (int index = 0; index < nodeCount; index++) { + optimalCost[index][index] = sortedNodes[index][1]; + root[index][index] = index; + } + + // Build solutions for longer and longer key ranges. + // optimalCost[start][end] stores the minimum search cost for keys in that range. + for (int length = 2; length <= nodeCount; length++) { + for (int start = 0; start <= nodeCount - length; start++) { + int end = start + length - 1; + + // Every key in this range moves one level deeper when we choose a root, + // so the sum of frequencies is added once to the subtree cost. + long frequencySum = prefixSums[end + 1] - prefixSums[start]; + optimalCost[start][end] = Long.MAX_VALUE; + + // Knuth's optimization: + // the best root for [start, end] lies between the best roots of + // [start, end - 1] and [start + 1, end], so we search only this interval. + int leftBoundary = root[start][end - 1]; + int rightBoundary = root[start + 1][end]; + for (int currentRoot = leftBoundary; currentRoot <= rightBoundary; currentRoot++) { + long leftCost = currentRoot > start ? optimalCost[start][currentRoot - 1] : 0L; + long rightCost = currentRoot < end ? optimalCost[currentRoot + 1][end] : 0L; + long currentCost = frequencySum + leftCost + rightCost; + + if (currentCost < optimalCost[start][end]) { + optimalCost[start][end] = currentCost; + root[start][end] = currentRoot; + } + } + } + } + + return optimalCost[0][nodeCount - 1]; + } + + private static void validateInput(int[] keys, int[] frequencies) { + if (keys == null || frequencies == null) { + throw new IllegalArgumentException("Keys and frequencies cannot be null"); + } + if (keys.length != frequencies.length) { + throw new IllegalArgumentException("Keys and frequencies must have the same length"); + } + + for (int frequency : frequencies) { + if (frequency < 0) { + throw new IllegalArgumentException("Frequencies cannot be negative"); + } + } + } + + private static int[][] sortNodes(int[] keys, int[] frequencies) { + int[][] sortedNodes = new int[keys.length][2]; + for (int index = 0; index < keys.length; index++) { + sortedNodes[index][0] = keys[index]; + sortedNodes[index][1] = frequencies[index]; + } + + // Sort by key so the nodes can be treated as an in-order BST sequence. + Arrays.sort(sortedNodes, Comparator.comparingInt(node -> node[0])); + + for (int index = 1; index < sortedNodes.length; index++) { + if (sortedNodes[index - 1][0] == sortedNodes[index][0]) { + throw new IllegalArgumentException("Keys must be distinct"); + } + } + + return sortedNodes; + } + + private static long[] buildPrefixSums(int[][] sortedNodes) { + long[] prefixSums = new long[sortedNodes.length + 1]; + for (int index = 0; index < sortedNodes.length; index++) { + // prefixSums[i] holds the total frequency of the first i sorted keys. + // This lets us get the frequency sum of any range in O(1) time. + prefixSums[index + 1] = prefixSums[index] + sortedNodes[index][1]; + } + return prefixSums; + } +} diff --git a/src/main/java/com/thealgorithms/geometry/LineIntersection.java b/src/main/java/com/thealgorithms/geometry/LineIntersection.java new file mode 100644 index 000000000000..8d65833816b3 --- /dev/null +++ b/src/main/java/com/thealgorithms/geometry/LineIntersection.java @@ -0,0 +1,105 @@ +package com.thealgorithms.geometry; + +import java.awt.geom.Point2D; +import java.util.Optional; + +/** + * Utility methods for checking and computing 2D line segment intersections. + */ +public final class LineIntersection { + private LineIntersection() { + } + + /** + * Checks whether two line segments intersect. + * + * @param p1 first endpoint of segment 1 + * @param p2 second endpoint of segment 1 + * @param q1 first endpoint of segment 2 + * @param q2 second endpoint of segment 2 + * @return true when the segments intersect (including touching endpoints) + */ + public static boolean intersects(Point p1, Point p2, Point q1, Point q2) { + int o1 = orientation(p1, p2, q1); + int o2 = orientation(p1, p2, q2); + int o3 = orientation(q1, q2, p1); + int o4 = orientation(q1, q2, p2); + + if (o1 != o2 && o3 != o4) { + return true; + } + + if (o1 == 0 && onSegment(p1, q1, p2)) { + return true; + } + if (o2 == 0 && onSegment(p1, q2, p2)) { + return true; + } + if (o3 == 0 && onSegment(q1, p1, q2)) { + return true; + } + if (o4 == 0 && onSegment(q1, p2, q2)) { + return true; + } + + return false; + } + + /** + * Computes the single geometric intersection point between two non-parallel + * segments when it exists. + * + *

For parallel/collinear overlap, this method returns {@code Optional.empty()}. + * + * @param p1 first endpoint of segment 1 + * @param p2 second endpoint of segment 1 + * @param q1 first endpoint of segment 2 + * @param q2 second endpoint of segment 2 + * @return the intersection point when uniquely defined and on both segments + */ + public static Optional intersectionPoint(Point p1, Point p2, Point q1, Point q2) { + if (!intersects(p1, p2, q1, q2)) { + return Optional.empty(); + } + + long x1 = p1.x(); + long y1 = p1.y(); + long x2 = p2.x(); + long y2 = p2.y(); + long x3 = q1.x(); + long y3 = q1.y(); + long x4 = q2.x(); + long y4 = q2.y(); + + long denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + if (denominator == 0L) { + return sharedEndpoint(p1, p2, q1, q2); + } + + long determinant1 = x1 * y2 - y1 * x2; + long determinant2 = x3 * y4 - y3 * x4; + long numeratorX = determinant1 * (x3 - x4) - (x1 - x2) * determinant2; + long numeratorY = determinant1 * (y3 - y4) - (y1 - y2) * determinant2; + + return Optional.of(new Point2D.Double(numeratorX / (double) denominator, numeratorY / (double) denominator)); + } + + private static int orientation(Point a, Point b, Point c) { + long cross = ((long) b.x() - a.x()) * ((long) c.y() - a.y()) - ((long) b.y() - a.y()) * ((long) c.x() - a.x()); + return Long.compare(cross, 0L); + } + + private static Optional sharedEndpoint(Point p1, Point p2, Point q1, Point q2) { + if (p1.equals(q1) || p1.equals(q2)) { + return Optional.of(new Point2D.Double(p1.x(), p1.y())); + } + if (p2.equals(q1) || p2.equals(q2)) { + return Optional.of(new Point2D.Double(p2.x(), p2.y())); + } + return Optional.empty(); + } + + private static boolean onSegment(Point a, Point b, Point c) { + return b.x() >= Math.min(a.x(), c.x()) && b.x() <= Math.max(a.x(), c.x()) && b.y() >= Math.min(a.y(), c.y()) && b.y() <= Math.max(a.y(), c.y()); + } +} diff --git a/src/main/java/com/thealgorithms/graph/AccountMerge.java b/src/main/java/com/thealgorithms/graph/AccountMerge.java new file mode 100644 index 000000000000..86f0a24b6c1c --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/AccountMerge.java @@ -0,0 +1,127 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Merges account records using Disjoint Set Union (Union-Find) on shared emails. + * + *

Each account is expected to be a list where the first element is the user name and the + * remaining elements are email addresses. Accounts that share at least one email are merged into a + * single record. + */ +public final class AccountMerge { + private AccountMerge() { + // Utility class; do not instantiate. + } + + /** + * Merges accounts that share one or more email addresses. + * + *

The returned list is sorted by account owner name, then by the first email address when + * multiple merged groups have the same owner name. Within each merged account, emails are + * returned in lexicographic order. + * + * @param accounts a list of accounts where each entry contains a user name followed by emails + * @return merged accounts, or an empty list when {@code accounts} is null or empty + */ + public static List> mergeAccounts(List> accounts) { + if (accounts == null || accounts.isEmpty()) { + return List.of(); + } + + UnionFind dsu = new UnionFind(accounts.size()); + Map emailToAccount = new HashMap<>(); + + for (int i = 0; i < accounts.size(); i++) { + List account = accounts.get(i); + for (int j = 1; j < account.size(); j++) { + String email = account.get(j); + Integer previous = emailToAccount.putIfAbsent(email, i); + if (previous != null) { + dsu.union(i, previous); + } + } + } + + Map> rootToEmails = new LinkedHashMap<>(); + for (Map.Entry entry : emailToAccount.entrySet()) { + int root = dsu.find(entry.getValue()); + rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>()).add(entry.getKey()); + } + for (int i = 0; i < accounts.size(); i++) { + if (accounts.get(i).size() <= 1) { + int root = dsu.find(i); + rootToEmails.computeIfAbsent(root, ignored -> new ArrayList<>()); + } + } + + List> merged = new ArrayList<>(); + for (Map.Entry> entry : rootToEmails.entrySet()) { + int root = entry.getKey(); + List emails = entry.getValue(); + Collections.sort(emails); + + List mergedAccount = new ArrayList<>(); + mergedAccount.add(accounts.get(root).getFirst()); + mergedAccount.addAll(emails); + merged.add(mergedAccount); + } + + merged.sort((a, b) -> { + int cmp = a.getFirst().compareTo(b.getFirst()); + if (cmp != 0) { + return cmp; + } + if (a.size() == 1 || b.size() == 1) { + return Integer.compare(a.size(), b.size()); + } + return a.get(1).compareTo(b.get(1)); + }); + return merged; + } + + /** + * Lightweight union-find structure with path compression and union by rank. + */ + private static final class UnionFind { + private final int[] parent; + private final int[] rank; + + private UnionFind(int size) { + this.parent = new int[size]; + this.rank = new int[size]; + for (int i = 0; i < size; i++) { + parent[i] = i; + } + } + + private int find(int x) { + if (parent[x] != x) { + parent[x] = find(parent[x]); + } + return parent[x]; + } + + private void union(int x, int y) { + int rootX = find(x); + int rootY = find(y); + if (rootX == rootY) { + return; + } + + if (rank[rootX] < rank[rootY]) { + parent[rootX] = rootY; + } else if (rank[rootX] > rank[rootY]) { + parent[rootY] = rootX; + } else { + parent[rootY] = rootX; + rank[rootX]++; + } + } + } +} diff --git a/src/main/java/com/thealgorithms/graph/GomoryHuTree.java b/src/main/java/com/thealgorithms/graph/GomoryHuTree.java new file mode 100644 index 000000000000..f8c110f25571 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/GomoryHuTree.java @@ -0,0 +1,144 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Queue; + +/** + * Gomory–Hu tree construction for undirected graphs via n−1 max-flow computations. + * + *

API: {@code buildTree(int[][])} returns {@code {parent, weight}} arrays for the tree. + * + * @see Wikipedia: Gomory–Hu tree + */ + +public final class GomoryHuTree { + private GomoryHuTree() { + } + + public static int[][] buildTree(int[][] cap) { + validateCapacityMatrix(cap); + final int n = cap.length; + if (n == 1) { + return new int[][] {new int[] {-1}, new int[] {0}}; + } + + int[] parent = new int[n]; + int[] weight = new int[n]; + Arrays.fill(parent, 0); + parent[0] = -1; + weight[0] = 0; + + for (int s = 1; s < n; s++) { + int t = parent[s]; + MaxFlowResult res = edmondsKarpWithMinCut(cap, s, t); + int f = res.flow; + weight[s] = f; + + for (int v = 0; v < n; v++) { + if (v != s && parent[v] == t && res.reachable[v]) { + parent[v] = s; + } + } + + if (t != 0 && res.reachable[parent[t]]) { + parent[s] = parent[t]; + parent[t] = s; + weight[s] = weight[t]; + weight[t] = f; + } + } + return new int[][] {parent, weight}; + } + + private static void validateCapacityMatrix(int[][] cap) { + if (cap == null || cap.length == 0) { + throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + } + final int n = cap.length; + for (int i = 0; i < n; i++) { + if (cap[i] == null || cap[i].length != n) { + throw new IllegalArgumentException("Capacity matrix must be square"); + } + for (int j = 0; j < n; j++) { + if (cap[i][j] < 0) { + throw new IllegalArgumentException("Capacities must be non-negative"); + } + } + } + } + + private static final class MaxFlowResult { + final int flow; + final boolean[] reachable; + MaxFlowResult(int flow, boolean[] reachable) { + this.flow = flow; + this.reachable = reachable; + } + } + + private static MaxFlowResult edmondsKarpWithMinCut(int[][] capacity, int source, int sink) { + final int n = capacity.length; + int[][] residual = new int[n][n]; + for (int i = 0; i < n; i++) { + residual[i] = Arrays.copyOf(capacity[i], n); + } + + 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; + } + + boolean[] reachable = new boolean[n]; + markReachable(residual, source, reachable); + return new MaxFlowResult(maxFlow, reachable); + } + + private static boolean bfs(int[][] residual, int source, int sink, int[] parent) { + Arrays.fill(parent, -1); + parent[source] = source; + 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 && parent[v] == -1) { + parent[v] = u; + if (v == sink) { + return true; + } + q.add(v); + } + } + } + return false; + } + + private static void markReachable(int[][] residual, int source, boolean[] vis) { + Arrays.fill(vis, false); + Queue q = new ArrayDeque<>(); + vis[source] = true; + q.add(source); + while (!q.isEmpty()) { + int u = q.poll(); + for (int v = 0; v < residual.length; v++) { + if (!vis[v] && residual[u][v] > 0) { + vis[v] = true; + q.add(v); + } + } + } + } +} diff --git a/src/main/java/com/thealgorithms/graph/TarjanBridges.java b/src/main/java/com/thealgorithms/graph/TarjanBridges.java new file mode 100644 index 000000000000..dbe2e710429a --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/TarjanBridges.java @@ -0,0 +1,122 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of Tarjan's Bridge-Finding Algorithm for undirected graphs. + * + *

A bridge (also called a cut-edge) is an edge in an undirected graph whose removal + * increases the number of connected components. Bridges represent critical links + * in a network — if any bridge is removed, part of the network becomes unreachable.

+ * + *

The algorithm performs a single Depth-First Search (DFS) traversal, tracking two + * values for each vertex:

+ *
    + *
  • discoveryTime — the time step at which the vertex was first visited.
  • + *
  • lowLink — the smallest discovery time reachable from the subtree rooted + * at that vertex (via back edges).
  • + *
+ * + *

An edge (u, v) is a bridge if and only if {@code lowLink[v] > discoveryTime[u]}, + * meaning there is no back edge from the subtree of v that can reach u or any ancestor of u.

+ * + *

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

+ *

Space Complexity: O(V + E) for the adjacency list, discovery/low arrays, and recursion stack.

+ * + * @see Wikipedia: Bridge (graph theory) + */ +public final class TarjanBridges { + + private TarjanBridges() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Finds all bridge edges in an undirected graph. + * + *

The graph is represented as an adjacency list where each vertex is identified by + * an integer in the range {@code [0, vertexCount)}. For each undirected edge (u, v), + * v must appear in {@code adjacencyList.get(u)} and u must appear in + * {@code adjacencyList.get(v)}.

+ * + * @param vertexCount the total number of vertices in the graph (must be non-negative) + * @param adjacencyList the adjacency list representation of the graph; must contain + * exactly {@code vertexCount} entries (one per vertex) + * @return a list of bridge edges, where each bridge is represented as an {@code int[]} + * of length 2 with {@code edge[0] < edge[1]}; returns an empty list if no bridges exist + * @throws IllegalArgumentException if {@code vertexCount} is negative, or if + * {@code adjacencyList} is null or its size does not match + * {@code vertexCount} + */ + public static List findBridges(int vertexCount, List> adjacencyList) { + if (vertexCount < 0) { + throw new IllegalArgumentException("vertexCount must be non-negative"); + } + if (adjacencyList == null || adjacencyList.size() != vertexCount) { + throw new IllegalArgumentException("adjacencyList size must equal vertexCount"); + } + + List bridges = new ArrayList<>(); + + if (vertexCount == 0) { + return bridges; + } + + BridgeFinder finder = new BridgeFinder(vertexCount, adjacencyList, bridges); + + // Run DFS from every unvisited vertex to handle disconnected graphs + for (int i = 0; i < vertexCount; i++) { + if (!finder.visited[i]) { + finder.dfs(i, -1); + } + } + + return bridges; + } + + private static class BridgeFinder { + private final List> adjacencyList; + private final List bridges; + private final int[] discoveryTime; + private final int[] lowLink; + boolean[] visited; + private int timer; + + BridgeFinder(int vertexCount, List> adjacencyList, List bridges) { + this.adjacencyList = adjacencyList; + this.bridges = bridges; + this.discoveryTime = new int[vertexCount]; + this.lowLink = new int[vertexCount]; + this.visited = new boolean[vertexCount]; + this.timer = 0; + } + + /** + * Performs DFS from the given vertex, computing discovery times and low-link values, + * and collects any bridge edges found. + * + * @param u the current vertex being explored + * @param parent the parent of u in the DFS tree (-1 if u is a root) + */ + void dfs(int u, int parent) { + visited[u] = true; + discoveryTime[u] = timer; + lowLink[u] = timer; + timer++; + + for (int v : adjacencyList.get(u)) { + if (!visited[v]) { + dfs(v, u); + lowLink[u] = Math.min(lowLink[u], lowLink[v]); + + if (lowLink[v] > discoveryTime[u]) { + bridges.add(new int[] {Math.min(u, v), Math.max(u, v)}); + } + } else if (v != parent) { + lowLink[u] = Math.min(lowLink[u], discoveryTime[v]); + } + } + } + } +} diff --git a/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java b/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java index 8054581d21d7..5f9f6080d0e1 100644 --- a/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java +++ b/src/main/java/com/thealgorithms/greedyalgorithms/CoinChange.java @@ -6,10 +6,30 @@ // Problem Link : https://en.wikipedia.org/wiki/Change-making_problem +/** + * The Coin Change problem finds the minimum number of coins needed + * to make a given amount using a greedy approach. + * + *

Note: This greedy approach works optimally for standard coin systems + * (like Indian currency), but may not work for all arbitrary coin sets. + * For arbitrary denominations, dynamic programming is preferred. + * + * @see Change-making problem + */ public final class CoinChange { private CoinChange() { } - // Function to solve the coin change problem + + /** + * Returns the list of coins used to make the given amount + * using a greedy algorithm with standard denominations. + * + *

Time Complexity: O(n log n) where n is the number of coin denominations + *

Space Complexity: O(n) + * + * @param amount the total amount to make change for + * @return list of coins used to make the amount + */ public static ArrayList coinChangeProblem(int amount) { // Define an array of coin denominations in descending order Integer[] coins = {1, 2, 5, 10, 20, 50, 100, 500, 2000}; diff --git a/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java b/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java new file mode 100644 index 000000000000..134d4eee8c4c --- /dev/null +++ b/src/main/java/com/thealgorithms/machinelearning/LinearRegression.java @@ -0,0 +1,102 @@ +package com.thealgorithms.machinelearning; + +/** + * A simple Linear Regression model implemented from scratch using Gradient Descent. + * + * @see Linear Regression (Wikipedia) + * @author Vraj Prajapati (Rosander0) + */ +public class LinearRegression { + private double m; // Slope (weight) + private double b; // Y-intercept (bias) + private final double learningRate; + private final int epochs; + + /** + * Constructs a Linear Regression model with the given hyperparameters. + * + * @param learningRate controls the step size during gradient descent + * @param epochs the number of iterations to train the model + */ + public LinearRegression(double learningRate, int epochs) { + this.learningRate = learningRate; + this.epochs = epochs; + this.m = 0.0; + this.b = 0.0; + } + + /** + * Trains the model on the provided dataset using batch gradient descent. + * + * @param x the input feature values + * @param y the corresponding target values + * @throws IllegalArgumentException if the arrays are null, empty, or of differing lengths + */ + public void fit(double[] x, double[] y) { + if (x == null || y == null || x.length != y.length || x.length == 0) { + throw new IllegalArgumentException("X and Y must be non-null, non-empty, and of the same length."); + } + + int n = x.length; + + for (int epoch = 0; epoch < epochs; epoch++) { + double mGradient = 0; + double bGradient = 0; + + // Calculate gradients across the entire dataset + for (int i = 0; i < n; i++) { + double prediction = (m * x[i]) + b; + double error = prediction - y[i]; + + // Partial derivatives of the Mean Squared Error cost function + mGradient += error * x[i]; + bGradient += error; + } + + // Average the gradients and update the parameters + m -= 2.0 / n * mGradient * learningRate; + b -= 2.0 / n * bGradient * learningRate; + } + } + + /** + * Predicts the output for a given input x. + * + * @param x the input value + * @return the predicted output + */ + public double predict(double x) { + return (m * x) + b; + } + + /** + * Calculates the Mean Squared Error of the model against a dataset. + * + * @param x the input feature values + * @param y the corresponding target values + * @return the mean squared error + */ + public double calculateMSE(double[] x, double[] y) { + double totalSquaredError = 0; + int n = x.length; + for (int i = 0; i < n; i++) { + double error = predict(x[i]) - y[i]; + totalSquaredError += error * error; + } + return totalSquaredError / n; + } + + /** + * @return the learned slope of the regression line + */ + public double getSlope() { + return m; + } + + /** + * @return the learned y-intercept of the regression line + */ + public double getIntercept() { + return b; + } +} diff --git a/src/main/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifier.java b/src/main/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifier.java new file mode 100644 index 000000000000..43e7e555814c --- /dev/null +++ b/src/main/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifier.java @@ -0,0 +1,143 @@ +package com.thealgorithms.machinelearning; + +import java.util.HashMap; +import java.util.Map; + +/** + * Multinomial Naive Bayes classifier. + * + *

Suited to discrete, count-based features (e.g. word frequencies in text + * classification). Class priors and feature likelihoods are estimated from + * training data with Laplace (add-alpha) smoothing to avoid zero + * probabilities for unseen feature/class combinations. Predictions are made + * by comparing summed log-probabilities across classes, which avoids the + * numerical underflow that repeated multiplication of small probabilities + * would cause. + * + *

Reference: + * Naive Bayes classifier + * + * @author Vraj Prajapati(Rosander0) + */ +public final class MultinomialNaiveBayesClassifier { + + private final double alpha; + private final Map logPriors; + private final Map logLikelihoods; + private int numFeatures; + + /** + * Constructs a classifier with the given Laplace smoothing parameter. + * + * @param alpha smoothing constant; must be greater than 0. A value of 1.0 + * corresponds to standard Laplace smoothing. + */ + public MultinomialNaiveBayesClassifier(double alpha) { + if (alpha <= 0) { + throw new IllegalArgumentException("alpha must be greater than 0"); + } + this.alpha = alpha; + this.logPriors = new HashMap<>(); + this.logLikelihoods = new HashMap<>(); + } + + /** Constructs a classifier using the standard Laplace smoothing constant of 1.0. */ + public MultinomialNaiveBayesClassifier() { + this(1.0); + } + + /** + * Fits the classifier on the given feature matrix and labels. + * + * @param features training samples, each row a vector of non-negative + * feature counts + * @param labels class label for each row of {@code features} + */ + public void fit(double[][] features, int[] labels) { + if (features.length == 0 || features.length != labels.length) { + throw new IllegalArgumentException("features and labels must be non-empty and of equal length"); + } + logPriors.clear(); + logLikelihoods.clear(); + numFeatures = features[0].length; + + Map classCounts = new HashMap<>(); + Map featureSums = new HashMap<>(); + Map totalFeatureCount = new HashMap<>(); + + for (int i = 0; i < features.length; i++) { + int label = labels[i]; + classCounts.merge(label, 1, Integer::sum); + double[] sums = featureSums.computeIfAbsent(label, k -> new double[numFeatures]); + double total = totalFeatureCount.getOrDefault(label, 0.0); + for (int j = 0; j < numFeatures; j++) { + sums[j] += features[i][j]; + total += features[i][j]; + } + totalFeatureCount.put(label, total); + } + + int totalSamples = features.length; + for (Map.Entry entry : featureSums.entrySet()) { + int label = entry.getKey(); + double[] sums = entry.getValue(); + int count = classCounts.getOrDefault(label, 0); + double total = totalFeatureCount.getOrDefault(label, 0.0); + + logPriors.put(label, Math.log((double) count / totalSamples)); + + double denom = total + alpha * numFeatures; + double[] logLikelihood = new double[numFeatures]; + for (int j = 0; j < numFeatures; j++) { + logLikelihood[j] = Math.log((sums[j] + alpha) / denom); + } + logLikelihoods.put(label, logLikelihood); + } + } + + /** + * Predicts the most likely class for a single sample. + * + * @param sample feature vector of non-negative counts + * @return the predicted class label + */ + public int predict(double[] sample) { + if (logPriors.isEmpty()) { + throw new IllegalStateException("classifier has not been fitted"); + } + if (sample.length != numFeatures) { + throw new IllegalArgumentException("sample length must match training feature count"); + } + + int bestLabel = -1; + double bestScore = Double.NEGATIVE_INFINITY; + + for (Map.Entry entry : logLikelihoods.entrySet()) { + int label = entry.getKey(); + double[] logLikelihood = entry.getValue(); + double score = logPriors.getOrDefault(label, Double.NEGATIVE_INFINITY); + for (int j = 0; j < numFeatures; j++) { + score += sample[j] * logLikelihood[j]; + } + if (score > bestScore) { + bestScore = score; + bestLabel = label; + } + } + return bestLabel; + } + + /** + * Predicts class labels for a batch of samples. + * + * @param samples feature vectors of non-negative counts + * @return predicted class label for each row of {@code samples} + */ + public int[] predict(double[][] samples) { + int[] predictions = new int[samples.length]; + for (int i = 0; i < samples.length; i++) { + predictions[i] = predict(samples[i]); + } + return predictions; + } +} diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java index 1b9575a330dd..aab6fe0f426d 100644 --- a/src/main/java/com/thealgorithms/maths/AbsoluteMin.java +++ b/src/main/java/com/thealgorithms/maths/AbsoluteMin.java @@ -1,7 +1,5 @@ package com.thealgorithms.maths; -import java.util.Arrays; - public final class AbsoluteMin { private AbsoluteMin() { } @@ -13,14 +11,17 @@ private AbsoluteMin() { * @return The absolute min value */ public static int getMinValue(int... numbers) { - if (numbers.length == 0) { - throw new IllegalArgumentException("Numbers array cannot be empty"); + if (numbers == null || numbers.length == 0) { + throw new IllegalArgumentException("Numbers array cannot be empty or null"); } - var absMinWrapper = new Object() { int value = numbers[0]; }; - - Arrays.stream(numbers).skip(1).filter(number -> Math.abs(number) <= Math.abs(absMinWrapper.value)).forEach(number -> absMinWrapper.value = Math.min(absMinWrapper.value, number)); - - return absMinWrapper.value; + long absMin = numbers[0]; + for (int i = 1; i < numbers.length; i++) { + long current = numbers[i]; + if (Math.abs(current) < Math.abs(absMin) || (Math.abs(current) == Math.abs(absMin) && current < absMin)) { + absMin = current; + } + } + return (int) absMin; } } diff --git a/src/main/java/com/thealgorithms/maths/AbsoluteValue.java b/src/main/java/com/thealgorithms/maths/AbsoluteValue.java index b9279d5a244a..114eb71b1015 100644 --- a/src/main/java/com/thealgorithms/maths/AbsoluteValue.java +++ b/src/main/java/com/thealgorithms/maths/AbsoluteValue.java @@ -11,6 +11,6 @@ private AbsoluteValue() { * @return The absolute value of the {@code number} */ public static int getAbsValue(int number) { - return number < 0 ? -number : number; + return Math.abs(number); } } diff --git a/src/main/java/com/thealgorithms/maths/AbundantNumber.java b/src/main/java/com/thealgorithms/maths/AbundantNumber.java new file mode 100644 index 000000000000..804ac4d71477 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/AbundantNumber.java @@ -0,0 +1,58 @@ +package com.thealgorithms.maths; + +/** + * In number theory, an abundant number or excessive number is a positive integer for which + * the sum of its proper divisors is greater than the number. + * Equivalently, it is a number for which the sum of proper divisors (or aliquot sum) is greater than n. + * + * The integer 12 is the first abundant number. Its proper divisors are 1, 2, 3, 4 and 6 for a total of 16. + * + * Wiki: https://en.wikipedia.org/wiki/Abundant_number + */ +public final class AbundantNumber { + + private AbundantNumber() { + } + + // Function to calculate sum of all divisors including n + private static int sumOfDivisors(int n) { + int sum = 1 + n; // 1 and n are always divisors + for (int i = 2; i <= n / 2; i++) { + if (n % i == 0) { + sum += i; // adding divisor to sum + } + } + return sum; + } + + // Common validation method + private static void validatePositiveNumber(int number) { + if (number <= 0) { + throw new IllegalArgumentException("Number must be positive."); + } + } + + /** + * Check if {@code number} is an Abundant number or not by checking sum of divisors > 2n + * + * @param number the number + * @return {@code true} if {@code number} is an Abundant number, otherwise false + */ + public static boolean isAbundant(int number) { + validatePositiveNumber(number); + + return sumOfDivisors(number) > 2 * number; + } + + /** + * Check if {@code number} is an Abundant number or not by checking Aliquot Sum > n + * + * @param number the number + * @return {@code true} if {@code number} is a Abundant number, otherwise false + */ + public static boolean isAbundantNumber(int number) { + validatePositiveNumber(number); + + return AliquotSum.getAliquotSum(number) > number; + } +} diff --git a/src/main/java/com/thealgorithms/maths/Area.java b/src/main/java/com/thealgorithms/maths/Area.java index a34ad6b01ab5..84fc67159379 100644 --- a/src/main/java/com/thealgorithms/maths/Area.java +++ b/src/main/java/com/thealgorithms/maths/Area.java @@ -10,17 +10,17 @@ private Area() { /** * String of IllegalArgumentException for radius */ - private static final String POSITIVE_RADIUS = "Must be a positive radius"; + private static final String POSITIVE_RADIUS = "Radius must be greater than 0"; /** * String of IllegalArgumentException for height */ - private static final String POSITIVE_HEIGHT = "Must be a positive height"; + private static final String POSITIVE_HEIGHT = "Height must be greater than 0"; /** * String of IllegalArgumentException for base */ - private static final String POSITIVE_BASE = "Must be a positive base"; + private static final String POSITIVE_BASE = "Base must be greater than 0"; /** * Calculate the surface area of a cube. @@ -30,11 +30,32 @@ private Area() { */ public static double surfaceAreaCube(final double sideLength) { if (sideLength <= 0) { - throw new IllegalArgumentException("Must be a positive sideLength"); + throw new IllegalArgumentException("Side length must be greater than 0"); } return 6 * sideLength * sideLength; } + /** + * Calculate the surface area of a cuboid. + * + * @param length length of the cuboid + * @param width width of the cuboid + * @param height height of the cuboid + * @return surface area of given cuboid + */ + public static double surfaceAreaCuboid(final double length, double width, double height) { + if (length <= 0) { + throw new IllegalArgumentException("Length must be greater than 0"); + } + if (width <= 0) { + throw new IllegalArgumentException("Width must be greater than 0"); + } + if (height <= 0) { + throw new IllegalArgumentException("Height must be greater than 0"); + } + return 2 * (length * width + length * height + width * height); + } + /** * Calculate the surface area of a sphere. * @@ -57,10 +78,10 @@ public static double surfaceAreaSphere(final double radius) { */ public static double surfaceAreaPyramid(final double sideLength, final double slantHeight) { if (sideLength <= 0) { - throw new IllegalArgumentException("Must be a positive sideLength"); + throw new IllegalArgumentException(""); } if (slantHeight <= 0) { - throw new IllegalArgumentException("Must be a positive slantHeight"); + throw new IllegalArgumentException("slant height must be greater than 0"); } double baseArea = sideLength * sideLength; double lateralSurfaceArea = 2 * sideLength * slantHeight; @@ -76,10 +97,10 @@ public static double surfaceAreaPyramid(final double sideLength, final double sl */ public static double surfaceAreaRectangle(final double length, final double width) { if (length <= 0) { - throw new IllegalArgumentException("Must be a positive length"); + throw new IllegalArgumentException("Length must be greater than 0"); } if (width <= 0) { - throw new IllegalArgumentException("Must be a positive width"); + throw new IllegalArgumentException("Width must be greater than 0"); } return length * width; } @@ -96,7 +117,7 @@ public static double surfaceAreaCylinder(final double radius, final double heigh throw new IllegalArgumentException(POSITIVE_RADIUS); } if (height <= 0) { - throw new IllegalArgumentException(POSITIVE_RADIUS); + throw new IllegalArgumentException(POSITIVE_HEIGHT); } return 2 * (Math.PI * radius * radius + Math.PI * radius * height); } @@ -109,7 +130,7 @@ public static double surfaceAreaCylinder(final double radius, final double heigh */ public static double surfaceAreaSquare(final double sideLength) { if (sideLength <= 0) { - throw new IllegalArgumentException("Must be a positive sideLength"); + throw new IllegalArgumentException("Side Length must be greater than 0"); } return sideLength * sideLength; } @@ -121,14 +142,14 @@ public static double surfaceAreaSquare(final double sideLength) { * @param height height of triangle * @return area of given triangle */ - public static double surfaceAreaTriangle(final double base, final double height) { - if (base <= 0) { + public static double surfaceAreaTriangle(final double baseLength, final double height) { + if (baseLength <= 0) { throw new IllegalArgumentException(POSITIVE_BASE); } if (height <= 0) { throw new IllegalArgumentException(POSITIVE_HEIGHT); } - return base * height / 2; + return baseLength * height / 2; } /** @@ -138,14 +159,14 @@ public static double surfaceAreaTriangle(final double base, final double height) * @param height height of a parallelogram * @return area of given parallelogram */ - public static double surfaceAreaParallelogram(final double base, final double height) { - if (base <= 0) { + public static double surfaceAreaParallelogram(final double baseLength, final double height) { + if (baseLength <= 0) { throw new IllegalArgumentException(POSITIVE_BASE); } if (height <= 0) { throw new IllegalArgumentException(POSITIVE_HEIGHT); } - return base * height; + return baseLength * height; } /** @@ -156,17 +177,17 @@ public static double surfaceAreaParallelogram(final double base, final double he * @param height height of trapezium * @return area of given trapezium */ - public static double surfaceAreaTrapezium(final double base1, final double base2, final double height) { - if (base1 <= 0) { + public static double surfaceAreaTrapezium(final double baseLength1, final double baseLength2, final double height) { + if (baseLength1 <= 0) { throw new IllegalArgumentException(POSITIVE_BASE + 1); } - if (base2 <= 0) { + if (baseLength2 <= 0) { throw new IllegalArgumentException(POSITIVE_BASE + 2); } if (height <= 0) { throw new IllegalArgumentException(POSITIVE_HEIGHT); } - return (base1 + base2) * height / 2; + return (baseLength1 + baseLength2) * height / 2; } /** diff --git a/src/main/java/com/thealgorithms/maths/Average.java b/src/main/java/com/thealgorithms/maths/Average.java index a550a7f6504d..cf55af509ccc 100644 --- a/src/main/java/com/thealgorithms/maths/Average.java +++ b/src/main/java/com/thealgorithms/maths/Average.java @@ -1,9 +1,16 @@ package com.thealgorithms.maths; +import java.util.Arrays; +import java.util.OptionalDouble; + /** * A utility class for computing the average of numeric arrays. - * This class provides static methods to calculate the average of arrays - * of both {@code double} and {@code int} values. + * + *

This class provides static methods to calculate the arithmetic mean + * of arrays of both {@code double} and {@code int} values. It also offers + * a Stream-based alternative for modern, declarative usage. + * + *

All methods guard against {@code null} or empty inputs. */ public final class Average { @@ -13,11 +20,14 @@ private Average() { } /** - * Computes the average of a {@code double} array. + * Computes the arithmetic mean of a {@code double} array. + * + *

The average is calculated as the sum of all elements divided + * by the number of elements: {@code avg = Σ(numbers[i]) / n}. * - * @param numbers an array of {@code double} values - * @return the average of the given numbers - * @throws IllegalArgumentException if the input array is {@code null} or empty + * @param numbers a non-null, non-empty array of {@code double} values + * @return the arithmetic mean of the given numbers + * @throws IllegalArgumentException if {@code numbers} is {@code null} or empty */ public static double average(double[] numbers) { if (numbers == null || numbers.length == 0) { @@ -31,11 +41,14 @@ public static double average(double[] numbers) { } /** - * Computes the average of an {@code int} array. + * Computes the arithmetic mean of an {@code int} array. + * + *

The sum is accumulated in a {@code long} to prevent integer overflow + * when processing large arrays or large values. * - * @param numbers an array of {@code int} values - * @return the average of the given numbers - * @throws IllegalArgumentException if the input array is {@code null} or empty + * @param numbers a non-null, non-empty array of {@code int} values + * @return the arithmetic mean as a {@code long} (truncated toward zero) + * @throws IllegalArgumentException if {@code numbers} is {@code null} or empty */ public static long average(int[] numbers) { if (numbers == null || numbers.length == 0) { @@ -47,4 +60,21 @@ public static long average(int[] numbers) { } return sum / numbers.length; } + + /** + * Computes the arithmetic mean of a {@code double} array using Java Streams. + * + *

This method is a declarative alternative to {@link #average(double[])}. + * Instead of throwing on empty input, it returns an empty {@link OptionalDouble}, + * following the convention of the Stream API. + * + * @param numbers an array of {@code double} values, may be {@code null} or empty + * @return an {@link OptionalDouble} with the mean, or empty if input is null/empty + */ + public static OptionalDouble averageStream(double[] numbers) { + if (numbers == null || numbers.length == 0) { + return OptionalDouble.empty(); + } + return Arrays.stream(numbers).average(); + } } diff --git a/src/main/java/com/thealgorithms/maths/BellNumbers.java b/src/main/java/com/thealgorithms/maths/BellNumbers.java new file mode 100644 index 000000000000..d4dc1014f48b --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/BellNumbers.java @@ -0,0 +1,59 @@ +package com.thealgorithms.maths; + +/** + * The Bell numbers count the number of partitions of a set. + * The n-th Bell number is the number of ways a set of n elements can be partitioned + * into nonempty subsets. + * + *

+ * This implementation uses the Bell Triangle (Aitken's array) method. + * Time Complexity: O(n^2) + * Space Complexity: O(n^2) + *

+ * + * @author Chahat Sandhu, singhc7 + * @see Bell Number (Wikipedia) + */ +public final class BellNumbers { + + private BellNumbers() { + } + + /** + * Calculates the n-th Bell number using the Bell Triangle. + * + * @param n the index of the Bell number (must be non-negative) + * @return the n-th Bell number + * @throws IllegalArgumentException if n is negative or n > 25 + */ + public static long compute(int n) { + if (n < 0) { + throw new IllegalArgumentException("n must be non-negative"); + } + if (n == 0) { + return 1; + } + if (n > 25) { + throw new IllegalArgumentException("n must be <= 25. For larger n, use BigInteger implementation."); + } + + // We use a 2D array to visualize the Bell Triangle + long[][] bellTriangle = new long[n + 1][n + 1]; + + // Base case: The triangle starts with 1 + bellTriangle[0][0] = 1; + + for (int i = 1; i <= n; i++) { + // Rule 1: The first number in a new row is the LAST number of the previous row + bellTriangle[i][0] = bellTriangle[i - 1][i - 1]; + + // Rule 2: Fill the rest of the row by adding the previous neighbor and the upper-left neighbor + for (int j = 1; j <= i; j++) { + bellTriangle[i][j] = bellTriangle[i][j - 1] + bellTriangle[i - 1][j - 1]; + } + } + + // The Bell number B_n is the first number in the n-th row + return bellTriangle[n][0]; + } +} diff --git a/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java b/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java new file mode 100644 index 000000000000..4b68b7824574 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/ComplexNumberMultiply.java @@ -0,0 +1,32 @@ +package com.thealgorithms.maths; + +/** + * Multiplies two complex numbers represented as strings in the form "a+bi". + * Supports negative values and validates input format. + */ +public final class ComplexNumberMultiply { + + private ComplexNumberMultiply() { + } + + private static int[] parse(String num) { + if (num == null || !num.matches("-?\\d+\\+-?\\d+i")) { + throw new IllegalArgumentException("Invalid complex number format: " + num); + } + + String[] parts = num.split("\\+"); + int real = Integer.parseInt(parts[0]); + int imaginary = Integer.parseInt(parts[1].replace("i", "")); + return new int[] {real, imaginary}; + } + + public static String multiply(String num1, String num2) { + int[] a = parse(num1); + int[] b = parse(num2); + + int real = a[0] * b[0] - a[1] * b[1]; + int imaginary = a[0] * b[1] + a[1] * b[0]; + + return real + "+" + imaginary + "i"; + } +} diff --git a/src/main/java/com/thealgorithms/maths/Correlation.java b/src/main/java/com/thealgorithms/maths/Correlation.java new file mode 100644 index 000000000000..a46445fb23b7 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/Correlation.java @@ -0,0 +1,51 @@ +package com.thealgorithms.maths; + +/** + * Class for correlation of two discrete variables + */ + +public final class Correlation { + private Correlation() { + } + + public static final double DELTA = 1e-9; + + /** + * Discrete correlation function. + * Correlation between two discrete variables is calculated + * according to the formula: Cor(x, y)=Cov(x, y)/sqrt(Var(x)*Var(y)). + * Correlation with a constant variable is taken to be zero. + * + * @param x The first discrete variable + * @param y The second discrete variable + * @param n The number of values for each variable + * @return The result of the correlation of variables x,y. + */ + public static double correlation(double[] x, double[] y, int n) { + double exy = 0; // E(XY) + double ex = 0; // E(X) + double exx = 0; // E(X^2) + double ey = 0; // E(Y) + double eyy = 0; // E(Y^2) + for (int i = 0; i < n; i++) { + exy += x[i] * y[i]; + ex += x[i]; + exx += x[i] * x[i]; + ey += y[i]; + eyy += y[i] * y[i]; + } + exy /= n; + ex /= n; + exx /= n; + ey /= n; + eyy /= n; + double cov = exy - ex * ey; // Cov(X, Y) = E(XY)-E(X)E(Y) + double varx = Math.sqrt(exx - ex * ex); // Var(X) = sqrt(E(X^2)-E(X)^2) + double vary = Math.sqrt(eyy - ey * ey); // Var(Y) = sqrt(E(Y^2)-E(Y)^2) + if (varx * vary < DELTA) { // Var(X) = 0 means X = const, the same about Y + return 0; + } else { + return cov / Math.sqrt(varx * vary); + } + } +} diff --git a/src/main/java/com/thealgorithms/maths/DisariumNumber.java b/src/main/java/com/thealgorithms/maths/DisariumNumber.java new file mode 100644 index 000000000000..0196d0797bcc --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/DisariumNumber.java @@ -0,0 +1,37 @@ +package com.thealgorithms.maths; + +/** + * Disarium number is a number where the sum of its digits powered + * with their respective positions is equal to the number itself. + * Example: 135 = 1^1 + 3^2 + 5^3 = 1 + 9 + 125 = 135 + * + * @see Disarium Number + */ +public final class DisariumNumber { + + private DisariumNumber() { + } + + /** + * Checks if a number is a Disarium number. + * + * @param number the number to check (must be positive) + * @return true if number is Disarium, false otherwise + * @throws IllegalArgumentException if number is not positive + */ + public static boolean isDisarium(int number) { + if (number <= 0) { + throw new IllegalArgumentException("Input must be a positive integer."); + } + int digits = String.valueOf(number).length(); + int temp = number; + int sum = 0; + while (temp > 0) { + int lastDigit = temp % 10; + sum += (int) Math.pow(lastDigit, digits); + digits--; + temp /= 10; + } + return sum == number; + } +} diff --git a/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java b/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java new file mode 100644 index 000000000000..cd1c9205b328 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/DistanceBetweenTwoPoints.java @@ -0,0 +1,33 @@ +package com.thealgorithms.maths; + +/** + * Distance Between Two Points in 2D Space. + * + *

This class provides a method to calculate the Euclidean distance between two points in a + * two-dimensional plane. + * + *

Formula: d = sqrt((x2 - x1)^2 + (y2 - y1)^2) + * + *

Reference: https://en.wikipedia.org/wiki/Euclidean_distance + */ +public final class DistanceBetweenTwoPoints { + + private DistanceBetweenTwoPoints() { + // Utility class; prevent instantiation + } + + /** + * Calculate the Euclidean distance between two points. + * + * @param x1 x-coordinate of the first point + * @param y1 y-coordinate of the first point + * @param x2 x-coordinate of the second point + * @param y2 y-coordinate of the second point + * @return Euclidean distance between the two points + */ + public static double calculate(final double x1, final double y1, final double x2, final double y2) { + final double deltaX = x2 - x1; + final double deltaY = y2 - y1; + return Math.sqrt(deltaX * deltaX + deltaY * deltaY); + } +} diff --git a/src/main/java/com/thealgorithms/maths/EvilNumber.java b/src/main/java/com/thealgorithms/maths/EvilNumber.java new file mode 100644 index 000000000000..419133702fd4 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/EvilNumber.java @@ -0,0 +1,39 @@ +package com.thealgorithms.maths; + +/** + * In number theory, an evil number is a non-negative integer that has an even number of 1s in its binary expansion. + * Non-negative integers that are not evil are called odious numbers. + * + * Evil Number Wiki: https://en.wikipedia.org/wiki/Evil_number + * Odious Number Wiki: https://en.wikipedia.org/wiki/Odious_number + */ +public final class EvilNumber { + + private EvilNumber() { + } + + // Function to count number of one bits in a number using bitwise operators + private static int countOneBits(int number) { + int oneBitCounter = 0; + while (number > 0) { + oneBitCounter += number & 1; // increment count if last bit is 1 + number >>= 1; // right shift to next bit + } + return oneBitCounter; + } + + /** + * Check either {@code number} is an Evil number or Odious number + * + * @param number the number + * @return {@code true} if {@code number} is an Evil number, otherwise false (in case of of Odious number) + */ + public static boolean isEvilNumber(int number) { + if (number < 0) { + throw new IllegalArgumentException("Negative numbers are not allowed."); + } + + int noOfOneBits = countOneBits(number); + return noOfOneBits % 2 == 0; + } +} diff --git a/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java b/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java new file mode 100644 index 000000000000..4934d4493bf2 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithm.java @@ -0,0 +1,48 @@ +package com.thealgorithms.maths; + +/** + * In mathematics, the extended Euclidean algorithm is an extension to the + * Euclidean algorithm, and computes, in addition to the greatest common divisor + * (gcd) of integers a and b, also the coefficients of Bézout's identity, which + * are integers x and y such that ax + by = gcd(a, b). + * + *

+ * For more details, see + * https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm + */ +public final class ExtendedEuclideanAlgorithm { + + private ExtendedEuclideanAlgorithm() { + } + + /** + * This method implements the extended Euclidean algorithm. + * + * @param a The first number. + * @param b The second number. + * @return An array of three integers: + *

    + *
  • Index 0: The greatest common divisor (gcd) of a and b.
  • + *
  • Index 1: The value of x in the equation ax + by = gcd(a, b).
  • + *
  • Index 2: The value of y in the equation ax + by = gcd(a, b).
  • + *
+ */ + public static long[] extendedGCD(long a, long b) { + if (b == 0) { + // Base case: gcd(a, 0) = a. The equation is a*1 + 0*0 = a. + return new long[] {a, 1, 0}; + } + + // Recursive call + long[] result = extendedGCD(b, a % b); + long gcd = result[0]; + long x1 = result[1]; + long y1 = result[2]; + + // Update coefficients using the results from the recursive call + long x = y1; + long y = x1 - a / b * y1; + + return new long[] {gcd, x, y}; + } +} diff --git a/src/main/java/com/thealgorithms/maths/Factorial.java b/src/main/java/com/thealgorithms/maths/Factorial.java index 511cc1f84f05..8ad219a3066c 100644 --- a/src/main/java/com/thealgorithms/maths/Factorial.java +++ b/src/main/java/com/thealgorithms/maths/Factorial.java @@ -1,23 +1,19 @@ package com.thealgorithms.maths; +import java.math.BigInteger; + public final class Factorial { private Factorial() { } - /** - * Calculate factorial N using iteration - * - * @param n the number - * @return the factorial of {@code n} - */ - public static long factorial(int n) { + public static BigInteger factorial(int n) { if (n < 0) { throw new IllegalArgumentException("Input number cannot be negative"); } - long factorial = 1; - for (int i = 1; i <= n; ++i) { - factorial *= i; + BigInteger result = BigInteger.ONE; + for (int i = 1; i <= n; i++) { + result = result.multiply(BigInteger.valueOf(i)); } - return factorial; + return result; } } diff --git a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java index 84390860ccc4..8a93580a4b72 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciJavaStreams.java @@ -6,9 +6,23 @@ import java.util.stream.Stream; /** - * @author: caos321 - * @date: 14 October 2021 (Thursday) + * Calculates Fibonacci numbers using a functional programming paradigm with Java Streams. + *

+ * This specific implementation uses {@link java.util.stream.Stream#iterate} and reductions to generate terms. + *

+ * For alternative approaches to compute or verify Fibonacci numbers, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
+ * * @author caos321 + * @date 14 October 2021 (Thursday) */ + public final class FibonacciJavaStreams { private FibonacciJavaStreams() { } diff --git a/src/main/java/com/thealgorithms/maths/FibonacciLoop.java b/src/main/java/com/thealgorithms/maths/FibonacciLoop.java index de23a4305c3f..f19e3a6969c5 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciLoop.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciLoop.java @@ -1,9 +1,20 @@ package com.thealgorithms.maths; - import java.math.BigInteger; - /** * This class provides methods for calculating Fibonacci numbers using BigInteger for large values of 'n'. + *

+ * This specific implementation uses an Iterative approach (Loop) with {@code O(n)} time complexity + * and {@code O(1)} space complexity. + *

+ * For alternative approaches to compute or verify Fibonacci numbers, see: + *

    + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
*/ public final class FibonacciLoop { diff --git a/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java b/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java index 781275d3130d..6bdc2d6ed9fb 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciNumberCheck.java @@ -4,6 +4,18 @@ * Fibonacci: 0 1 1 2 3 5 8 13 21 ... * This code checks Fibonacci Numbers up to 45th number. * Other checks fail because of 'long'-type overflow. + *

+ * This class serves as a verification utility rather than a generation algorithm. + *

+ * For approaches that actively compute the n-th Fibonacci number, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
*/ public final class FibonacciNumberCheck { private FibonacciNumberCheck() { diff --git a/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java b/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java index 4df37a40f541..eca6379b93bc 100644 --- a/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java +++ b/src/main/java/com/thealgorithms/maths/FibonacciNumberGoldenRation.java @@ -3,6 +3,18 @@ /** * This class provides methods for calculating Fibonacci numbers using Binet's formula. * Binet's formula is based on the golden ratio and allows computing Fibonacci numbers efficiently. + *

+ * This specific implementation provides a closed-form solution with an expected {@code O(1)} time complexity. + *

+ * For alternative approaches to compute or verify Fibonacci numbers, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.recursion.FibonacciSeries} - Naive Recursive approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming approaches (Memoization, Bottom-Up, Optimized)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
* * @see Binet's formula on Wikipedia */ diff --git a/src/main/java/com/thealgorithms/maths/FriendlyNumber.java b/src/main/java/com/thealgorithms/maths/FriendlyNumber.java new file mode 100644 index 000000000000..900ce89295a4 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/FriendlyNumber.java @@ -0,0 +1,49 @@ +package com.thealgorithms.maths; + +/** + * Two numbers are Friendly if they share the same abundancy index, + * which is the ratio of the sum of divisors to the number itself. + * Example: 6 and 28 are friendly because sigma(6)/6 = 2 and sigma(28)/28 = 2 + * + * @see + * Wikipedia: Friendly Number + * + * @author Vraj Prajapati @Rosander0 + */ +public final class FriendlyNumber { + + private FriendlyNumber() { + // Utility class + } + + private static int sumOfDivisors(final int number) { + int sum = 0; + final int root = (int) Math.sqrt(number); + for (int i = 1; i <= root; i++) { + if (number % i == 0) { + sum += i; + final int other = number / i; + if (other != i) { + sum += other; + } + } + } + return sum; + } + + /** + * Checks whether two numbers are Friendly Numbers. + * + * @param a First number (must be positive) + * @param b Second number (must be positive) + * @return true if a and b are friendly numbers, false otherwise + */ + public static boolean areFriendly(final int a, final int b) { + if (a <= 0 || b <= 0) { + return false; + } + final long sigmaA = sumOfDivisors(a); + final long sigmaB = sumOfDivisors(b); + return sigmaA * b == sigmaB * a; + } +} diff --git a/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java b/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java new file mode 100644 index 000000000000..f4f2e23c3932 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/JacobsthalNumber.java @@ -0,0 +1,44 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +/** + * The Jacobsthal Sequence is a sequence of integers defined by the recurrence relation: + * J(n) = J(n-1) + 2*J(n-2) with initial values J(0) = 0, J(1) = 1. + * Example: 0, 1, 1, 3, 5, 11, 21, 43, 85, 171, 341... + * + * @see + * Wikipedia: Jacobsthal Number + */ +public final class JacobsthalNumber { + + private JacobsthalNumber() { + // Utility class + } + + /** + * Calculates the nth term of the Jacobsthal Sequence. + * + * @param n the index of the sequence (must be non-negative) + * @return the nth term of the Jacobsthal Sequence + */ + public static long jacobsthal(final int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative!"); + } + if (n == 0) { + return 0; + } + if (n == 1) { + return 1; + } + long a = 0; + long b = 1; + long result = 0; + for (int i = 2; i <= n; i++) { + result = b + 2 * a; + a = b; + b = result; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java b/src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java similarity index 94% rename from src/main/java/com/thealgorithms/others/LowestBasePalindrome.java rename to src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java index a3ca8d6f6db8..4a79b4298fc4 100644 --- a/src/main/java/com/thealgorithms/others/LowestBasePalindrome.java +++ b/src/main/java/com/thealgorithms/maths/LowestBasePalindrome.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.maths; import java.util.ArrayList; import java.util.List; @@ -23,6 +23,12 @@ * * @see OEIS A016026 - Smallest base in which * n is palindromic + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber * @author TheAlgorithms Contributors */ public final class LowestBasePalindrome { diff --git a/src/main/java/com/thealgorithms/maths/LuckyNumber.java b/src/main/java/com/thealgorithms/maths/LuckyNumber.java new file mode 100644 index 000000000000..70308e1e0edd --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/LuckyNumber.java @@ -0,0 +1,78 @@ +package com.thealgorithms.maths; + +/** + * In number theory, a lucky number is a natural number in a set which is generated by a certain "sieve". + * This sieve is similar to the sieve of Eratosthenes that generates the primes, + * but it eliminates numbers based on their position in the remaining set, + * instead of their value (or position in the initial set of natural numbers). + * + * Wiki: https://en.wikipedia.org/wiki/Lucky_number + */ +public final class LuckyNumber { + + private LuckyNumber() { + } + + // Common validation method + private static void validatePositiveNumber(int number) { + if (number <= 0) { + throw new IllegalArgumentException("Number must be positive."); + } + } + + // Function to check recursively for Lucky Number + private static boolean isLuckyRecursiveApproach(int n, int counter) { + // Base case: If counter exceeds n, number is lucky + if (counter > n) { + return true; + } + + // If number is eliminated in this step, it's not lucky + if (n % counter == 0) { + return false; + } + + // Calculate new position after removing every counter-th number + int newNumber = n - (n / counter); + + // Recursive call for next round + return isLuckyRecursiveApproach(newNumber, counter + 1); + } + + /** + * Check if {@code number} is a Lucky number or not using recursive approach + * + * @param number the number + * @return {@code true} if {@code number} is a Lucky number, otherwise false + */ + public static boolean isLuckyNumber(int number) { + validatePositiveNumber(number); + int counterStarting = 2; + return isLuckyRecursiveApproach(number, counterStarting); + } + + /** + * Check if {@code number} is a Lucky number or not using iterative approach + * + * @param number the number + * @return {@code true} if {@code number} is a Lucky number, otherwise false + */ + public static boolean isLucky(int number) { + validatePositiveNumber(number); + + int counter = 2; // Position starts from 2 (since first elimination happens at 2) + int position = number; // The position of the number in the sequence + + while (counter <= position) { + if (position % counter == 0) { + return false; + } // Number is eliminated + + // Update the position of n after removing every counter-th number + position = position - (position / counter); + counter++; + } + + return true; // Survives all eliminations → Lucky Number + } +} diff --git a/src/main/java/com/thealgorithms/maths/Means.java b/src/main/java/com/thealgorithms/maths/Means.java index 5445a3caebc7..d77eb1d3f661 100644 --- a/src/main/java/com/thealgorithms/maths/Means.java +++ b/src/main/java/com/thealgorithms/maths/Means.java @@ -107,6 +107,28 @@ public static Double harmonic(final Iterable numbers) { return size / sumOfReciprocals; } + /** + * Computes the quadratic mean (root mean square) of the given numbers. + *

+ * The quadratic mean is calculated as: √[(x₁^2 × x₂^2 × ... × xₙ^2)/n] + *

+ *

+ * Example: For numbers [1, 7], the quadratic mean is √[(1^2+7^2)/2] = √25 = 5.0 + *

+ * + * @param numbers the input numbers (must not be empty) + * @return the quadratic mean of the input numbers + * @throws IllegalArgumentException if the input is empty + * @see Quadratic + * Mean + */ + public static Double quadratic(final Iterable numbers) { + checkIfNotEmpty(numbers); + double sumOfSquares = StreamSupport.stream(numbers.spliterator(), false).reduce(0d, (x, y) -> x + y * y); + int size = IterableUtils.size(numbers); + return Math.pow(sumOfSquares / size, 0.5); + } + /** * Validates that the input iterable is not empty. * diff --git a/src/main/java/com/thealgorithms/maths/PadovanSequence.java b/src/main/java/com/thealgorithms/maths/PadovanSequence.java new file mode 100644 index 000000000000..51e7d2441b15 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/PadovanSequence.java @@ -0,0 +1,43 @@ +package com.thealgorithms.maths; + +/** + * The Padovan Sequence is a sequence of integers defined by the recurrence relation: + * P(n) = P(n-2) + P(n-3) with initial values P(0) = P(1) = P(2) = 1. + * Example: 1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21, 28, 37... + * + * @see + * Wikipedia: Padovan Sequence + * @author Vraj Prajapati (@Rosander0) + */ +public final class PadovanSequence { + + private PadovanSequence() { + // Utility class + } + + /** + * Calculates the nth term of the Padovan Sequence. + * + * @param n the index of the sequence (must be non-negative) + * @return the nth term of the Padovan Sequence + */ + public static long padovan(final int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative. Received: " + n); + } + if (n <= 2) { + return 1; + } + long a = 1; + long b = 1; + long c = 1; + long result = 0; + for (int i = 3; i <= n; i++) { + result = a + b; + a = b; + b = c; + c = result; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/maths/PalindromeNumber.java b/src/main/java/com/thealgorithms/maths/PalindromeNumber.java index a22d63897b37..9543f83332a7 100644 --- a/src/main/java/com/thealgorithms/maths/PalindromeNumber.java +++ b/src/main/java/com/thealgorithms/maths/PalindromeNumber.java @@ -1,5 +1,16 @@ package com.thealgorithms.maths; +/** + * A class to check if a given number is a palindrome. + * A palindromic number is a number that remains the same when its digits are reversed. + * + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + */ public final class PalindromeNumber { private PalindromeNumber() { } diff --git a/src/main/java/com/thealgorithms/misc/PalindromePrime.java b/src/main/java/com/thealgorithms/maths/PalindromePrime.java similarity index 73% rename from src/main/java/com/thealgorithms/misc/PalindromePrime.java rename to src/main/java/com/thealgorithms/maths/PalindromePrime.java index 164e957a9d12..21b76acefee8 100644 --- a/src/main/java/com/thealgorithms/misc/PalindromePrime.java +++ b/src/main/java/com/thealgorithms/maths/PalindromePrime.java @@ -1,8 +1,19 @@ -package com.thealgorithms.misc; +package com.thealgorithms.maths; import java.util.ArrayList; import java.util.List; +/** + * A class to check and generate palindromic prime numbers. + * A palindromic prime is a prime number that is also a palindromic number. + * + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromeNumber + */ public final class PalindromePrime { private PalindromePrime() { } diff --git a/src/main/java/com/thealgorithms/maths/PerfectSquare.java b/src/main/java/com/thealgorithms/maths/PerfectSquare.java index e9318bd7d805..aec43062121a 100644 --- a/src/main/java/com/thealgorithms/maths/PerfectSquare.java +++ b/src/main/java/com/thealgorithms/maths/PerfectSquare.java @@ -15,6 +15,9 @@ private PerfectSquare() { * false */ public static boolean isPerfectSquare(final int number) { + if (number < 0) { + return false; + } final int sqrt = (int) Math.sqrt(number); return sqrt * sqrt == number; } @@ -27,6 +30,9 @@ public static boolean isPerfectSquare(final int number) { * {@code false} */ public static boolean isPerfectSquareUsingPow(long number) { + if (number < 0) { + return false; + } long a = (long) Math.pow(number, 1.0 / 2); return a * a == number; } diff --git a/src/main/java/com/thealgorithms/maths/Perimeter.java b/src/main/java/com/thealgorithms/maths/Perimeter.java index f8aa1876d388..670851eb346b 100644 --- a/src/main/java/com/thealgorithms/maths/Perimeter.java +++ b/src/main/java/com/thealgorithms/maths/Perimeter.java @@ -27,7 +27,7 @@ public static float perimeterRegularPolygon(int n, float side) { * @param side2 for length of side 2 * @param side3 for length of side 3 * @param sides for length of remaining sides - * @return Perimeter of given trapezoid. + * @return Perimeter of given irregular polygon. */ public static float perimeterIrregularPolygon(float side1, float side2, float side3, float... sides) { float perimeter = side1 + side2 + side3; diff --git a/src/main/java/com/thealgorithms/maths/PerrinNumber.java b/src/main/java/com/thealgorithms/maths/PerrinNumber.java new file mode 100644 index 000000000000..cee45a1c5538 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/PerrinNumber.java @@ -0,0 +1,53 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +/** + * The Perrin Sequence is a sequence of integers defined by the recurrence relation: + * P(n) = P(n-2) + P(n-3) with initial values P(0) = 3, P(1) = 0, P(2) = 2. + * Example: 3, 0, 2, 3, 2, 5, 5, 7, 10, 12, 17, 22, 29, 39, 51... + * + * Note: The Perrin Sequence uses the same recurrence relation as the Padovan Sequence + * but has different initial values. + * + * @see + * Wikipedia: Perrin Number + * @see PadovanSequence + */ +public final class PerrinNumber { + + private PerrinNumber() { + // Utility class + } + + /** + * Calculates the nth term of the Perrin Sequence. + * + * @param n the index of the sequence (must be non-negative) + * @return the nth term of the Perrin Sequence + */ + public static long perrin(final int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative!"); + } + if (n == 0) { + return 3; + } + if (n == 1) { + return 0; + } + if (n == 2) { + return 2; + } + long a = 3; + long b = 0; + long c = 2; + long result = 0; + for (int i = 3; i <= n; i++) { + result = a + b; + a = b; + b = c; + c = result; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/maths/PowerOfFour.java b/src/main/java/com/thealgorithms/maths/PowerOfFour.java new file mode 100644 index 000000000000..e5fe6255821b --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/PowerOfFour.java @@ -0,0 +1,36 @@ +package com.thealgorithms.maths; + +/** + * Utility class for checking if a number is a power of four. + * A power of four is a number that can be expressed as 4^n where n is a non-negative integer. + * This class provides a method to determine if a given integer is a power of four using bit manipulation. + * + * @author krishna-medapati (https://github.com/krishna-medapati) + */ +public final class PowerOfFour { + private PowerOfFour() { + } + + /** + * Checks if the given integer is a power of four. + * + * A number is considered a power of four if: + * 1. It is greater than zero + * 2. It has exactly one '1' bit in its binary representation (power of two) + * 3. The '1' bit is at an even position (0, 2, 4, 6, ...) + * + * The method uses the mask 0x55555555 (binary: 01010101010101010101010101010101) + * to check if the set bit is at an even position. + * + * @param number the integer to check + * @return true if the number is a power of four, false otherwise + */ + public static boolean isPowerOfFour(int number) { + if (number <= 0) { + return false; + } + boolean isPowerOfTwo = (number & (number - 1)) == 0; + boolean hasEvenBitPosition = (number & 0x55555555) != 0; + return isPowerOfTwo && hasEvenBitPosition; + } +} diff --git a/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java b/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java index f22d22e8c6af..5a15c4201a15 100644 --- a/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java +++ b/src/main/java/com/thealgorithms/maths/SieveOfEratosthenes.java @@ -1,66 +1,82 @@ package com.thealgorithms.maths; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; /** - * @brief utility class implementing Sieve of Eratosthenes + * Sieve of Eratosthenes Algorithm + * An efficient algorithm to find all prime numbers up to a given limit. + * + * Algorithm: + * 1. Create a boolean array of size n+1, initially all true + * 2. Mark 0 and 1 as not prime + * 3. For each number i from 2 to sqrt(n): + * - If i is still marked as prime + * - Mark all multiples of i (starting from i²) as not prime + * 4. Collect all numbers still marked as prime + * + * Time Complexity: O(n log log n) + * Space Complexity: O(n) + * + * @author Navadeep0007 + * @see Sieve of Eratosthenes */ public final class SieveOfEratosthenes { + private SieveOfEratosthenes() { + // Utility class, prevent instantiation } - private static void checkInput(int n) { - if (n <= 0) { - throw new IllegalArgumentException("n must be positive."); + /** + * Finds all prime numbers up to n using the Sieve of Eratosthenes algorithm + * + * @param n the upper limit (inclusive) + * @return a list of all prime numbers from 2 to n + * @throws IllegalArgumentException if n is negative + */ + public static List findPrimes(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative"); } - } - private static Type[] sievePrimesTill(int n) { - checkInput(n); - Type[] isPrimeArray = new Type[n + 1]; - Arrays.fill(isPrimeArray, Type.PRIME); - isPrimeArray[0] = Type.NOT_PRIME; - isPrimeArray[1] = Type.NOT_PRIME; + if (n < 2) { + return new ArrayList<>(); + } + + // Create boolean array, initially all true + boolean[] isPrime = new boolean[n + 1]; + for (int i = 2; i <= n; i++) { + isPrime[i] = true; + } - double cap = Math.sqrt(n); - for (int i = 2; i <= cap; i++) { - if (isPrimeArray[i] == Type.PRIME) { - for (int j = 2; i * j <= n; j++) { - isPrimeArray[i * j] = Type.NOT_PRIME; + // Sieve process + for (int i = 2; i * i <= n; i++) { + if (isPrime[i]) { + // Mark all multiples of i as not prime + for (int j = i * i; j <= n; j += i) { + isPrime[j] = false; } } } - return isPrimeArray; - } - - private static int countPrimes(Type[] isPrimeArray) { - return (int) Arrays.stream(isPrimeArray).filter(element -> element == Type.PRIME).count(); - } - private static int[] extractPrimes(Type[] isPrimeArray) { - int numberOfPrimes = countPrimes(isPrimeArray); - int[] primes = new int[numberOfPrimes]; - int primeIndex = 0; - for (int curNumber = 0; curNumber < isPrimeArray.length; ++curNumber) { - if (isPrimeArray[curNumber] == Type.PRIME) { - primes[primeIndex++] = curNumber; + // Collect all prime numbers + List primes = new ArrayList<>(); + for (int i = 2; i <= n; i++) { + if (isPrime[i]) { + primes.add(i); } } + return primes; } /** - * @brief finds all of the prime numbers up to the given upper (inclusive) limit - * @param n upper (inclusive) limit - * @exception IllegalArgumentException n is non-positive - * @return the array of all primes up to the given number (inclusive) + * Counts the number of prime numbers up to n + * + * @param n the upper limit (inclusive) + * @return count of prime numbers from 2 to n */ - public static int[] findPrimesTill(int n) { - return extractPrimes(sievePrimesTill(n)); - } - - private enum Type { - PRIME, - NOT_PRIME, + public static int countPrimes(int n) { + return findPrimes(n).size(); } } diff --git a/src/main/java/com/thealgorithms/maths/SmithNumber.java b/src/main/java/com/thealgorithms/maths/SmithNumber.java new file mode 100644 index 000000000000..c06e0023d9bb --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SmithNumber.java @@ -0,0 +1,52 @@ +package com.thealgorithms.maths; + +import com.thealgorithms.maths.Prime.PrimeCheck; + +/** + * In number theory, a smith number is a composite number for which, in a given number base, + * the sum of its digits is equal to the sum of the digits in its prime factorization in the same base. + * + * For example, in base 10, 378 = 21 X 33 X 71 is a Smith number since 3 + 7 + 8 = 2 X 1 + 3 X 3 + 7 X 1, + * and 22 = 21 X 111 is a Smith number, because 2 + 2 = 2 X 1 + (1 + 1) X 1. + * + * Wiki: https://en.wikipedia.org/wiki/Smith_number + */ +public final class SmithNumber { + + private SmithNumber() { + } + + private static int primeFactorDigitSum(int n) { + int sum = 0; + int num = n; + + // Factorize the number using trial division + for (int i = 2; i * i <= num; i++) { + while (n % i == 0) { // while i divides n + sum += SumOfDigits.sumOfDigits(i); // add sum of digits of factor + n /= i; // divide n by the factor + } + } + + // If n is still > 1, it itself is a prime factor + if (n > 1) { + sum += SumOfDigits.sumOfDigits(n); + } + + return sum; + } + + /** + * Check if {@code number} is Smith number or not + * + * @param number the number + * @return {@code true} if {@code number} is a Smith number, otherwise false + */ + public static boolean isSmithNumber(int number) { + if (PrimeCheck.isPrime(number)) { + return false; // Smith numbers must be composite + } + + return SumOfDigits.sumOfDigits(number) == primeFactorDigitSum(number); + } +} diff --git a/src/main/java/com/thealgorithms/maths/SociableNumber.java b/src/main/java/com/thealgorithms/maths/SociableNumber.java new file mode 100644 index 000000000000..9ce644e61dfc --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/SociableNumber.java @@ -0,0 +1,67 @@ +package com.thealgorithms.maths; + +/** + * Sociable numbers are natural numbers that form a cyclic sequence where the + * sum of proper divisors of each number equals the next number in the sequence, + * with the sequence eventually returning to the starting number. + * Amicable numbers are a special case of sociable numbers with a cycle length of 2. + * Example: (12496, 14288, 15472, 14536, 14264) is a sociable cycle of length 5. + * + * @author Vraj Prajapati (@Rosander0) + * @see Wikipedia: Sociable Number + * @see AmicableNumber + */ +public final class SociableNumber { + + private SociableNumber() { + // Utility class + } + + /** + * Calculates the sum of proper divisors of a number + * (all divisors excluding the number itself). + * + * @param number the number to calculate proper divisors sum for + * @return sum of proper divisors, or 0 if number is less than or equal to 1 + */ + static int sumOfProperDivisors(final int number) { + if (number <= 1) { + return 0; + } + int sum = 1; // 1 is a proper divisor of every number > 1 + final int root = (int) Math.sqrt(number); + for (int i = 2; i <= root; i++) { + if (number % i == 0) { + final int other = number / i; + sum += i; + if (other != i) { + sum += other; + } + } + } + return sum; + } + + /** + * Checks whether a number is part of a sociable cycle of a given length. + * Starting from the given number, it follows the chain of proper divisor + * sums and checks if it returns to the starting number in exactly cycleLength steps. + * + * @param number the starting number (must be positive) + * @param cycleLength the expected cycle length (must be greater than 1) + * @return true if the number is part of a sociable cycle of given length, false otherwise + */ + public static boolean isSociable(final int number, final int cycleLength) { + if (number <= 0 || cycleLength <= 1) { + return false; + } + int current = number; + for (int i = 0; i < cycleLength; i++) { + current = sumOfProperDivisors(current); + if (current == number) { + return i == cycleLength - 1; + } + } + return false; + } +} diff --git a/src/main/java/com/thealgorithms/maths/SumOfSquares.java b/src/main/java/com/thealgorithms/maths/SumOfSquares.java index c050d5a75f7b..77acbcc2a609 100644 --- a/src/main/java/com/thealgorithms/maths/SumOfSquares.java +++ b/src/main/java/com/thealgorithms/maths/SumOfSquares.java @@ -5,7 +5,6 @@ * Find minimum number of perfect squares that sum to given number * * @see Lagrange's Four Square Theorem - * @author BEASTSHRIRAM */ public final class SumOfSquares { @@ -16,10 +15,15 @@ private SumOfSquares() { /** * Find minimum number of perfect squares that sum to n * - * @param n the target number + * @param n the target number (must be non-negative) * @return minimum number of squares needed + * @throws IllegalArgumentException if n is negative */ public static int minSquares(int n) { + if (n < 0) { + throw new IllegalArgumentException("Input must be non-negative"); + } + if (isPerfectSquare(n)) { return 1; } diff --git a/src/main/java/com/thealgorithms/maths/Volume.java b/src/main/java/com/thealgorithms/maths/Volume.java index 0f282b2abae2..488b921cae83 100644 --- a/src/main/java/com/thealgorithms/maths/Volume.java +++ b/src/main/java/com/thealgorithms/maths/Volume.java @@ -102,4 +102,39 @@ public static double volumePyramid(double baseArea, double height) { public static double volumeFrustumOfCone(double r1, double r2, double height) { return (Math.PI * height / 3) * (r1 * r1 + r2 * r2 + r1 * r2); } + + /** + * Calculate the volume of a frustum of a pyramid. + * + * @param upperBaseArea area of the upper base + * @param lowerBaseArea area of the lower base + * @param height height of the frustum + * @return volume of the frustum + */ + public static double volumeFrustumOfPyramid(double upperBaseArea, double lowerBaseArea, double height) { + return (upperBaseArea + lowerBaseArea + Math.sqrt(upperBaseArea * lowerBaseArea)) * height / 3; + } + + /** + * Calculate the volume of a torus. + * + * @param majorRadius major radius of a torus + * @param minorRadius minor radius of a torus + * @return volume of the torus + */ + public static double volumeTorus(double majorRadius, double minorRadius) { + return 2 * Math.PI * Math.PI * majorRadius * minorRadius * minorRadius; + } + + /** + * Calculate the volume of an ellipsoid. + * + * @param a first semi-axis of an ellipsoid + * @param b second semi-axis of an ellipsoid + * @param c third semi-axis of an ellipsoid + * @return volume of the ellipsoid + */ + public static double volumeEllipsoid(double a, double b, double c) { + return (4 * Math.PI * a * b * c) / 3; + } } diff --git a/src/main/java/com/thealgorithms/matrix/QRDecomposition.java b/src/main/java/com/thealgorithms/matrix/QRDecomposition.java new file mode 100644 index 000000000000..45f56bc14729 --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/QRDecomposition.java @@ -0,0 +1,149 @@ +package com.thealgorithms.matrix; + +/** + * @brief Implementation of QR Decomposition using the Gram-Schmidt process + * @details Decomposes a matrix A into an orthogonal matrix Q and an upper + * triangular matrix R such that A = Q * R. The Gram-Schmidt process + * orthogonalizes the columns of A to produce Q, and R is computed as Q^T * A. + * This decomposition is useful for solving linear least squares problems, + * eigenvalue computations, and numerical stability in linear algebra. + * @see QR Decomposition + */ +public final class QRDecomposition { + + private QRDecomposition() { + } + + /** + * A helper class to store both Q and R matrices + */ + public static class QR { + private final double[][] q; + private final double[][] r; + + QR(double[][] q, double[][] r) { + this.q = q; + this.r = r; + } + + public double[][] getQ() { + return q; + } + + public double[][] getR() { + return r; + } + } + + /** + * @brief Performs QR decomposition on a matrix using the Gram-Schmidt process + * @param matrix the input matrix (m x n) + * @return QR object containing orthogonal matrix Q (m x n) and upper triangular matrix R (n x n) + * @throws IllegalArgumentException if the matrix is null, empty, or has invalid rows + */ + public static QR decompose(double[][] matrix) { + validateInputMatrix(matrix); + + int m = matrix.length; + int n = matrix[0].length; + + double[][] q = new double[m][n]; + double[][] r = new double[n][n]; + + for (int j = 0; j < n; j++) { + double[] v = getColumn(matrix, j); + + for (int i = 0; i < j; i++) { + double[] qi = getColumn(q, i); + r[i][j] = dotProduct(qi, v); + v = subtractVectors(v, scalarMultiply(qi, r[i][j])); + } + + r[j][j] = norm(v); + if (r[j][j] == 0) { + throw new ArithmeticException("Matrix is rank deficient. Cannot perform QR decomposition."); + } + double[] qj = scalarMultiply(v, 1.0 / r[j][j]); + setColumn(q, j, qj); + } + + return new QR(q, r); + } + + private static double[] getColumn(double[][] matrix, int col) { + int m = matrix.length; + double[] column = new double[m]; + for (int i = 0; i < m; i++) { + column[i] = matrix[i][col]; + } + return column; + } + + private static void setColumn(double[][] matrix, int col, double[] column) { + for (int i = 0; i < matrix.length; i++) { + matrix[i][col] = column[i]; + } + } + + private static double dotProduct(double[] a, double[] b) { + double sum = 0; + for (int i = 0; i < a.length; i++) { + sum += a[i] * b[i]; + } + return sum; + } + + private static double[] subtractVectors(double[] a, double[] b) { + double[] result = new double[a.length]; + for (int i = 0; i < a.length; i++) { + result[i] = a[i] - b[i]; + } + return result; + } + + private static double[] scalarMultiply(double[] v, double scalar) { + double[] result = new double[v.length]; + for (int i = 0; i < v.length; i++) { + result[i] = v[i] * scalar; + } + return result; + } + + private static double norm(double[] v) { + return Math.sqrt(dotProduct(v, v)); + } + + private static void validateInputMatrix(double[][] matrix) { + if (matrix == null) { + throw new IllegalArgumentException("The input matrix cannot be null"); + } + if (matrix.length == 0) { + throw new IllegalArgumentException("The input matrix cannot be empty"); + } + if (!hasValidRows(matrix)) { + throw new IllegalArgumentException("The input matrix cannot have null or empty rows"); + } + if (isJaggedMatrix(matrix)) { + throw new IllegalArgumentException("The input matrix cannot be jagged"); + } + } + + private static boolean hasValidRows(double[][] matrix) { + for (double[] row : matrix) { + if (row == null || row.length == 0) { + return false; + } + } + return true; + } + + private static boolean isJaggedMatrix(double[][] matrix) { + int numColumns = matrix[0].length; + for (double[] row : matrix) { + if (row.length != numColumns) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/thealgorithms/matrix/StochasticMatrix.java b/src/main/java/com/thealgorithms/matrix/StochasticMatrix.java new file mode 100644 index 000000000000..8b071113f9cc --- /dev/null +++ b/src/main/java/com/thealgorithms/matrix/StochasticMatrix.java @@ -0,0 +1,74 @@ +package com.thealgorithms.matrix; + +/** + * Utility class to check whether a matrix is stochastic. + * A matrix is stochastic if all its elements are non-negative + * and the sum of each row or column is equal to 1. + *Reference: https://en.wikipedia.org/wiki/Stochastic_matrix + */ +public final class StochasticMatrix { + + private static final double TOLERANCE = 1e-9; + + private StochasticMatrix() { + // Utility class + } + /** + * Checks if a matrix is row-stochastic. + * + * @param matrix the matrix to check + * @return true if the matrix is row-stochastic + * @throws IllegalArgumentException if matrix is null or empty + */ + public static boolean isRowStochastic(double[][] matrix) { + validateMatrix(matrix); + + for (double[] row : matrix) { + double sum = 0.0; + for (double value : row) { + if (value < 0) { + return false; + } + sum += value; + } + if (Math.abs(sum - 1.0) > TOLERANCE) { + return false; + } + } + return true; + } + + /** + * Checks if a matrix is column-stochastic. + * + * @param matrix the matrix to check + * @return true if the matrix is column-stochastic + * @throws IllegalArgumentException if matrix is null or empty + */ + public static boolean isColumnStochastic(double[][] matrix) { + validateMatrix(matrix); + + int rows = matrix.length; + int cols = matrix[0].length; + + for (int j = 0; j < cols; j++) { + double sum = 0.0; + for (int i = 0; i < rows; i++) { + if (matrix[i][j] < 0) { + return false; + } + sum += matrix[i][j]; + } + if (Math.abs(sum - 1.0) > TOLERANCE) { + return false; + } + } + return true; + } + + private static void validateMatrix(double[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + throw new IllegalArgumentException("Matrix must not be null or empty"); + } + } +} diff --git a/src/main/java/com/thealgorithms/others/Dijkstra.java b/src/main/java/com/thealgorithms/others/Dijkstra.java deleted file mode 100644 index a379100a2f3b..000000000000 --- a/src/main/java/com/thealgorithms/others/Dijkstra.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.thealgorithms.others; - -import java.util.HashMap; -import java.util.Map; -import java.util.NavigableSet; -import java.util.TreeSet; -/** - * Dijkstra's algorithm,is a graph search algorithm that solves the - * single-source shortest path problem for a graph with nonnegative edge path - * costs, producing a shortest path tree. - * - *

- * NOTE: The inputs to Dijkstra's algorithm are a directed and weighted graph - * consisting of 2 or more nodes, generally represented by an adjacency matrix - * or list, and a start node. - * - *

- * Original source of code: - * https://rosettacode.org/wiki/Dijkstra%27s_algorithm#Java Also most of the - * comments are from RosettaCode. - */ -public final class Dijkstra { - private Dijkstra() { - } - - private static final Graph.Edge[] GRAPH = { - // Distance from node "a" to node "b" is 7. - // In the current Graph there is no way to move the other way (e,g, from "b" to "a"), - // a new edge would be needed for that - new Graph.Edge("a", "b", 7), - new Graph.Edge("a", "c", 9), - new Graph.Edge("a", "f", 14), - new Graph.Edge("b", "c", 10), - new Graph.Edge("b", "d", 15), - new Graph.Edge("c", "d", 11), - new Graph.Edge("c", "f", 2), - new Graph.Edge("d", "e", 6), - new Graph.Edge("e", "f", 9), - }; - private static final String START = "a"; - private static final String END = "e"; - - /** - * main function Will run the code with "GRAPH" that was defined above. - */ - public static void main(String[] args) { - Graph g = new Graph(GRAPH); - g.dijkstra(START); - g.printPath(END); - // g.printAllPaths(); - } -} - -class Graph { - - // mapping of vertex names to Vertex objects, built from a set of Edges - - private final Map graph; - - /** - * One edge of the graph (only used by Graph constructor) - */ - public static class Edge { - - public final String v1; - public final String v2; - public final int dist; - - Edge(String v1, String v2, int dist) { - this.v1 = v1; - this.v2 = v2; - this.dist = dist; - } - } - - /** - * One vertex of the graph, complete with mappings to neighbouring vertices - */ - public static class Vertex implements Comparable { - - public final String name; - // MAX_VALUE assumed to be infinity - public int dist = Integer.MAX_VALUE; - public Vertex previous = null; - public final Map neighbours = new HashMap<>(); - - Vertex(String name) { - this.name = name; - } - - private void printPath() { - if (this == this.previous) { - System.out.printf("%s", this.name); - } else if (this.previous == null) { - System.out.printf("%s(unreached)", this.name); - } else { - this.previous.printPath(); - System.out.printf(" -> %s(%d)", this.name, this.dist); - } - } - - public int compareTo(Vertex other) { - if (dist == other.dist) { - return name.compareTo(other.name); - } - - return Integer.compare(dist, other.dist); - } - - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object == null || getClass() != object.getClass()) { - return false; - } - if (!super.equals(object)) { - return false; - } - - Vertex vertex = (Vertex) object; - - if (dist != vertex.dist) { - return false; - } - if (name != null ? !name.equals(vertex.name) : vertex.name != null) { - return false; - } - if (previous != null ? !previous.equals(vertex.previous) : vertex.previous != null) { - return false; - } - return neighbours != null ? neighbours.equals(vertex.neighbours) : vertex.neighbours == null; - } - - @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (name != null ? name.hashCode() : 0); - result = 31 * result + dist; - result = 31 * result + (previous != null ? previous.hashCode() : 0); - result = 31 * result + (neighbours != null ? neighbours.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "(" + name + ", " + dist + ")"; - } - } - - /** - * Builds a graph from a set of edges - */ - Graph(Edge[] edges) { - graph = new HashMap<>(edges.length); - - // one pass to find all vertices - for (Edge e : edges) { - if (!graph.containsKey(e.v1)) { - graph.put(e.v1, new Vertex(e.v1)); - } - if (!graph.containsKey(e.v2)) { - graph.put(e.v2, new Vertex(e.v2)); - } - } - - // another pass to set neighbouring vertices - for (Edge e : edges) { - graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist); - // graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also do this for an - // undirected graph - } - } - - /** - * Runs dijkstra using a specified source vertex - */ - public void dijkstra(String startName) { - if (!graph.containsKey(startName)) { - System.err.printf("Graph doesn't contain start vertex \"%s\"%n", startName); - return; - } - final Vertex source = graph.get(startName); - NavigableSet q = new TreeSet<>(); - - // set-up vertices - for (Vertex v : graph.values()) { - v.previous = v == source ? source : null; - v.dist = v == source ? 0 : Integer.MAX_VALUE; - q.add(v); - } - - dijkstra(q); - } - - /** - * Implementation of dijkstra's algorithm using a binary heap. - */ - private void dijkstra(final NavigableSet q) { - Vertex u; - Vertex v; - while (!q.isEmpty()) { - // vertex with shortest distance (first iteration will return source) - u = q.pollFirst(); - if (u.dist == Integer.MAX_VALUE) { - break; // we can ignore u (and any other remaining vertices) since they are - // unreachable - } - // look at distances to each neighbour - for (Map.Entry a : u.neighbours.entrySet()) { - v = a.getKey(); // the neighbour in this iteration - - final int alternateDist = u.dist + a.getValue(); - if (alternateDist < v.dist) { // shorter path to neighbour found - q.remove(v); - v.dist = alternateDist; - v.previous = u; - q.add(v); - } - } - } - } - - /** - * Prints a path from the source to the specified vertex - */ - public void printPath(String endName) { - if (!graph.containsKey(endName)) { - System.err.printf("Graph doesn't contain end vertex \"%s\"%n", endName); - return; - } - - graph.get(endName).printPath(); - System.out.println(); - } - - /** - * Prints the path from the source to every vertex (output order is not - * guaranteed) - */ - public void printAllPaths() { - for (Vertex v : graph.values()) { - v.printPath(); - System.out.println(); - } - } -} diff --git a/src/main/java/com/thealgorithms/others/cn/HammingDistance.java b/src/main/java/com/thealgorithms/others/cn/HammingDistance.java deleted file mode 100644 index c8239d53d606..000000000000 --- a/src/main/java/com/thealgorithms/others/cn/HammingDistance.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.thealgorithms.others.cn; - -public final class HammingDistance { - private HammingDistance() { - } - - private static void checkChar(char inChar) { - if (inChar != '0' && inChar != '1') { - throw new IllegalArgumentException("Input must be a binary string."); - } - } - - public static int compute(char charA, char charB) { - checkChar(charA); - checkChar(charB); - return charA == charB ? 0 : 1; - } - - public static int compute(String bitsStrA, String bitsStrB) { - if (bitsStrA.length() != bitsStrB.length()) { - throw new IllegalArgumentException("Input strings must have the same length."); - } - - int totalErrorBitCount = 0; - - for (int i = 0; i < bitsStrA.length(); i++) { - totalErrorBitCount += compute(bitsStrA.charAt(i), bitsStrB.charAt(i)); - } - - return totalErrorBitCount; - } -} diff --git a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java index 399c3f1e041f..d096e0a8d7cd 100644 --- a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java +++ b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java @@ -41,7 +41,7 @@ public static void resolveCollision(Body a, Body b) { double dy = b.y - a.y; double dist = Math.hypot(dx, dy); - if (dist == 0) { + if (dist < a.radius + b.radius) { return; // overlapping } diff --git a/src/main/java/com/thealgorithms/physics/Relativity.java b/src/main/java/com/thealgorithms/physics/Relativity.java new file mode 100644 index 000000000000..ed823c2cc879 --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/Relativity.java @@ -0,0 +1,81 @@ +package com.thealgorithms.physics; + +/** + * Implements relativity theory formulae. + * Provides simple static methods to calculate length contraction and time dilation + * in the laboratory frame with respect to the object's own frame, and velocity + * with respect to the moving frame. + * + * @see Wikipedia + */ +public final class Relativity { + + /* Speed of light in m s^-1 */ + public static final double SPEED_OF_LIGHT = 299792458.0; + + /** + * Private constructor to prevent instantiation of this utility class. + */ + private Relativity() { + } + + /** + * Calculates the gamma parameter that is of paramount importance in relativity + * theory. It is a dimensionless parameter that is equal to 1 for zero velocity + * but tends to infinity when velocity approaches the speed of light. + * + * @param v The velocity (m/s). + * @return The value of gamma parameter. + */ + public static double gamma(double v) { + if (Math.abs(v) >= SPEED_OF_LIGHT) { + throw new IllegalArgumentException("Speed must be lower than the speed of light"); + } + return 1.0 / Math.sqrt(1 - v * v / (SPEED_OF_LIGHT * SPEED_OF_LIGHT)); + } + + /** + * Calculates the length of an object in the moving frame. + * + * @param length The length of an object in its own frame (m). + * @param v The velocity of the object (m/s). + * @return The length of an object in the laboratory frame (m). + */ + public static double lengthContraction(double length, double v) { + if (length < 0) { + throw new IllegalArgumentException("Length must be non-negative"); + } + return length / gamma(v); + } + + /** + * Calculates the time that has passed in the moving frame. + * + * @param length The time that has passed in the object's own frame (s). + * @param v The velocity of the object (m/s). + * @return The time that has passed in the laboratory frame (s). + */ + public static double timeDilation(double time, double v) { + if (time < 0) { + throw new IllegalArgumentException("Time must be non-negative"); + } + return time * gamma(v); + } + + /** + * Calculates the velocity with respect to the moving frame. + * + * @param v1 The velocity of the object with respect to laboratory frame (m/s). + * @param v The velocity of the moving frame (m/s). + * @return The velocity with respect to the moving frame (m/s). + */ + public static double velocityAddition(double v1, double v) { + if (Math.abs(v1) > SPEED_OF_LIGHT) { + throw new IllegalArgumentException("Speed must not exceed the speed of light"); + } + if (Math.abs(v) >= SPEED_OF_LIGHT) { + throw new IllegalArgumentException("Frame speed must be lower than the speed of light"); + } + return (v1 - v) / (1 - v1 * v / (SPEED_OF_LIGHT * SPEED_OF_LIGHT)); + } +} diff --git a/src/main/java/com/thealgorithms/physics/SnellLaw.java b/src/main/java/com/thealgorithms/physics/SnellLaw.java new file mode 100644 index 000000000000..2736984814fd --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/SnellLaw.java @@ -0,0 +1,33 @@ +package com.thealgorithms.physics; + +/** + * Calculates refraction angle using Snell's Law: + * n1 * sin(theta1) = n2 * sin(theta2) + * @see Snell's Law + */ +public final class SnellLaw { + + private SnellLaw() { + throw new AssertionError("No instances."); + } + + /** + * Computes the refracted angle (theta2) in radians. + * + * @param n1 index of refraction of medium 1 + * @param n2 index of refraction of medium 2 + * @param theta1 incident angle in radians + * @return refracted angle (theta2) in radians + * @throws IllegalArgumentException if total internal reflection occurs + */ + public static double refractedAngle(double n1, double n2, double theta1) { + double ratio = n1 / n2; + double sinTheta2 = ratio * Math.sin(theta1); + + if (Math.abs(sinTheta2) > 1.0) { + throw new IllegalArgumentException("Total internal reflection: no refraction possible."); + } + + return Math.asin(sinTheta2); + } +} diff --git a/src/main/java/com/thealgorithms/physics/ThinLens.java b/src/main/java/com/thealgorithms/physics/ThinLens.java new file mode 100644 index 000000000000..5fb29d8c41e4 --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/ThinLens.java @@ -0,0 +1,74 @@ +package com.thealgorithms.physics; + +/** + * Implements the Thin Lens Formula used in ray optics: + * + *

+ *     1/f = 1/v + 1/u
+ * 
+ * + * where: + *
    + *
  • f = focal length
  • + *
  • u = object distance
  • + *
  • v = image distance
  • + *
+ * + * Uses the Cartesian sign convention. + * + * @see Thin Lens + */ +public final class ThinLens { + + private ThinLens() { + throw new AssertionError("No instances."); + } + + /** + * Computes the image distance using the thin lens formula. + * + * @param focalLength focal length of the lens (f) + * @param objectDistance object distance (u) + * @return image distance (v) + * @throws IllegalArgumentException if focal length or object distance is zero + */ + public static double imageDistance(double focalLength, double objectDistance) { + + if (focalLength == 0 || objectDistance == 0) { + throw new IllegalArgumentException("Focal length and object distance must be non-zero."); + } + + return 1.0 / ((1.0 / focalLength) - (1.0 / objectDistance)); + } + + /** + * Computes magnification of the image. + * + *
+     *     m = v / u
+     * 
+ * + * @param imageDistance image distance (v) + * @param objectDistance object distance (u) + * @return magnification + * @throws IllegalArgumentException if object distance is zero + */ + public static double magnification(double imageDistance, double objectDistance) { + + if (objectDistance == 0) { + throw new IllegalArgumentException("Object distance must be non-zero."); + } + + return imageDistance / objectDistance; + } + + /** + * Determines whether the image formed is real or virtual. + * + * @param imageDistance image distance (v) + * @return {@code true} if image is real, {@code false} if virtual + */ + public static boolean isRealImage(double imageDistance) { + return imageDistance > 0; + } +} diff --git a/src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java b/src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java new file mode 100644 index 000000000000..1be55039cff0 --- /dev/null +++ b/src/main/java/com/thealgorithms/prefixsum/DifferenceArray.java @@ -0,0 +1,87 @@ +package com.thealgorithms.prefixsum; + +/** + * Implements the Difference Array algorithm. + * + *

+ * The Difference Array is an auxiliary data structure that enables efficient range update operations. + * It is based on the mathematical concept of Finite Differences. + *

+ * + *

+ * Key Operations: + *

    + *
  • Range Update (Add value to [L, R]): O(1)
  • + *
  • Reconstruction (Prefix Sum): O(N)
  • + *
+ *

+ * + * @see Finite Difference (Wikipedia) + * @see Prefix Sum (Wikipedia) + * @author Chahat Sandhu, singhc7 + */ +public class DifferenceArray { + + private final long[] differenceArray; + private final int n; + + /** + * Initializes the Difference Array from a given integer array. + * + * @param inputArray The initial array. Cannot be null or empty. + * @throws IllegalArgumentException if the input array is null or empty. + */ + public DifferenceArray(int[] inputArray) { + if (inputArray == null || inputArray.length == 0) { + throw new IllegalArgumentException("Input array cannot be null or empty."); + } + this.n = inputArray.length; + // Size n + 1 allows for branchless updates at the right boundary (r + 1). + this.differenceArray = new long[n + 1]; + initializeDifferenceArray(inputArray); + } + + private void initializeDifferenceArray(int[] inputArray) { + differenceArray[0] = inputArray[0]; + for (int i = 1; i < n; i++) { + differenceArray[i] = inputArray[i] - inputArray[i - 1]; + } + } + + /** + * Adds a value to all elements in the range [l, r]. + * + *

+ * This method uses a branchless approach by allocating an extra element at the end + * of the array, avoiding the conditional check for the right boundary. + *

+ * + * @param l The starting index (inclusive). + * @param r The ending index (inclusive). + * @param val The value to add. + * @throws IllegalArgumentException if the range is invalid. + */ + public void update(int l, int r, int val) { + if (l < 0 || r >= n || l > r) { + throw new IllegalArgumentException(String.format("Invalid range: [%d, %d] for array of size %d", l, r, n)); + } + + differenceArray[l] += val; + differenceArray[r + 1] -= val; + } + + /** + * Reconstructs the final array using prefix sums. + * + * @return The resulting array after all updates. Returns long[] to handle potential overflows. + */ + public long[] getResultArray() { + long[] result = new long[n]; + result[0] = differenceArray[0]; + + for (int i = 1; i < n; i++) { + result[i] = differenceArray[i] + result[i - 1]; + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/prefixsum/PrefixSum.java b/src/main/java/com/thealgorithms/prefixsum/PrefixSum.java new file mode 100644 index 000000000000..47f6366e2924 --- /dev/null +++ b/src/main/java/com/thealgorithms/prefixsum/PrefixSum.java @@ -0,0 +1,54 @@ +package com.thealgorithms.prefixsum; + +/** + * A class that implements the Prefix Sum algorithm. + * + *

Prefix Sum is a technique used to preprocess an array such that + * range sum queries can be answered in O(1) time. + * The preprocessing step takes O(N) time. + * + *

This implementation uses a long array for the prefix sums to prevent + * integer overflow when the sum of elements exceeds Integer.MAX_VALUE. + * + * @see Prefix Sum (Wikipedia) + * @author Chahat Sandhu, singhc7 + */ +public class PrefixSum { + + private final long[] prefixSums; + + /** + * Constructor to preprocess the input array. + * + * @param array The input integer array. + * @throws IllegalArgumentException if the array is null. + */ + public PrefixSum(int[] array) { + if (array == null) { + throw new IllegalArgumentException("Input array cannot be null"); + } + this.prefixSums = new long[array.length + 1]; + this.prefixSums[0] = 0; + + for (int i = 0; i < array.length; i++) { + // Automatically promotes int to long during addition + this.prefixSums[i + 1] = this.prefixSums[i] + array[i]; + } + } + + /** + * Calculates the sum of elements in the range [left, right]. + * Indices are 0-based. + * + * @param left The starting index (inclusive). + * @param right The ending index (inclusive). + * @return The sum of elements from index left to right as a long. + * @throws IndexOutOfBoundsException if indices are out of valid range. + */ + public long sumRange(int left, int right) { + if (left < 0 || right >= prefixSums.length - 1 || left > right) { + throw new IndexOutOfBoundsException("Invalid range indices"); + } + return prefixSums[right + 1] - prefixSums[left]; + } +} diff --git a/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java b/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java new file mode 100644 index 000000000000..9c168bc6bcc4 --- /dev/null +++ b/src/main/java/com/thealgorithms/prefixsum/PrefixSum2D.java @@ -0,0 +1,64 @@ +package com.thealgorithms.prefixsum; + +/** + * A class that implements the 2D Prefix Sum algorithm. + * + *

2D Prefix Sum is a technique used to preprocess a 2D matrix such that + * sub-matrix sum queries can be answered in O(1) time. + * The preprocessing step takes O(N*M) time. + * + *

This implementation uses a long array for the prefix sums to prevent + * integer overflow. + * + * @see Summed-area table (Wikipedia) + * @author Chahat Sandhu, singhc7 + */ +public class PrefixSum2D { + + private final long[][] prefixSums; + + /** + * Constructor to preprocess the input matrix. + * + * @param matrix The input integer matrix. + * @throws IllegalArgumentException if the matrix is null or empty. + */ + public PrefixSum2D(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + throw new IllegalArgumentException("Input matrix cannot be null or empty"); + } + + int rows = matrix.length; + int cols = matrix[0].length; + this.prefixSums = new long[rows + 1][cols + 1]; + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + // P[i+1][j+1] = current + above + left - diagonal_overlap + this.prefixSums[i + 1][j + 1] = matrix[i][j] + this.prefixSums[i][j + 1] + this.prefixSums[i + 1][j] - this.prefixSums[i][j]; + } + } + } + + /** + * Calculates the sum of the sub-matrix defined by (row1, col1) to (row2, col2). + * Indices are 0-based. + * + * @param row1 Top row index. + * @param col1 Left column index. + * @param row2 Bottom row index. + * @param col2 Right column index. + * @return The sum of the sub-matrix. + * @throws IndexOutOfBoundsException if indices are invalid. + */ + public long sumRegion(int row1, int col1, int row2, int col2) { + if (row1 < 0 || row2 >= prefixSums.length - 1 || row2 < row1) { + throw new IndexOutOfBoundsException("Invalid row indices"); + } + if (col1 < 0 || col2 >= prefixSums[0].length - 1 || col2 < col1) { + throw new IndexOutOfBoundsException("Invalid column indices"); + } + + return prefixSums[row2 + 1][col2 + 1] - prefixSums[row1][col2 + 1] - prefixSums[row2 + 1][col1] + prefixSums[row1][col1]; + } +} diff --git a/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java b/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java new file mode 100644 index 000000000000..14a02a2de4d0 --- /dev/null +++ b/src/main/java/com/thealgorithms/prefixsum/RangeSumQuery.java @@ -0,0 +1,73 @@ +package com.thealgorithms.prefixsum; + +/** + * Implements an algorithm to efficiently compute the sum of elements + * between any two indices in an integer array using the Prefix Sum technique. + * + *

+ * Given an array nums, this algorithm precomputes the prefix sum array + * to allow O(1) sum queries for any range [left, right]. + *

+ * + *

+ * Let prefixSum[i] be the sum of elements from index 0 to i-1. + * The sum of elements from left to right is: + * + *

+ * prefixSum[right + 1] - prefixSum[left]
+ * 
+ *

+ * + *

+ * Time Complexity: O(N) for preprocessing, O(1) per query
+ * Space Complexity: O(N) + *

+ * + * @author Ruturaj Jadhav, ruturajjadhav07 + */ +public final class RangeSumQuery { + + private RangeSumQuery() { + // Utility class; prevent instantiation + } + + /** + * Computes the prefix sum array for efficient range queries. + * + * @param nums The input integer array. + * @return Prefix sum array where prefixSum[i+1] = sum of nums[0..i]. + * @throws IllegalArgumentException if nums is null. + */ + public static int[] buildPrefixSum(int[] nums) { + if (nums == null) { + throw new IllegalArgumentException("Input array cannot be null"); + } + + int n = nums.length; + int[] prefixSum = new int[n + 1]; + for (int i = 0; i < n; i++) { + prefixSum[i + 1] = prefixSum[i] + nums[i]; + } + return prefixSum; + } + + /** + * Returns the sum of elements from index left to right (inclusive) + * using the provided prefix sum array. + * + * @param prefixSum The prefix sum array computed using buildPrefixSum. + * @param left The start index (inclusive). + * @param right The end index (inclusive). + * @return The sum of elements in the range [left, right]. + * @throws IllegalArgumentException if indices are invalid. + */ + public static int sumRange(int[] prefixSum, int left, int right) { + if (prefixSum == null) { + throw new IllegalArgumentException("Prefix sum array cannot be null"); + } + if (left < 0 || right >= prefixSum.length - 1 || left > right) { + throw new IllegalArgumentException("Invalid range indices"); + } + return prefixSum[right + 1] - prefixSum[left]; + } +} diff --git a/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java b/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java new file mode 100644 index 000000000000..d6a6bbc01663 --- /dev/null +++ b/src/main/java/com/thealgorithms/prefixsum/SubarraySumEqualsK.java @@ -0,0 +1,72 @@ +package com.thealgorithms.prefixsum; + +import java.util.HashMap; +import java.util.Map; + +/** + * Implements an algorithm to count the number of continuous subarrays + * whose sum equals a given value k. + * + *

+ * This algorithm uses the Prefix Sum technique combined with a HashMap + * to achieve O(N) time complexity. + *

+ * + *

+ * Let prefixSum[i] be the sum of elements from index 0 to i. + * A subarray (j + 1) to i has sum k if: + * + *

+ * prefixSum[i] - prefixSum[j] = k
+ * 
+ *

+ * + *

+ * The HashMap stores the frequency of each prefix sum encountered so far. + *

+ * + *

+ * Time Complexity: O(N)
+ * Space Complexity: O(N) + *

+ * + * @see Prefix Sum (Wikipedia) + * @author Ruturaj Jadhav, ruturajjadhav07 + */ +public final class SubarraySumEqualsK { + + private SubarraySumEqualsK() { + // Utility class; prevent instantiation + } + + /** + * Counts the number of subarrays whose sum equals k. + * + * @param nums The input integer array. + * @param k The target sum. + * @return The number of continuous subarrays summing to k. + * @throws IllegalArgumentException if nums is null. + */ + public static int countSubarrays(int[] nums, int k) { + if (nums == null) { + throw new IllegalArgumentException("Input array cannot be null"); + } + + Map prefixSumFrequency = new HashMap<>(); + prefixSumFrequency.put(0L, 1); + + long prefixSum = 0; + int count = 0; + + for (int num : nums) { + prefixSum += num; + + long requiredSum = prefixSum - k; + count += prefixSumFrequency.getOrDefault(requiredSum, 0); + + prefixSumFrequency.put(prefixSum, prefixSumFrequency.getOrDefault(prefixSum, 0) + 1); + } + + return count; + } +} diff --git a/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java b/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java deleted file mode 100644 index fce665c4de00..000000000000 --- a/src/main/java/com/thealgorithms/puzzlesandgames/Sudoku.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.thealgorithms.puzzlesandgames; - -/** - * A class that provides methods to solve Sudoku puzzles of any n x n size - * using a backtracking approach, where n must be a perfect square. - * The algorithm checks for safe number placements in rows, columns, - * and subgrids (which are sqrt(n) x sqrt(n) in size) and recursively solves the puzzle. - * Though commonly used for 9x9 grids, it is adaptable to other valid Sudoku dimensions. - */ -final class Sudoku { - - private Sudoku() { - } - - /** - * Checks if placing a number in a specific position on the Sudoku board is safe. - * The number is considered safe if it does not violate any of the Sudoku rules: - * - It should not be present in the same row. - * - It should not be present in the same column. - * - It should not be present in the corresponding 3x3 subgrid. - * - It should not be present in the corresponding subgrid, which is sqrt(n) x sqrt(n) in size (e.g., for a 9x9 grid, the subgrid will be 3x3). - * - * @param board The current state of the Sudoku board. - * @param row The row index where the number is to be placed. - * @param col The column index where the number is to be placed. - * @param num The number to be placed on the board. - * @return True if the placement is safe, otherwise false. - */ - public static boolean isSafe(int[][] board, int row, int col, int num) { - // Check the row for duplicates - for (int d = 0; d < board.length; d++) { - if (board[row][d] == num) { - return false; - } - } - - // Check the column for duplicates - for (int r = 0; r < board.length; r++) { - if (board[r][col] == num) { - return false; - } - } - - // Check the corresponding 3x3 subgrid for duplicates - int sqrt = (int) Math.sqrt(board.length); - int boxRowStart = row - row % sqrt; - int boxColStart = col - col % sqrt; - - for (int r = boxRowStart; r < boxRowStart + sqrt; r++) { - for (int d = boxColStart; d < boxColStart + sqrt; d++) { - if (board[r][d] == num) { - return false; - } - } - } - - return true; - } - - /** - * Solves the Sudoku puzzle using backtracking. - * The algorithm finds an empty cell and tries placing numbers - * from 1 to n, where n is the size of the board - * (for example, from 1 to 9 in a standard 9x9 Sudoku). - * The algorithm finds an empty cell and tries placing numbers from 1 to 9. - * The standard version of Sudoku uses numbers from 1 to 9, so the algorithm can be - * easily modified for other variations of the game. - * If a number placement is valid (checked via `isSafe`), the number is - * placed and the function recursively attempts to solve the rest of the puzzle. - * If no solution is possible, the number is removed (backtracked), - * and the process is repeated. - * - * @param board The current state of the Sudoku board. - * @param n The size of the Sudoku board (typically 9 for a standard puzzle). - * @return True if the Sudoku puzzle is solvable, false otherwise. - */ - public static boolean solveSudoku(int[][] board, int n) { - int row = -1; - int col = -1; - boolean isEmpty = true; - - // Find the next empty cell - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - if (board[i][j] == 0) { - row = i; - col = j; - isEmpty = false; - break; - } - } - if (!isEmpty) { - break; - } - } - - // No empty space left - if (isEmpty) { - return true; - } - - // Try placing numbers 1 to n in the empty cell (n should be a perfect square) - // Eg: n=9 for a standard 9x9 Sudoku puzzle, n=16 for a 16x16 puzzle, etc. - for (int num = 1; num <= n; num++) { - if (isSafe(board, row, col, num)) { - board[row][col] = num; - if (solveSudoku(board, n)) { - return true; - } else { - // replace it - board[row][col] = 0; - } - } - } - return false; - } - - /** - * Prints the current state of the Sudoku board in a readable format. - * Each row is printed on a new line, with numbers separated by spaces. - * - * @param board The current state of the Sudoku board. - * @param n The size of the Sudoku board (typically 9 for a standard puzzle). - */ - public static void print(int[][] board, int n) { - // Print the board in a nxn grid format - // if n=9, print the board in a 9x9 grid format - // if n=16, print the board in a 16x16 grid format - for (int r = 0; r < n; r++) { - for (int d = 0; d < n; d++) { - System.out.print(board[r][d]); - System.out.print(" "); - } - System.out.print("\n"); - - if ((r + 1) % (int) Math.sqrt(n) == 0) { - System.out.print(""); - } - } - } - - /** - * The driver method to demonstrate solving a Sudoku puzzle. - * A sample 9x9 Sudoku puzzle is provided, and the program attempts to solve it - * using the `solveSudoku` method. If a solution is found, it is printed to the console. - * - * @param args Command-line arguments (not used in this program). - */ - public static void main(String[] args) { - int[][] board = new int[][] { - {3, 0, 6, 5, 0, 8, 4, 0, 0}, - {5, 2, 0, 0, 0, 0, 0, 0, 0}, - {0, 8, 7, 0, 0, 0, 0, 3, 1}, - {0, 0, 3, 0, 1, 0, 0, 8, 0}, - {9, 0, 0, 8, 6, 3, 0, 0, 5}, - {0, 5, 0, 0, 9, 0, 6, 0, 0}, - {1, 3, 0, 0, 0, 0, 2, 5, 0}, - {0, 0, 0, 0, 0, 0, 0, 7, 4}, - {0, 0, 5, 2, 0, 6, 3, 0, 0}, - }; - int n = board.length; - - if (solveSudoku(board, n)) { - print(board, n); - } else { - System.out.println("No solution"); - } - } -} diff --git a/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java index 72e9a14ac070..d94bef69cd3a 100644 --- a/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java +++ b/src/main/java/com/thealgorithms/puzzlesandgames/TowerOfHanoi.java @@ -3,27 +3,32 @@ import java.util.List; /** - * The {@code TowerOfHanoi} class provides a recursive solution to the Tower of Hanoi puzzle. - * This puzzle involves moving a set of discs from one pole to another, following specific rules: + * Recursive solution to the Tower of Hanoi puzzle. + * + *

+ * The puzzle rules are: * 1. Only one disc can be moved at a time. * 2. A disc can only be placed on top of a larger disc. * 3. All discs must start on one pole and end on another. + *

* - * This implementation recursively calculates the steps required to solve the puzzle and stores them - * in a provided list. + *

+ * The recursion follows three steps: + * 1. Move {@code n-1} discs from start to intermediate. + * 2. Move the largest disc from start to end. + * 3. Move {@code n-1} discs from intermediate to end. + *

* *

- * For more information about the Tower of Hanoi, see - * Tower of Hanoi on Wikipedia. + * Time Complexity: O(2^n) - exponential due to recursive expansion. + * Space Complexity: O(n) - recursion stack depth. *

* - * The {@code shift} method takes the number of discs and the names of the poles, - * and appends the steps required to solve the puzzle to the provided list. - * Time Complexity: O(2^n) - Exponential time complexity due to the recursive nature of the problem. - * Space Complexity: O(n) - Linear space complexity due to the recursion stack. - * Wikipedia: https://en.wikipedia.org/wiki/Tower_of_Hanoi + *

+ * See Tower of Hanoi on Wikipedia. + *

*/ -final class TowerOfHanoi { +public final class TowerOfHanoi { private TowerOfHanoi() { } @@ -36,6 +41,7 @@ private TowerOfHanoi() { * @param intermediatePole The name of the intermediate pole used as a temporary holding area. * @param endPole The name of the end pole to which discs are moved. * @param result A list to store the steps required to solve the puzzle. + * @throws IllegalArgumentException if {@code n} is negative. * *

* This method is called recursively to move n-1 discs @@ -51,15 +57,20 @@ private TowerOfHanoi() { *

*/ public static void shift(int n, String startPole, String intermediatePole, String endPole, List result) { - if (n != 0) { - // Move n-1 discs from startPole to intermediatePole - shift(n - 1, startPole, endPole, intermediatePole, result); + if (n < 0) { + throw new IllegalArgumentException("Number of discs must be non-negative"); + } + if (n == 0) { + return; + } - // Add the move of the nth disc from startPole to endPole - result.add(String.format("Move %d from %s to %s", n, startPole, endPole)); + // Move n-1 discs from startPole to intermediatePole + shift(n - 1, startPole, endPole, intermediatePole, result); - // Move the n-1 discs from intermediatePole to endPole - shift(n - 1, intermediatePole, startPole, endPole, result); - } + // Add the move of the nth disc from startPole to endPole + result.add(String.format("Move %d from %s to %s", n, startPole, endPole)); + + // Move the n-1 discs from intermediatePole to endPole + shift(n - 1, intermediatePole, startPole, endPole, result); } } diff --git a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java index 05d7abbbcd6c..06101295e880 100644 --- a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java +++ b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java @@ -64,13 +64,21 @@ private static double doApproximate(Function fx, double a, doubl if (!validate(fx, a, b, n)) { throw new IllegalArgumentException("Invalid input parameters"); } - double totalArea = 0.0; + double total = 0.0; double interval = b - a; - for (int i = 0; i < n; i++) { + int pairs = n / 2; + for (int i = 0; i < pairs; i++) { + double u = generator.nextDouble(); + double x1 = a + u * interval; + double x2 = a + (1.0 - u) * interval; + total += fx.apply(x1); + total += fx.apply(x2); + } + if ((n & 1) == 1) { double x = a + generator.nextDouble() * interval; - totalArea += fx.apply(x); + total += fx.apply(x); } - return interval * totalArea / n; + return interval * total / n; } private static boolean validate(Function fx, double a, double b, int n) { diff --git a/src/main/java/com/thealgorithms/maths/FactorialRecursion.java b/src/main/java/com/thealgorithms/recursion/FactorialRecursion.java similarity index 92% rename from src/main/java/com/thealgorithms/maths/FactorialRecursion.java rename to src/main/java/com/thealgorithms/recursion/FactorialRecursion.java index d9bafd1e39e9..673f216bdc9a 100644 --- a/src/main/java/com/thealgorithms/maths/FactorialRecursion.java +++ b/src/main/java/com/thealgorithms/recursion/FactorialRecursion.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.recursion; public final class FactorialRecursion { private FactorialRecursion() { diff --git a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java index e5f474085367..404adfdf0cf1 100644 --- a/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java +++ b/src/main/java/com/thealgorithms/recursion/FibonacciSeries.java @@ -1,21 +1,43 @@ package com.thealgorithms.recursion; -/* - The Fibonacci series is a sequence of numbers where each number is the sum of the two preceding ones, - starting with 0 and 1. - NUMBER 0 1 2 3 4 5 6 7 8 9 10 ... - FIBONACCI 0 1 1 2 3 5 8 13 21 34 55 ... -*/ - +/** + * The Fibonacci series is a sequence of numbers where each number is the sum of the two preceding ones, + * starting with 0 and 1. + *

+ * Example: + * 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ... + *

+ *

+ * This specific implementation demonstrates a Naive Recursive approach with {@code O(2^n)} time complexity. + * For more performant variations or different programming paradigms, see: + *

    + *
  • {@link com.thealgorithms.maths.FibonacciLoop} - Standard Iterative (Loop) approach
  • + *
  • {@link com.thealgorithms.dynamicprogramming.Fibonacci} - Dynamic Programming variants (Memoization / Bottom-Up)
  • + *
  • {@link com.thealgorithms.maths.FibonacciJavaStreams} - Functional approach using Java Streams
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberGoldenRation} - Closed-form expression using Binet's formula
  • + *
  • {@link com.thealgorithms.maths.FibonacciNumberCheck} - Utility to check if a given number is a Fibonacci number
  • + *
  • {@link com.thealgorithms.matrix.matrixexponentiation.Fibonacci} - O(log n) Matrix Exponentiation approach
  • + *
+ */ public final class FibonacciSeries { private FibonacciSeries() { throw new UnsupportedOperationException("Utility class"); } + + /** + * Calculates the nth term in the Fibonacci sequence using recursion. + * + * @param n the position in the Fibonacci sequence (must be non-negative) + * @return the nth Fibonacci number + * @throws IllegalArgumentException if n is negative + */ public static int fibonacci(int n) { + if (n < 0) { + throw new IllegalArgumentException("n must be a non-negative integer"); + } if (n <= 1) { return n; - } else { - return fibonacci(n - 1) + fibonacci(n - 2); } + return fibonacci(n - 1) + fibonacci(n - 2); } } diff --git a/src/main/java/com/thealgorithms/searches/BinarySearch.java b/src/main/java/com/thealgorithms/searches/BinarySearch.java index bedad1667f33..ca873fc6eafa 100644 --- a/src/main/java/com/thealgorithms/searches/BinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/BinarySearch.java @@ -3,12 +3,32 @@ import com.thealgorithms.devutils.searches.SearchAlgorithm; /** - * Binary search is one of the most popular algorithms The algorithm finds the - * position of a target value within a sorted array + * Binary Search Algorithm Implementation * - *

- * Worst-case performance O(log n) Best-case performance O(1) Average - * performance O(log n) Worst-case space complexity O(1) + *

Binary search is one of the most efficient searching algorithms for finding a target element + * in a SORTED array. It works by repeatedly dividing the search space in half, eliminating half of + * the remaining elements in each step. + * + *

IMPORTANT: This algorithm ONLY works correctly if the input array is sorted in ascending + * order. + * + *

Algorithm Overview: 1. Start with the entire array (left = 0, right = array.length - 1) 2. + * Calculate the middle index 3. Compare the middle element with the target: - If middle element + * equals target: Found! Return the index - If middle element is less than target: Search the right + * half - If middle element is greater than target: Search the left half 4. Repeat until element is + * found or search space is exhausted + * + *

Performance Analysis: - Best-case time complexity: O(1) - Element found at middle on first + * try - Average-case time complexity: O(log n) - Most common scenario - Worst-case time + * complexity: O(log n) - Element not found or at extreme end - Space complexity: O(1) - Only uses + * a constant amount of extra space + * + *

Example Walkthrough: Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] Target: 7 + * + *

Step 1: left=0, right=9, mid=4, array[4]=9 (9 > 7, search left half) Step 2: left=0, + * right=3, mid=1, array[1]=3 (3 < 7, search right half) Step 3: left=2, right=3, mid=2, + * array[2]=5 (5 < 7, search right half) Step 4: left=3, right=3, mid=3, array[3]=7 (Found! + * Return index 3) * * @author Varun Upadhyay (https://github.com/varunu28) * @author Podshivalov Nikita (https://github.com/nikitap492) @@ -18,38 +38,96 @@ class BinarySearch implements SearchAlgorithm { /** - * @param array is an array where the element should be found - * @param key is an element which should be found - * @param is any comparable type - * @return index of the element + * Generic method to perform binary search on any comparable type. This is the main entry point + * for binary search operations. + * + *

Example Usage: + *

+     * Integer[] numbers = {1, 3, 5, 7, 9, 11};
+     * int result = new BinarySearch().find(numbers, 7);
+     * // result will be 3 (index of element 7)
+     *
+     * int notFound = new BinarySearch().find(numbers, 4);
+     * // notFound will be -1 (element 4 does not exist)
+     * 
+ * + * @param The type of elements in the array (must be Comparable) + * @param array The sorted array to search in (MUST be sorted in ascending order) + * @param key The element to search for + * @return The index of the key if found, -1 if not found or if array is null/empty */ @Override public > int find(T[] array, T key) { + // Handle edge case: null or empty array + if (array == null || array.length == 0) { + return -1; + } + + // Handle edge case: null key + // Searching for null in an array of Comparables is undefined behavior + // Return -1 to indicate not found rather than throwing NPE + if (key == null) { + return -1; + } + + // Delegate to the core search implementation return search(array, key, 0, array.length - 1); } /** - * This method implements the Generic Binary Search + * Core recursive implementation of binary search algorithm. This method divides the problem + * into smaller subproblems recursively. + * + *

How it works: + *

    + *
  1. Calculate the middle index to avoid integer overflow
  2. + *
  3. Check if middle element matches the target
  4. + *
  5. If not, recursively search either left or right half
  6. + *
  7. Base case: left > right means element not found
  8. + *
+ * + *

Time Complexity: O(log n) because we halve the search space each time. + * Space Complexity: O(log n) due to recursive call stack. * - * @param array The array to make the binary search - * @param key The number you are looking for - * @param left The lower bound - * @param right The upper bound - * @return the location of the key + * @param The type of elements (must be Comparable) + * @param array The sorted array to search in + * @param key The element we're looking for + * @param left The leftmost index of current search range (inclusive) + * @param right The rightmost index of current search range (inclusive) + * @return The index where key is located, or -1 if not found */ private > int search(T[] array, T key, int left, int right) { + // Base case: Search space is exhausted + // This happens when left pointer crosses right pointer if (right < left) { - return -1; // this means that the key not found + return -1; // Key not found in the array } - // find median - int median = (left + right) >>> 1; + + // Calculate middle index + // Using (left + right) / 2 could cause integer overflow for large arrays + // So we use: left + (right - left) / 2 which is mathematically equivalent + // but prevents overflow + int median = (left + right) >>> 1; // Unsigned right shift is faster division by 2 + + // Get the value at middle position for comparison int comp = key.compareTo(array[median]); + // Case 1: Found the target element at middle position if (comp == 0) { - return median; - } else if (comp < 0) { + return median; // Return the index where element was found + } + // Case 2: Target is smaller than middle element + // This means if target exists, it must be in the LEFT half + else if (comp < 0) { + // Recursively search the left half + // New search range: [left, median - 1] return search(array, key, left, median - 1); - } else { + } + // Case 3: Target is greater than middle element + // This means if target exists, it must be in the RIGHT half + else { + // Recursively search the right half + // New search range: [median + 1, right] return search(array, key, median + 1, right); } } diff --git a/src/main/java/com/thealgorithms/searches/ExponentialSearch.java b/src/main/java/com/thealgorithms/searches/ExponentialSearch.java index 9187dcbc2f4b..e666b9148aaa 100644 --- a/src/main/java/com/thealgorithms/searches/ExponentialSearch.java +++ b/src/main/java/com/thealgorithms/searches/ExponentialSearch.java @@ -46,6 +46,9 @@ public > int find(T[] array, T key) { range = range * 2; } - return Arrays.binarySearch(array, range / 2, Math.min(range, array.length), key); + // The candidate block is the inclusive index range [range / 2, range], so the + // exclusive upper bound handed to binarySearch has to be range + 1. + final int index = Arrays.binarySearch(array, range / 2, Math.min(range + 1, array.length), key); + return index >= 0 ? index : -1; } } diff --git a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java index 78dac0f0a712..fa91cd14a1af 100644 --- a/src/main/java/com/thealgorithms/searches/FibonacciSearch.java +++ b/src/main/java/com/thealgorithms/searches/FibonacciSearch.java @@ -69,7 +69,7 @@ public > int find(T[] array, T key) { } } - if (fibMinus1 == 1 && array[offset + 1] == key) { + if (fibMinus1 == 1 && offset + 1 < n && array[offset + 1].compareTo(key) == 0) { return offset + 1; } diff --git a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java index 3ac6be25bf53..52dbc0b7e2c5 100644 --- a/src/main/java/com/thealgorithms/searches/InterpolationSearch.java +++ b/src/main/java/com/thealgorithms/searches/InterpolationSearch.java @@ -1,3 +1,4 @@ + package com.thealgorithms.searches; /** @@ -35,9 +36,12 @@ public int find(int[] array, int key) { // Since array is sorted, an element present // in array must be in range defined by corner while (start <= end && key >= array[start] && key <= array[end]) { + if (array[start] == array[end]) { + return start; + } // Probing the position with keeping // uniform distribution in mind. - int pos = start + (((end - start) / (array[end] - array[start])) * (key - array[start])); + int pos = start + (int) (((long) (end - start) * (key - array[start])) / ((long) array[end] - array[start])); // Condition of target found if (array[pos] == key) { diff --git a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java index 05fab0534267..d051dbc7b823 100644 --- a/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/IterativeBinarySearch.java @@ -3,50 +3,53 @@ import com.thealgorithms.devutils.searches.SearchAlgorithm; /** - * Binary search is one of the most popular algorithms This class represents - * iterative version {@link BinarySearch} Iterative binary search is likely to - * have lower constant factors because it doesn't involve the overhead of - * manipulating the call stack. But in java the recursive version can be - * optimized by the compiler to this version. + * Binary search is one of the most popular algorithms. + * This class represents the iterative version of {@link BinarySearch}. * - *

- * Worst-case performance O(log n) Best-case performance O(1) Average - * performance O(log n) Worst-case space complexity O(1) + *

Iterative binary search avoids recursion overhead and uses constant space. * - * @author Gabriele La Greca : https://github.com/thegabriele97 - * @author Podshivalov Nikita (https://github.com/nikitap492) + *

Performance: + *

    + *
  • Best-case: O(1)
  • + *
  • Average-case: O(log n)
  • + *
  • Worst-case: O(log n)
  • + *
  • Space complexity: O(1)
  • + *
+ * + * @author Gabriele La Greca + * @author Podshivalov Nikita * @see SearchAlgorithm * @see BinarySearch */ public final class IterativeBinarySearch implements SearchAlgorithm { /** - * This method implements an iterative version of binary search algorithm + * Performs iterative binary search on a sorted array. * - * @param array a sorted array - * @param key the key to search in array - * @return the index of key in the array or -1 if not found + * @param array the sorted array + * @param key the element to search + * @param type of elements (must be Comparable) + * @return index of the key if found, otherwise -1 */ @Override public > int find(T[] array, T key) { - int l; - int r; - int k; - int cmp; + if (array == null || array.length == 0 || key == null) { + return -1; + } - l = 0; - r = array.length - 1; + int left = 0; + int right = array.length - 1; - while (l <= r) { - k = (l + r) >>> 1; - cmp = key.compareTo(array[k]); + while (left <= right) { + int mid = (left + right) >>> 1; + int cmp = key.compareTo(array[mid]); if (cmp == 0) { - return k; + return mid; } else if (cmp < 0) { - r = --k; + right = mid - 1; } else { - l = ++k; + left = mid + 1; } } diff --git a/src/main/java/com/thealgorithms/searches/JumpSearch.java b/src/main/java/com/thealgorithms/searches/JumpSearch.java index 8dcec3a819a4..4253f4182db1 100644 --- a/src/main/java/com/thealgorithms/searches/JumpSearch.java +++ b/src/main/java/com/thealgorithms/searches/JumpSearch.java @@ -12,26 +12,57 @@ * Once the range is found, a linear search is performed within that block. * *

- * The Jump Search algorithm is particularly effective for large sorted arrays where the cost of - * performing a linear search on the entire array would be prohibitive. + * How it works: + *

    + *
  1. Calculate the optimal block size as √n (square root of array length)
  2. + *
  3. Jump ahead by the block size until the current element is greater than the target
  4. + *
  5. Perform a linear search backwards within the identified block
  6. + *
* *

- * Worst-case performance: O(√N)
- * Best-case performance: O(1)
- * Average performance: O(√N)
- * Worst-case space complexity: O(1) + * Example:
+ * Array: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19], Target: 9
+ * Step 1: Jump from index 0 → 3 → 6 (9 < 13, so we found the block)
+ * Step 2: Linear search from index 3 to 6: found 9 at index 4
+ * Result: Index = 4 + * + *

+ * Time Complexity:
+ * - Best-case: O(1) - element found at first position
+ * - Average: O(√n) - optimal block size reduces jumps
+ * - Worst-case: O(√n) - element at end of array or not present
+ * + *

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

+ * Edge Cases: + *

    + *
  • Empty array → returns -1
  • + *
  • Element not present → returns -1
  • + *
  • Single element array
  • + *
+ *

+ * Note: Jump Search requires a sorted array. For unsorted arrays, use Linear Search. + * Compared to Linear Search (O(n)), Jump Search is faster for large arrays. + * Compared to Binary Search (O(log n)), Jump Search is less efficient but may be + * preferable when jumping through a linked list or when backward scanning is costly. * *

* This class implements the {@link SearchAlgorithm} interface, providing a generic search method * for any comparable type. + * + * @see SearchAlgorithm + * @see BinarySearch + * @see LinearSearch */ public class JumpSearch implements SearchAlgorithm { /** * Jump Search algorithm implementation. * - * @param array the sorted array containing elements - * @param key the element to be searched + * @param array the sorted array containing elements (must be sorted in ascending order) + * @param key the element to be searched for * @return the index of {@code key} if found, otherwise -1 */ @Override @@ -42,7 +73,7 @@ public > int find(T[] array, T key) { int limit = blockSize; // Jumping ahead to find the block where the key may be located while (limit < length && key.compareTo(array[limit]) > 0) { - limit = Math.min(limit + blockSize, length - 1); + limit += blockSize; } // Perform linear search within the identified block diff --git a/src/main/java/com/thealgorithms/searches/LinearSearch.java b/src/main/java/com/thealgorithms/searches/LinearSearch.java index c7b70edb5112..c5f6e6ba9776 100644 --- a/src/main/java/com/thealgorithms/searches/LinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/LinearSearch.java @@ -1,37 +1,75 @@ + package com.thealgorithms.searches; import com.thealgorithms.devutils.searches.SearchAlgorithm; /** - * Linear search is the easiest search algorithm It works with sorted and - * unsorted arrays (an binary search works only with sorted array) This - * algorithm just compares all elements of an array to find a value + * Linear Search is a simple searching algorithm that checks + * each element of the array sequentially until the target + * value is found or the array ends. + * + * It works for both sorted and unsorted arrays. + * + *

How it works step-by-step: + *

    + *
  1. Start from the first element of the array.
  2. + *
  3. Compare the current element with the target value.
  4. + *
  5. If they match, return the current index.
  6. + *
  7. If they don't match, move to the next element.
  8. + *
  9. Repeat until the element is found or the array ends.
  10. + *
  11. If not found, return -1.
  12. + *
+ * + *

Example: + *

+ *   Input array: [5, 3, 8, 1, 9]
+ *   Target: 8
+ *
+ *   Step 1: Compare 5 with 8 → no match, move on
+ *   Step 2: Compare 3 with 8 → no match, move on
+ *   Step 3: Compare 8 with 8 → match found at index 2!
+ *
+ *   Output: 2
  *
- * 

- * Worst-case performance O(n) Best-case performance O(1) Average performance - * O(n) Worst-case space complexity + * If target = 7: + * Output: -1 (not found) + *

+ * Time Complexity: + * - Best case: O(1) - target is the first element + * - Average case: O(n) - target is somewhere in the middle + * - Worst case: O(n) - target is last or not present * - * @author Varun Upadhyay (https://github.com/varunu28) - * @author Podshivalov Nikita (https://github.com/nikitap492) + * Space Complexity: O(1) + * + * @author Varun Upadhyay + * @author Podshivalov Nikita * @see BinarySearch * @see SearchAlgorithm */ public class LinearSearch implements SearchAlgorithm { /** - * Generic Linear search method + * Generic Linear search method that searches for a value + * in the given array by checking each element one by one. * - * @param array List to be searched + * @param array List to be searched (can be unsorted) * @param value Key being searched for - * @return Location of the key + * @return Location of the key, -1 if array is null or empty, or key not found */ @Override public > int find(T[] array, T value) { + + if (array == null || array.length == 0 || value == null) { + return -1; + } + for (int i = 0; i < array.length; i++) { - if (array[i].compareTo(value) == 0) { + T currentElement = array[i]; + if (currentElement != null && currentElement.compareTo(value) == 0) { return i; } } + return -1; } } diff --git a/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java b/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java deleted file mode 100644 index 495e2e41bc5b..000000000000 --- a/src/main/java/com/thealgorithms/searches/PerfectBinarySearch.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.thealgorithms.searches; - -import com.thealgorithms.devutils.searches.SearchAlgorithm; - -/** - * Binary search is one of the most popular algorithms The algorithm finds the - * position of a target value within a sorted array - * - *

- * Worst-case performance O(log n) Best-case performance O(1) Average - * performance O(log n) Worst-case space complexity O(1) - * - * @author D Sunil (https://github.com/sunilnitdgp) - * @see SearchAlgorithm - */ - -public class PerfectBinarySearch implements SearchAlgorithm { - - /** - * @param array is an array where the element should be found - * @param key is an element which should be found - * @param is any comparable type - * @return index of the element - */ - @Override - public > int find(T[] array, T key) { - return search(array, key, 0, array.length - 1); - } - - /** - * This method implements the Generic Binary Search iteratively. - * - * @param array The array to make the binary search - * @param key The number you are looking for - * @return the location of the key, or -1 if not found - */ - private static > int search(T[] array, T key, int left, int right) { - while (left <= right) { - int median = (left + right) >>> 1; - int comp = key.compareTo(array[median]); - - if (comp == 0) { - return median; // Key found - } - - if (comp < 0) { - right = median - 1; // Adjust the right bound - } else { - left = median + 1; // Adjust the left bound - } - } - return -1; // Key not found - } -} diff --git a/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java b/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java index daf0c12c0978..1716e78964ae 100644 --- a/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java +++ b/src/main/java/com/thealgorithms/searches/RecursiveBinarySearch.java @@ -23,28 +23,27 @@ public int find(T[] arr, T target) { // Recursive binary search function public int binsear(T[] arr, int left, int right, T target) { - if (right >= left) { - int mid = left + (right - left) / 2; - - // Compare the element at the middle with the target - int comparison = arr[mid].compareTo(target); + if (right < left) { + // Element is not present in the array + return -1; + } + final int mid = left + (right - left) / 2; - // If the element is equal to the target, return its index - if (comparison == 0) { - return mid; - } + // Compare the element at the middle with the target + final int comparison = arr[mid].compareTo(target); - // If the element is greater than the target, search in the left subarray - if (comparison > 0) { - return binsear(arr, left, mid - 1, target); - } + // If the element is equal to the target, return its index + if (comparison == 0) { + return mid; + } - // Otherwise, search in the right subarray - return binsear(arr, mid + 1, right, target); + // If the element is greater than the target, search in the left subarray + if (comparison > 0) { + return binsear(arr, left, mid - 1, target); } - // Element is not present in the array - return -1; + // Otherwise, search in the right subarray + return binsear(arr, mid + 1, right, target); } public static void main(String[] args) { diff --git a/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java b/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java new file mode 100644 index 000000000000..86099b2fa2fa --- /dev/null +++ b/src/main/java/com/thealgorithms/searches/RotatedBinarySearch.java @@ -0,0 +1,60 @@ +package com.thealgorithms.searches; + +import com.thealgorithms.devutils.searches.SearchAlgorithm; + +/** + * Searches for a key in a sorted array that has been rotated at an unknown pivot. + * + *

+ * Example: + * {@code [8, 9, 10, 1, 2, 3, 4, 5, 6, 7]} + * + *

+ * This is a modified binary search. When the array contains no duplicates, the + * time complexity is {@code O(log n)}. With duplicates, the algorithm still + * works but may degrade to {@code O(n)} in the worst case. + * + * @see Search in rotated sorted array + * @see SearchAlgorithm + */ +public final class RotatedBinarySearch implements SearchAlgorithm { + + @Override + public > int find(T[] array, T key) { + int left = 0; + int right = array.length - 1; + + while (left <= right) { + int middle = (left + right) >>> 1; + int cmp = key.compareTo(array[middle]); + if (cmp == 0) { + return middle; + } + + // Handle duplicates: if we cannot determine which side is sorted. + if (array[left].compareTo(array[middle]) == 0 && array[middle].compareTo(array[right]) == 0) { + left++; + right--; + continue; + } + + // Left half is sorted. + if (array[left].compareTo(array[middle]) <= 0) { + if (array[left].compareTo(key) <= 0 && key.compareTo(array[middle]) < 0) { + right = middle - 1; + } else { + left = middle + 1; + } + } else { + // Right half is sorted. + if (array[middle].compareTo(key) < 0 && key.compareTo(array[right]) <= 0) { + left = middle + 1; + } else { + right = middle - 1; + } + } + } + + return -1; + } +} diff --git a/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java b/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java index 1a5903a5d134..473fc2c3f094 100644 --- a/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java +++ b/src/main/java/com/thealgorithms/searches/SentinelLinearSearch.java @@ -65,7 +65,8 @@ public > int find(T[] array, T key) { int i = 0; // Search without bound checking since sentinel guarantees we'll find the key - while (array[i].compareTo(key) != 0) { + // Null check for array element to prevent NPE when array contains null elements + while (array[i] != null && array[i].compareTo(key) != 0) { i++; } diff --git a/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java b/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java deleted file mode 100644 index 6a2a46c2821f..000000000000 --- a/src/main/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearch.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.thealgorithms.searches; -public final class SortOrderAgnosticBinarySearch { - private SortOrderAgnosticBinarySearch() { - } - public static int find(int[] arr, int key) { - int start = 0; - int end = arr.length - 1; - boolean arrDescending = arr[start] > arr[end]; // checking for Array is in ascending order or descending order. - while (start <= end) { - int mid = end - start / 2; - if (arr[mid] == key) { - return mid; - } - if (arrDescending) { // boolean is true then our array is in descending order - if (key < arr[mid]) { - start = mid + 1; - } else { - end = mid - 1; - } - } else { // otherwise our array is in ascending order - if (key > arr[mid]) { - start = mid + 1; - } else { - end = mid - 1; - } - } - } - return -1; - } -} diff --git a/src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java b/src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java new file mode 100644 index 000000000000..19e573437f6d --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindow.java @@ -0,0 +1,57 @@ +package com.thealgorithms.slidingwindow; + +import java.util.HashMap; +import java.util.Map; + +/** + * Counts the number of distinct elements in every window of size k. + * + * @see Reference + */ +public final class CountDistinctElementsInWindow { + + private CountDistinctElementsInWindow() { + } + + /** + * Returns an array where each element is the count of distinct + * elements in the corresponding window of size k. + * + * @param arr the input array + * @param k the window size + * @return array of distinct element counts per window + */ + public static int[] countDistinct(int[] arr, int k) { + if (arr == null || arr.length == 0 || k <= 0 || k > arr.length) { + throw new IllegalArgumentException("Invalid input"); + } + + int n = arr.length; + int[] result = new int[n - k + 1]; + Map freqMap = new HashMap<>(); + + for (int i = 0; i < k; i++) { + freqMap.merge(arr[i], 1, Integer::sum); + } + result[0] = freqMap.size(); + + for (int i = k; i < n; i++) { + freqMap.merge(arr[i], 1, Integer::sum); + + int outgoing = arr[i - k]; + + Integer count = freqMap.get(outgoing); + if (count != null) { + if (count == 1) { + freqMap.remove(outgoing); + } else { + freqMap.put(outgoing, count - 1); + } + } + + result[i - k + 1] = freqMap.size(); + } + + return result; + } +} diff --git a/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java b/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java new file mode 100644 index 000000000000..46f8deeb58dd --- /dev/null +++ b/src/main/java/com/thealgorithms/slidingwindow/CountNiceSubarrays.java @@ -0,0 +1,99 @@ +package com.thealgorithms.slidingwindow; + +/** + * Counts the number of "nice subarrays". + * A nice subarray is a contiguous subarray that contains exactly k odd numbers. + * + * This implementation uses the sliding window technique. + * + * Reference: + * https://leetcode.com/problems/count-number-of-nice-subarrays/ + * + * Time Complexity: O(n) + * Space Complexity: O(n) + */ +public final class CountNiceSubarrays { + + // Private constructor to prevent instantiation + private CountNiceSubarrays() { + } + + /** + * Returns the count of subarrays containing exactly k odd numbers. + * + * @param nums input array of integers + * @param k number of odd elements required in the subarray + * @return number of nice subarrays + */ + public static int countNiceSubarrays(int[] nums, int k) { + + int n = nums.length; + + // Left pointer of the sliding window + int left = 0; + + // Tracks number of odd elements in the current window + int oddCount = 0; + + // Final answer: total number of nice subarrays + int result = 0; + + /* + * memo[i] stores how many valid starting positions exist + * when the left pointer is at index i. + * + * This avoids recomputing the same values again. + */ + int[] memo = new int[n]; + + // Right pointer moves forward to expand the window + for (int right = 0; right < n; right++) { + + // If current element is odd, increment odd count + if ((nums[right] & 1) == 1) { + oddCount++; + } + + /* + * If oddCount exceeds k, shrink the window from the left + * until oddCount becomes valid again. + */ + if (oddCount > k) { + left += memo[left]; + oddCount--; + } + + /* + * When the window contains exactly k odd numbers, + * count all possible valid subarrays starting at `left`. + */ + if (oddCount == k) { + + /* + * If this left index hasn't been processed before, + * count how many consecutive even numbers follow it. + */ + if (memo[left] == 0) { + int count = 0; + int temp = left; + + // Count consecutive even numbers + while ((nums[temp] & 1) == 0) { + count++; + temp++; + } + + /* + * Number of valid subarrays starting at `left` + * is (count of even numbers + 1) + */ + memo[left] = count + 1; + } + + // Add number of valid subarrays for this left position + result += memo[left]; + } + } + return result; + } +} diff --git a/src/main/java/com/thealgorithms/sorts/BitonicSort.java b/src/main/java/com/thealgorithms/sorts/BitonicSort.java index 1c1a3ac45540..a714809ea5b2 100644 --- a/src/main/java/com/thealgorithms/sorts/BitonicSort.java +++ b/src/main/java/com/thealgorithms/sorts/BitonicSort.java @@ -21,6 +21,9 @@ private enum Direction { */ @Override public > T[] sort(T[] array) { + if (array == null) { + throw new IllegalArgumentException("The input array cannot be null"); + } if (array.length == 0) { return array; } diff --git a/src/main/java/com/thealgorithms/sorts/BubbleSort.java b/src/main/java/com/thealgorithms/sorts/BubbleSort.java index 6823c68d0a74..d2eca3506c2d 100644 --- a/src/main/java/com/thealgorithms/sorts/BubbleSort.java +++ b/src/main/java/com/thealgorithms/sorts/BubbleSort.java @@ -10,6 +10,13 @@ class BubbleSort implements SortAlgorithm { /** * Implements generic bubble sort algorithm. * + * Time Complexity: + * - Best case: O(n) – array is already sorted. + * - Average case: O(n^2) + * - Worst case: O(n^2) + * + * Space Complexity: O(1) – in-place sorting. + * * @param array the array to be sorted. * @param the type of elements in the array. * @return the sorted array. diff --git a/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java b/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java new file mode 100644 index 000000000000..062da01f380e --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/ConcurrentMergeSort.java @@ -0,0 +1,145 @@ +package com.thealgorithms.sorts; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * A concurrent implementation of the Merge Sort algorithm. + * + *

This implementation utilizes a divide-and-conquer strategy, distributing + * the sorting of sub-arrays across multiple threads using a {@link ThreadPoolExecutor}. + * To prevent the overhead of thread creation and context switching from outweighing + * the benefits of concurrency, it falls back to a standard sequential merge sort + * when the sub-array size drops below a predefined threshold, or when the maximum + * concurrency depth is reached (preventing thread starvation deadlocks). + * + *

Complexity: + *

    + *
  • Time Complexity: $O(N \log N)$
  • + *
  • Space Complexity: $O(N)$
  • + *
+ */ +public final class ConcurrentMergeSort { + + private ConcurrentMergeSort() { + } + + /** + * Fallback threshold where the algorithm switches to standard sequential + * Merge Sort to prevent thread-creation overhead from ruining performance. + */ + private static final int SEQUENTIAL_THRESHOLD = 8192; + + /** + * Sorts the specified array of integers concurrently using Merge Sort. + * + * @param array the array to be sorted + */ + public static void sort(int[] array) { + if (array == null || array.length <= 1) { + return; + } + + int availableProcessors = Runtime.getRuntime().availableProcessors(); + + // Calculate a safe maximum depth to prevent creating more tasks than the pool can handle. + // This effectively prevents thread starvation deadlock in fixed-size thread pools, + // by forcing leaf tasks to run sequentially and eventually complete. + int maxDepth = (int) (Math.log(availableProcessors) / Math.log(2)) + 1; + + ThreadPoolExecutor executor = new ThreadPoolExecutor(availableProcessors, availableProcessors, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); + + try { + int[] tempArray = new int[array.length]; + concurrentMergeSort(array, tempArray, 0, array.length - 1, executor, maxDepth); + } finally { + // Ensure the executor is gracefully shut down + executor.shutdown(); + } + } + + /** + * Recursively sorts the array utilizing the provided executor for concurrency. + * + * @param array the array to sort + * @param temp a temporary array for merging + * @param left the starting index of the sub-array + * @param right the ending index of the sub-array + * @param executor the {@link ThreadPoolExecutor} to handle concurrent tasks + * @param depth the remaining depth for allowing concurrent execution + */ + private static void concurrentMergeSort(int[] array, int[] temp, int left, int right, ThreadPoolExecutor executor, int depth) { + int length = right - left + 1; + + // Switch to sequential sort if the array is small or we have reached the maximum concurrent depth + if (length < SEQUENTIAL_THRESHOLD || depth <= 0) { + sequentialMergeSort(array, temp, left, right); + return; + } + + int mid = left + (right - left) / 2; + + // Submit the left half for concurrent execution + CompletableFuture leftTask = CompletableFuture.runAsync(() -> concurrentMergeSort(array, temp, left, mid, executor, depth - 1), executor); + + // Process the right half in the current thread to optimize resource usage + concurrentMergeSort(array, temp, mid + 1, right, executor, depth - 1); + + // Wait for the concurrently executed left half to complete + leftTask.join(); + + merge(array, temp, left, mid, right); + } + + /** + * Sorts the specified sub-array sequentially using standard Merge Sort. + * + * @param array the array to sort + * @param temp a temporary array for merging + * @param left the starting index of the sub-array + * @param right the ending index of the sub-array + */ + private static void sequentialMergeSort(int[] array, int[] temp, int left, int right) { + if (left >= right) { + return; + } + + int mid = left + (right - left) / 2; + sequentialMergeSort(array, temp, left, mid); + sequentialMergeSort(array, temp, mid + 1, right); + merge(array, temp, left, mid, right); + } + + /** + * Merges two sorted sub-arrays into a single sorted sub-array. + * + * @param array the original array containing the sub-arrays + * @param temp a temporary array used for merging + * @param left the starting index of the first sub-array + * @param mid the ending index of the first sub-array (and the partition point) + * @param right the ending index of the second sub-array + */ + private static void merge(int[] array, int[] temp, int left, int mid, int right) { + System.arraycopy(array, left, temp, left, right - left + 1); + + int i = left; + int j = mid + 1; + int k = left; + + while (i <= mid && j <= right) { + if (temp[i] <= temp[j]) { + array[k++] = temp[i++]; + } else { + array[k++] = temp[j++]; + } + } + + while (i <= mid) { + array[k++] = temp[i++]; + } + + // Remaining elements from the right half are already in their correct relative positions + } +} diff --git a/src/main/java/com/thealgorithms/sorts/HeapSort.java b/src/main/java/com/thealgorithms/sorts/HeapSort.java index e798fb91b925..5e3b20f43e10 100644 --- a/src/main/java/com/thealgorithms/sorts/HeapSort.java +++ b/src/main/java/com/thealgorithms/sorts/HeapSort.java @@ -1,9 +1,20 @@ package com.thealgorithms.sorts; /** - * Heap Sort Algorithm Implementation + * Heap Sort algorithm implementation. + * + * Heap sort converts the array into a max-heap and repeatedly extracts the maximum + * element to sort the array in increasing order. + * + * Time Complexity: + * - Best case: O(n log n) + * - Average case: O(n log n) + * - Worst case: O(n log n) + * + * Space Complexity: O(1) – in-place sorting * * @see Heap Sort Algorithm + * @see SortAlgorithm */ public class HeapSort implements SortAlgorithm { diff --git a/src/main/java/com/thealgorithms/sorts/InsertionSort.java b/src/main/java/com/thealgorithms/sorts/InsertionSort.java index 21ebf3827b5f..1e42f2a61271 100644 --- a/src/main/java/com/thealgorithms/sorts/InsertionSort.java +++ b/src/main/java/com/thealgorithms/sorts/InsertionSort.java @@ -1,5 +1,23 @@ package com.thealgorithms.sorts; +/** + * Generic Insertion Sort algorithm. + * + * Standard insertion sort iterates through the array and inserts each element into its + * correct position in the sorted portion of the array. + * + * Sentinel sort is a variation that first places the minimum element at index 0 to + * avoid redundant comparisons in subsequent passes. + * + * Time Complexity: + * - Best case: O(n) – array is already sorted (sentinel sort can improve slightly) + * - Average case: O(n^2) + * - Worst case: O(n^2) – array is reverse sorted + * + * Space Complexity: O(1) – in-place sorting + * + * @see SortAlgorithm + */ class InsertionSort implements SortAlgorithm { /** @@ -15,30 +33,30 @@ public > T[] sort(T[] array) { } /** - * Sorts a subarray of the given array using the standard Insertion Sort algorithm. + * Sorts a subarray of the given items using the standard Insertion Sort algorithm. * - * @param array The array to be sorted - * @param lo The starting index of the subarray - * @param hi The ending index of the subarray (exclusive) - * @param The type of elements in the array, which must be comparable - * @return The sorted array + * @param items The items to be sorted + * @param startIndex The starting index of the subarray + * @param endIndex The ending index of the subarray (exclusive) + * @param The type of elements in the items, which must be comparable + * @return The sorted items */ - public > T[] sort(T[] array, final int lo, final int hi) { - if (array == null || lo >= hi) { - return array; + public > T[] sort(T[] items, final int startIndex, final int endIndex) { + if (items == null || startIndex >= endIndex) { + return items; } - for (int i = lo + 1; i < hi; i++) { - final T key = array[i]; + for (int i = startIndex + 1; i < endIndex; i++) { + final T key = items[i]; int j = i - 1; - while (j >= lo && SortUtils.less(key, array[j])) { - array[j + 1] = array[j]; + while (j >= startIndex && SortUtils.less(key, items[j])) { + items[j + 1] = items[j]; j--; } - array[j + 1] = key; + items[j + 1] = key; } - return array; + return items; } /** diff --git a/src/main/java/com/thealgorithms/sorts/LibrarySort.java b/src/main/java/com/thealgorithms/sorts/LibrarySort.java new file mode 100644 index 000000000000..28f16e3016ff --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/LibrarySort.java @@ -0,0 +1,207 @@ +package com.thealgorithms.sorts; + +import java.util.Arrays; + +/** + * Library Sort (also known as Gapped Insertion Sort) maintains a sparse + * working array with gaps distributed between elements, so that most + * insertions land directly in an empty gap without shifting anything. + * Elements are inserted in rounds that double in size (1, 2, 4, 8, ...); + * after each round the array is rebalanced so gaps are spread out evenly + * again for the next round. + * Time Complexity: O(n log n) expected, O(n^2) worst case if gaps collapse + * Space Complexity: O(n) + * + * @see + * Wikipedia: Library Sort + * @author Vraj Prajapati (@Rosander0) + */ +public final class LibrarySort { + + private static final int GAP_FACTOR = 2; + + private LibrarySort() { + // Utility class + } + + /** + * Sorts an array using the Library Sort algorithm. + * + * @param array the array to sort (must not be null) + * @return the sorted array + * @throws IllegalArgumentException if {@code array} is {@code null} + */ + public static int[] sort(final int[] array) { + if (array == null) { + throw new IllegalArgumentException("Input array must not be null."); + } + if (array.length <= 1) { + return array; + } + + final int n = array.length; + final int capacity = GAP_FACTOR * n; + final int[] data = new int[capacity]; + final boolean[] occupied = new boolean[capacity]; + + final int mid = capacity / 2; + data[mid] = array[0]; + occupied[mid] = true; + + int filled = 1; + int nextToInsert = 1; + int round = 0; + while (nextToInsert < n) { + final int roundSize = Math.min(1 << round, n - nextToInsert); + for (int i = 0; i < roundSize; i++) { + insert(data, occupied, array[nextToInsert + i]); + filled++; + } + nextToInsert += roundSize; + round++; + if (nextToInsert < n) { + rebalance(data, occupied, filled); + } + } + + int idx = 0; + for (int i = 0; i < capacity; i++) { + if (occupied[i]) { + array[idx++] = data[i]; + } + } + return array; + } + + /** + * Inserts {@code value} into the gapped array, placing it directly in an + * empty gap when possible, otherwise shifting toward the nearest gap. + */ + private static void insert(final int[] data, final boolean[] occupied, final int value) { + final int pos = findInsertionIndex(data, occupied, value); + if (pos >= data.length) { + insertAtEnd(data, occupied, value); + return; + } + + if (!occupied[pos]) { + data[pos] = value; + occupied[pos] = true; + return; + } + + int right = pos; + while (right < data.length && occupied[right]) { + right++; + } + int left = pos - 1; + while (left >= 0 && occupied[left]) { + left--; + } + + final boolean canGoRight = right < data.length; + final boolean canGoLeft = left >= 0; + + if (canGoRight && (!canGoLeft || (right - pos) <= (pos - left))) { + // Shift data[pos, right) one slot to the right, opening a gap at pos. + // occupied[pos] is untouched by the copy and was already true. + System.arraycopy(data, pos, data, pos + 1, right - pos); + occupied[right] = true; + data[pos] = value; + } else if (canGoLeft) { + // Shift data[left + 1, pos) one slot to the left, opening a gap at pos - 1. + // occupied[pos - 1] is untouched by the copy and was already true. + System.arraycopy(data, left + 1, data, left, pos - 1 - left); + occupied[left] = true; + data[pos - 1] = value; + } else { + // Unreachable in practice: canGoRight and canGoLeft can only both be false if + // every slot in this capacity-2n array is occupied, but at most n elements are + // ever present at once. Kept as a defensive guard against that invariant breaking. + throw new IllegalStateException("No gap available for insertion; rebalance too infrequent."); + } + } + + /** + * Handles insertion of a new global maximum, which must land after every + * currently occupied slot. Since there is no room to its right, this + * shifts occupied slots left into the nearest gap instead. + */ + private static void insertAtEnd(final int[] data, final boolean[] occupied, final int value) { + final int last = data.length - 1; + // occupied[last] is unreachable as false here: insertAtEnd() is only called when + // findInsertionIndex() returns data.length, which requires data[last] to already be + // occupied. Kept as a defensive guard in case that invariant is ever broken. + if (!occupied[last]) { + data[last] = value; + occupied[last] = true; + return; + } + int left = last - 1; + while (left >= 0 && occupied[left]) { + left--; + } + // left < 0 is unreachable in practice: at most n elements ever occupy this + // capacity-2n array, so fewer than half the slots left of `last` can be filled, + // guaranteeing a gap exists before the scan reaches index -1. + if (left < 0) { + throw new IllegalStateException("No gap available for insertion; rebalance too infrequent."); + } + // Shift data[left + 1, last] one slot to the left, opening a gap at last. + // occupied[last] is untouched by the copy and was already true. + System.arraycopy(data, left + 1, data, left, last - left); + occupied[left] = true; + data[last] = value; + } + + /** + * Finds the leftmost index at which {@code value} can be inserted so + * that occupied slots remain sorted. Empty slots are compared using the + * value of the nearest occupied slot at or after them, which is a + * monotonic function of index and therefore safe to binary search over. + */ + private static int findInsertionIndex(final int[] data, final boolean[] occupied, final int value) { + int lo = 0; + int hi = data.length; + while (lo < hi) { + final int mid = lo + (hi - lo) / 2; + final int probe = nearestOccupiedValueAtOrAfter(data, occupied, mid); + if (probe != Integer.MAX_VALUE && probe <= value) { + lo = mid + 1; + } else { + hi = mid; + } + } + return lo; + } + + private static int nearestOccupiedValueAtOrAfter(final int[] data, final boolean[] occupied, final int index) { + for (int i = index; i < data.length; i++) { + if (occupied[i]) { + return data[i]; + } + } + return Integer.MAX_VALUE; + } + + /** + * Redistributes the {@code filled} occupied elements evenly across the + * full capacity of {@code data}, restoring uniform gaps between them. + */ + private static void rebalance(final int[] data, final boolean[] occupied, final int filled) { + final int capacity = data.length; + final int[] temp = new int[filled]; + int idx = 0; + for (int i = 0; i < capacity; i++) { + if (occupied[i]) { + temp[idx++] = data[i]; + } + } + Arrays.fill(occupied, false); + for (int k = 0; k < filled; k++) { + final int pos = (int) ((long) k * capacity / filled); + data[pos] = temp[k]; + occupied[pos] = true; + } + } +} diff --git a/src/main/java/com/thealgorithms/sorts/MergeSort.java b/src/main/java/com/thealgorithms/sorts/MergeSort.java index 86a184f67b26..5db9c48b4f61 100644 --- a/src/main/java/com/thealgorithms/sorts/MergeSort.java +++ b/src/main/java/com/thealgorithms/sorts/MergeSort.java @@ -10,18 +10,23 @@ @SuppressWarnings("rawtypes") class MergeSort implements SortAlgorithm { - private Comparable[] aux; + private Comparable[] tempArray; /** - * Generic merge sort algorithm implements. + * Generic merge sort algorithm. * - * @param unsorted the array which should be sorted. - * @param Comparable class. - * @return sorted array. + * Time Complexity: + * - Best case: O(n log n) + * - Average case: O(n log n) + * - Worst case: O(n log n) + * + * Space Complexity: O(n) – requires auxiliary array for merging. + * + * @see SortAlgorithm */ @Override public > T[] sort(T[] unsorted) { - aux = new Comparable[unsorted.length]; + tempArray = new Comparable[unsorted.length]; doSort(unsorted, 0, unsorted.length - 1); return unsorted; } @@ -53,17 +58,17 @@ private > void doSort(T[] arr, int left, int right) { private > void merge(T[] arr, int left, int mid, int right) { int i = left; int j = mid + 1; - System.arraycopy(arr, left, aux, left, right + 1 - left); + System.arraycopy(arr, left, tempArray, left, right + 1 - left); for (int k = left; k <= right; k++) { if (j > right) { - arr[k] = (T) aux[i++]; + arr[k] = (T) tempArray[i++]; } else if (i > mid) { - arr[k] = (T) aux[j++]; - } else if (less(aux[j], aux[i])) { - arr[k] = (T) aux[j++]; + arr[k] = (T) tempArray[j++]; + } else if (less(tempArray[j], tempArray[i])) { + arr[k] = (T) tempArray[j++]; } else { - arr[k] = (T) aux[i++]; + arr[k] = (T) tempArray[i++]; } } } diff --git a/src/main/java/com/thealgorithms/sorts/PancakeSort.java b/src/main/java/com/thealgorithms/sorts/PancakeSort.java index 6079672a1d77..6522aefd7ae3 100644 --- a/src/main/java/com/thealgorithms/sorts/PancakeSort.java +++ b/src/main/java/com/thealgorithms/sorts/PancakeSort.java @@ -15,7 +15,7 @@ public > T[] sort(T[] array) { } for (int currentSize = 0; currentSize < array.length; currentSize++) { - int maxIndex = findMaxIndex(array, currentSize); + int maxIndex = findIndexOfMax(array, currentSize); SortUtils.flip(array, maxIndex, array.length - 1 - currentSize); } @@ -30,7 +30,7 @@ public > T[] sort(T[] array) { * @param the type of elements in the array * @return the index of the maximum element */ - private > int findMaxIndex(T[] array, int currentSize) { + private > int findIndexOfMax(T[] array, int currentSize) { T max = array[0]; int maxIndex = 0; for (int i = 0; i < array.length - currentSize; i++) { diff --git a/src/main/java/com/thealgorithms/sorts/QuickSort.java b/src/main/java/com/thealgorithms/sorts/QuickSort.java index 3abb1aae2306..b0ca8b9f159d 100644 --- a/src/main/java/com/thealgorithms/sorts/QuickSort.java +++ b/src/main/java/com/thealgorithms/sorts/QuickSort.java @@ -1,17 +1,36 @@ package com.thealgorithms.sorts; /** - * @author Varun Upadhyay (https://github.com/varunu28) - * @author Podshivalov Nikita (https://github.com/nikitap492) + * QuickSort is a divide-and-conquer sorting algorithm. + * + *

The algorithm selects a pivot element and partitions the array into two + * subarrays such that: + *

    + *
  • Elements smaller than the pivot are placed on the left
  • + *
  • Elements greater than the pivot are placed on the right
  • + *
+ * + *

The subarrays are then recursively sorted until the entire array is ordered. + * + *

This implementation uses randomization to reduce the probability of + * encountering worst-case performance on already sorted inputs. + * + *

Time Complexity: + *

    + *
  • Best Case: O(n log n)
  • + *
  • Average Case: O(n log n)
  • + *
  • Worst Case: O(n^2)
  • + *
+ * + *

Space Complexity: O(log n) due to recursion stack (in-place sorting). + * + * @author Varun Upadhyay + * @author Podshivalov Nikita * @see SortAlgorithm */ + class QuickSort implements SortAlgorithm { - /** - * This method implements the Generic Quick Sort - * - * @param array The array to be sorted Sorts the array in increasing order - */ @Override public > T[] sort(T[] array) { doSort(array, 0, array.length - 1); @@ -21,27 +40,33 @@ public > T[] sort(T[] array) { /** * The sorting process * - * @param left The first index of an array - * @param right The last index of an array * @param array The array to be sorted + * @param left The first index of an array + * @param right The last index of an array */ private static > void doSort(T[] array, final int left, final int right) { + // Continue sorting only if the subarray has more than one element if (left < right) { + // Randomly choose a pivot and partition the array final int pivot = randomPartition(array, left, right); + // Recursively sort elements before the pivot doSort(array, left, pivot - 1); + // Recursively sort elements after the pivot doSort(array, pivot, right); } } /** - * Randomize the array to avoid the basically ordered sequences + * Randomizes the array to avoid already ordered or nearly ordered sequences * * @param array The array to be sorted - * @param left The first index of an array + * @param left The first index of an array * @param right The last index of an array * @return the partition index of the array */ private static > int randomPartition(T[] array, final int left, final int right) { + // Randomizing the pivot helps avoid worst-case performance + // for already sorted or nearly sorted arrays final int randomIndex = left + (int) (Math.random() * (right - left + 1)); SortUtils.swap(array, randomIndex, right); return partition(array, left, right); @@ -51,21 +76,26 @@ private static > int randomPartition(T[] array, final in * This method finds the partition index for an array * * @param array The array to be sorted - * @param left The first index of an array - * @param right The last index of an array Finds the partition index of an - * array + * @param left The first index of an array + * @param right The last index of an array */ private static > int partition(T[] array, int left, int right) { final int mid = (left + right) >>> 1; + // Choose the middle element as the pivot final T pivot = array[mid]; - + // Move the left and right pointers towards each other while (left <= right) { + // Move left pointer until an element >= pivot is found while (SortUtils.less(array[left], pivot)) { ++left; } + + // Move right pointer until an element <= pivot is found while (SortUtils.less(pivot, array[right])) { --right; } + + // Swap elements that are on the wrong side of the pivot if (left <= right) { SortUtils.swap(array, left, right); ++left; diff --git a/src/main/java/com/thealgorithms/sorts/SelectionSort.java b/src/main/java/com/thealgorithms/sorts/SelectionSort.java index db7732d7e218..e6d1a16a7af7 100644 --- a/src/main/java/com/thealgorithms/sorts/SelectionSort.java +++ b/src/main/java/com/thealgorithms/sorts/SelectionSort.java @@ -1,16 +1,27 @@ package com.thealgorithms.sorts; +/** + * Implementation of the Selection Sort algorithm. + * + * @see SortAlgorithm + */ public class SelectionSort implements SortAlgorithm { /** - * Sorts an array of comparable elements in increasing order using the selection sort algorithm. + * Generic Selection Sort algorithm. + * + * Time Complexity: + * - Best case: O(n^2) + * - Average case: O(n^2) + * - Worst case: O(n^2) + * + * Space Complexity: O(1) – in-place sorting. * * @param array the array to be sorted - * @param the class of array elements + * @param the type of elements in the array * @return the sorted array */ @Override public > T[] sort(T[] array) { - for (int i = 0; i < array.length - 1; i++) { final int minIndex = findIndexOfMin(array, i); SortUtils.swap(array, i, minIndex); @@ -18,6 +29,14 @@ public > T[] sort(T[] array) { return array; } + /** + * Finds the index of the minimum element in the array starting from a given index. + * + * @param array the array to search + * @param startIndex the index to start searching from + * @param the type of elements in the array + * @return the index of the minimum element + */ private static > int findIndexOfMin(T[] array, final int startIndex) { int minIndex = startIndex; for (int i = startIndex + 1; i < array.length; i++) { diff --git a/src/main/java/com/thealgorithms/sorts/SmoothSort.java b/src/main/java/com/thealgorithms/sorts/SmoothSort.java new file mode 100644 index 000000000000..c45d6f1f02b2 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/SmoothSort.java @@ -0,0 +1,168 @@ +package com.thealgorithms.sorts; + +/** + * Smooth Sort is an in-place, comparison-based sorting algorithm proposed by Edsger W. Dijkstra (1981). + * + *

It can be viewed as a variant of heapsort that maintains a forest of heap-ordered Leonardo trees + * (trees whose sizes are Leonardo numbers). The algorithm is adaptive: when the input is already + * sorted or nearly sorted, the heap invariants are often satisfied and the expensive rebalancing + * operations do little work, yielding near-linear behavior. + * + *

Time Complexity: + *

    + *
  • Best case: O(n) for already sorted input
  • + *
  • Average case: O(n log n)
  • + *
  • Worst case: O(n log n)
  • + *
+ * + *

Space Complexity: O(1) auxiliary space (in-place). + * + * @see Smoothsort + * @see Leonardo numbers + * @see SortAlgorithm + */ +public class SmoothSort implements SortAlgorithm { + + /** + * Leonardo numbers (L(0) = L(1) = 1, L(k+2) = L(k+1) + L(k) + 1) up to the largest value that + * fits into a signed 32-bit integer. + */ + private static final int[] LEONARDO = {1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, 287, 465, 753, 1219, 1973, 3193, 5167, 8361, 13529, 21891, 35421, 57313, 92735, 150049, 242785, 392835, 635621, 1028457, 1664079, 2692537, 4356617, 7049155, 11405773, 18454929, 29860703, 48315633, 78176337, + 126491971, 204668309, 331160281, 535828591, 866988873, 1402817465}; + + /** + * Sorts the given array in ascending order using Smooth Sort. + * + * @param array the array to sort + * @param the element type + * @return the sorted array + */ + @Override + public > T[] sort(final T[] array) { + if (array.length < 2) { + return array; + } + + final int last = array.length - 1; + + // The forest shape is encoded as (p, pshift): p is a bit-vector of present tree orders, + // shifted right by pshift. pshift is the order of the rightmost (current) Leonardo tree. + long p = 1L; + int pshift = 1; + + int head = 0; + while (head < last) { + if ((p & 3L) == 3L) { + sift(array, pshift, head); + p >>>= 2; + pshift += 2; + } else { + // Add a new singleton tree; if it will not be merged anymore, we must fully trinkle. + if (LEONARDO[pshift - 1] >= last - head) { + trinkle(array, p, pshift, head, false); + } else { + // This tree will be merged later, so it is enough to restore its internal heap property. + sift(array, pshift, head); + } + + if (pshift == 1) { + // If L(1) is used, the new singleton is L(0). + p <<= 1; + pshift = 0; + } else { + // Otherwise, shift to order 1 and append a singleton of order 1. + p <<= (pshift - 1); + pshift = 1; + } + } + + p |= 1L; + head++; + } + + trinkle(array, p, pshift, head, false); + + // Repeatedly remove the maximum (always at head) by shrinking the heap region. + while (pshift != 1 || p != 1L) { + if (pshift <= 1) { + // Rightmost tree is a singleton (order 0 or 1). Move to the previous tree root. + final long mask = p & ~1L; + final int shift = Long.numberOfTrailingZeros(mask); + p >>>= shift; + pshift += shift; + } else { + // Split a tree of order (pshift) into two children trees of orders (pshift-1) and (pshift-2). + p <<= 2; + p ^= 7L; + pshift -= 2; + + trinkle(array, p >>> 1, pshift + 1, head - LEONARDO[pshift] - 1, true); + trinkle(array, p, pshift, head - 1, true); + } + + head--; + } + + return array; + } + + private static > void sift(final T[] array, int order, int root) { + final T value = array[root]; + + while (order > 1) { + final int right = root - 1; + final int left = root - 1 - LEONARDO[order - 2]; + + if (!SortUtils.less(value, array[left]) && !SortUtils.less(value, array[right])) { + break; + } + + if (!SortUtils.less(array[left], array[right])) { + array[root] = array[left]; + root = left; + order -= 1; + } else { + array[root] = array[right]; + root = right; + order -= 2; + } + } + + array[root] = value; + } + + private static > void trinkle(final T[] array, long p, int order, int root, boolean trusty) { + final T value = array[root]; + + while (p != 1L) { + final int stepson = root - LEONARDO[order]; + + if (!SortUtils.less(value, array[stepson])) { + break; + } + + if (!trusty && order > 1) { + final int right = root - 1; + final int left = root - 1 - LEONARDO[order - 2]; + + if (!SortUtils.less(array[right], array[stepson]) || !SortUtils.less(array[left], array[stepson])) { + break; + } + } + + array[root] = array[stepson]; + root = stepson; + + final long mask = p & ~1L; + final int shift = Long.numberOfTrailingZeros(mask); + p >>>= shift; + order += shift; + trusty = false; + } + + if (!trusty) { + array[root] = value; + sift(array, order, root); + } + } +} diff --git a/src/main/java/com/thealgorithms/sorts/TopologicalSort.java b/src/main/java/com/thealgorithms/sorts/TopologicalSort.java index e4ed240a9947..382ddde9a6f2 100644 --- a/src/main/java/com/thealgorithms/sorts/TopologicalSort.java +++ b/src/main/java/com/thealgorithms/sorts/TopologicalSort.java @@ -11,9 +11,16 @@ * a linked list. A Directed Graph is proven to be acyclic when a DFS or Depth First Search is * performed, yielding no back-edges. * - * https://en.wikipedia.org/wiki/Topological_sorting + * Time Complexity: O(V + E) + * - V: number of vertices + * - E: number of edges * - * @author Jonathan Taylor (https://github.com/Jtmonument) + * Space Complexity: O(V + E) + * - adjacency list and recursion stack in DFS + * + * Reference: https://en.wikipedia.org/wiki/Topological_sorting + * + * Author: Jonathan Taylor (https://github.com/Jtmonument) * Based on Introduction to Algorithms 3rd Edition */ public final class TopologicalSort { diff --git a/src/main/java/com/thealgorithms/sorts/TournamentSort.java b/src/main/java/com/thealgorithms/sorts/TournamentSort.java new file mode 100644 index 000000000000..ec51a1e2c0a9 --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/TournamentSort.java @@ -0,0 +1,84 @@ +package com.thealgorithms.sorts; + +import java.util.Arrays; + +/** + * Tournament Sort algorithm implementation. + * + * Tournament sort builds a winner tree (a complete binary tree storing the index + * of the smallest element in each subtree). It then repeatedly extracts the + * winner (minimum) and updates the path from the removed leaf to the root. + * + * Time Complexity: + * - Best case: O(n log n) + * - Average case: O(n log n) + * - Worst case: O(n log n) + * + * Space Complexity: O(n) – additional winner-tree storage + * + * @see Tournament Sort Algorithm + * @see SortAlgorithm + */ +public class TournamentSort implements SortAlgorithm { + + @Override + public > T[] sort(T[] array) { + if (array == null || array.length < 2) { + return array; + } + + final int n = array.length; + final int leafCount = nextPowerOfTwo(n); + + // Winner tree represented as an array: + // - Leaves live at [leafCount .. 2*leafCount) + // - Internal nodes live at [1 .. leafCount) + // Each node stores an index into the original array or -1 for "empty". + final int[] tree = new int[2 * leafCount]; + Arrays.fill(tree, -1); + + for (int i = 0; i < n; i++) { + tree[leafCount + i] = i; + } + + for (int node = leafCount - 1; node >= 1; node--) { + tree[node] = winnerIndex(array, tree[node * 2], tree[node * 2 + 1]); + } + + final T[] result = array.clone(); + for (int out = 0; out < n; out++) { + final int winner = tree[1]; + result[out] = array[winner]; + + int node = leafCount + winner; + tree[node] = -1; + + for (node /= 2; node >= 1; node /= 2) { + tree[node] = winnerIndex(array, tree[node * 2], tree[node * 2 + 1]); + } + } + + System.arraycopy(result, 0, array, 0, n); + return array; + } + + private static int nextPowerOfTwo(int n) { + int power = 1; + while (power < n) { + power <<= 1; + } + return power; + } + + private static > int winnerIndex(T[] array, int leftIndex, int rightIndex) { + if (leftIndex == -1) { + return rightIndex; + } + if (rightIndex == -1) { + return leftIndex; + } + + // If equal, prefer the left element to keep ordering deterministic. + return SortUtils.less(array[rightIndex], array[leftIndex]) ? rightIndex : leftIndex; + } +} diff --git a/src/main/java/com/thealgorithms/sorts/WiggleSort.java b/src/main/java/com/thealgorithms/sorts/WiggleSort.java index c272b820d07a..0349971d1c95 100644 --- a/src/main/java/com/thealgorithms/sorts/WiggleSort.java +++ b/src/main/java/com/thealgorithms/sorts/WiggleSort.java @@ -11,9 +11,9 @@ * https://cs.stackexchange.com/questions/125372/how-to-wiggle-sort-an-array-in-linear-time-complexity * Also have a look at: * https://cs.stackexchange.com/questions/125372/how-to-wiggle-sort-an-array-in-linear-time-complexity?noredirect=1&lq=1 - * Not all arrays are wiggle-sortable. This algorithm will find some obviously not wiggle-sortable - * arrays and throw an error, but there are some exceptions that won't be caught, for example [1, 2, - * 2]. + * Not all arrays are wiggle-sortable. This algorithm detects non-wiggle-sortable inputs — for + * example [1, 2, 2], or arrays where more than half the values are equal — and throws an + * IllegalArgumentException instead of returning a wrongly ordered result. */ public class WiggleSort implements SortAlgorithm { diff --git a/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java index 98c439341a21..7afe2c99aae8 100644 --- a/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java +++ b/src/main/java/com/thealgorithms/stacks/PalindromeWithStack.java @@ -8,6 +8,13 @@ * which we will pop one-by-one to create the string in reverse. * * Reference: https://www.geeksforgeeks.org/check-whether-the-given-string-is-palindrome-using-stack/ + * + * @see com.thealgorithms.strings.Palindrome + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber */ public class PalindromeWithStack { private LinkedList stack; diff --git a/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java b/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java new file mode 100644 index 000000000000..2e9f6863c90a --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/StockSpanProblem.java @@ -0,0 +1,67 @@ +package com.thealgorithms.stacks; + +import java.util.Stack; + +/** + * Calculates the stock span for each day in a series of stock prices. + * + *

The span of a price on a given day is the number of consecutive days ending on that day + * for which the price was less than or equal to the current day's price. + * + *

Idea: keep a stack of indices whose prices are strictly greater than the current price. + * While processing each day, pop smaller or equal prices because they are part of the current + * span. After popping, the nearest greater price left on the stack tells us where the span stops. + * + *

Time complexity is O(n) because each index is pushed onto the stack once and popped at most + * once, so the total number of stack operations grows linearly with the number of prices. This + * makes the stack approach efficient because it avoids rechecking earlier days repeatedly, unlike + * a naive nested-loop solution that can take O(n^2) time. + * + *

Example: for prices [100, 80, 60, 70, 60, 75, 85], the spans are + * [1, 1, 1, 2, 1, 4, 6]. + */ +public final class StockSpanProblem { + private StockSpanProblem() { + } + + /** + * Calculates the stock span for each price in the input array. + * + * @param prices the stock prices + * @return the span for each day + * @throws IllegalArgumentException if the input array is null + */ + public static int[] calculateSpan(int[] prices) { + if (prices == null) { + throw new IllegalArgumentException("Input prices cannot be null"); + } + + int[] spans = new int[prices.length]; + Stack stack = new Stack<>(); + + // Small example: + // prices = [100, 80, 60, 70] + // spans = [ 1, 1, 1, 2] + // When we process 70, we pop 60 because 60 <= 70, so the span becomes 2. + // + // The stack stores indices of days with prices greater than the current day's price. + for (int index = 0; index < prices.length; index++) { + // Remove all previous days whose prices are less than or equal to the current price. + while (!stack.isEmpty() && prices[stack.peek()] <= prices[index]) { + stack.pop(); + } + + // If the stack is empty, there is no earlier day with a greater price, + // so the count will be from day 0 to this day (index + 1). + // + // Otherwise, the span is the number of days between + // the nearest earlier day with a greater price and the current day. + spans[index] = stack.isEmpty() ? index + 1 : index - stack.peek(); + + // Store the current index as a candidate for future span calculations. + stack.push(index); + } + + return spans; + } +} diff --git a/src/main/java/com/thealgorithms/stacks/ValidParentheses.java b/src/main/java/com/thealgorithms/stacks/ValidParentheses.java new file mode 100644 index 000000000000..2cc616a38826 --- /dev/null +++ b/src/main/java/com/thealgorithms/stacks/ValidParentheses.java @@ -0,0 +1,74 @@ +package com.thealgorithms.stacks; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * Valid Parentheses Problem + * + * Given a string containing just the characters '(', ')', '{', '}', '[' and ']', + * determine if the input string is valid. + * + * An input string is valid if: + * 1. Open brackets must be closed by the same type of brackets. + * 2. Open brackets must be closed in the correct order. + * 3. Every close bracket has a corresponding open bracket of the same type. + * + * Examples: + * Input: "()" + * Output: true + * + * Input: "()[]{}" + * Output: true + * + * Input: "(]" + * Output: false + * + * Input: "([)]" + * Output: false + * + * @author Gokul45-45 + */ +public final class ValidParentheses { + private ValidParentheses() { + } + + /** + * Checks if the given string has valid parentheses + * + * @param s the input string containing parentheses + * @return true if valid, false otherwise + */ + public static boolean isValid(String s) { + if (s == null || s.length() % 2 != 0) { + return false; + } + + Map parenthesesMap = new HashMap<>(); + parenthesesMap.put('(', ')'); + parenthesesMap.put('{', '}'); + parenthesesMap.put('[', ']'); + + Stack stack = new Stack<>(); + + for (char c : s.toCharArray()) { + if (parenthesesMap.containsKey(c)) { + // Opening bracket - push to stack + stack.push(c); + } else { + // Closing bracket - check if it matches + if (stack.isEmpty()) { + return false; + } + char openBracket = stack.pop(); + if (parenthesesMap.get(openBracket) != c) { + return false; + } + } + } + + // Stack should be empty if all brackets are matched + return stack.isEmpty(); + } +} diff --git a/src/main/java/com/thealgorithms/strings/Alphabetical.java b/src/main/java/com/thealgorithms/strings/Alphabetical.java index ef2974eb427d..37b1fb068b44 100644 --- a/src/main/java/com/thealgorithms/strings/Alphabetical.java +++ b/src/main/java/com/thealgorithms/strings/Alphabetical.java @@ -1,32 +1,58 @@ package com.thealgorithms.strings; +import java.util.Locale; + /** - * Utility class for checking if a string's characters are in alphabetical order. + * Utility class for checking whether a string's characters are in non-decreasing + * lexicographical order based on Unicode code points (case-insensitive). + *

+ * This does NOT implement language-aware alphabetical ordering (collation rules). + * It simply compares lowercase Unicode character values. *

- * 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. + * Non-letter characters are not allowed and will cause the check to fail. *

- * Reference: Wikipedia: Alphabetical Order + * Reference: + * Wikipedia: Alphabetical order */ public final class Alphabetical { + private Alphabetical() { } /** - * Checks whether the characters in the given string are in alphabetical order. - * Non-letter characters will cause the check to fail. + * Checks whether the characters in the given string are in non-decreasing + * lexicographical order (case-insensitive). + *

+ * Rules: + *

    + *
  • String must not be null or blank
  • + *
  • All characters must be letters
  • + *
  • Comparison is based on lowercase Unicode values
  • + *
  • Order must be non-decreasing (equal or increasing allowed)
  • + *
* - * @param s the input string - * @return {@code true} if all characters are in alphabetical order (case-insensitive), otherwise {@code false} + * @param s input string + * @return {@code true} if characters are in non-decreasing order, 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 (s == null || s.isBlank()) { + return false; + } + + String normalized = s.toLowerCase(Locale.ROOT); + + if (!Character.isLetter(normalized.charAt(0))) { + return false; + } + + for (int i = 1; i < normalized.length(); i++) { + char prev = normalized.charAt(i - 1); + char curr = normalized.charAt(i); + + if (!Character.isLetter(curr) || prev > curr) { return false; } } - return !s.isEmpty() && Character.isLetter(s.charAt(s.length() - 1)); + return true; } } diff --git a/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java index cf736dbd8cab..016ee2821a17 100644 --- a/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java +++ b/src/main/java/com/thealgorithms/strings/AlternativeStringArrange.java @@ -21,12 +21,19 @@ private AlternativeStringArrange() { /** * Arranges two strings by alternating their characters. + * If one string is longer than the other, the remaining characters of the longer string + * are appended at the end of the result. * - * @param firstString the first input string - * @param secondString the second input string + * @param firstString the first input string, must not be {@code null} + * @param secondString the second input string, must not be {@code null} * @return a new string with characters from both strings arranged alternately + * @throws IllegalArgumentException if {@code firstString} or {@code secondString} is {@code null} */ public static String arrange(String firstString, String secondString) { + if (firstString == null || secondString == null) { + throw new IllegalArgumentException("Input strings must not be null"); + } + StringBuilder result = new StringBuilder(); int length1 = firstString.length(); int length2 = secondString.length(); diff --git a/src/main/java/com/thealgorithms/strings/Anagrams.java b/src/main/java/com/thealgorithms/strings/Anagrams.java index 5b97af0758f2..7bd84d47508f 100644 --- a/src/main/java/com/thealgorithms/strings/Anagrams.java +++ b/src/main/java/com/thealgorithms/strings/Anagrams.java @@ -5,7 +5,7 @@ /** * An anagram is a word or phrase formed by rearranging the letters of a different word or phrase, - * typically using all the original letters exactly once.[1] + * typically using all the original letters exactly once. * For example, the word anagram itself can be rearranged into nag a ram, * also the word binary into brainy and the word adobe into abode. * Reference from https://en.wikipedia.org/wiki/Anagram diff --git a/src/main/java/com/thealgorithms/strings/KMP.java b/src/main/java/com/thealgorithms/strings/KMP.java index 07d3b0415006..0317abe6f39a 100644 --- a/src/main/java/com/thealgorithms/strings/KMP.java +++ b/src/main/java/com/thealgorithms/strings/KMP.java @@ -1,5 +1,8 @@ package com.thealgorithms.strings; +import java.util.ArrayList; +import java.util.List; + /** * Implementation of Knuth–Morris–Pratt algorithm Usage: see the main function * for an example @@ -8,16 +11,19 @@ public final class KMP { private KMP() { } - // a working example - - public static void main(String[] args) { - final String haystack = "AAAAABAAABA"; // This is the full string - final String needle = "AAAA"; // This is the substring that we want to find - kmpMatcher(haystack, needle); - } + /** + * find the starting index in string haystack[] that matches the search word P[] + * + * @param haystack The text to be searched + * @param needle The pattern to be searched for + * @return A list of starting indices where the pattern is found + */ + public static List kmpMatcher(final String haystack, final String needle) { + List occurrences = new ArrayList<>(); + if (haystack == null || needle == null || needle.isEmpty()) { + return occurrences; + } - // find the starting index in string haystack[] that matches the search word P[] - public static void kmpMatcher(final String haystack, final String needle) { final int m = haystack.length(); final int n = needle.length(); final int[] pi = computePrefixFunction(needle); @@ -32,10 +38,11 @@ public static void kmpMatcher(final String haystack, final String needle) { } if (q == n) { - System.out.println("Pattern starts: " + (i + 1 - n)); + occurrences.add(i + 1 - n); q = pi[q - 1]; } } + return occurrences; } // return the prefix function diff --git a/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java b/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java new file mode 100644 index 000000000000..b8b10dcf4538 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/KasaiAlgorithm.java @@ -0,0 +1,79 @@ +package com.thealgorithms.strings; + +/** + * Kasai's Algorithm for constructing the Longest Common Prefix (LCP) array. + * + *

+ * The LCP array stores the lengths of the longest common prefixes between + * lexicographically adjacent suffixes of a string. Kasai's algorithm computes + * this array in O(N) time given the string and its suffix array. + *

+ * + * @see LCP array - Wikipedia + */ +public final class KasaiAlgorithm { + + private KasaiAlgorithm() { + } + + /** + * Computes the LCP array using Kasai's algorithm. + * + * @param text the original string + * @param suffixArr the suffix array of the string + * @return the LCP array of length N, where LCP[i] is the length of the longest + * common prefix of the suffixes indexed by suffixArr[i] and suffixArr[i+1]. + * The last element LCP[N-1] is always 0. + * @throws IllegalArgumentException if text or suffixArr is null, or their lengths differ + */ + public static int[] kasai(String text, int[] suffixArr) { + if (text == null || suffixArr == null) { + throw new IllegalArgumentException("Text and suffix array must not be null."); + } + int n = text.length(); + if (suffixArr.length != n) { + throw new IllegalArgumentException("Suffix array length must match text length."); + } + if (n == 0) { + return new int[0]; + } + + // Compute the inverse suffix array + // invSuff[i] stores the index of the suffix text.substring(i) in the suffix array + int[] invSuff = new int[n]; + for (int i = 0; i < n; i++) { + if (suffixArr[i] < 0 || suffixArr[i] >= n) { + throw new IllegalArgumentException("Suffix array contains out-of-bounds index."); + } + invSuff[suffixArr[i]] = i; + } + + int[] lcp = new int[n]; + int k = 0; // Length of the longest common prefix + + for (int i = 0; i < n; i++) { + // Suffix at index i has not a next suffix in suffix array + int rank = invSuff[i]; + if (rank == n - 1) { + k = 0; + continue; + } + + int nextSuffixIndex = suffixArr[rank + 1]; + + // Directly match characters to find LCP + while (i + k < n && nextSuffixIndex + k < n && text.charAt(i + k) == text.charAt(nextSuffixIndex + k)) { + k++; + } + + lcp[rank] = k; + + // Delete the starting character from the string + if (k > 0) { + k--; + } + } + + return lcp; + } +} diff --git a/src/main/java/com/thealgorithms/strings/LengthOfLastWord.java b/src/main/java/com/thealgorithms/strings/LengthOfLastWord.java new file mode 100644 index 000000000000..7eed59a5ef99 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/LengthOfLastWord.java @@ -0,0 +1,51 @@ +package com.thealgorithms.strings; + +/** + * The {@code LengthOfLastWord} class provides a utility method to determine + * the length of the last word in a given string. + * + *

A "word" is defined as a maximal substring consisting of non-space + * characters only. Trailing spaces at the end of the string are ignored. + * + *

Example: + *

{@code
+ * LengthOfLastWord obj = new LengthOfLastWord();
+ * System.out.println(obj.lengthOfLastWord("Hello World"));  // Output: 5
+ * System.out.println(obj.lengthOfLastWord("  fly me   to   the moon  "));  // Output: 4
+ * System.out.println(obj.lengthOfLastWord("luffy is still joyboy"));  // Output: 6
+ * }
+ * + *

This implementation runs in O(n) time complexity, where n is the length + * of the input string, and uses O(1) additional space. + */ +public class LengthOfLastWord { + + /** + * Returns the length of the last word in the specified string. + * + *

The method iterates from the end of the string, skipping trailing + * spaces first, and then counts the number of consecutive non-space characters + * characters until another space (or the beginning of the string) is reached. + * + * @param s the input string to analyze + * @return the length of the last word in {@code s}; returns 0 if there is no word + * @throws NullPointerException if {@code s} is {@code null} + */ + public int lengthOfLastWord(String s) { + int sizeOfString = s.length() - 1; + int lastWordLength = 0; + + // Skip trailing spaces from the end of the string + while (sizeOfString >= 0 && s.charAt(sizeOfString) == ' ') { + sizeOfString--; + } + + // Count the characters of the last word + while (sizeOfString >= 0 && s.charAt(sizeOfString) != ' ') { + lastWordLength++; + sizeOfString--; + } + + return lastWordLength; + } +} diff --git a/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java new file mode 100644 index 000000000000..b2190316aff2 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java @@ -0,0 +1,55 @@ +package com.thealgorithms.strings; + +/** + * Longest Common Substring finds the longest string that is a + * contiguous substring of two input strings. + * Example: "abcdef" and "zcdemf" -> "cde" + * + * @see + * Wikipedia: Longest Common Substring + * + * author: Vraj Prajapati @Rosander0 + */ +public final class LongestCommonSubstring { + + private LongestCommonSubstring() { + // Utility class + } + + /** + * Finds the longest common substring of two strings. + * + * @param a First input string + * @param b Second input string + * @return The longest common substring, or empty string if none exists. + * If multiple substrings share the maximum length, the first one found is returned. + */ + public static String longestCommonSubstring(final String a, final String b) { + if (a == null || b == null || a.isEmpty() || b.isEmpty()) { + return ""; + } + + int[][] dp = new int[a.length() + 1][b.length() + 1]; + int maxLength = 0; + int endIndex = 0; + + for (int i = 1; i <= a.length(); i++) { + for (int j = 1; j <= b.length(); j++) { + if (a.charAt(i - 1) == b.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1] + 1; + if (dp[i][j] > maxLength) { + maxLength = dp[i][j]; + endIndex = i; + } + } else { + dp[i][j] = 0; + } + } + } + + if (maxLength == 0) { + return ""; + } + return a.substring(endIndex - maxLength, endIndex); + } +} diff --git a/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java b/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java index 6808cd50602f..51e8dc6b02c3 100644 --- a/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java +++ b/src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java @@ -13,6 +13,12 @@ private LongestNonRepetitiveSubstring() { /** * Finds the length of the longest substring without repeating characters. * + * Uses the sliding window technique with a HashMap to track + * the last seen index of each character. + * + * Time Complexity: O(n), where n is the length of the input string. + * Space Complexity: O(min(n, m)), where m is the size of the character set. + * * @param s the input string * @return the length of the longest non-repetitive substring */ diff --git a/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java b/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java deleted file mode 100644 index ca500357ba77..000000000000 --- a/src/main/java/com/thealgorithms/strings/LongestPalindromicSubstring.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.thealgorithms.strings; - -final class LongestPalindromicSubstring { - private LongestPalindromicSubstring() { - } - - /** - * Finds the longest palindromic substring in the given string. - * - * @param s the input string - * @return the longest palindromic substring - */ - public static String longestPalindrome(String s) { - if (s == null || s.isEmpty()) { - return ""; - } - String maxStr = ""; - for (int i = 0; i < s.length(); ++i) { - for (int j = i; j < s.length(); ++j) { - if (isValid(s, i, j) && (j - i + 1 > maxStr.length())) { - maxStr = s.substring(i, j + 1); - } - } - } - return maxStr; - } - - private static boolean isValid(String s, int lo, int hi) { - int n = hi - lo + 1; - for (int i = 0; i < n / 2; ++i) { - if (s.charAt(lo + i) != s.charAt(hi - i)) { - return false; - } - } - return true; - } -} diff --git a/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java b/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java new file mode 100644 index 000000000000..87c9278fd4bf --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/LongestRepeatedSubstring.java @@ -0,0 +1,83 @@ +package com.thealgorithms.strings; + +/** + * Finds the longest substring that occurs at least twice in a given string. + * + *

Uses the suffix array (via {@link SuffixArray}) and Kasai's algorithm + * to build the LCP (Longest Common Prefix) array, then returns the substring + * corresponding to the maximum LCP value.

+ * + *

Time complexity: O(n log² n) for suffix array construction + O(n) for LCP.

+ * + * @see Longest repeated substring problem + * @see SuffixArray + */ +public final class LongestRepeatedSubstring { + + private LongestRepeatedSubstring() { + } + + /** + * Returns the longest substring that appears at least twice in the given text. + * + * @param text the input string + * @return the longest repeated substring, or an empty string if none exists + */ + public static String longestRepeatedSubstring(String text) { + if (text == null || text.length() <= 1) { + return ""; + } + + final int[] suffixArray = SuffixArray.buildSuffixArray(text); + final int[] lcp = buildLcpArray(text, suffixArray); + + int maxLen = 0; + int maxIdx = 0; + for (int i = 0; i < lcp.length; i++) { + if (lcp[i] > maxLen) { + maxLen = lcp[i]; + maxIdx = suffixArray[i + 1]; + } + } + + return text.substring(maxIdx, maxIdx + maxLen); + } + + /** + * Builds the LCP (Longest Common Prefix) array using Kasai's algorithm. + * + *

LCP[i] is the length of the longest common prefix between the suffixes + * at positions suffixArray[i] and suffixArray[i+1] in sorted order.

+ * + * @param text the original string + * @param suffixArray the suffix array of the string + * @return the LCP array of length n-1 + */ + static int[] buildLcpArray(String text, int[] suffixArray) { + final int n = text.length(); + final int[] rank = new int[n]; + final int[] lcp = new int[n - 1]; + + for (int i = 0; i < n; i++) { + rank[suffixArray[i]] = i; + } + + int k = 0; + for (int i = 0; i < n; i++) { + if (rank[i] == n - 1) { + k = 0; + continue; + } + final int j = suffixArray[rank[i] + 1]; + while (i + k < n && j + k < n && text.charAt(i + k) == text.charAt(j + k)) { + k++; + } + lcp[rank[i]] = k; + if (k > 0) { + k--; + } + } + + return lcp; + } +} diff --git a/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java b/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java new file mode 100644 index 000000000000..bd686a0c5ba0 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/MoveHashToEnd.java @@ -0,0 +1,56 @@ +package com.thealgorithms.strings; + +/** + * Moves all '#' characters to the end of the given string while preserving + * the order of the other characters. + * + * Example: + * Input : "h#e#l#llo" + * Output : "helllo###" + * + * The algorithm works by iterating through the string and collecting + * all non-# characters first, then filling the remaining positions + * with '#'. + * + * Time Complexity: O(n) + * Space Complexity: O(n) + * + * @see Move all special characters to end - GeeksForGeeks + */ +public final class MoveHashToEnd { + + /** + * Private constructor to prevent instantiation of utility class. + */ + private MoveHashToEnd() { + } + + /** + * Moves all '#' characters in the input string to the end. + * + * @param str the input string containing characters and '#' + * @return a new string with all '#' characters moved to the end + */ + public static String moveHashToEnd(String str) { + if (str == null || str.isEmpty()) { + return str; + } + + char[] arr = str.toCharArray(); + int insertPos = 0; + + // Place all non-# characters at the beginning + for (char ch : arr) { + if (ch != '#') { + arr[insertPos++] = ch; + } + } + + // Fill remaining positions with '#' + while (insertPos < arr.length) { + arr[insertPos++] = '#'; + } + + return new String(arr); + } +} diff --git a/src/main/java/com/thealgorithms/strings/MyAtoi.java b/src/main/java/com/thealgorithms/strings/MyAtoi.java index 5a7c2ce53b1c..92de4039a582 100644 --- a/src/main/java/com/thealgorithms/strings/MyAtoi.java +++ b/src/main/java/com/thealgorithms/strings/MyAtoi.java @@ -45,7 +45,9 @@ public static int myAtoi(String s) { int number = 0; while (index < length) { char ch = s.charAt(index); - if (!Character.isDigit(ch)) { + + // Accept only ASCII digits + if (ch < '0' || ch > '9') { break; } diff --git a/src/main/java/com/thealgorithms/strings/Palindrome.java b/src/main/java/com/thealgorithms/strings/Palindrome.java index 3567a371d70e..64de657df359 100644 --- a/src/main/java/com/thealgorithms/strings/Palindrome.java +++ b/src/main/java/com/thealgorithms/strings/Palindrome.java @@ -2,6 +2,13 @@ /** * Wikipedia: https://en.wikipedia.org/wiki/Palindrome + * + * @see com.thealgorithms.stacks.PalindromeWithStack + * @see com.thealgorithms.bitmanipulation.BinaryPalindromeCheck + * @see com.thealgorithms.maths.LowestBasePalindrome + * @see com.thealgorithms.datastructures.lists.PalindromeSinglyLinkedList + * @see com.thealgorithms.maths.PalindromePrime + * @see com.thealgorithms.maths.PalindromeNumber */ final class Palindrome { private Palindrome() { diff --git a/src/main/java/com/thealgorithms/strings/RabinKarp.java b/src/main/java/com/thealgorithms/strings/RabinKarp.java index bb8df3358453..be17f87c3656 100644 --- a/src/main/java/com/thealgorithms/strings/RabinKarp.java +++ b/src/main/java/com/thealgorithms/strings/RabinKarp.java @@ -1,32 +1,30 @@ package com.thealgorithms.strings; -import java.util.Scanner; +import java.util.ArrayList; +import java.util.List; /** * @author Prateek Kumar Oraon (https://github.com/prateekKrOraon) * - An implementation of Rabin-Karp string matching algorithm - Program will simply end if there is no match + * An implementation of Rabin-Karp string matching algorithm + * Program will simply end if there is no match */ public final class RabinKarp { private RabinKarp() { } - public static Scanner scanner = null; - public static final int ALPHABET_SIZE = 256; + private static final int ALPHABET_SIZE = 256; - public static void main(String[] args) { - scanner = new Scanner(System.in); - System.out.println("Enter String"); - String text = scanner.nextLine(); - System.out.println("Enter pattern"); - String pattern = scanner.nextLine(); - - int q = 101; - searchPat(text, pattern, q); + public static List search(String text, String pattern) { + return search(text, pattern, 101); } - private static void searchPat(String text, String pattern, int q) { + public static List search(String text, String pattern, int q) { + List occurrences = new ArrayList<>(); + if (text == null || pattern == null || pattern.isEmpty()) { + return occurrences; + } + int m = pattern.length(); int n = text.length(); int t = 0; @@ -35,48 +33,42 @@ private static void searchPat(String text, String pattern, int q) { int j = 0; int i = 0; - h = (int) Math.pow(ALPHABET_SIZE, m - 1) % q; + if (m > n) { + return new ArrayList<>(); + } + + // h = pow(ALPHABET_SIZE, m-1) % q + for (i = 0; i < m - 1; i++) { + h = h * ALPHABET_SIZE % q; + } for (i = 0; i < m; i++) { - // hash value is calculated for each character and then added with the hash value of the - // next character for pattern as well as the text for length equal to the length of - // pattern p = (ALPHABET_SIZE * p + pattern.charAt(i)) % q; t = (ALPHABET_SIZE * t + text.charAt(i)) % q; } for (i = 0; i <= n - m; i++) { - // if the calculated hash value of the pattern and text matches then - // all the characters of the pattern is matched with the text of length equal to length - // of the pattern if all matches then pattern exist in string if not then the hash value - // of the first character of the text is subtracted and hash value of the next character - // after the end of the evaluated characters is added if (p == t) { - // if hash value matches then the individual characters are matched for (j = 0; j < m; j++) { - // if not matched then break out of the loop if (text.charAt(i + j) != pattern.charAt(j)) { break; } } - // if all characters are matched then pattern exist in the string if (j == m) { - System.out.println("Pattern found at index " + i); + occurrences.add(i); } } - // if i 0) { + result.deleteCharAt(result.length() - 1); + } + } else { + result.append(c); + } + } + return result.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/strings/ReverseString.java b/src/main/java/com/thealgorithms/strings/ReverseString.java index 7b918ebe1a59..e373dd0b7174 100644 --- a/src/main/java/com/thealgorithms/strings/ReverseString.java +++ b/src/main/java/com/thealgorithms/strings/ReverseString.java @@ -62,7 +62,7 @@ public static String reverse3(String string) { /** * 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. + * @param str The input string to be reversed. * @return The reversed string. */ public static String reverseStringUsingStack(String str) { diff --git a/src/main/java/com/thealgorithms/strings/TitleCase.java b/src/main/java/com/thealgorithms/strings/TitleCase.java new file mode 100644 index 000000000000..eb6c73623681 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/TitleCase.java @@ -0,0 +1,54 @@ +package com.thealgorithms.strings; + +/** + * Title Case converts a string so that the first letter of each word + * is capitalized and the rest are lowercase. + * Example: "the quick brown fox" -> "The Quick Brown Fox" + * + * @see + * Wikipedia: Title Case + */ +public final class TitleCase { + + private TitleCase() { + // Utility class + } + + /** + * Converts a string to title case. + * + * @param input The string to convert + * @return The title-cased string, or empty string if input is null/empty. + * If input contains only whitespace, it is returned as is. + */ + public static String toTitleCase(final String input) { + if (input == null || input.isEmpty()) { + return ""; + } + + StringBuilder result = new StringBuilder(input.length()); + boolean capitalizeNext = true; + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (Character.isWhitespace(c)) { + capitalizeNext = true; + result.append(c); + continue; + } + + if (capitalizeNext) { + if (Character.isLetter(c)) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + // Keep capitalizeNext=true so the first *letter* after + // punctuation/digits is capitalized. + result.append(c); + } + } else { + result.append(Character.toLowerCase(c)); + } + } + return result.toString(); + } +} diff --git a/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java b/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java new file mode 100644 index 000000000000..106de304cf40 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/TopKFrequentWords.java @@ -0,0 +1,56 @@ +package com.thealgorithms.strings; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility class to find the top-k most frequent words. + * + *

Words are ranked by frequency in descending order. For equal frequencies, + * words are ranked in lexicographical ascending order. + * + *

Reference: + * https://en.wikipedia.org/wiki/Top-k_problem + * + */ +public final class TopKFrequentWords { + private TopKFrequentWords() { + } + + /** + * Finds the k most frequent words. + * + * @param words input array of words + * @param k number of words to return + * @return list of top-k words ordered by frequency then lexicographical order + * @throws IllegalArgumentException if words is null, k is negative, or words contains null + */ + public static List findTopKFrequentWords(String[] words, int k) { + if (words == null) { + throw new IllegalArgumentException("Input words array cannot be null."); + } + if (k < 0) { + throw new IllegalArgumentException("k cannot be negative."); + } + if (k == 0 || words.length == 0) { + return List.of(); + } + + Map frequency = new HashMap<>(); + for (String word : words) { + if (word == null) { + throw new IllegalArgumentException("Input words cannot contain null values."); + } + frequency.put(word, frequency.getOrDefault(word, 0) + 1); + } + + List candidates = new ArrayList<>(frequency.keySet()); + candidates.sort(Comparator.comparingInt(frequency::get).reversed().thenComparing(Comparator.naturalOrder())); + + int limit = Math.min(k, candidates.size()); + return new ArrayList<>(candidates.subList(0, limit)); + } +} diff --git a/src/main/java/com/thealgorithms/strings/Upper.java b/src/main/java/com/thealgorithms/strings/Upper.java index 5e248cb6ee39..85db7d41e1aa 100644 --- a/src/main/java/com/thealgorithms/strings/Upper.java +++ b/src/main/java/com/thealgorithms/strings/Upper.java @@ -15,23 +15,27 @@ public static void main(String[] args) { } /** - * Converts all the characters in this {@code String} to upper case + * Converts all the characters in this {@code String} to upper case. * * @param s the string to convert * @return the {@code String}, converted to uppercase. */ public static String toUpperCase(String s) { if (s == null) { - throw new IllegalArgumentException("Input string connot be null"); + throw new IllegalArgumentException("Input string cannot be null"); } if (s.isEmpty()) { return s; } - StringBuilder result = new StringBuilder(s); - for (int i = 0; i < result.length(); ++i) { - char currentChar = result.charAt(i); - if (Character.isLetter(currentChar) && Character.isLowerCase(currentChar)) { - result.setCharAt(i, Character.toUpperCase(currentChar)); + + StringBuilder result = new StringBuilder(s.length()); + + for (int i = 0; i < s.length(); ++i) { + char currentChar = s.charAt(i); + if (Character.isLowerCase(currentChar)) { + result.append(Character.toUpperCase(currentChar)); + } else { + result.append(currentChar); } } return result.toString(); diff --git a/src/main/java/com/thealgorithms/strings/ValidParentheses.java b/src/main/java/com/thealgorithms/strings/ValidParentheses.java deleted file mode 100644 index 25a72f379dec..000000000000 --- a/src/main/java/com/thealgorithms/strings/ValidParentheses.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.thealgorithms.strings; - -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) { - if (s == null) { - throw new IllegalArgumentException("Input string cannot be null"); - } - - Deque stack = new ArrayDeque<>(); - - for (char c : s.toCharArray()) { - 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; - } - } else { - throw new IllegalArgumentException("Unexpected character: " + c); - } - } - - return stack.isEmpty(); - } -} diff --git a/src/main/java/com/thealgorithms/strings/ZAlgorithm.java b/src/main/java/com/thealgorithms/strings/ZAlgorithm.java new file mode 100644 index 000000000000..dc029b751f45 --- /dev/null +++ b/src/main/java/com/thealgorithms/strings/ZAlgorithm.java @@ -0,0 +1,48 @@ +/* + * https://en.wikipedia.org/wiki/Z-algorithm + */ +package com.thealgorithms.strings; + +public final class ZAlgorithm { + + private ZAlgorithm() { + throw new UnsupportedOperationException("Utility class"); + } + + public static int[] zFunction(String s) { + int n = s.length(); + int[] z = new int[n]; + int l = 0; + int r = 0; + + for (int i = 1; i < n; i++) { + if (i <= r) { + z[i] = Math.min(r - i + 1, z[i - l]); + } + + while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i])) { + z[i]++; + } + + if (i + z[i] - 1 > r) { + l = i; + r = i + z[i] - 1; + } + } + + return z; + } + + public static int search(String text, String pattern) { + String s = pattern + "$" + text; + int[] z = zFunction(s); + int p = pattern.length(); + + for (int i = 0; i < z.length; i++) { + if (z[i] == p) { + return i - p - 1; + } + } + return -1; + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/CombinationTest.java b/src/test/java/com/thealgorithms/backtracking/CombinationTest.java index a9d1163f3ecd..5d2f99ccadf8 100644 --- a/src/test/java/com/thealgorithms/backtracking/CombinationTest.java +++ b/src/test/java/com/thealgorithms/backtracking/CombinationTest.java @@ -28,16 +28,16 @@ void testNoElement() { @Test void testLengthOne() { List> result = Combination.combination(new Integer[] {1, 2}, 1); - assertTrue(result.get(0).iterator().next() == 1); - assertTrue(result.get(1).iterator().next() == 2); + assertEquals(1, result.get(0).iterator().next()); + assertEquals(2, result.get(1).iterator().next()); } @Test void testLengthTwo() { List> result = Combination.combination(new Integer[] {1, 2}, 2); Integer[] arr = result.get(0).toArray(new Integer[2]); - assertTrue(arr[0] == 1); - assertTrue(arr[1] == 2); + assertEquals(1, arr[0]); + assertEquals(2, arr[1]); } @Test diff --git a/src/test/java/com/thealgorithms/backtracking/PermutationTest.java b/src/test/java/com/thealgorithms/backtracking/PermutationTest.java index 76a714829109..54747e5e73a1 100644 --- a/src/test/java/com/thealgorithms/backtracking/PermutationTest.java +++ b/src/test/java/com/thealgorithms/backtracking/PermutationTest.java @@ -12,13 +12,13 @@ public class PermutationTest { @Test void testNoElement() { List result = Permutation.permutation(new Integer[] {}); - assertEquals(result.get(0).length, 0); + assertEquals(0, result.get(0).length); } @Test void testSingleElement() { List result = Permutation.permutation(new Integer[] {1}); - assertEquals(result.get(0)[0], 1); + assertEquals(1, result.get(0)[0]); } @Test diff --git a/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java new file mode 100644 index 000000000000..ecd1f3c4dfae --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/RatInAMazeTest.java @@ -0,0 +1,99 @@ +package com.thealgorithms.backtracking; + +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.Test; + +class RatInAMazeTest { + + @Test + void testMultiplePathsExist() { + int[][] maze = {{1, 0, 0, 0}, {1, 1, 0, 1}, {0, 1, 0, 0}, {0, 1, 1, 1}}; + + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.size() >= 1); + for (String path : paths) { + assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0)); + } + } + + @Test + void testSinglePath() { + int[][] maze = {{1, 0, 0}, {1, 1, 0}, {0, 1, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertEquals(1, paths.size()); + assertEquals("DRDR", paths.get(0)); + } + + @Test + void testNoPathExists() { + int[][] maze = {{1, 0, 0}, {0, 0, 0}, {0, 0, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testSourceBlocked() { + int[][] maze = {{0, 1}, {1, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testDestinationBlocked() { + int[][] maze = {{1, 1}, {1, 0}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testSingleCellMazeOpen() { + int[][] maze = {{1}}; + List paths = RatInAMaze.findPaths(maze); + assertEquals(1, paths.size()); + assertEquals("", paths.get(0)); + } + + @Test + void testSingleCellMazeBlocked() { + int[][] maze = {{0}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.isEmpty()); + } + + @Test + void testNullMazeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(null)); + } + + @Test + void testEmptyMazeThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(new int[][] {})); + } + + @Test + void testNonSquareMazeThrowsException() { + int[][] maze = {{1, 0, 1}, {1, 1, 1}}; + assertThrows(IllegalArgumentException.class, () -> RatInAMaze.findPaths(maze)); + } + + @Test + void testAllCellsOpen() { + int[][] maze = {{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.size() > 1); + } + + @Test + void testLargerMazeWithPath() { + int[][] maze = {{1, 1, 1, 1}, {0, 1, 0, 1}, {0, 1, 0, 1}, {0, 1, 1, 1}}; + List paths = RatInAMaze.findPaths(maze); + assertTrue(paths.size() >= 1); + for (String path : paths) { + assertTrue(path.chars().allMatch(c -> "DLRU".indexOf(c) >= 0), "Path contains invalid characters: " + path); + } + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java new file mode 100644 index 000000000000..75d3eae08629 --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/SudokuSolverTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.backtracking; + +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; + +class SudokuSolverTest { + + @Test + void testSolveSudokuEasyPuzzle() { + int[][] board = {{5, 3, 0, 0, 7, 0, 0, 0, 0}, {6, 0, 0, 1, 9, 5, 0, 0, 0}, {0, 9, 8, 0, 0, 0, 0, 6, 0}, {8, 0, 0, 0, 6, 0, 0, 0, 3}, {4, 0, 0, 8, 0, 3, 0, 0, 1}, {7, 0, 0, 0, 2, 0, 0, 0, 6}, {0, 6, 0, 0, 0, 0, 2, 8, 0}, {0, 0, 0, 4, 1, 9, 0, 0, 5}, {0, 0, 0, 0, 8, 0, 0, 7, 9}}; + + assertTrue(SudokuSolver.solveSudoku(board)); + + int[][] expected = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}}; + + assertArrayEquals(expected, board); + } + + @Test + void testSolveSudokuHardPuzzle() { + int[][] board = {{0, 0, 0, 0, 0, 0, 6, 8, 0}, {0, 0, 0, 0, 7, 3, 0, 0, 9}, {3, 0, 9, 0, 0, 0, 0, 4, 5}, {4, 9, 0, 0, 0, 0, 0, 0, 0}, {8, 0, 3, 0, 5, 0, 9, 0, 2}, {0, 0, 0, 0, 0, 0, 0, 3, 6}, {9, 6, 0, 0, 0, 0, 3, 0, 8}, {7, 0, 0, 6, 8, 0, 0, 0, 0}, {0, 2, 8, 0, 0, 0, 0, 0, 0}}; + + assertTrue(SudokuSolver.solveSudoku(board)); + } + + @Test + void testSolveSudokuAlreadySolved() { + int[][] board = {{5, 3, 4, 6, 7, 8, 9, 1, 2}, {6, 7, 2, 1, 9, 5, 3, 4, 8}, {1, 9, 8, 3, 4, 2, 5, 6, 7}, {8, 5, 9, 7, 6, 1, 4, 2, 3}, {4, 2, 6, 8, 5, 3, 7, 9, 1}, {7, 1, 3, 9, 2, 4, 8, 5, 6}, {9, 6, 1, 5, 3, 7, 2, 8, 4}, {2, 8, 7, 4, 1, 9, 6, 3, 5}, {3, 4, 5, 2, 8, 6, 1, 7, 9}}; + + assertTrue(SudokuSolver.solveSudoku(board)); + } + + @Test + void testSolveSudokuInvalidSize() { + int[][] board = {{1, 2, 3}, {4, 5, 6}}; + assertFalse(SudokuSolver.solveSudoku(board)); + } + + @Test + void testSolveSudokuNullBoard() { + assertFalse(SudokuSolver.solveSudoku(null)); + } + + @Test + void testSolveSudokuEmptyBoard() { + int[][] board = {{0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0}}; + + assertTrue(SudokuSolver.solveSudoku(board)); + } +} diff --git a/src/test/java/com/thealgorithms/backtracking/UniquePermutationTest.java b/src/test/java/com/thealgorithms/backtracking/UniquePermutationTest.java new file mode 100644 index 000000000000..c8e7cd0af0dd --- /dev/null +++ b/src/test/java/com/thealgorithms/backtracking/UniquePermutationTest.java @@ -0,0 +1,31 @@ +package com.thealgorithms.backtracking; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class UniquePermutationTest { + + @Test + void testUniquePermutationsAab() { + List expected = Arrays.asList("AAB", "ABA", "BAA"); + List result = UniquePermutation.generateUniquePermutations("AAB"); + assertEquals(expected, result); + } + + @Test + void testUniquePermutationsAbc() { + List expected = Arrays.asList("ABC", "ACB", "BAC", "BCA", "CAB", "CBA"); + List result = UniquePermutation.generateUniquePermutations("ABC"); + assertEquals(expected, result); + } + + @Test + void testEmptyString() { + List expected = Arrays.asList(""); + List result = UniquePermutation.generateUniquePermutations(""); + assertEquals(expected, result); + } +} diff --git a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java index 61e0757f9c12..757c6edc0151 100644 --- a/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java +++ b/src/test/java/com/thealgorithms/bitmanipulation/CountSetBitsTest.java @@ -1,26 +1,56 @@ 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 CountSetBitsTest { +class CountSetBitsTest { @Test - void testSetBits() { - CountSetBits csb = new CountSetBits(); - assertEquals(1L, csb.countSetBits(16)); - assertEquals(4, csb.countSetBits(15)); - assertEquals(5, csb.countSetBits(10000)); - assertEquals(5, csb.countSetBits(31)); + void testCountSetBitsZero() { + assertEquals(0, CountSetBits.countSetBits(0)); } @Test - void testSetBitsLookupApproach() { - CountSetBits csb = new CountSetBits(); - assertEquals(1L, csb.lookupApproach(16)); - assertEquals(4, csb.lookupApproach(15)); - assertEquals(5, csb.lookupApproach(10000)); - assertEquals(5, csb.lookupApproach(31)); + void testCountSetBitsOne() { + assertEquals(1, CountSetBits.countSetBits(1)); + } + + @Test + void testCountSetBitsSmallNumber() { + assertEquals(4, CountSetBits.countSetBits(3)); // 1(1) + 10(1) + 11(2) = 4 + } + + @Test + void testCountSetBitsFive() { + assertEquals(7, CountSetBits.countSetBits(5)); // 1 + 1 + 2 + 1 + 2 = 7 + } + + @Test + void testCountSetBitsTen() { + assertEquals(17, CountSetBits.countSetBits(10)); + } + + @Test + void testCountSetBitsLargeNumber() { + assertEquals(42, CountSetBits.countSetBits(20)); // Changed from 93 to 42 + } + + @Test + void testCountSetBitsPowerOfTwo() { + assertEquals(13, CountSetBits.countSetBits(8)); // Changed from 9 to 13 + } + + @Test + void testCountSetBitsNegativeInput() { + assertThrows(IllegalArgumentException.class, () -> CountSetBits.countSetBits(-1)); + } + + @Test + void testNaiveApproachMatchesOptimized() { + for (int i = 0; i <= 100; i++) { + assertEquals(CountSetBits.countSetBitsNaive(i), CountSetBits.countSetBits(i), "Mismatch at n = " + i); + } } } diff --git a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java index 7aa41c4cf423..c8b20ad8d8f9 100644 --- a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java +++ b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java @@ -32,6 +32,29 @@ void caesarDecryptTest() { assertEquals("Encrypt this text", cipherText); } + @Test + void caesarEncryptWithNegativeShiftTest() { + // a shift of -1 must wrap 'A' backwards to 'Z', like a shift of +25 would + assertEquals("Z", caesar.encode("A", -1)); + assertEquals("z", caesar.encode("a", -1)); + assertEquals("EBIIL", caesar.encode("HELLO", -3)); + } + + @Test + void caesarDecryptWithNegativeShiftTest() { + assertEquals("A", caesar.decode("Z", -1)); + assertEquals("HELLO", caesar.decode("EBIIL", -3)); + } + + @Test + void caesarNegativeShiftRoundTripTest() { + // encode followed by decode with the same shift must return the original text + for (int shift : new int[] {-1, -5, -25, -26, -27, -52}) { + String message = "The quick brown Fox"; + assertEquals(message, caesar.decode(caesar.encode(message, shift), shift)); + } + } + @Test void caesarBruteForce() { // given diff --git a/src/test/java/com/thealgorithms/ciphers/ECCTest.java b/src/test/java/com/thealgorithms/ciphers/ECCTest.java index 701f801af1c8..b78ba51f7c3e 100644 --- a/src/test/java/com/thealgorithms/ciphers/ECCTest.java +++ b/src/test/java/com/thealgorithms/ciphers/ECCTest.java @@ -37,7 +37,7 @@ void testEncrypt() { System.out.println("Base Point G: " + curve.getBasePoint()); // Verify that the ciphertext is not empty - assertEquals(cipherText.length, 2); // Check if the ciphertext contains two points (R and S) + assertEquals(2, cipherText.length); // Check if the ciphertext contains two points (R and S) // Output the encrypted coordinate points System.out.println("Encrypted Points:"); diff --git a/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java b/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java new file mode 100644 index 000000000000..63dec4846bbc --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/ElGamalCipherTest.java @@ -0,0 +1,145 @@ +package com.thealgorithms.ciphers; + +import java.math.BigInteger; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Unit tests for ElGamalCipher. + * Includes property-based testing (homomorphism), probabilistic checks, + * and boundary validation. + */ +class ElGamalCipherTest { + + private static ElGamalCipher.KeyPair sharedKeys; + + @BeforeAll + static void setup() { + // Generate 256-bit keys for efficient unit testing + sharedKeys = ElGamalCipher.generateKeys(256); + } + + @Test + @DisplayName("Test Key Generation Validity") + void testKeyGeneration() { + Assertions.assertNotNull(sharedKeys.p()); + Assertions.assertNotNull(sharedKeys.g()); + Assertions.assertNotNull(sharedKeys.x()); + Assertions.assertNotNull(sharedKeys.y()); + + // Verify generator bounds: 1 < g < p + Assertions.assertTrue(sharedKeys.g().compareTo(BigInteger.ONE) > 0); + Assertions.assertTrue(sharedKeys.g().compareTo(sharedKeys.p()) < 0); + + // Verify private key bounds: 1 < x < p-1 + Assertions.assertTrue(sharedKeys.x().compareTo(BigInteger.ONE) > 0); + Assertions.assertTrue(sharedKeys.x().compareTo(sharedKeys.p().subtract(BigInteger.ONE)) < 0); + } + + @Test + @DisplayName("Security Check: Probabilistic Encryption") + void testSemanticSecurity() { + // Encrypting the same message twice MUST yield different ciphertexts + // due to the random ephemeral key 'k'. + BigInteger message = new BigInteger("123456789"); + + ElGamalCipher.CipherText c1 = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + ElGamalCipher.CipherText c2 = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + + // Check that the ephemeral keys (and thus 'a' components) were different + Assertions.assertNotEquals(c1.a(), c2.a(), "Ciphertexts must be randomized (Semantic Security violation)"); + Assertions.assertNotEquals(c1.b(), c2.b()); + + // But both must decrypt to the original message + Assertions.assertEquals(ElGamalCipher.decrypt(c1, sharedKeys.x(), sharedKeys.p()), message); + Assertions.assertEquals(ElGamalCipher.decrypt(c2, sharedKeys.x(), sharedKeys.p()), message); + } + + @ParameterizedTest + @MethodSource("provideMessages") + @DisplayName("Parameterized Test: Encrypt and Decrypt various messages") + void testEncryptDecrypt(String messageStr) { + BigInteger message = new BigInteger(messageStr.getBytes()); + + // Skip if message exceeds the test key size (256 bits) + if (message.compareTo(sharedKeys.p()) >= 0) { + return; + } + + ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p()); + + Assertions.assertEquals(message, decrypted, "Decrypted BigInteger must match original"); + Assertions.assertEquals(messageStr, new String(decrypted.toByteArray()), "Decrypted string must match original"); + } + + static Stream provideMessages() { + return Stream.of("Hello World", "TheAlgorithms", "A", "1234567890", "!@#$%^&*()"); + } + + @Test + @DisplayName("Edge Case: Message equals 0") + void testMessageZero() { + BigInteger zero = BigInteger.ZERO; + ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(zero, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p()); + + Assertions.assertEquals(zero, decrypted, "Should successfully encrypt/decrypt zero"); + } + + @Test + @DisplayName("Edge Case: Message equals p-1") + void testMessageMaxBound() { + BigInteger pMinus1 = sharedKeys.p().subtract(BigInteger.ONE); + ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(pMinus1, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, sharedKeys.x(), sharedKeys.p()); + + Assertions.assertEquals(pMinus1, decrypted, "Should successfully encrypt/decrypt p-1"); + } + + @Test + @DisplayName("Negative Test: Message >= p should fail") + void testMessageTooLarge() { + BigInteger tooLarge = sharedKeys.p(); + Assertions.assertThrows(IllegalArgumentException.class, () -> ElGamalCipher.encrypt(tooLarge, sharedKeys.p(), sharedKeys.g(), sharedKeys.y())); + } + + @Test + @DisplayName("Negative Test: Decrypt with wrong private key") + void testWrongKeyDecryption() { + BigInteger message = new BigInteger("99999"); + ElGamalCipher.CipherText ciphertext = ElGamalCipher.encrypt(message, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + + // Generate a fake private key + BigInteger wrongX = sharedKeys.x().add(BigInteger.ONE); + + BigInteger decrypted = ElGamalCipher.decrypt(ciphertext, wrongX, sharedKeys.p()); + + Assertions.assertNotEquals(message, decrypted, "Decryption with wrong key must yield incorrect result"); + } + + @Test + @DisplayName("Property Test: Multiplicative Homomorphism") + void testHomomorphism() { + BigInteger m1 = new BigInteger("50"); + BigInteger m2 = BigInteger.TEN; // Fix: Replaced new BigInteger("10") with BigInteger.TEN + + ElGamalCipher.CipherText c1 = ElGamalCipher.encrypt(m1, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + ElGamalCipher.CipherText c2 = ElGamalCipher.encrypt(m2, sharedKeys.p(), sharedKeys.g(), sharedKeys.y()); + + // Multiply ciphertexts component-wise: (a1*a2, b1*b2) + BigInteger aNew = c1.a().multiply(c2.a()).mod(sharedKeys.p()); + BigInteger bNew = c1.b().multiply(c2.b()).mod(sharedKeys.p()); + ElGamalCipher.CipherText cCombined = new ElGamalCipher.CipherText(aNew, bNew); + + BigInteger decrypted = ElGamalCipher.decrypt(cCombined, sharedKeys.x(), sharedKeys.p()); + BigInteger expected = m1.multiply(m2).mod(sharedKeys.p()); + + Assertions.assertEquals(expected, decrypted, "Cipher must satisfy multiplicative homomorphism"); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java b/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java new file mode 100644 index 000000000000..837c56c603d4 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/OneTimePadCipherTest.java @@ -0,0 +1,49 @@ +package com.thealgorithms.ciphers; + +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 java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class OneTimePadCipherTest { + + @Test + void encryptAndDecryptWithRandomKeyRestoresPlaintext() { + String plaintext = "The quick brown fox jumps over the lazy dog."; + byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8); + + byte[] key = OneTimePadCipher.generateKey(plaintextBytes.length); + + byte[] ciphertext = OneTimePadCipher.encrypt(plaintextBytes, key); + byte[] decrypted = OneTimePadCipher.decrypt(ciphertext, key); + + assertArrayEquals(plaintextBytes, decrypted); + assertEquals(plaintext, new String(decrypted, StandardCharsets.UTF_8)); + } + + @Test + void generateKeyWithNegativeLengthThrowsException() { + assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.generateKey(-1)); + } + + @Test + void encryptWithMismatchedKeyLengthThrowsException() { + byte[] data = "hello".getBytes(StandardCharsets.UTF_8); + byte[] shortKey = OneTimePadCipher.generateKey(2); + + assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.encrypt(data, shortKey)); + } + + @Test + void decryptWithMismatchedKeyLengthThrowsException() { + byte[] data = "hello".getBytes(StandardCharsets.UTF_8); + byte[] key = OneTimePadCipher.generateKey(data.length); + byte[] ciphertext = OneTimePadCipher.encrypt(data, key); + + byte[] wrongSizedKey = OneTimePadCipher.generateKey(data.length + 1); + + assertThrows(IllegalArgumentException.class, () -> OneTimePadCipher.decrypt(ciphertext, wrongSizedKey)); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java index 4ba6787cc97e..ecb7455c1ba2 100644 --- a/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java +++ b/src/test/java/com/thealgorithms/ciphers/PermutationCipherTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.ciphers; 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 org.junit.jupiter.api.Test; @@ -121,8 +122,8 @@ void testNullString() { String decrypted = cipher.decrypt(encrypted, key); // then - assertEquals(null, encrypted); - assertEquals(null, decrypted); + assertNull(encrypted); + assertNull(decrypted); } @Test diff --git a/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java b/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java new file mode 100644 index 000000000000..041f8c0dd4c1 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/RailFenceCipherTest.java @@ -0,0 +1,70 @@ +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; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +class RailFenceCipherTest { + + private final RailFenceCipher railFenceCipher = new RailFenceCipher(); + + @Test + void testEncrypt() { + assertEquals("WECRLTEERDSOEEFEAOCAIVDEN", railFenceCipher.encrypt("WEAREDISCOVEREDFLEEATONCE", 3)); + } + + @Test + void testDecrypt() { + assertEquals("WEAREDISCOVEREDFLEEATONCE", railFenceCipher.decrypt("WECRLTEERDSOEEFEAOCAIVDEN", 3)); + } + + @ParameterizedTest + @CsvSource({"HELLOWORLD, 2", "HELLOWORLD, 3", "HELLOWORLD, 4", "ATTACKATDAWN, 5", "abcdefghij, 6"}) + void testRoundTrip(String message, int rails) { + assertEquals(message, railFenceCipher.decrypt(railFenceCipher.encrypt(message, rails), rails)); + } + + /** + * Every character of the input must survive encryption, including the ones that used to collide + * with the placeholder that marked unused cells of the rail matrix. + */ + @ParameterizedTest + @ValueSource(strings = {"ab\ncdef", "line1\nline2\nline3", "\n\n\n\n\n", "a\nb", "tabs\tand\nnewlines\r\n"}) + void testControlCharactersArePreserved(String message) { + for (int rails = 2; rails <= 5; rails++) { + String encrypted = railFenceCipher.encrypt(message, rails); + assertEquals(message.length(), encrypted.length(), "characters were dropped with " + rails + " rails"); + assertEquals(message, railFenceCipher.decrypt(encrypted, rails), "round trip failed with " + rails + " rails"); + } + } + + @Test + void testEncryptWithNewlineMatchesReferencePattern() { + // Rails of "ab\ncdef" with 3 rails: {a, d} / {b, c, e} / {\n, f} + assertEquals("adbce\nf", railFenceCipher.encrypt("ab\ncdef", 3)); + } + + @ParameterizedTest + @CsvSource({"HELLO, 1", "HELLO, 5", "HELLO, 9", "'', 1", "'', 4"}) + void testDegenerateRailCountsReturnInput(String message, int rails) { + assertEquals(message, railFenceCipher.encrypt(message, rails)); + assertEquals(message, railFenceCipher.decrypt(message, rails)); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1, -7}) + void testNonPositiveRailCountThrows(int rails) { + assertThrows(IllegalArgumentException.class, () -> railFenceCipher.encrypt("HELLO", rails)); + assertThrows(IllegalArgumentException.class, () -> railFenceCipher.decrypt("HELLO", rails)); + } + + @Test + void testNullInputThrows() { + assertThrows(IllegalArgumentException.class, () -> railFenceCipher.encrypt(null, 3)); + assertThrows(IllegalArgumentException.class, () -> railFenceCipher.decrypt(null, 3)); + } +} diff --git a/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java b/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java new file mode 100644 index 000000000000..f919417899db --- /dev/null +++ b/src/test/java/com/thealgorithms/compression/HuffmanCodingTest.java @@ -0,0 +1,110 @@ +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 org.junit.jupiter.api.Test; + +class HuffmanCodingTest { + + @Test + void testStandardLifecycle() { + String input = "efficiency is key"; + HuffmanCoding huffman = new HuffmanCoding(input); + + String encoded = huffman.encode(input); + assertNotNull(encoded); + assertTrue(encoded.matches("[01]+")); + assertEquals(input, huffman.decode(encoded)); + } + + @Test + void testNullAndEmptyHandling() { + HuffmanCoding huffman = new HuffmanCoding(""); + assertEquals("", huffman.encode("")); + assertEquals("", huffman.decode("")); + + HuffmanCoding huffmanNull = new HuffmanCoding(null); + assertEquals("", huffmanNull.encode(null)); + assertEquals("", huffmanNull.decode(null)); + } + + @Test + void testSingleCharacterEdgeCase() { + String input = "aaaaa"; + HuffmanCoding huffman = new HuffmanCoding(input); + + String encoded = huffman.encode(input); + assertEquals("00000", encoded); + assertEquals(input, huffman.decode(encoded)); + } + + @Test + void testUnicodeAndSpecialCharacters() { + // Tests spacing, symbols, non-latin alphabets, and surrogate pairs (emojis) + String input = "Hello, World! 🚀\nLine 2: こんにちは"; + HuffmanCoding huffman = new HuffmanCoding(input); + + String encoded = huffman.encode(input); + assertEquals(input, huffman.decode(encoded)); + } + + @Test + void testFailFastOnUnseenCharacter() { + HuffmanCoding huffman = new HuffmanCoding("abc"); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> huffman.encode("abcd") // 'd' was not in the original tree + ); + assertTrue(exception.getMessage().contains("not found in Huffman dictionary")); + } + + @Test + void testFailFastOnInvalidBinaryCharacter() { + HuffmanCoding huffman = new HuffmanCoding("abc"); + String encoded = huffman.encode("abc"); + + // Inject a '2' into the binary stream + String corruptedEncoded = encoded + "2"; + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> huffman.decode(corruptedEncoded)); + assertTrue(exception.getMessage().contains("contains invalid characters")); + } + + @Test + void testFailFastOnIncompleteSequence() { + HuffmanCoding huffman = new HuffmanCoding("abcd"); + String encoded = huffman.encode("abc"); + + // Truncate the last bit to simulate an incomplete byte/sequence transfer + String truncatedEncoded = encoded.substring(0, encoded.length() - 1); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> huffman.decode(truncatedEncoded)); + assertTrue(exception.getMessage().contains("incomplete sequence")); + } + + @Test + void testImmutabilityOfDictionary() { + HuffmanCoding huffman = new HuffmanCoding("abc"); + var codes = huffman.getHuffmanCodes(); + + assertThrows(UnsupportedOperationException.class, () -> codes.put('z', "0101")); + } + + @Test + void testStressVolume() { + StringBuilder sb = new StringBuilder(); + // Generate a 100,000 character string + for (int i = 0; i < 100000; i++) { + sb.append((char) ('a' + (i % 26))); + } + String largeInput = sb.toString(); + + HuffmanCoding huffman = new HuffmanCoding(largeInput); + String encoded = huffman.encode(largeInput); + + assertEquals(largeInput, huffman.decode(encoded)); + } +} diff --git a/src/test/java/com/thealgorithms/compression/LZ78Test.java b/src/test/java/com/thealgorithms/compression/LZ78Test.java index 7889b50b76f3..da1fd8d23318 100644 --- a/src/test/java/com/thealgorithms/compression/LZ78Test.java +++ b/src/test/java/com/thealgorithms/compression/LZ78Test.java @@ -1,7 +1,6 @@ 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; @@ -286,7 +285,6 @@ void testTokenStructure() { // All tokens should have valid indices (>= 0) for (LZ78.Token token : compressed) { assertTrue(token.index() >= 0); - assertNotNull(token.nextChar()); } String decompressed = LZ78.decompress(compressed); diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java index fbc220c0ca95..cd0d6c8b38a8 100644 --- a/src/test/java/com/thealgorithms/conversions/Base64Test.java +++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java @@ -127,6 +127,9 @@ void testInvalidPaddingPosition() { assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=QQ")); assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=Q=")); assertThrows(IllegalArgumentException.class, () -> Base64.decode("=QQQ")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQ=Q")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("AB=C")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("AB=A")); } @Test diff --git a/src/test/java/com/thealgorithms/conversions/TemperatureConverterTest.java b/src/test/java/com/thealgorithms/conversions/TemperatureConverterTest.java new file mode 100644 index 000000000000..24d55b706f36 --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/TemperatureConverterTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class TemperatureConverterTest { + + private static final double DELTA = 0.01; + + @Test + void testCelsiusToFahrenheit() { + assertEquals(32.0, TemperatureConverter.celsiusToFahrenheit(0.0), DELTA); + assertEquals(212.0, TemperatureConverter.celsiusToFahrenheit(100.0), DELTA); + assertEquals(-40.0, TemperatureConverter.celsiusToFahrenheit(-40.0), DELTA); + assertEquals(98.6, TemperatureConverter.celsiusToFahrenheit(37.0), DELTA); + } + + @Test + void testCelsiusToKelvin() { + assertEquals(273.15, TemperatureConverter.celsiusToKelvin(0.0), DELTA); + assertEquals(373.15, TemperatureConverter.celsiusToKelvin(100.0), DELTA); + assertEquals(233.15, TemperatureConverter.celsiusToKelvin(-40.0), DELTA); + } + + @Test + void testFahrenheitToCelsius() { + assertEquals(0.0, TemperatureConverter.fahrenheitToCelsius(32.0), DELTA); + assertEquals(100.0, TemperatureConverter.fahrenheitToCelsius(212.0), DELTA); + assertEquals(-40.0, TemperatureConverter.fahrenheitToCelsius(-40.0), DELTA); + assertEquals(37.0, TemperatureConverter.fahrenheitToCelsius(98.6), DELTA); + } + + @Test + void testFahrenheitToKelvin() { + assertEquals(273.15, TemperatureConverter.fahrenheitToKelvin(32.0), DELTA); + assertEquals(373.15, TemperatureConverter.fahrenheitToKelvin(212.0), DELTA); + assertEquals(233.15, TemperatureConverter.fahrenheitToKelvin(-40.0), DELTA); + } + + @Test + void testKelvinToCelsius() { + assertEquals(0.0, TemperatureConverter.kelvinToCelsius(273.15), DELTA); + assertEquals(100.0, TemperatureConverter.kelvinToCelsius(373.15), DELTA); + assertEquals(-273.15, TemperatureConverter.kelvinToCelsius(0.0), DELTA); + } + + @Test + void testKelvinToFahrenheit() { + assertEquals(32.0, TemperatureConverter.kelvinToFahrenheit(273.15), DELTA); + assertEquals(212.0, TemperatureConverter.kelvinToFahrenheit(373.15), DELTA); + assertEquals(-40.0, TemperatureConverter.kelvinToFahrenheit(233.15), DELTA); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java index 8212793dfb79..f85a4628a1ac 100644 --- a/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java +++ b/src/test/java/com/thealgorithms/datastructures/bag/BagTest.java @@ -98,11 +98,9 @@ void testIterator() { @Test void testIteratorEmptyBag() { Bag bag = new Bag<>(); - int count = 0; - for (String ignored : bag) { - org.junit.jupiter.api.Assertions.fail("Iterator should not return any items for an empty bag"); + for (String item : bag) { + org.junit.jupiter.api.Assertions.fail("Iterator returned item for an empty bag:" + item); } - assertEquals(0, count, "Iterator should not traverse any items in an empty bag"); } @Test diff --git a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java index 8fdc93e1ca22..39e3fa0abe77 100644 --- a/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java @@ -255,4 +255,23 @@ public void testCapacityDoubling() { assertEquals(3, array.getSize()); assertEquals("Charlie", array.get(2)); } + + @Test + public void testContains() { + DynamicArray array = new DynamicArray<>(); + array.add(1); + array.add(2); + array.add(3); + + assertTrue(array.contains(2)); + assertFalse(array.contains(5)); + } + + @Test + public void testContainsWithNull() { + DynamicArray array = new DynamicArray<>(); + array.add(null); + + assertTrue(array.contains(null)); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/BellmanFordTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/BellmanFordTest.java new file mode 100644 index 000000000000..c824241c680d --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/BellmanFordTest.java @@ -0,0 +1,158 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the BellmanFord algorithm implementation. + * Tests cover various graph scenarios including: + * - Simple weighted graphs + * - Graphs with negative weights + * - Single vertex graphs + * - Disconnected graphs + * - Linear path graphs + */ +class BellmanFordTest { + + @Test + void testSimpleGraph() { + // Create a simple graph with 5 vertices and 8 edges + // Graph visualization: + // 1 + // /|\ + // 6 | 7 + // / | \ + // 0 5 2 + // \ | / + // 8 | -2 + // \|/ + // 4---3 + // 9 + BellmanFord bellmanFord = new BellmanFord(5, 8); + bellmanFord.addEdge(0, 1, 6); + bellmanFord.addEdge(0, 4, 8); + bellmanFord.addEdge(1, 2, 7); + bellmanFord.addEdge(1, 4, 5); + bellmanFord.addEdge(2, 3, -2); + bellmanFord.addEdge(2, 4, -3); + bellmanFord.addEdge(3, 4, 9); + bellmanFord.addEdge(4, 3, 7); + + // Verify edge array creation + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(8, bellmanFord.getEdgeArray().length); + } + + @Test + void testGraphWithNegativeWeights() { + // Graph with negative edge weights (but no negative cycle) + BellmanFord bellmanFord = new BellmanFord(4, 5); + bellmanFord.addEdge(0, 1, 4); + bellmanFord.addEdge(0, 2, 5); + bellmanFord.addEdge(1, 2, -3); + bellmanFord.addEdge(2, 3, 4); + bellmanFord.addEdge(1, 3, 6); + + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(5, bellmanFord.getEdgeArray().length); + } + + @Test + void testSingleVertexGraph() { + // Graph with single vertex and no edges + BellmanFord bellmanFord = new BellmanFord(1, 0); + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(0, bellmanFord.getEdgeArray().length); + } + + @Test + void testLinearGraph() { + // Linear graph: 0 -> 1 -> 2 -> 3 + BellmanFord bellmanFord = new BellmanFord(4, 3); + bellmanFord.addEdge(0, 1, 2); + bellmanFord.addEdge(1, 2, 3); + bellmanFord.addEdge(2, 3, 4); + + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(3, bellmanFord.getEdgeArray().length); + } + + @Test + void testEdgeAddition() { + BellmanFord bellmanFord = new BellmanFord(3, 3); + + bellmanFord.addEdge(0, 1, 5); + bellmanFord.addEdge(1, 2, 3); + bellmanFord.addEdge(0, 2, 10); + + // Verify all edges were added + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(3, bellmanFord.getEdgeArray().length); + } + + @Test + void testGraphWithZeroWeightEdges() { + // Graph with zero weight edges + BellmanFord bellmanFord = new BellmanFord(3, 3); + bellmanFord.addEdge(0, 1, 0); + bellmanFord.addEdge(1, 2, 0); + bellmanFord.addEdge(0, 2, 1); + + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(3, bellmanFord.getEdgeArray().length); + } + + @Test + void testLargerGraph() { + // Larger graph with 6 vertices + BellmanFord bellmanFord = new BellmanFord(6, 9); + bellmanFord.addEdge(0, 1, 5); + bellmanFord.addEdge(0, 2, 3); + bellmanFord.addEdge(1, 3, 6); + bellmanFord.addEdge(1, 2, 2); + bellmanFord.addEdge(2, 4, 4); + bellmanFord.addEdge(2, 5, 2); + bellmanFord.addEdge(2, 3, 7); + bellmanFord.addEdge(3, 4, -1); + bellmanFord.addEdge(4, 5, -2); + + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(9, bellmanFord.getEdgeArray().length); + } + + @Test + void testVertexAndEdgeCount() { + BellmanFord bellmanFord = new BellmanFord(10, 15); + assertEquals(10, bellmanFord.vertex); + assertEquals(15, bellmanFord.edge); + } + + @Test + void testMultipleEdgesBetweenSameVertices() { + // Graph allowing multiple edges between same vertices + BellmanFord bellmanFord = new BellmanFord(2, 3); + bellmanFord.addEdge(0, 1, 5); + bellmanFord.addEdge(0, 1, 3); + bellmanFord.addEdge(1, 0, 2); + + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(3, bellmanFord.getEdgeArray().length); + } + + @Test + void testCompleteGraph() { + // Complete graph with 4 vertices (6 edges for undirected equivalent) + BellmanFord bellmanFord = new BellmanFord(4, 6); + bellmanFord.addEdge(0, 1, 1); + bellmanFord.addEdge(0, 2, 2); + bellmanFord.addEdge(0, 3, 3); + bellmanFord.addEdge(1, 2, 4); + bellmanFord.addEdge(1, 3, 5); + bellmanFord.addEdge(2, 3, 6); + + assertNotNull(bellmanFord.getEdgeArray()); + assertEquals(6, bellmanFord.getEdgeArray().length); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/ConnectedComponentTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/ConnectedComponentTest.java new file mode 100644 index 000000000000..b5cfdd9de04f --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/ConnectedComponentTest.java @@ -0,0 +1,204 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the Graph class in ConnectedComponent.java. + * Tests the depth-first search implementation and connected component counting. + * Covers various graph topologies including: + * - Single connected components + * - Multiple disconnected components + * - Self-loops + * - Linear chains + * - Cyclic graphs + */ +class ConnectedComponentTest { + + @Test + void testSingleConnectedComponent() { + Graph graph = new Graph<>(); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + graph.addEdge(4, 1); + + assertEquals(1, graph.countGraphs()); + } + + @Test + void testTwoDisconnectedComponents() { + Graph graph = new Graph<>(); + // Component 1: 1-2-3 + graph.addEdge(1, 2); + graph.addEdge(2, 3); + // Component 2: 4-5 + graph.addEdge(4, 5); + + assertEquals(2, graph.countGraphs()); + } + + @Test + void testThreeDisconnectedComponents() { + Graph graph = new Graph<>(); + // Component 1: a-b-c-d-e + graph.addEdge('a', 'b'); + graph.addEdge('a', 'e'); + graph.addEdge('b', 'e'); + graph.addEdge('b', 'c'); + graph.addEdge('c', 'd'); + graph.addEdge('d', 'a'); + // Component 2: x-y-z + graph.addEdge('x', 'y'); + graph.addEdge('x', 'z'); + // Component 3: w (self-loop) + graph.addEdge('w', 'w'); + + assertEquals(3, graph.countGraphs()); + } + + @Test + void testSingleNodeSelfLoop() { + Graph graph = new Graph<>(); + graph.addEdge(1, 1); + + assertEquals(1, graph.countGraphs()); + } + + @Test + void testLinearChain() { + Graph graph = new Graph<>(); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + graph.addEdge(4, 5); + + assertEquals(1, graph.countGraphs()); + } + + @Test + void testStarTopology() { + // Star graph with center node 0 connected to nodes 1, 2, 3, 4 + Graph graph = new Graph<>(); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(0, 3); + graph.addEdge(0, 4); + + assertEquals(1, graph.countGraphs()); + } + + @Test + void testCompleteGraph() { + // Complete graph K4: every node connected to every other node + Graph graph = new Graph<>(); + graph.addEdge(1, 2); + graph.addEdge(1, 3); + graph.addEdge(1, 4); + graph.addEdge(2, 3); + graph.addEdge(2, 4); + graph.addEdge(3, 4); + + assertEquals(1, graph.countGraphs()); + } + + @Test + void testStringVertices() { + Graph graph = new Graph<>(); + // Component 1 + graph.addEdge("New York", "Los Angeles"); + graph.addEdge("Los Angeles", "Chicago"); + // Component 2 + graph.addEdge("London", "Paris"); + // Component 3 + graph.addEdge("Tokyo", "Tokyo"); + + assertEquals(3, graph.countGraphs()); + } + + @Test + void testEmptyGraph() { + Graph graph = new Graph<>(); + assertEquals(0, graph.countGraphs()); + } + + @Test + void testDepthFirstSearchBasic() { + Graph graph = new Graph<>(); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + + // Get the first node and perform DFS + assertNotNull(graph.nodeList); + assertEquals(3, graph.nodeList.size()); + } + + @Test + void testManyIsolatedComponents() { + Graph graph = new Graph<>(); + // Create 5 isolated components (each is a self-loop) + graph.addEdge(1, 1); + graph.addEdge(2, 2); + graph.addEdge(3, 3); + graph.addEdge(4, 4); + graph.addEdge(5, 5); + + assertEquals(5, graph.countGraphs()); + } + + @Test + void testBidirectionalEdges() { + Graph graph = new Graph<>(); + // Note: This is a directed graph representation + // Adding edge 1->2 does not automatically add 2->1 + graph.addEdge(1, 2); + graph.addEdge(2, 1); + graph.addEdge(2, 3); + graph.addEdge(3, 2); + + assertEquals(1, graph.countGraphs()); + } + + @Test + void testCyclicGraph() { + Graph graph = new Graph<>(); + // Create a cycle: 1 -> 2 -> 3 -> 4 -> 1 + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + graph.addEdge(4, 1); + + assertEquals(1, graph.countGraphs()); + } + + @Test + void testMultipleCycles() { + Graph graph = new Graph<>(); + // Cycle 1: 1 -> 2 -> 3 -> 1 + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 1); + // Cycle 2: 4 -> 5 -> 4 + graph.addEdge(4, 5); + graph.addEdge(5, 4); + + assertEquals(2, graph.countGraphs()); + } + + @Test + void testIntegerGraphFromMainExample() { + // Recreate the example from main method + Graph graph = new Graph<>(); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(2, 4); + graph.addEdge(3, 5); + graph.addEdge(7, 8); + graph.addEdge(8, 10); + graph.addEdge(10, 8); + + assertEquals(2, graph.countGraphs()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java index c5df9acdf33b..a189091c17d3 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.datastructures.graphs; 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.BeforeEach; @@ -61,4 +62,120 @@ void testInvalidSourceVertex() { assertThrows(IllegalArgumentException.class, () -> dijkstraAlgorithm.run(graph, -1)); assertThrows(IllegalArgumentException.class, () -> dijkstraAlgorithm.run(graph, graph.length)); } + + @Test + void testLinearGraph() { + // Linear graph: 0 - 1 - 2 - 3 + // with weights: 2 3 4 + int[][] linearGraph = {{0, 2, 0, 0}, {2, 0, 3, 0}, {0, 3, 0, 4}, {0, 0, 4, 0}}; + + DijkstraAlgorithm dijkstraLinear = new DijkstraAlgorithm(4); + int[] distances = dijkstraLinear.run(linearGraph, 0); + + assertArrayEquals(new int[] {0, 2, 5, 9}, distances); + } + + @Test + void testStarTopology() { + // Star graph: center node 0 connected to all others + // 1(2) + // | + // 3(4)-0-2(3) + // | + // 4(5) + int[][] starGraph = {{0, 2, 3, 4, 5}, {2, 0, 0, 0, 0}, {3, 0, 0, 0, 0}, {4, 0, 0, 0, 0}, {5, 0, 0, 0, 0}}; + + DijkstraAlgorithm dijkstraStar = new DijkstraAlgorithm(5); + int[] distances = dijkstraStar.run(starGraph, 0); + + assertArrayEquals(new int[] {0, 2, 3, 4, 5}, distances); + } + + @Test + void testCompleteGraphK4() { + // Complete graph K4 with varying weights + int[][] completeGraph = {{0, 1, 2, 3}, {1, 0, 4, 5}, {2, 4, 0, 6}, {3, 5, 6, 0}}; + + DijkstraAlgorithm dijkstraComplete = new DijkstraAlgorithm(4); + int[] distances = dijkstraComplete.run(completeGraph, 0); + + // Direct paths from 0 are shortest + assertArrayEquals(new int[] {0, 1, 2, 3}, distances); + } + + @Test + void testDifferentSourceVertex() { + // Test running from different source vertices + int[][] simpleGraph = {{0, 5, 0, 0}, {5, 0, 3, 0}, {0, 3, 0, 2}, {0, 0, 2, 0}}; + + DijkstraAlgorithm dijkstra = new DijkstraAlgorithm(4); + + // From vertex 0 + int[] distFrom0 = dijkstra.run(simpleGraph, 0); + assertArrayEquals(new int[] {0, 5, 8, 10}, distFrom0); + + // From vertex 2 + int[] distFrom2 = dijkstra.run(simpleGraph, 2); + assertArrayEquals(new int[] {8, 3, 0, 2}, distFrom2); + + // From vertex 3 + int[] distFrom3 = dijkstra.run(simpleGraph, 3); + assertArrayEquals(new int[] {10, 5, 2, 0}, distFrom3); + } + + @Test + void testUnitWeightGraph() { + // Graph with all unit weights (like BFS distance) + int[][] unitGraph = {{0, 1, 1, 0}, {1, 0, 1, 1}, {1, 1, 0, 1}, {0, 1, 1, 0}}; + + DijkstraAlgorithm dijkstraUnit = new DijkstraAlgorithm(4); + int[] distances = dijkstraUnit.run(unitGraph, 0); + + assertArrayEquals(new int[] {0, 1, 1, 2}, distances); + } + + @Test + void testTwoVertexGraph() { + int[][] twoVertexGraph = {{0, 7}, {7, 0}}; + + DijkstraAlgorithm dijkstraTwo = new DijkstraAlgorithm(2); + int[] distances = dijkstraTwo.run(twoVertexGraph, 0); + + assertArrayEquals(new int[] {0, 7}, distances); + } + + @Test + void testShortcutPath() { + // Graph where direct path is longer than indirect path + // 0 --(10)--> 2 + // 0 --(1)--> 1 --(2)--> 2 + int[][] shortcutGraph = {{0, 1, 10}, {1, 0, 2}, {10, 2, 0}}; + + DijkstraAlgorithm dijkstraShortcut = new DijkstraAlgorithm(3); + int[] distances = dijkstraShortcut.run(shortcutGraph, 0); + + // The shortest path to vertex 2 should be 3 (via vertex 1), not 10 (direct) + assertArrayEquals(new int[] {0, 1, 3}, distances); + } + + @Test + void testSourceToSourceDistanceIsZero() { + // Verify distance from source to itself is always 0 + int[] distances = dijkstraAlgorithm.run(graph, 0); + assertEquals(0, distances[0]); + + distances = dijkstraAlgorithm.run(graph, 5); + assertEquals(0, distances[5]); + } + + @Test + void testLargeWeights() { + // Graph with large weights + int[][] largeWeightGraph = {{0, 1000, 0}, {1000, 0, 2000}, {0, 2000, 0}}; + + DijkstraAlgorithm dijkstraLarge = new DijkstraAlgorithm(3); + int[] distances = dijkstraLarge.run(largeWeightGraph, 0); + + assertArrayEquals(new int[] {0, 1000, 3000}, distances); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java deleted file mode 100644 index bf4e2828e069..000000000000 --- a/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraOptimizedAlgorithmTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.thealgorithms.datastructures.graphs; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class DijkstraOptimizedAlgorithmTest { - - private DijkstraOptimizedAlgorithm dijkstraOptimizedAlgorithm; - private int[][] graph; - - @BeforeEach - void setUp() { - graph = new int[][] { - {0, 4, 0, 0, 0, 0, 0, 8, 0}, - {4, 0, 8, 0, 0, 0, 0, 11, 0}, - {0, 8, 0, 7, 0, 4, 0, 0, 2}, - {0, 0, 7, 0, 9, 14, 0, 0, 0}, - {0, 0, 0, 9, 0, 10, 0, 0, 0}, - {0, 0, 4, 14, 10, 0, 2, 0, 0}, - {0, 0, 0, 0, 0, 2, 0, 1, 6}, - {8, 11, 0, 0, 0, 0, 1, 0, 7}, - {0, 0, 2, 0, 0, 0, 6, 7, 0}, - }; - - dijkstraOptimizedAlgorithm = new DijkstraOptimizedAlgorithm(graph.length); - } - - @Test - void testRunAlgorithm() { - int[] expectedDistances = {0, 4, 12, 19, 21, 11, 9, 8, 14}; - assertArrayEquals(expectedDistances, dijkstraOptimizedAlgorithm.run(graph, 0)); - } - - @Test - void testGraphWithDisconnectedNodes() { - int[][] disconnectedGraph = { - {0, 3, 0, 0}, {3, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0} // Node 3 is disconnected - }; - - DijkstraOptimizedAlgorithm dijkstraDisconnected = new DijkstraOptimizedAlgorithm(disconnectedGraph.length); - - // Testing from vertex 0 - int[] expectedDistances = {0, 3, 4, Integer.MAX_VALUE}; // Node 3 is unreachable - assertArrayEquals(expectedDistances, dijkstraDisconnected.run(disconnectedGraph, 0)); - } - - @Test - void testSingleVertexGraph() { - int[][] singleVertexGraph = {{0}}; - DijkstraOptimizedAlgorithm dijkstraSingleVertex = new DijkstraOptimizedAlgorithm(1); - - int[] expectedDistances = {0}; // The only vertex's distance to itself is 0 - assertArrayEquals(expectedDistances, dijkstraSingleVertex.run(singleVertexGraph, 0)); - } - - @Test - void testInvalidSourceVertex() { - assertThrows(IllegalArgumentException.class, () -> dijkstraOptimizedAlgorithm.run(graph, -1)); - assertThrows(IllegalArgumentException.class, () -> dijkstraOptimizedAlgorithm.run(graph, graph.length)); - } -} diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java index cc8a2df872ce..eaff0222bd36 100644 --- a/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java +++ b/src/test/java/com/thealgorithms/datastructures/graphs/MatrixGraphsTest.java @@ -137,4 +137,215 @@ void testDisconnectedGraph() { assertTrue(dfs.containsAll(Arrays.asList(0, 1))); assertTrue(bfs.containsAll(Arrays.asList(0, 1))); } + + @Test + void testSingleVertexGraphDfs() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(1); + + List dfs = graph.depthFirstOrder(0); + assertEquals(1, dfs.size()); + assertEquals(0, dfs.getFirst()); + } + + @Test + void testSingleVertexGraphBfs() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(1); + + List bfs = graph.breadthFirstOrder(0); + assertEquals(1, bfs.size()); + assertEquals(0, bfs.getFirst()); + } + + @Test + void testBfsLevelOrder() { + // Create a graph where BFS should visit level by level + // 0 + // /|\ + // 1 2 3 + // | + // 4 + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(0, 3); + graph.addEdge(1, 4); + + List bfs = graph.breadthFirstOrder(0); + assertEquals(5, bfs.size()); + assertEquals(0, bfs.get(0)); + // Level 1 vertices (1, 2, 3) should appear before level 2 vertex (4) + int indexOf4 = bfs.indexOf(4); + assertTrue(bfs.indexOf(1) < indexOf4); + assertTrue(bfs.indexOf(2) < indexOf4); + assertTrue(bfs.indexOf(3) < indexOf4); + } + + @Test + void testDfsStartFromDifferentVertices() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(4); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + + // DFS from vertex 0 + List dfs0 = graph.depthFirstOrder(0); + assertEquals(4, dfs0.size()); + assertEquals(0, dfs0.get(0)); + + // DFS from vertex 2 + List dfs2 = graph.depthFirstOrder(2); + assertEquals(4, dfs2.size()); + assertEquals(2, dfs2.get(0)); + + // DFS from vertex 3 + List dfs3 = graph.depthFirstOrder(3); + assertEquals(4, dfs3.size()); + assertEquals(3, dfs3.get(0)); + } + + @Test + void testBfsStartFromDifferentVertices() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(4); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + + // BFS from vertex 0 + List bfs0 = graph.breadthFirstOrder(0); + assertEquals(4, bfs0.size()); + assertEquals(0, bfs0.get(0)); + + // BFS from vertex 2 + List bfs2 = graph.breadthFirstOrder(2); + assertEquals(4, bfs2.size()); + assertEquals(2, bfs2.get(0)); + } + + @Test + void testStarTopologyBfs() { + // Star graph: 0 is center connected to 1, 2, 3, 4 + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(0, 3); + graph.addEdge(0, 4); + + List bfs = graph.breadthFirstOrder(0); + assertEquals(5, bfs.size()); + assertEquals(0, bfs.get(0)); + // All neighbors should be at distance 1 + assertTrue(bfs.containsAll(Arrays.asList(1, 2, 3, 4))); + } + + @Test + void testStarTopologyDfs() { + // Star graph: 0 is center connected to 1, 2, 3, 4 + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(0, 3); + graph.addEdge(0, 4); + + List dfs = graph.depthFirstOrder(0); + assertEquals(5, dfs.size()); + assertEquals(0, dfs.get(0)); + assertTrue(dfs.containsAll(Arrays.asList(1, 2, 3, 4))); + } + + @Test + void testNegativeStartVertexDfs() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + + List dfs = graph.depthFirstOrder(-1); + assertTrue(dfs.isEmpty()); + } + + @Test + void testNegativeStartVertexBfs() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + + List bfs = graph.breadthFirstOrder(-1); + assertTrue(bfs.isEmpty()); + } + + @Test + void testCompleteGraphKFour() { + // Complete graph K4: every vertex connected to every other vertex + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(4); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(0, 3); + graph.addEdge(1, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 3); + + assertEquals(6, graph.numberOfEdges()); + + List dfs = graph.depthFirstOrder(0); + List bfs = graph.breadthFirstOrder(0); + + assertEquals(4, dfs.size()); + assertEquals(4, bfs.size()); + assertTrue(dfs.containsAll(Arrays.asList(0, 1, 2, 3))); + assertTrue(bfs.containsAll(Arrays.asList(0, 1, 2, 3))); + } + + @Test + void testLargerGraphTraversal() { + // Create a larger graph with 10 vertices + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(10); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(1, 4); + graph.addEdge(2, 5); + graph.addEdge(2, 6); + graph.addEdge(3, 7); + graph.addEdge(4, 8); + graph.addEdge(5, 9); + + List dfs = graph.depthFirstOrder(0); + List bfs = graph.breadthFirstOrder(0); + + assertEquals(10, dfs.size()); + assertEquals(10, bfs.size()); + assertEquals(0, dfs.get(0)); + assertEquals(0, bfs.get(0)); + } + + @Test + void testSelfLoop() { + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(3); + graph.addEdge(0, 0); // Self loop + graph.addEdge(0, 1); + graph.addEdge(1, 2); + + List dfs = graph.depthFirstOrder(0); + List bfs = graph.breadthFirstOrder(0); + + assertEquals(3, dfs.size()); + assertEquals(3, bfs.size()); + } + + @Test + void testLinearGraphTraversal() { + // Linear graph: 0 - 1 - 2 - 3 - 4 + AdjacencyMatrixGraph graph = new AdjacencyMatrixGraph(5); + graph.addEdge(0, 1); + graph.addEdge(1, 2); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + + List dfs = graph.depthFirstOrder(0); + List bfs = graph.breadthFirstOrder(0); + + assertEquals(5, dfs.size()); + assertEquals(5, bfs.size()); + + // In a linear graph, BFS and DFS starting from 0 should be the same + assertEquals(Arrays.asList(0, 1, 2, 3, 4), dfs); + assertEquals(Arrays.asList(0, 1, 2, 3, 4), bfs); + } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java new file mode 100644 index 000000000000..003f6fcee812 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/RottingOrangesTest.java @@ -0,0 +1,134 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class RottingOrangesTest { + + @Test + void testAllOrangesRotInSingleMinute() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 1}, {1, 1, 0}, {0, 1, 1}}; + + assertEquals(4, rottingOranges.run(grid)); + } + + @Test + void testImpossibleToRotAllOranges() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 1}, {0, 1, 1}, {1, 0, 1}}; + + assertEquals(-1, rottingOranges.run(grid)); + } + + @Test + void testNoFreshOranges() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 2}, {2, 2}}; + + assertEquals(0, rottingOranges.run(grid)); + } + + @Test + void testNoRottenOranges() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{1, 1}, {1, 1}}; + + assertEquals(-1, rottingOranges.run(grid)); + } + + @Test + void testEmptyGrid() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {}; + + assertEquals(0, rottingOranges.run(grid)); + } + + @Test + void testSingleRottenOrange() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2}}; + + assertEquals(0, rottingOranges.run(grid)); + } + + @Test + void testSingleFreshOrange() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{1}}; + + assertEquals(-1, rottingOranges.run(grid)); + } + + @Test + void testSingleFreshOrangeNextToRottenOrange() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1}}; + + assertEquals(1, rottingOranges.run(grid)); + } + + @Test + void testMultipleRottenSources() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 0, 2}, {1, 1, 1, 1}, {0, 1, 1, 1}}; + + assertEquals(3, rottingOranges.run(grid)); + } + + @Test + void testFreshOrangeBlockedByEmptyCells() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 0, 1}, {0, 0, 0}, {1, 0, 1}}; + + assertEquals(-1, rottingOranges.run(grid)); + } + + @Test + void testLinearSpread() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 1, 1, 1}}; + + assertEquals(4, rottingOranges.run(grid)); + } + + @Test + void testVerticalSpread() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2}, {1}, {1}, {1}}; + + assertEquals(3, rottingOranges.run(grid)); + } + + @Test + void testGridWithOnlyEmptyCells() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{0, 0}, {0, 0}}; + + assertEquals(0, rottingOranges.run(grid)); + } + + @Test + void testComplexGrid() { + RottingOranges rottingOranges = new RottingOranges(); + + int[][] grid = {{2, 1, 1}, {1, 1, 1}, {1, 1, 1}}; + + assertEquals(4, rottingOranges.run(grid)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java index 5d1733a3e97c..6b6e670a258b 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/GenericHashMapUsingArrayTest.java @@ -1,11 +1,9 @@ package com.thealgorithms.datastructures.hashmap.hashing; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class GenericHashMapUsingArrayTest { @@ -16,10 +14,10 @@ void testGenericHashmapWhichUsesArrayAndBothKeyAndValueAreStrings() { map.put("Nepal", "Kathmandu"); map.put("India", "New Delhi"); map.put("Australia", "Sydney"); - assertNotNull(map); - assertEquals(4, map.size()); - assertEquals("Kathmandu", map.get("Nepal")); - assertEquals("Sydney", map.get("Australia")); + Assertions.assertNotNull(map); + Assertions.assertEquals(4, map.size()); + Assertions.assertEquals("Kathmandu", map.get("Nepal")); + Assertions.assertEquals("Sydney", map.get("Australia")); } @Test @@ -29,12 +27,12 @@ void testGenericHashmapWhichUsesArrayAndKeyIsStringValueIsInteger() { map.put("Nepal", 25); map.put("India", 101); map.put("Australia", 99); - assertNotNull(map); - assertEquals(4, map.size()); - assertEquals(25, map.get("Nepal")); - assertEquals(99, map.get("Australia")); + Assertions.assertNotNull(map); + Assertions.assertEquals(4, map.size()); + Assertions.assertEquals(25, map.get("Nepal")); + Assertions.assertEquals(99, map.get("Australia")); map.remove("Nepal"); - assertFalse(map.containsKey("Nepal")); + Assertions.assertFalse(map.containsKey("Nepal")); } @Test @@ -44,11 +42,11 @@ void testGenericHashmapWhichUsesArrayAndKeyIsIntegerValueIsString() { map.put(34, "Kathmandu"); map.put(46, "New Delhi"); map.put(89, "Sydney"); - assertNotNull(map); - assertEquals(4, map.size()); - assertEquals("Sydney", map.get(89)); - assertEquals("Washington DC", map.get(101)); - assertTrue(map.containsKey(46)); + Assertions.assertNotNull(map); + Assertions.assertEquals(4, map.size()); + Assertions.assertEquals("Sydney", map.get(89)); + Assertions.assertEquals("Washington DC", map.get(101)); + Assertions.assertTrue(map.containsKey(46)); } @Test @@ -56,7 +54,7 @@ void testRemoveNonExistentKey() { GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); map.put("USA", "Washington DC"); map.remove("Nepal"); // Attempting to remove a non-existent key - assertEquals(1, map.size()); // Size should remain the same + Assertions.assertEquals(1, map.size()); // Size should remain the same } @Test @@ -65,8 +63,8 @@ void testRehashing() { for (int i = 0; i < 20; i++) { map.put("Key" + i, "Value" + i); } - assertEquals(20, map.size()); // Ensure all items were added - assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash + Assertions.assertEquals(20, map.size()); // Ensure all items were added + Assertions.assertEquals("Value5", map.get("Key5")); // Check retrieval after rehash } @Test @@ -74,7 +72,7 @@ void testUpdateValueForExistingKey() { GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); map.put("USA", "Washington DC"); map.put("USA", "New Washington DC"); // Updating value for existing key - assertEquals("New Washington DC", map.get("USA")); + Assertions.assertEquals("New Washington DC", map.get("USA")); } @Test @@ -83,14 +81,154 @@ void testToStringMethod() { map.put("USA", "Washington DC"); map.put("Nepal", "Kathmandu"); String expected = "{USA : Washington DC, Nepal : Kathmandu}"; - assertEquals(expected, map.toString()); + Assertions.assertEquals(expected, map.toString()); } @Test void testContainsKey() { GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); map.put("USA", "Washington DC"); - assertTrue(map.containsKey("USA")); - assertFalse(map.containsKey("Nepal")); + Assertions.assertTrue(map.containsKey("USA")); + Assertions.assertFalse(map.containsKey("Nepal")); + } + + // ======= Added tests from the new version ======= + + @Test + void shouldThrowNullPointerExceptionForNullKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + String nullKey = null; // Use variable to avoid static analysis false positive + Assertions.assertThrows(NullPointerException.class, () -> map.put(nullKey, "value")); + } + + @Test + void shouldStoreNullValueForKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("keyWithNullValue", null); + Assertions.assertEquals(1, map.size()); + Assertions.assertNull(map.get("keyWithNullValue")); + // Note: containsKey returns false for null values due to implementation + Assertions.assertFalse(map.containsKey("keyWithNullValue")); + } + + @Test + void shouldHandleCollisionWhenKeysHashToSameBucket() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + Integer key1 = 1; + Integer key2 = 17; + map.put(key1, 100); + map.put(key2, 200); + Assertions.assertEquals(2, map.size()); + Assertions.assertEquals(100, map.get(key1)); + Assertions.assertEquals(200, map.get(key2)); + Assertions.assertTrue(map.containsKey(key1)); + Assertions.assertTrue(map.containsKey(key2)); + } + + @Test + void shouldHandleEmptyStringAsKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("", "valueForEmptyKey"); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("valueForEmptyKey", map.get("")); + Assertions.assertTrue(map.containsKey("")); + } + + @Test + void shouldHandleEmptyStringAsValue() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("keyForEmptyValue", ""); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("", map.get("keyForEmptyValue")); + Assertions.assertTrue(map.containsKey("keyForEmptyValue")); + } + + @Test + void shouldHandleNegativeIntegerKeys() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put(-1, 100); + map.put(-100, 200); + Assertions.assertEquals(2, map.size()); + Assertions.assertEquals(100, map.get(-1)); + Assertions.assertEquals(200, map.get(-100)); + Assertions.assertTrue(map.containsKey(-1)); + Assertions.assertTrue(map.containsKey(-100)); + } + + @Test + void shouldHandleZeroAsKey() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put(0, 100); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals(100, map.get(0)); + Assertions.assertTrue(map.containsKey(0)); + } + + @Test + void shouldHandleStringWithSpecialCharacters() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put("key!@#$%^&*()", "value<>?/\\|"); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("value<>?/\\|", map.get("key!@#$%^&*()")); + Assertions.assertTrue(map.containsKey("key!@#$%^&*()")); + } + + @Test + void shouldHandleLongStrings() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + StringBuilder longKey = new StringBuilder(); + StringBuilder longValue = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + longKey.append("a"); + longValue.append("b"); + } + String key = longKey.toString(); + String value = longValue.toString(); + map.put(key, value); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals(value, map.get(key)); + Assertions.assertTrue(map.containsKey(key)); + } + + @ParameterizedTest + @ValueSource(strings = {"a", "ab", "abc", "test", "longerString"}) + void shouldHandleKeysOfDifferentLengths(String key) { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + map.put(key, "value"); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("value", map.get(key)); + Assertions.assertTrue(map.containsKey(key)); + } + + @Test + void shouldHandleUpdateOnExistingKeyInCollisionBucket() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + Integer key1 = 1; + Integer key2 = 17; + map.put(key1, 100); + map.put(key2, 200); + Assertions.assertEquals(2, map.size()); + map.put(key2, 999); + Assertions.assertEquals(2, map.size()); + Assertions.assertEquals(100, map.get(key1)); + Assertions.assertEquals(999, map.get(key2)); + Assertions.assertTrue(map.containsKey(key1)); + Assertions.assertTrue(map.containsKey(key2)); + } + + @Test + void shouldHandleExactlyLoadFactorBoundary() { + GenericHashMapUsingArray map = new GenericHashMapUsingArray<>(); + // Fill exactly to load factor (12 items with capacity 16 and 0.75 load factor) + for (int i = 0; i < 12; i++) { + map.put(i, i * 10); + } + Assertions.assertEquals(12, map.size()); + // Act - This should trigger rehash on 13th item + map.put(12, 120); + // Assert - Rehash should have happened + Assertions.assertEquals(13, map.size()); + Assertions.assertEquals(120, map.get(12)); + Assertions.assertTrue(map.containsKey(12)); } } diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMapTest.java new file mode 100644 index 000000000000..3d3fe63d775a --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/ImmutableHashMapTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.datastructures.hashmap.hashing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ImmutableHashMapTest { + + @Test + void testEmptyMap() { + ImmutableHashMap map = ImmutableHashMap.empty(); + + assertEquals(0, map.size()); + assertNull(map.get("A")); + } + + @Test + void testPutDoesNotModifyOriginalMap() { + ImmutableHashMap map1 = ImmutableHashMap.empty(); + + ImmutableHashMap map2 = map1.put("A", 1); + + assertEquals(0, map1.size()); + assertEquals(1, map2.size()); + assertNull(map1.get("A")); + assertEquals(1, map2.get("A")); + } + + @Test + void testMultiplePuts() { + ImmutableHashMap map = ImmutableHashMap.empty().put("A", 1).put("B", 2); + + assertEquals(2, map.size()); + assertEquals(1, map.get("A")); + assertEquals(2, map.get("B")); + } + + @Test + void testContainsKey() { + ImmutableHashMap map = ImmutableHashMap.empty().put("X", 100); + + assertTrue(map.containsKey("X")); + assertFalse(map.containsKey("Y")); + } + + @Test + void testNullKey() { + ImmutableHashMap map = ImmutableHashMap.empty().put(null, 50); + + assertEquals(50, map.get(null)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java index 44551a8adac6..ef7739a2e8a9 100644 --- a/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java +++ b/src/test/java/com/thealgorithms/datastructures/hashmap/hashing/MapTest.java @@ -81,19 +81,19 @@ void containsTest() { @Test void sizeTest() { Map map = getMap(); - assertEquals(map.size(), 0); + assertEquals(0, map.size()); for (int i = -100; i < 100; i++) { map.put(i, String.valueOf(i)); } - assertEquals(map.size(), 200); + assertEquals(200, map.size()); for (int i = -50; i < 50; i++) { map.delete(i); } - assertEquals(map.size(), 100); + assertEquals(100, map.size()); } @Test diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java index d04a9de8a94b..792969200c82 100644 --- a/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java +++ b/src/test/java/com/thealgorithms/datastructures/heaps/HeapElementTest.java @@ -2,6 +2,7 @@ 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.assertNull; import org.junit.jupiter.api.Test; @@ -39,7 +40,7 @@ void testEquals() { assertEquals(element1, element2); // Same key and info assertNotEquals(element1, element3); // Different key - assertNotEquals(null, element1); // Check for null + assertNotNull(element1); assertNotEquals("String", element1); // Check for different type } diff --git a/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java new file mode 100644 index 000000000000..8d8c4e1db6bd --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/heaps/IndexedPriorityQueueTest.java @@ -0,0 +1,350 @@ +package com.thealgorithms.datastructures.heaps; + +import java.util.Comparator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link IndexedPriorityQueue}. + * + * Notes: + * - We mainly use a Node class with a mutable "prio" field to test changeKey/decreaseKey/increaseKey. + * - The queue is a min-heap, so smaller "prio" means higher priority. + * - By default the implementation uses IdentityHashMap so duplicate-equals objects are allowed. + */ +public class IndexedPriorityQueueTest { + + // ------------------------ + // Helpers + // ------------------------ + + /** Simple payload with mutable priority. */ + static class Node { + final String id; + int prio; // lower is better (min-heap) + + Node(String id, int prio) { + this.id = id; + this.prio = prio; + } + + @Override + public String toString() { + return id + "(" + prio + ")"; + } + } + + /** Same as Node but overrides equals/hashCode to simulate "duplicate-equals" scenario. */ + static class NodeWithEquals { + final String id; + int prio; + + NodeWithEquals(String id, int prio) { + this.id = id; + this.prio = prio; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof NodeWithEquals)) { + return false; + } + NodeWithEquals other = (NodeWithEquals) o; + // Intentionally naive equality: equal if priority is equal + return this.prio == other.prio; + } + + @Override + public int hashCode() { + return Integer.hashCode(prio); + } + + @Override + public String toString() { + return id + "(" + prio + ")"; + } + } + + private static IndexedPriorityQueue newNodePQ() { + return new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio)); + } + + // ------------------------ + // Basic operations + // ------------------------ + + @Test + void testOfferPollWithIntegersComparableMode() { + // cmp == null -> elements must be Comparable + IndexedPriorityQueue pq = new IndexedPriorityQueue<>(); + Assertions.assertTrue(pq.isEmpty()); + + pq.offer(5); + pq.offer(1); + pq.offer(3); + + Assertions.assertEquals(3, pq.size()); + Assertions.assertEquals(1, pq.peek()); + Assertions.assertEquals(1, pq.poll()); + Assertions.assertEquals(3, pq.poll()); + Assertions.assertEquals(5, pq.poll()); + Assertions.assertNull(pq.poll()); // empty -> null + Assertions.assertTrue(pq.isEmpty()); + } + + @Test + void testPeekAndIsEmpty() { + IndexedPriorityQueue pq = newNodePQ(); + Assertions.assertTrue(pq.isEmpty()); + Assertions.assertNull(pq.peek()); + + pq.offer(new Node("A", 10)); + pq.offer(new Node("B", 5)); + pq.offer(new Node("C", 7)); + + Assertions.assertFalse(pq.isEmpty()); + Assertions.assertEquals("B(5)", pq.peek().toString()); + } + + @Test + void testRemoveSpecificElement() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 10); + Node b = new Node("B", 5); + Node c = new Node("C", 7); + + pq.offer(a); + pq.offer(b); + pq.offer(c); + + // remove by reference (O(log n)) + Assertions.assertTrue(pq.remove(b)); + Assertions.assertEquals(2, pq.size()); + // now min should be C(7) + Assertions.assertEquals("C(7)", pq.peek().toString()); + // removing an element not present -> false + Assertions.assertFalse(pq.remove(b)); + } + + @Test + void testContainsAndClear() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 2); + Node b = new Node("B", 3); + + pq.offer(a); + pq.offer(b); + + Assertions.assertTrue(pq.contains(a)); + Assertions.assertTrue(pq.contains(b)); + + pq.clear(); + Assertions.assertTrue(pq.isEmpty()); + Assertions.assertFalse(pq.contains(a)); + Assertions.assertNull(pq.peek()); + } + + // ------------------------ + // Key updates + // ------------------------ + + @Test + void testDecreaseKeyMovesUp() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 10); + Node b = new Node("B", 5); + Node c = new Node("C", 7); + + pq.offer(a); + pq.offer(b); + pq.offer(c); + + // current min is B(5) + Assertions.assertEquals("B(5)", pq.peek().toString()); + + // Make A more important: 10 -> 1 (smaller is better) + pq.decreaseKey(a, n -> n.prio = 1); + + // Now A should be at the top + Assertions.assertEquals("A(1)", pq.peek().toString()); + } + + @Test + void testIncreaseKeyMovesDown() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 1); + Node b = new Node("B", 2); + Node c = new Node("C", 3); + + pq.offer(a); + pq.offer(b); + pq.offer(c); + + // min is A(1) + Assertions.assertEquals("A(1)", pq.peek().toString()); + + // Make A worse: 1 -> 100 + pq.increaseKey(a, n -> n.prio = 100); + + // Now min should be B(2) + Assertions.assertEquals("B(2)", pq.peek().toString()); + } + + @Test + void testChangeKeyChoosesDirectionAutomatically() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 10); + Node b = new Node("B", 20); + Node c = new Node("C", 30); + + pq.offer(a); + pq.offer(b); + pq.offer(c); + + // Decrease B to 0 -> should move up + pq.changeKey(b, n -> n.prio = 0); + Assertions.assertEquals("B(0)", pq.peek().toString()); + + // Increase B to 100 -> should move down + pq.changeKey(b, n -> n.prio = 100); + Assertions.assertEquals("A(10)", pq.peek().toString()); + } + + @Test + void testDirectMutationWithoutChangeKeyDoesNotReheapByDesign() { + // Demonstrates the contract: do NOT mutate comparator fields directly. + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 5); + Node b = new Node("B", 10); + + pq.offer(a); + pq.offer(b); + + // Illegally mutate priority directly + a.prio = 100; // worse than b now, but heap wasn't notified + + // The heap structure is unchanged; peek still returns A(100) (was A(5) before) + // This test documents the behavior/contract rather than relying on it. + Assertions.assertEquals("A(100)", pq.peek().toString()); + + // Now fix properly via changeKey (no change in value, but triggers reheap) + pq.changeKey(a, n -> n.prio = n.prio); + Assertions.assertEquals("B(10)", pq.peek().toString()); + } + + // ------------------------ + // Identity semantics & duplicates + // ------------------------ + + @Test + void testDuplicateEqualsElementsAreSupportedIdentityMap() { + IndexedPriorityQueue pq = new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio)); + + NodeWithEquals x1 = new NodeWithEquals("X1", 7); + NodeWithEquals x2 = new NodeWithEquals("X2", 7); // equals to X1 by prio, but different instance + + // With IdentityHashMap internally, both can coexist + pq.offer(x1); + pq.offer(x2); + + Assertions.assertEquals(2, pq.size()); + // Poll twice; both 7s should be returned (order between x1/x2 is unspecified) + Assertions.assertEquals(7, pq.poll().prio); + Assertions.assertEquals(7, pq.poll().prio); + Assertions.assertTrue(pq.isEmpty()); + } + + // ------------------------ + // Capacity growth + // ------------------------ + + @Test + void testGrowByManyInserts() { + IndexedPriorityQueue pq = new IndexedPriorityQueue<>(); + int n = 100; // beyond default capacity (11) + + for (int i = n; i >= 1; i--) { + pq.offer(i); + } + + Assertions.assertEquals(n, pq.size()); + // Ensure min-to-max order when polling + for (int expected = 1; expected <= n; expected++) { + Integer v = pq.poll(); + Assertions.assertEquals(expected, v); + } + Assertions.assertTrue(pq.isEmpty()); + Assertions.assertNull(pq.poll()); + } + + // ------------------------ + // remove/contains edge cases + // ------------------------ + + @Test + void testRemoveHeadAndMiddleAndTail() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 1); + Node b = new Node("B", 2); + Node c = new Node("C", 3); + Node d = new Node("D", 4); + + pq.offer(a); + pq.offer(b); + pq.offer(c); + pq.offer(d); + + // remove head + Assertions.assertTrue(pq.remove(a)); + Assertions.assertFalse(pq.contains(a)); + Assertions.assertEquals("B(2)", pq.peek().toString()); + + // remove middle + Assertions.assertTrue(pq.remove(c)); + Assertions.assertFalse(pq.contains(c)); + Assertions.assertEquals("B(2)", pq.peek().toString()); + + // remove tail (last) + Assertions.assertTrue(pq.remove(d)); + Assertions.assertFalse(pq.contains(d)); + Assertions.assertEquals("B(2)", pq.peek().toString()); + + // remove last remaining + Assertions.assertTrue(pq.remove(b)); + Assertions.assertTrue(pq.isEmpty()); + Assertions.assertNull(pq.peek()); + } + + // ------------------------ + // Error / edge cases for coverage + // ------------------------ + + @Test + void testInvalidInitialCapacityThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new IndexedPriorityQueue(0, Comparator.naturalOrder())); + } + + @Test + void testChangeKeyOnMissingElementThrows() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 10); + + Assertions.assertThrows(IllegalArgumentException.class, () -> pq.changeKey(a, n -> n.prio = 5)); + } + + @Test + void testDecreaseKeyOnMissingElementThrows() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 10); + + Assertions.assertThrows(IllegalArgumentException.class, () -> pq.decreaseKey(a, n -> n.prio = 5)); + } + + @Test + void testIncreaseKeyOnMissingElementThrows() { + IndexedPriorityQueue pq = newNodePQ(); + Node a = new Node("A", 10); + + Assertions.assertThrows(IllegalArgumentException.class, () -> pq.increaseKey(a, n -> n.prio = 15)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java index 5483bbcd0394..4390c0f5f2eb 100644 --- a/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/MergeSortedArrayListTest.java @@ -2,98 +2,51 @@ 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.List; +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; class MergeSortedArrayListTest { - @Test - void testMergeTwoSortedLists() { - List listA = Arrays.asList(1, 3, 5, 7, 9); - List listB = Arrays.asList(2, 4, 6, 8, 10); + @ParameterizedTest(name = "{3}") + @MethodSource("provideMergeTestData") + void testMergeParameterizedScenarios(List listA, List listB, List expected, String scenarioName) { List result = new ArrayList<>(); - MergeSortedArrayList.merge(listA, listB, result); - - List expected = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - assertEquals(expected, result, "Merged list should be sorted and contain all elements from both input lists."); + assertEquals(expected, result, () -> "Failed scenario: " + scenarioName); } - @Test - void testMergeWithEmptyList() { - List listA = Arrays.asList(1, 2, 3); - List listB = new ArrayList<>(); // Empty list - List result = new ArrayList<>(); - - MergeSortedArrayList.merge(listA, listB, result); - - List expected = Arrays.asList(1, 2, 3); - assertEquals(expected, result, "Merged list should match listA when listB is empty."); - } - - @Test - void testMergeWithBothEmptyLists() { - List listA = new ArrayList<>(); // Empty list - List listB = new ArrayList<>(); // Empty list - List result = new ArrayList<>(); - - MergeSortedArrayList.merge(listA, listB, result); - - assertTrue(result.isEmpty(), "Merged list should be empty when both input lists are empty."); + private static Stream provideMergeTestData() { + return Stream.of(Arguments.of(Arrays.asList(1, 3, 5, 7, 9), Arrays.asList(2, 4, 6, 8, 10), Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), "Standard alternating sorted lists"), Arguments.of(Arrays.asList(1, 2, 3), new ArrayList<>(), Arrays.asList(1, 2, 3), "Merge with empty second list"), + Arguments.of(new ArrayList<>(), Arrays.asList(4, 5, 6), Arrays.asList(4, 5, 6), "Merge with empty first list"), Arguments.of(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), "Merge with both lists empty"), + Arguments.of(Arrays.asList(1, 2, 2, 3), Arrays.asList(2, 3, 4), Arrays.asList(1, 2, 2, 2, 3, 3, 4), "Handling duplicate elements gracefully"), + Arguments.of(Arrays.asList(-3, -1, 2), Arrays.asList(-2, 0, 3), Arrays.asList(-3, -2, -1, 0, 2, 3), "Handling negative numbers mixed with positive numbers")); } @Test - void testMergeWithDuplicateElements() { - List listA = Arrays.asList(1, 2, 2, 3); - List listB = Arrays.asList(2, 3, 4); + void testMergeThrowsExceptionWhenListAIsNull() { + List listB = Arrays.asList(1, 2, 3); List result = new ArrayList<>(); - - MergeSortedArrayList.merge(listA, listB, result); - - List expected = Arrays.asList(1, 2, 2, 2, 3, 3, 4); - assertEquals(expected, result, "Merged list should correctly handle and include duplicate elements."); + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(null, listB, result)); } @Test - void testMergeWithNegativeAndPositiveNumbers() { - List listA = Arrays.asList(-3, -1, 2); - List listB = Arrays.asList(-2, 0, 3); + void testMergeThrowsExceptionWhenListBIsNull() { + List listA = Arrays.asList(1, 2, 3); List result = new ArrayList<>(); - - MergeSortedArrayList.merge(listA, listB, result); - - List expected = Arrays.asList(-3, -2, -1, 0, 2, 3); - assertEquals(expected, result, "Merged list should correctly handle negative and positive numbers."); + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(listA, null, result)); } @Test - void testMergeThrowsExceptionOnNullInput() { - List listA = null; - List listB = Arrays.asList(1, 2, 3); - List result = new ArrayList<>(); - - List finalListB = listB; - List finalListA = listA; - List finalResult = result; - assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA, finalListB, finalResult), "Should throw NullPointerException if any input list is null."); - - listA = Arrays.asList(1, 2, 3); - listB = null; - List finalListA1 = listA; - List finalListB1 = listB; - List finalResult1 = result; - assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA1, finalListB1, finalResult1), "Should throw NullPointerException if any input list is null."); - - listA = Arrays.asList(1, 2, 3); - listB = Arrays.asList(4, 5, 6); - result = null; - List finalListA2 = listA; - List finalListB2 = listB; - List finalResult2 = result; - assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(finalListA2, finalListB2, finalResult2), "Should throw NullPointerException if the result collection is null."); + void testMergeThrowsExceptionWhenResultCollectionIsNull() { + List listA = Arrays.asList(1, 2, 3); + List listB = Arrays.asList(4, 5, 6); + assertThrows(NullPointerException.class, () -> MergeSortedArrayList.merge(listA, listB, null)); } } diff --git a/src/test/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedListTest.java new file mode 100644 index 000000000000..ba5614a07916 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/lists/MiddleOfLinkedListTest.java @@ -0,0 +1,74 @@ +package com.thealgorithms.datastructures.lists; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.Objects; +import org.junit.jupiter.api.Test; + +public class MiddleOfLinkedListTest { + + private static SinglyLinkedListNode listOf(int firstValue, int... remainingValues) { + SinglyLinkedListNode head = new SinglyLinkedListNode(firstValue); + SinglyLinkedListNode current = head; + + for (int i = 0; i < remainingValues.length; i++) { + current.next = new SinglyLinkedListNode(remainingValues[i]); + current = current.next; + } + return head; + } + + @Test + void middleNodeOddLength() { + SinglyLinkedListNode head = listOf(1, 2, 3, 4, 5); + SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head)); + assertEquals(3, middle.value); + } + + @Test + void middleNodeEvenLengthReturnsSecondMiddle() { + SinglyLinkedListNode head = listOf(1, 2, 3, 4, 5, 6); + SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head)); + assertEquals(4, middle.value); + } + + @Test + void middleNodeSingleElement() { + SinglyLinkedListNode head = listOf(42); + SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head)); + assertEquals(42, middle.value); + } + + @Test + void middleNodeTwoElementsReturnsSecond() { + SinglyLinkedListNode head = listOf(10, 20); + SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(head)); + assertEquals(20, middle.value); + } + + @Test + void middleNodeNullHead() { + assertNull(MiddleOfLinkedList.middleNode(null)); + } + + @Test + void middleNodeDoesNotModifyListStructure() { + SinglyLinkedListNode first = new SinglyLinkedListNode(1); + SinglyLinkedListNode second = new SinglyLinkedListNode(2); + SinglyLinkedListNode third = new SinglyLinkedListNode(3); + SinglyLinkedListNode fourth = new SinglyLinkedListNode(4); + + first.next = second; + second.next = third; + third.next = fourth; + + SinglyLinkedListNode middle = Objects.requireNonNull(MiddleOfLinkedList.middleNode(first)); + assertEquals(3, middle.value); + + assertEquals(second, first.next); + assertEquals(third, second.next); + assertEquals(fourth, third.next); + assertNull(fourth.next); + } +} diff --git a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java b/src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java similarity index 98% rename from src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java rename to src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java index 0f0577d39094..10f6b8536b19 100644 --- a/src/test/java/com/thealgorithms/misc/PalindromeSinglyLinkedListTest.java +++ b/src/test/java/com/thealgorithms/datastructures/lists/PalindromeSinglyLinkedListTest.java @@ -1,9 +1,8 @@ -package com.thealgorithms.misc; +package com.thealgorithms.datastructures.lists; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.thealgorithms.datastructures.lists.SinglyLinkedList; import org.junit.jupiter.api.Test; public class PalindromeSinglyLinkedListTest { diff --git a/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java b/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java index e97fe091c556..3bb8bbabb761 100644 --- a/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java +++ b/src/test/java/com/thealgorithms/datastructures/queues/PriorityQueuesTest.java @@ -9,14 +9,14 @@ class PriorityQueuesTest { void testPQInsertion() { PriorityQueue myQueue = new PriorityQueue(4); myQueue.insert(2); - Assertions.assertEquals(myQueue.peek(), 2); + Assertions.assertEquals(2, myQueue.peek()); myQueue.insert(5); myQueue.insert(3); - Assertions.assertEquals(myQueue.peek(), 5); + Assertions.assertEquals(5, myQueue.peek()); myQueue.insert(10); - Assertions.assertEquals(myQueue.peek(), 10); + Assertions.assertEquals(10, myQueue.peek()); } @Test @@ -28,32 +28,32 @@ void testPQDeletion() { myQueue.insert(10); myQueue.remove(); - Assertions.assertEquals(myQueue.peek(), 5); + Assertions.assertEquals(5, myQueue.peek()); myQueue.remove(); myQueue.remove(); - Assertions.assertEquals(myQueue.peek(), 2); + Assertions.assertEquals(2, myQueue.peek()); } @Test void testPQExtra() { PriorityQueue myQueue = new PriorityQueue(4); - Assertions.assertEquals(myQueue.isEmpty(), true); - Assertions.assertEquals(myQueue.isFull(), false); + Assertions.assertTrue(myQueue.isEmpty()); + Assertions.assertFalse(myQueue.isFull()); myQueue.insert(2); myQueue.insert(5); - Assertions.assertEquals(myQueue.isFull(), false); + Assertions.assertFalse(myQueue.isFull()); myQueue.insert(3); myQueue.insert(10); - Assertions.assertEquals(myQueue.isEmpty(), false); - Assertions.assertEquals(myQueue.isFull(), true); + Assertions.assertFalse(myQueue.isEmpty()); + Assertions.assertTrue(myQueue.isFull()); myQueue.remove(); - Assertions.assertEquals(myQueue.getSize(), 3); - Assertions.assertEquals(myQueue.peek(), 5); + Assertions.assertEquals(3, myQueue.getSize()); + Assertions.assertEquals(5, myQueue.peek()); myQueue.remove(); myQueue.remove(); - Assertions.assertEquals(myQueue.peek(), 2); - Assertions.assertEquals(myQueue.getSize(), 1); + Assertions.assertEquals(2, myQueue.peek()); + Assertions.assertEquals(1, myQueue.getSize()); } @Test diff --git a/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java b/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java new file mode 100644 index 000000000000..e3abe15b6a46 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/queues/ReverseQueueRecursionTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.datastructures.queues; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedList; +import java.util.Queue; +import org.junit.jupiter.api.Test; + +class ReverseQueueRecursionTest { + @Test + void testReverseMultipleElements() { + Queue queue = new LinkedList<>(); + queue.add(1); + queue.add(2); + queue.add(3); + queue.add(4); + ReverseQueueRecursion.reverseQueue(queue); + assertEquals(4, queue.poll()); + assertEquals(3, queue.poll()); + assertEquals(2, queue.poll()); + assertEquals(1, queue.poll()); + assertTrue(queue.isEmpty()); + } + + @Test + void testReverseSingleElement() { + Queue queue = new LinkedList<>(); + queue.add(42); + ReverseQueueRecursion.reverseQueue(queue); + assertEquals(42, queue.poll()); + assertTrue(queue.isEmpty()); + } + + @Test + void testReverseEmptyQueue() { + Queue queue = new LinkedList<>(); + ReverseQueueRecursion.reverseQueue(queue); + assertTrue(queue.isEmpty()); + } + + @Test + void testReverseStringQueue() { + Queue queue = new LinkedList<>(); + queue.add("A"); + queue.add("B"); + queue.add("C"); + ReverseQueueRecursion.reverseQueue(queue); + assertEquals("C", queue.poll()); + assertEquals("B", queue.poll()); + assertEquals("A", queue.poll()); + assertTrue(queue.isEmpty()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java b/src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java new file mode 100644 index 000000000000..4c038c05b167 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/queues/ThreadSafeQueueTest.java @@ -0,0 +1,295 @@ +package com.thealgorithms.datastructures.queues; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ThreadSafeQueueTest { + + @Test + public void testEnqueueDequeue() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + queue.enqueue(1); + queue.enqueue(2); + queue.enqueue(3); + + Assertions.assertEquals(3, queue.size()); + Assertions.assertEquals(1, queue.dequeue()); + Assertions.assertEquals(2, queue.dequeue()); + Assertions.assertEquals(3, queue.dequeue()); + Assertions.assertTrue(queue.isEmpty()); + } + + @Test + public void testOfferPoll() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(2); + Assertions.assertTrue(queue.offer("a")); + Assertions.assertTrue(queue.offer("b")); + Assertions.assertFalse(queue.offer("c")); + + Assertions.assertEquals("a", queue.poll()); + Assertions.assertEquals("b", queue.poll()); + Assertions.assertNull(queue.poll()); + } + + @Test + public void testOfferRejectsWhenFull() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(2); + Assertions.assertTrue(queue.offer(1)); + Assertions.assertTrue(queue.offer(2)); + Assertions.assertFalse(queue.offer(3)); + Assertions.assertEquals(2, queue.size()); + } + + @Test + public void testPollReturnsNullWhenEmpty() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + Assertions.assertNull(queue.poll()); + } + + @Test + public void testEnqueueNullThrows() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + Assertions.assertThrows(IllegalArgumentException.class, () -> queue.enqueue(null)); + } + + @Test + public void testOfferNullThrows() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + Assertions.assertThrows(IllegalArgumentException.class, () -> queue.offer(null)); + } + + @Test + public void testInvalidCapacityThrows() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new ThreadSafeQueue<>(0)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new ThreadSafeQueue<>(-1)); + } + + @Test + public void testIsEmptyAndIsFull() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(2); + Assertions.assertTrue(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + + queue.enqueue(1); + Assertions.assertFalse(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + + queue.enqueue(2); + Assertions.assertFalse(queue.isEmpty()); + Assertions.assertTrue(queue.isFull()); + + queue.dequeue(); + Assertions.assertFalse(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + + queue.dequeue(); + Assertions.assertTrue(queue.isEmpty()); + Assertions.assertFalse(queue.isFull()); + } + + @Test + public void testCapacity() { + ThreadSafeQueue queue = new ThreadSafeQueue<>(10); + Assertions.assertEquals(10, queue.capacity()); + } + + @Test + public void testCircularBufferWrapAround() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(3); + queue.enqueue(1); + queue.enqueue(2); + queue.enqueue(3); + + Assertions.assertEquals(1, queue.dequeue()); + Assertions.assertEquals(2, queue.dequeue()); + + queue.enqueue(4); + queue.enqueue(5); + + Assertions.assertEquals(3, queue.dequeue()); + Assertions.assertEquals(4, queue.dequeue()); + Assertions.assertEquals(5, queue.dequeue()); + } + + @Test + public void testMultipleProducersSingleConsumer() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(100); + int numProducers = 4; + int itemsPerProducer = 250; + int totalItems = numProducers * itemsPerProducer; + CountDownLatch doneLatch = new CountDownLatch(numProducers); + List results = new ArrayList<>(); + + ExecutorService executor = Executors.newFixedThreadPool(numProducers + 1); + + for (int p = 0; p < numProducers; p++) { + final int producerId = p; + executor.submit(() -> { + try { + for (int i = 0; i < itemsPerProducer; i++) { + queue.enqueue(producerId * itemsPerProducer + i); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + doneLatch.countDown(); + } + }); + } + + Thread consumerThread = new Thread(() -> { + try { + while (results.size() < totalItems) { + Integer item = queue.poll(); + if (item != null) { + synchronized (results) { + results.add(item); + } + } + } + } catch (Exception e) { + Thread.currentThread().interrupt(); + } + }); + consumerThread.start(); + + Assertions.assertTrue(doneLatch.await(10, TimeUnit.SECONDS)); + consumerThread.join(5000); + + Assertions.assertEquals(totalItems, results.size()); + executor.shutdown(); + Assertions.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)); + } + + @Test + public void testSingleProducerMultipleConsumers() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(50); + int numConsumers = 4; + int totalItems = 1000; + CountDownLatch doneLatch = new CountDownLatch(numConsumers); + AtomicInteger consumedCount = new AtomicInteger(0); + + ExecutorService executor = Executors.newFixedThreadPool(numConsumers + 1); + + executor.submit(() -> { + try { + for (int i = 0; i < totalItems; i++) { + queue.enqueue(i); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + + for (int c = 0; c < numConsumers; c++) { + executor.submit(() -> { + try { + while (consumedCount.get() < totalItems) { + Integer item = queue.poll(); + if (item != null) { + consumedCount.incrementAndGet(); + } + } + } finally { + doneLatch.countDown(); + } + }); + } + + Assertions.assertTrue(doneLatch.await(10, TimeUnit.SECONDS)); + Assertions.assertEquals(totalItems, consumedCount.get()); + executor.shutdown(); + Assertions.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)); + } + + @Test + public void testBlockingEnqueueWhenFull() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(1); + queue.enqueue(1); + + AtomicInteger blockedCount = new AtomicInteger(0); + Thread producer = new Thread(() -> { + try { + queue.enqueue(2); + blockedCount.incrementAndGet(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + producer.start(); + + Thread.sleep(100); + Assertions.assertEquals(1, queue.dequeue()); + + producer.join(2000); + Assertions.assertEquals(1, blockedCount.get()); + Assertions.assertEquals(2, queue.dequeue()); + } + + @Test + public void testBlockingDequeueWhenEmpty() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(5); + + AtomicInteger result = new AtomicInteger(-1); + Thread consumer = new Thread(() -> { + try { + result.set(queue.dequeue()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + consumer.start(); + + Thread.sleep(100); + queue.enqueue(42); + + consumer.join(2000); + Assertions.assertEquals(42, result.get()); + } + + @Test + public void testStressConcurrentAccess() throws InterruptedException { + ThreadSafeQueue queue = new ThreadSafeQueue<>(10); + int numThreads = 8; + int opsPerThread = 500; + CountDownLatch latch = new CountDownLatch(numThreads); + AtomicInteger enqueueCount = new AtomicInteger(0); + AtomicInteger dequeueCount = new AtomicInteger(0); + + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + + for (int t = 0; t < numThreads; t++) { + final boolean isProducer = t % 2 == 0; + executor.submit(() -> { + try { + for (int i = 0; i < opsPerThread; i++) { + if (isProducer) { + if (queue.offer(i)) { + enqueueCount.incrementAndGet(); + } + } else { + if (queue.poll() != null) { + dequeueCount.incrementAndGet(); + } + } + } + } finally { + latch.countDown(); + } + }); + } + + Assertions.assertTrue(latch.await(10, TimeUnit.SECONDS)); + Assertions.assertTrue(enqueueCount.get() >= dequeueCount.get()); + Assertions.assertEquals(enqueueCount.get() - dequeueCount.get(), queue.size()); + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java new file mode 100644 index 000000000000..2461fd74143d --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/BinaryTreeToStringTest.java @@ -0,0 +1,57 @@ +package com.thealgorithms.datastructures.trees; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests for the BinaryTreeToString class. + */ +public class BinaryTreeToStringTest { + + @Test + public void testTreeToStringBasic() { + BinaryTree tree = new BinaryTree(); + tree.put(1); + tree.put(2); + tree.put(3); + tree.put(4); + + BinaryTreeToString converter = new BinaryTreeToString(); + String result = converter.tree2str(tree.getRoot()); + + // Output will depend on insertion logic of BinaryTree.put() + // which is BST-style, so result = "1()(2()(3()(4)))" + Assertions.assertEquals("1()(2()(3()(4)))", result); + } + + @Test + public void testSingleNodeTree() { + BinaryTree tree = new BinaryTree(); + tree.put(10); + + BinaryTreeToString converter = new BinaryTreeToString(); + String result = converter.tree2str(tree.getRoot()); + + Assertions.assertEquals("10", result); + } + + @Test + public void testComplexTreeStructure() { + BinaryTree.Node root = new BinaryTree.Node(10); + root.left = new BinaryTree.Node(5); + root.right = new BinaryTree.Node(20); + root.right.left = new BinaryTree.Node(15); + root.right.right = new BinaryTree.Node(25); + + BinaryTreeToString converter = new BinaryTreeToString(); + String result = converter.tree2str(root); + + Assertions.assertEquals("10(5)(20(15)(25))", result); + } + + @Test + public void testNullTree() { + BinaryTreeToString converter = new BinaryTreeToString(); + Assertions.assertEquals("", converter.tree2str(null)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java b/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java new file mode 100644 index 000000000000..43d732e54f34 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/CentroidDecompositionTest.java @@ -0,0 +1,236 @@ +package com.thealgorithms.datastructures.trees; + +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.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Test cases for CentroidDecomposition + * + * @author lens161 + */ +class CentroidDecompositionTest { + + @Test + void testSingleNode() { + // Tree with just one node + int[][] edges = {}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(1, edges); + + assertEquals(1, tree.size()); + assertEquals(0, tree.getRoot()); + assertEquals(-1, tree.getParent(0)); + } + + @Test + void testTwoNodes() { + // Simple tree: 0 - 1 + int[][] edges = {{0, 1}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(2, edges); + + assertEquals(2, tree.size()); + int root = tree.getRoot(); + assertTrue(root == 0 || root == 1, "Root should be either node 0 or 1"); + + // One node should be root, other should have the root as parent + int nonRoot = (root == 0) ? 1 : 0; + assertEquals(-1, tree.getParent(root)); + assertEquals(root, tree.getParent(nonRoot)); + } + + @Test + void testLinearTree() { + // Linear tree: 0 - 1 - 2 - 3 - 4 + int[][] edges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges); + + assertEquals(5, tree.size()); + // For a linear tree of 5 nodes, the centroid should be the middle node (node 2) + assertEquals(2, tree.getRoot()); + assertEquals(-1, tree.getParent(2)); + } + + @Test + void testBalancedBinaryTree() { + // Balanced binary tree: + // 0 + // / \ + // 1 2 + // / \ + // 3 4 + int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges); + + assertEquals(5, tree.size()); + // Root should be 0 or 1 (both are valid centroids) + int root = tree.getRoot(); + assertTrue(root == 0 || root == 1); + assertEquals(-1, tree.getParent(root)); + + // All nodes should have a parent in centroid tree except root + for (int i = 0; i < 5; i++) { + if (i != root) { + assertTrue(tree.getParent(i) >= 0 && tree.getParent(i) < 5); + } + } + } + + @Test + void testStarTree() { + // Star tree: center node 0 connected to 1, 2, 3, 4 + int[][] edges = {{0, 1}, {0, 2}, {0, 3}, {0, 4}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(5, edges); + + assertEquals(5, tree.size()); + // Center node (0) should be the root + assertEquals(0, tree.getRoot()); + + // All other nodes should have 0 as parent + for (int i = 1; i < 5; i++) { + assertEquals(0, tree.getParent(i)); + } + } + + @Test + void testCompleteTree() { + // Complete binary tree of 7 nodes: + // 0 + // / \ + // 1 2 + // / \ / \ + // 3 4 5 6 + int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(7, edges); + + assertEquals(7, tree.size()); + assertEquals(0, tree.getRoot()); // Root should be the center + + // Verify all nodes are reachable in centroid tree + boolean[] visited = new boolean[7]; + visited[0] = true; + for (int i = 1; i < 7; i++) { + int parent = tree.getParent(i); + assertTrue(parent >= 0 && parent < 7); + assertTrue(visited[parent], "Parent should be processed before child"); + visited[i] = true; + } + } + + @Test + void testLargerTree() { + // Tree with 10 nodes + int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 5}, {2, 6}, {3, 7}, {4, 8}, {5, 9}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(10, edges); + + assertEquals(10, tree.size()); + int root = tree.getRoot(); + assertTrue(root >= 0 && root < 10); + assertEquals(-1, tree.getParent(root)); + + // Verify centroid tree structure is valid + for (int i = 0; i < 10; i++) { + if (i != root) { + assertTrue(tree.getParent(i) >= -1 && tree.getParent(i) < 10); + } + } + } + + @Test + void testPathGraph() { + // Path graph with 8 nodes: 0-1-2-3-4-5-6-7 + int[][] edges = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}, {6, 7}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(8, edges); + + assertEquals(8, tree.size()); + // For path of 8 nodes, centroid should be around middle + int root = tree.getRoot(); + assertTrue(root >= 2 && root <= 5, "Root should be near the middle of path"); + } + + @Test + void testInvalidEmptyTree() { + assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(0, new int[][] {}); }); + } + + @Test + void testInvalidNegativeNodes() { + assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(-1, new int[][] {}); }); + } + + @Test + void testInvalidNullEdges() { + assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(5, null); }); + } + + @Test + void testInvalidEdgeCount() { + // Tree with n nodes must have n-1 edges + int[][] edges = {{0, 1}, {1, 2}}; // 2 edges for 5 nodes (should be 4) + assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(5, edges); }); + } + + @Test + void testInvalidEdgeFormat() { + int[][] edges = {{0, 1, 2}}; // Edge with 3 elements instead of 2 + assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(3, edges); }); + } + + @Test + void testInvalidNodeInEdge() { + int[][] edges = {{0, 5}}; // Node 5 doesn't exist in tree of size 3 + assertThrows(IllegalArgumentException.class, () -> { CentroidDecomposition.buildFromEdges(3, edges); }); + } + + @Test + void testInvalidNodeQuery() { + int[][] edges = {{0, 1}, {1, 2}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(3, edges); + + assertThrows(IllegalArgumentException.class, () -> { tree.getParent(-1); }); + + assertThrows(IllegalArgumentException.class, () -> { tree.getParent(5); }); + } + + @Test + void testToString() { + int[][] edges = {{0, 1}, {1, 2}}; + CentroidDecomposition.CentroidTree tree = CentroidDecomposition.buildFromEdges(3, edges); + + String result = tree.toString(); + assertNotNull(result); + assertTrue(result.contains("Centroid Tree")); + assertTrue(result.contains("Node")); + assertTrue(result.contains("ROOT")); + } + + @Test + void testAdjacencyListConstructor() { + List> adj = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + adj.add(new ArrayList<>()); + } + adj.get(0).add(1); + adj.get(1).add(0); + adj.get(1).add(2); + adj.get(2).add(1); + + CentroidDecomposition.CentroidTree tree = new CentroidDecomposition.CentroidTree(adj); + assertEquals(3, tree.size()); + assertEquals(1, tree.getRoot()); + } + + @Test + void testNullAdjacencyList() { + assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition.CentroidTree(null); }); + } + + @Test + void testEmptyAdjacencyList() { + assertThrows(IllegalArgumentException.class, () -> { new CentroidDecomposition.CentroidTree(new ArrayList<>()); }); + } +} diff --git a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java b/src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java similarity index 97% rename from src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java rename to src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java index 29189290e1d4..f0cb1724f67c 100644 --- a/src/test/java/com/thealgorithms/tree/HeavyLightDecompositionTest.java +++ b/src/test/java/com/thealgorithms/datastructures/trees/HeavyLightDecompositionTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.tree; +package com.thealgorithms.datastructures.trees; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java new file mode 100644 index 000000000000..db081da2550a --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/SegmentTree2DTest.java @@ -0,0 +1,71 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class SegmentTree2DTest { + + @Test + void testInitialEmptyQueries() { + SegmentTree2D segmentTree = new SegmentTree2D(4, 4); + + // Initial tree should return 0 for any query + assertEquals(0, segmentTree.query(0, 4, 0, 4)); + assertEquals(0, segmentTree.query(1, 3, 1, 3)); + } + + @Test + void testUpdateAndPointQuery() { + SegmentTree2D segmentTree = new SegmentTree2D(5, 5); + + segmentTree.update(2, 3, 10); + segmentTree.update(0, 0, 5); + + // Querying single points [row, row+1) x [col, col+1) + assertEquals(10, segmentTree.query(2, 3, 3, 4)); + assertEquals(5, segmentTree.query(0, 1, 0, 1)); + + // Empty point should be 0 + assertEquals(0, segmentTree.query(1, 2, 1, 2)); + } + + @Test + void testSubmatrixQuery() { + SegmentTree2D segmentTree = new SegmentTree2D(4, 4); + + // Matrix simulation: + // [1, 2, 0, 0] + // [3, 4, 0, 0] + // [0, 0, 0, 0] + // [0, 0, 0, 0] + segmentTree.update(0, 0, 1); + segmentTree.update(0, 1, 2); + segmentTree.update(1, 0, 3); + segmentTree.update(1, 1, 4); + + // Top-left 2x2 sum: 1+2+3+4 = 10 + assertEquals(10, segmentTree.query(0, 2, 0, 2)); + + // First row sum: 1+2 = 3 + assertEquals(3, segmentTree.query(0, 1, 0, 4)); + + // Second column sum: 2+4 = 6 + assertEquals(6, segmentTree.query(0, 4, 1, 2)); + } + + @Test + void testUpdateOverwriting() { + SegmentTree2D segmentTree = new SegmentTree2D(3, 3); + + segmentTree.update(1, 1, 5); + assertEquals(5, segmentTree.query(1, 2, 1, 2)); + + // Overwrite the same point + segmentTree.update(1, 1, 20); + assertEquals(20, segmentTree.query(1, 2, 1, 2)); + + // Full matrix sum should just be this point + assertEquals(20, segmentTree.query(0, 3, 0, 3)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java new file mode 100644 index 000000000000..c5973168438e --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/ThreadedBinaryTreeTest.java @@ -0,0 +1,50 @@ +/* + * TheAlgorithms (https://github.com/TheAlgorithms/Java) + * Author: Shewale41 + * This file is licensed under the MIT License. + */ + +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Basic tests for ThreadedBinaryTree inorder traversal. + */ +public class ThreadedBinaryTreeTest { + + @Test + public void testInorderTraversalSimple() { + ThreadedBinaryTree tree = new ThreadedBinaryTree(); + tree.insert(50); + tree.insert(30); + tree.insert(70); + tree.insert(20); + tree.insert(40); + tree.insert(60); + tree.insert(80); + + List expected = List.of(20, 30, 40, 50, 60, 70, 80); + List actual = tree.inorderTraversal(); + + assertEquals(expected, actual); + } + + @Test + public void testInorderWithDuplicates() { + ThreadedBinaryTree tree = new ThreadedBinaryTree(); + tree.insert(5); + tree.insert(3); + tree.insert(7); + tree.insert(7); // duplicate + tree.insert(2); + + List expected = List.of(2, 3, 5, 7, 7); + List actual = tree.inorderTraversal(); + + assertEquals(expected, actual); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java b/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java index 09ada594faca..52b74a7a1faf 100644 --- a/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java +++ b/src/test/java/com/thealgorithms/datastructures/trees/TreapTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; @@ -30,7 +31,7 @@ public void searchAndNotFound() { treap.insert(3); treap.insert(8); treap.insert(1); - assertEquals(null, treap.search(4)); + assertNull(treap.search(4)); } @Test diff --git a/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java new file mode 100644 index 000000000000..592170673a3a --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/WaveletTreeTest.java @@ -0,0 +1,117 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class WaveletTreeTest { + + @Test + public void testRank() { + int[] arr = {5, 1, 2, 5, 1}; + WaveletTree wt = new WaveletTree(arr); + + // x = 1 + assertEquals(1, wt.rank(1, 1)); // In [5, 1], '1' appears 1 time + assertEquals(2, wt.rank(1, 4)); // In [5, 1, 2, 5, 1], '1' appears 2 times + assertEquals(0, wt.rank(1, 0)); // In [5], '1' appears 0 times + + // x = 5 + assertEquals(1, wt.rank(5, 0)); // In [5], '5' appears 1 time + assertEquals(1, wt.rank(5, 2)); // In [5, 1, 2], '5' appears 1 time + assertEquals(2, wt.rank(5, 4)); // In [5, 1, 2, 5, 1], '5' appears 2 times + + // Out of bounds / invalid value + assertEquals(0, wt.rank(10, 4)); // '10' is not in the array + assertEquals(0, wt.rank(5, -1)); // Invalid end index + } + + @Test + public void testSelect() { + int[] arr = {5, 1, 2, 5, 1}; + WaveletTree wt = new WaveletTree(arr); + + assertEquals(1, wt.select(1, 1)); // 1st '1' is at index 1 + assertEquals(4, wt.select(1, 2)); // 2nd '1' is at index 4 + + assertEquals(0, wt.select(5, 1)); // 1st '5' is at index 0 + assertEquals(3, wt.select(5, 2)); // 2nd '5' is at index 3 + + assertEquals(2, wt.select(2, 1)); // 1st '2' is at index 2 + + assertEquals(-1, wt.select(5, 3)); // 3rd '5' doesn't exist + assertEquals(-1, wt.select(10, 1)); // '10' doesn't exist + assertEquals(-1, wt.select(5, 0)); // invalid k + } + + @Test + public void testKthSmallest() { + int[] arr = {5, 1, 2, 5, 1}; + WaveletTree wt = new WaveletTree(arr); + + // Array: [5, 1, 2, 5, 1] -> Sorted: [1, 1, 2, 5, 5] + assertEquals(1, wt.kthSmallest(0, 4, 1)); // 1st smallest in [5, 1, 2, 5, 1] is 1 + assertEquals(1, wt.kthSmallest(0, 4, 2)); // 2nd smallest in [5, 1, 2, 5, 1] is 1 + assertEquals(2, wt.kthSmallest(0, 4, 3)); // 3rd smallest in [5, 1, 2, 5, 1] is 2 + assertEquals(5, wt.kthSmallest(0, 4, 4)); // 4th smallest in [5, 1, 2, 5, 1] is 5 + assertEquals(5, wt.kthSmallest(0, 4, 5)); // 5th smallest in [5, 1, 2, 5, 1] is 5 + + // Subarray: arr[1..3] = [1, 2, 5] -> Sorted: [1, 2, 5] + assertEquals(1, wt.kthSmallest(1, 3, 1)); // 1st smallest in [1, 2, 5] is 1 + assertEquals(2, wt.kthSmallest(1, 3, 2)); // 2nd smallest in [1, 2, 5] is 2 + assertEquals(5, wt.kthSmallest(1, 3, 3)); // 3rd smallest in [1, 2, 5] is 5 + + // Invalid ranges / arguments + assertEquals(-1, wt.kthSmallest(4, 2, 1)); // Invalid range (left > right) + assertEquals(-1, wt.kthSmallest(0, 4, 10)); // k > range length + assertEquals(-1, wt.kthSmallest(0, 4, 0)); // k < 1 + } + + @Test + public void testEmptyAndSingleElementArray() { + WaveletTree wtEmpty = new WaveletTree(new int[] {}); + assertEquals(0, wtEmpty.rank(1, 0)); + assertEquals(-1, wtEmpty.select(1, 1)); + assertEquals(-1, wtEmpty.kthSmallest(0, 0, 1)); + + WaveletTree wtSingle = new WaveletTree(new int[] {42}); + assertEquals(1, wtSingle.rank(42, 0)); + assertEquals(0, wtSingle.rank(42, -1)); + assertEquals(0, wtSingle.select(42, 1)); + assertEquals(-1, wtSingle.select(42, 2)); + assertEquals(42, wtSingle.kthSmallest(0, 0, 1)); + } + + @Test + public void testNullArrayAndCustomBounds() { + WaveletTree wtNull = new WaveletTree(null); + assertEquals(0, wtNull.rank(1, 0)); + + WaveletTree wtNullCustom = new WaveletTree(null, 1, 5); + assertEquals(-1, wtNullCustom.select(1, 1)); + + int[] arr = {5, 1, 2, 5, 1}; + WaveletTree wtCustom = new WaveletTree(arr, 1, 10); + assertEquals(2, wtCustom.rank(5, 4)); + assertEquals(0, wtCustom.rank(4, 4)); // Query an element inside bounds but not in array + assertEquals(0, wtCustom.rank(10, 4)); // Query upper bound + } + + @Test + public void testNegativeValues() { + int[] arr = {-5, 10, -2, 0, -5}; + WaveletTree wt = new WaveletTree(arr); + + assertEquals(2, wt.rank(-5, 4)); + assertEquals(1, wt.rank(0, 3)); + + assertEquals(0, wt.select(-5, 1)); + assertEquals(4, wt.select(-5, 2)); + assertEquals(3, wt.select(0, 1)); + + // Sorted: [-5, -5, -2, 0, 10] + assertEquals(-5, wt.kthSmallest(0, 4, 1)); + assertEquals(-2, wt.kthSmallest(0, 4, 3)); + assertEquals(10, wt.kthSmallest(0, 4, 5)); + } +} diff --git a/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java b/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java index 38784228d68e..b25fd796b112 100644 --- a/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java +++ b/src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java @@ -16,14 +16,6 @@ public void testBuildLocation() { assertEquals(4.0, point.y); } - @Test - public void testCreateLocation() { - ClosestPair cp = new ClosestPair(5); - ClosestPair.Location[] locations = cp.createLocation(5); - assertNotNull(locations); - assertEquals(5, locations.length); - } - @Test public void testXPartition() { ClosestPair cp = new ClosestPair(5); diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java new file mode 100644 index 000000000000..762fe86d4d65 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/DigitDPTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.dynamicprogramming; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the generalized DigitDP implementation. + */ +public class DigitDPTest { + + @Test + public void testDigitDPBasicRange() { + // Numbers between 1 and 20 with a digit sum of 5: 5, 14 + long result = DigitDP.countRangeWithDigitSum(1, 20, 5); + assertEquals(2, result); + } + + @Test + public void testDigitDPZeroBound() { + // Number 0 has a digit sum of 0 + long result = DigitDP.countRangeWithDigitSum(0, 0, 0); + assertEquals(1, result); + } + + @Test + public void testDigitDPLargeRange() { + // Count numbers between 1 and 100 with a digit sum of 9 + // 9, 18, 27, 36, 45, 54, 63, 72, 81, 90 (10 numbers) + long result = DigitDP.countRangeWithDigitSum(1, 100, 9); + assertEquals(10, result); + } + + @Test + public void testDigitDPNoMatches() { + // No numbers between 10 and 15 can have a digit sum of 20 + long result = DigitDP.countRangeWithDigitSum(10, 15, 20); + assertEquals(0, result); + } + + @Test + public void testDigitDPExceedsMaxSum() { + // Sum condition that exceeds max possible physical sum array constraints + // gracefully returns 0 + long result = DigitDP.countRangeWithDigitSum(1, 100, 200); + assertEquals(0, result); + } + + @Test + public void testDigitDPInvalidRange() { + // Lower bound greater than upper bound should evaluate gracefully to 0 + long result = DigitDP.countRangeWithDigitSum(50, 20, 5); + assertEquals(0, result); + } + + @Test + public void testDigitDPExceedsMaxSumEdgeCase() { + // Yeh test case target > MAX_DIGIT_SUM wali condition ko hit karega + long result = DigitDP.countRangeWithDigitSum(1, 100, 180); + assertEquals(0, result); + } + + @Test + public void testDigitDPMemoizationHit() { + // Badi range dene se overlapping subproblems bante hain, + // jisse memoization hit hogi aur coverage 100% ho jayegi. + long result1 = DigitDP.countRangeWithDigitSum(1, 100000, 15); + long result2 = DigitDP.countRangeWithDigitSum(1, 100000, 15); + assertEquals(result1, result2); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java index 40bbdff15ca6..91169c4cc9d8 100644 --- a/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestCommonSubsequenceTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.dynamicprogramming; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; @@ -55,27 +56,24 @@ public void testLCSWithBothEmptyStrings() { public void testLCSWithNullFirstString() { String str1 = null; String str2 = "XYZ"; - String expected = null; // Should return null if first string is null String result = LongestCommonSubsequence.getLCS(str1, str2); - assertEquals(expected, result); + assertNull(result); } @Test public void testLCSWithNullSecondString() { String str1 = "ABC"; String str2 = null; - String expected = null; // Should return null if second string is null String result = LongestCommonSubsequence.getLCS(str1, str2); - assertEquals(expected, result); + assertNull(result); } @Test public void testLCSWithNullBothStrings() { String str1 = null; String str2 = null; - String expected = null; // Should return null if both strings are null String result = LongestCommonSubsequence.getLCS(str1, str2); - assertEquals(expected, result); + assertNull(result); } @Test diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java new file mode 100644 index 000000000000..a1ee624e94d2 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/LongestPalindromicSubsequenceTest.java @@ -0,0 +1,54 @@ +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; + +public class LongestPalindromicSubsequenceTest { + + @ParameterizedTest + @CsvSource({"BBABCBCAB, BACBCAB", "BABCBAB, BABCBAB", "A, A", "AA, AA", "AB, B"}) + void testLpsKnownCases(String input, String expectedLps) { + assertEquals(expectedLps, LongestPalindromicSubsequence.lps(input)); + } + + @Test + void testLpsEmptyString() { + assertEquals("", LongestPalindromicSubsequence.lps("")); + } + + @Test + void testLpsSingleCharacter() { + assertEquals("Z", LongestPalindromicSubsequence.lps("Z")); + } + + @Test + void testLpsAllSameCharacters() { + assertEquals("AAAA", LongestPalindromicSubsequence.lps("AAAA")); + } + + @Test + void testLpsAlreadyPalindrome() { + assertEquals("RACECAR", LongestPalindromicSubsequence.lps("RACECAR")); + } + + @Test + void testLpsNoRepeatingCharacters() { + assertEquals(1, LongestPalindromicSubsequence.lps("ABCDE").length()); + } + + @Test + void testLpsNullThrowsException() { + assertThrows(IllegalArgumentException.class, () -> { LongestPalindromicSubsequence.lps(null); }); + } + + @Test + void testLpsResultIsActuallyPalindrome() { + String result = LongestPalindromicSubsequence.lps("BBABCBCAB"); + String reversed = new StringBuilder(result).reverse().toString(); + assertEquals(result, reversed); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java new file mode 100644 index 000000000000..17ff3ec728dc --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/OptimalBinarySearchTreeTest.java @@ -0,0 +1,73 @@ +package com.thealgorithms.dynamicprogramming; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Arrays; +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 OptimalBinarySearchTreeTest { + + @ParameterizedTest + @MethodSource("validTestCases") + void testFindOptimalCost(int[] keys, int[] frequencies, long expectedCost) { + assertEquals(expectedCost, OptimalBinarySearchTree.findOptimalCost(keys, frequencies)); + } + + private static Stream validTestCases() { + return Stream.of(Arguments.of(new int[] {}, new int[] {}, 0L), Arguments.of(new int[] {15}, new int[] {9}, 9L), Arguments.of(new int[] {10, 12}, new int[] {34, 50}, 118L), Arguments.of(new int[] {20, 10, 30}, new int[] {50, 34, 8}, 134L), + Arguments.of(new int[] {12, 10, 20, 42, 25, 37}, new int[] {8, 34, 50, 3, 40, 30}, 324L), Arguments.of(new int[] {1, 2, 3}, new int[] {0, 0, 0}, 0L)); + } + + @ParameterizedTest + @MethodSource("crossCheckTestCases") + void testFindOptimalCostAgainstBruteForce(int[] keys, int[] frequencies) { + assertEquals(bruteForceOptimalCost(keys, frequencies), OptimalBinarySearchTree.findOptimalCost(keys, frequencies)); + } + + private static Stream crossCheckTestCases() { + return Stream.of(Arguments.of(new int[] {3, 1, 2}, new int[] {4, 2, 6}), Arguments.of(new int[] {5, 2, 8, 6}, new int[] {3, 7, 1, 4}), Arguments.of(new int[] {9, 4, 11, 2}, new int[] {1, 8, 2, 5})); + } + + @ParameterizedTest + @MethodSource("invalidTestCases") + void testFindOptimalCostInvalidInput(int[] keys, int[] frequencies) { + assertThrows(IllegalArgumentException.class, () -> OptimalBinarySearchTree.findOptimalCost(keys, frequencies)); + } + + private static Stream invalidTestCases() { + return Stream.of(Arguments.of(null, new int[] {}), Arguments.of(new int[] {}, null), Arguments.of(new int[] {1, 2}, new int[] {3}), Arguments.of(new int[] {1, 1}, new int[] {2, 3}), Arguments.of(new int[] {1, 2}, new int[] {3, -1})); + } + + private static long bruteForceOptimalCost(int[] keys, int[] frequencies) { + int[][] sortedNodes = new int[keys.length][2]; + for (int index = 0; index < keys.length; index++) { + sortedNodes[index][0] = keys[index]; + sortedNodes[index][1] = frequencies[index]; + } + Arrays.sort(sortedNodes, java.util.Comparator.comparingInt(node -> node[0])); + + int[] sortedFrequencies = new int[sortedNodes.length]; + for (int index = 0; index < sortedNodes.length; index++) { + sortedFrequencies[index] = sortedNodes[index][1]; + } + + return bruteForceOptimalCost(sortedFrequencies, 0, sortedFrequencies.length - 1, 1); + } + + private static long bruteForceOptimalCost(int[] frequencies, int start, int end, int depth) { + if (start > end) { + return 0L; + } + + long minimumCost = Long.MAX_VALUE; + for (int root = start; root <= end; root++) { + long currentCost = (long) depth * frequencies[root] + bruteForceOptimalCost(frequencies, start, root - 1, depth + 1) + bruteForceOptimalCost(frequencies, root + 1, end, depth + 1); + minimumCost = Math.min(minimumCost, currentCost); + } + return minimumCost; + } +} diff --git a/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java b/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java new file mode 100644 index 000000000000..9f60df51b65f --- /dev/null +++ b/src/test/java/com/thealgorithms/geometry/LineIntersectionTest.java @@ -0,0 +1,101 @@ +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.awt.geom.Point2D; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class LineIntersectionTest { + + @Test + void testCrossingSegments() { + Point p1 = new Point(0, 0); + Point p2 = new Point(4, 4); + Point q1 = new Point(0, 4); + Point q2 = new Point(4, 0); + + assertTrue(LineIntersection.intersects(p1, p2, q1, q2)); + Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2); + assertTrue(intersection.isPresent()); + assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9); + assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9); + } + + @Test + void testParallelSegments() { + Point p1 = new Point(0, 0); + Point p2 = new Point(3, 3); + Point q1 = new Point(0, 1); + Point q2 = new Point(3, 4); + + assertFalse(LineIntersection.intersects(p1, p2, q1, q2)); + assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty()); + } + + @Test + void testTouchingAtEndpoint() { + Point p1 = new Point(0, 0); + Point p2 = new Point(2, 2); + Point q1 = new Point(2, 2); + Point q2 = new Point(4, 0); + + assertTrue(LineIntersection.intersects(p1, p2, q1, q2)); + Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2); + assertTrue(intersection.isPresent()); + assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9); + assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9); + } + + @Test + void testCollinearOverlapHasNoUniquePoint() { + Point p1 = new Point(0, 0); + Point p2 = new Point(4, 4); + Point q1 = new Point(2, 2); + Point q2 = new Point(6, 6); + + assertTrue(LineIntersection.intersects(p1, p2, q1, q2)); + assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty()); + } + + @Test + void testCollinearDisjointSegments() { + Point p1 = new Point(0, 0); + Point p2 = new Point(2, 2); + Point q1 = new Point(3, 3); + Point q2 = new Point(5, 5); + + assertFalse(LineIntersection.intersects(p1, p2, q1, q2)); + assertTrue(LineIntersection.intersectionPoint(p1, p2, q1, q2).isEmpty()); + } + + @Test + void testCollinearSegmentsTouchingAtEndpointHaveUniquePoint() { + Point p1 = new Point(0, 0); + Point p2 = new Point(2, 2); + Point q1 = new Point(2, 2); + Point q2 = new Point(4, 4); + + assertTrue(LineIntersection.intersects(p1, p2, q1, q2)); + Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2); + assertTrue(intersection.isPresent()); + assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9); + assertEquals(2.0, intersection.orElseThrow().getY(), 1e-9); + } + + @Test + void testVerticalAndHorizontalCrossingSegments() { + Point p1 = new Point(2, 0); + Point p2 = new Point(2, 5); + Point q1 = new Point(0, 3); + Point q2 = new Point(4, 3); + + assertTrue(LineIntersection.intersects(p1, p2, q1, q2)); + Optional intersection = LineIntersection.intersectionPoint(p1, p2, q1, q2); + assertTrue(intersection.isPresent()); + assertEquals(2.0, intersection.orElseThrow().getX(), 1e-9); + assertEquals(3.0, intersection.orElseThrow().getY(), 1e-9); + } +} diff --git a/src/test/java/com/thealgorithms/graph/AccountMergeTest.java b/src/test/java/com/thealgorithms/graph/AccountMergeTest.java new file mode 100644 index 000000000000..291be677d894 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/AccountMergeTest.java @@ -0,0 +1,61 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class AccountMergeTest { + + @Test + void testMergeAccountsWithSharedEmails() { + List> accounts = List.of(List.of("abc", "abc@mail.com", "abx@mail.com"), List.of("abc", "abc@mail.com", "aby@mail.com"), List.of("Mary", "mary@mail.com"), List.of("John", "johnnybravo@mail.com")); + + List> merged = AccountMerge.mergeAccounts(accounts); + + List> expected = List.of(List.of("John", "johnnybravo@mail.com"), List.of("Mary", "mary@mail.com"), List.of("abc", "abc@mail.com", "abx@mail.com", "aby@mail.com")); + + assertEquals(expected, merged); + } + + @Test + void testAccountsWithSameNameButNoSharedEmailStaySeparate() { + List> accounts = List.of(List.of("Alex", "alex1@mail.com"), List.of("Alex", "alex2@mail.com")); + + List> merged = AccountMerge.mergeAccounts(accounts); + List> expected = List.of(List.of("Alex", "alex1@mail.com"), List.of("Alex", "alex2@mail.com")); + + assertEquals(expected, merged); + } + + @Test + void testEmptyInput() { + assertEquals(List.of(), AccountMerge.mergeAccounts(List.of())); + } + + @Test + void testNullInput() { + assertEquals(List.of(), AccountMerge.mergeAccounts(null)); + } + + @Test + void testTransitiveMergeAndDuplicateEmails() { + List> accounts = List.of(List.of("A", "a1@mail.com", "a2@mail.com"), List.of("A", "a2@mail.com", "a3@mail.com"), List.of("A", "a3@mail.com", "a4@mail.com", "a4@mail.com")); + + List> merged = AccountMerge.mergeAccounts(accounts); + + List> expected = List.of(List.of("A", "a1@mail.com", "a2@mail.com", "a3@mail.com", "a4@mail.com")); + + assertEquals(expected, merged); + } + + @Test + void testAccountsWithNoEmailsArePreserved() { + List> accounts = List.of(List.of("Alex"), List.of("Alex", "alex1@mail.com"), List.of("Bob")); + + List> merged = AccountMerge.mergeAccounts(accounts); + List> expected = List.of(List.of("Alex"), List.of("Alex", "alex1@mail.com"), List.of("Bob")); + + assertEquals(expected, merged); + } +} diff --git a/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java b/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java new file mode 100644 index 000000000000..241f23c0fa1d --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java @@ -0,0 +1,132 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Queue; +import java.util.Random; +import java.util.random.RandomGenerator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GomoryHuTreeTest { + + @Test + @DisplayName("Single node graph") + void singleNode() { + int[][] cap = {{0}}; + int[][] res = GomoryHuTree.buildTree(cap); + int[] parent = res[0]; + int[] weight = res[1]; + assertEquals(-1, parent[0]); + assertEquals(0, weight[0]); + } + + @Test + @DisplayName("Triangle undirected graph with known min-cuts") + void triangleGraph() { + // 0-1:3, 1-2:2, 0-2:4 + int[][] cap = new int[3][3]; + cap[0][1] = 3; + cap[1][0] = 3; + cap[1][2] = 2; + cap[2][1] = 2; + cap[0][2] = 4; + cap[2][0] = 4; + + int[][] tree = GomoryHuTree.buildTree(cap); + // validate all pairs via path-min-edge equals maxflow + validateAllPairs(cap, tree); + } + + @Test + @DisplayName("Random small undirected graphs compare to EdmondsKarp") + void randomSmallGraphs() { + Random rng = new Random(42); + for (int n = 2; n <= 6; n++) { + for (int iter = 0; iter < 10; iter++) { + int[][] cap = randSymmetricMatrix(n, 0, 5, rng); + int[][] tree = GomoryHuTree.buildTree(cap); + validateAllPairs(cap, tree); + } + } + } + + private static int[][] randSymmetricMatrix(int n, int lo, int hi, RandomGenerator rng) { + int[][] a = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + int w = rng.nextInt(hi - lo + 1) + lo; + a[i][j] = w; + a[j][i] = w; + } + } + // zero diagonal + for (int i = 0; i < n; i++) { + a[i][i] = 0; + } + return a; + } + + private static void validateAllPairs(int[][] cap, int[][] tree) { + int n = cap.length; + int[] parent = tree[0]; + int[] weight = tree[1]; + + // build adjacency list of tree without generic array creation + List> g = new ArrayList<>(); + for (int i = 0; i < n; i++) { + g.add(new ArrayList<>()); + } + for (int v = 1; v < n; v++) { + int u = parent[v]; + int w = weight[v]; + g.get(u).add(new int[] {v, w}); + g.get(v).add(new int[] {u, w}); + } + + for (int s = 0; s < n; s++) { + for (int t = s + 1; t < n; t++) { + int treeVal = minEdgeOnPath(g, s, t); + int flowVal = EdmondsKarp.maxFlow(cap, s, t); + assertEquals(flowVal, treeVal, "pair (" + s + "," + t + ")"); + } + } + } + + private static int minEdgeOnPath(List> g, int s, int t) { + // BFS to record parent and edge weight along the path, since it's a tree, unique path exists + int n = g.size(); + int[] parent = new int[n]; + int[] edgeW = new int[n]; + Arrays.fill(parent, -1); + Queue q = new ArrayDeque<>(); + q.add(s); + parent[s] = s; + while (!q.isEmpty()) { + int u = q.poll(); + if (u == t) { + break; + } + for (int[] e : g.get(u)) { + int v = e[0]; + int w = e[1]; + if (parent[v] == -1) { + parent[v] = u; + edgeW[v] = w; + q.add(v); + } + } + } + int cur = t; + int ans = Integer.MAX_VALUE; + while (cur != s) { + ans = Math.min(ans, edgeW[cur]); + cur = parent[cur]; + } + return ans == Integer.MAX_VALUE ? 0 : ans; + } +} diff --git a/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java b/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java new file mode 100644 index 000000000000..8608bfb2dfc9 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/TarjanBridgesTest.java @@ -0,0 +1,207 @@ +package com.thealgorithms.graph; + +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.Comparator; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link TarjanBridges}. + * + *

Tests cover a wide range of graph configurations including simple graphs, + * cycles, trees, disconnected components, multigraph-like structures, and + * various edge cases to ensure correct bridge detection.

+ */ +class TarjanBridgesTest { + + /** + * Helper to build a symmetric adjacency list for an undirected graph. + */ + private static List> buildGraph(int vertexCount, int[][] edges) { + List> adj = new ArrayList<>(); + for (int i = 0; i < vertexCount; i++) { + adj.add(new ArrayList<>()); + } + for (int[] edge : edges) { + adj.get(edge[0]).add(edge[1]); + adj.get(edge[1]).add(edge[0]); + } + return adj; + } + + /** + * Sorts bridges for deterministic comparison. + */ + private static void sortBridges(List bridges) { + bridges.sort(Comparator.comparingInt((int[] a) -> a[0]).thenComparingInt(a -> a[1])); + } + + @Test + void testSimpleGraphWithOneBridge() { + // Graph: 0-1-2-3 where 1-2 is the only bridge + // 0---1---2---3 + // | | + // +-------+ (via 0-2 would make cycle, but not here) + // Actually: 0-1 in a cycle with 0-1, and 2-3 in a cycle with 2-3 + // Let's use: 0--1--2 (linear chain). All edges are bridges. + List> adj = buildGraph(3, new int[][] {{0, 1}, {1, 2}}); + List bridges = TarjanBridges.findBridges(3, adj); + sortBridges(bridges); + assertEquals(2, bridges.size()); + assertEquals(0, bridges.get(0)[0]); + assertEquals(1, bridges.get(0)[1]); + assertEquals(1, bridges.get(1)[0]); + assertEquals(2, bridges.get(1)[1]); + } + + @Test + void testCycleGraphHasNoBridges() { + // Graph: 0-1-2-0 (triangle). No bridges. + List> adj = buildGraph(3, new int[][] {{0, 1}, {1, 2}, {2, 0}}); + List bridges = TarjanBridges.findBridges(3, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testTreeGraphAllEdgesAreBridges() { + // Tree: 0 + // / \ + // 1 2 + // / \ + // 3 4 + List> adj = buildGraph(5, new int[][] {{0, 1}, {0, 2}, {1, 3}, {1, 4}}); + List bridges = TarjanBridges.findBridges(5, adj); + assertEquals(4, bridges.size()); + } + + @Test + void testGraphWithMixedBridgesAndCycles() { + // Graph: + // 0---1 + // | | + // 3---2---4---5 + // | + // 6 + // Cycle: 0-1-2-3-0 (no bridges within) + // Bridges: 2-4, 4-5, 5-6 + List> adj = buildGraph(7, new int[][] {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {2, 4}, {4, 5}, {5, 6}}); + List bridges = TarjanBridges.findBridges(7, adj); + sortBridges(bridges); + assertEquals(3, bridges.size()); + assertEquals(2, bridges.get(0)[0]); + assertEquals(4, bridges.get(0)[1]); + assertEquals(4, bridges.get(1)[0]); + assertEquals(5, bridges.get(1)[1]); + assertEquals(5, bridges.get(2)[0]); + assertEquals(6, bridges.get(2)[1]); + } + + @Test + void testDisconnectedGraphWithBridges() { + // Component 1: 0-1 (bridge) + // Component 2: 2-3-4-2 (cycle, no bridges) + List> adj = buildGraph(5, new int[][] {{0, 1}, {2, 3}, {3, 4}, {4, 2}}); + List bridges = TarjanBridges.findBridges(5, adj); + assertEquals(1, bridges.size()); + assertEquals(0, bridges.get(0)[0]); + assertEquals(1, bridges.get(0)[1]); + } + + @Test + void testSingleVertex() { + List> adj = buildGraph(1, new int[][] {}); + List bridges = TarjanBridges.findBridges(1, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testTwoVerticesWithOneEdge() { + List> adj = buildGraph(2, new int[][] {{0, 1}}); + List bridges = TarjanBridges.findBridges(2, adj); + assertEquals(1, bridges.size()); + assertEquals(0, bridges.get(0)[0]); + assertEquals(1, bridges.get(0)[1]); + } + + @Test + void testEmptyGraph() { + List> adj = buildGraph(0, new int[][] {}); + List bridges = TarjanBridges.findBridges(0, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testIsolatedVertices() { + // 5 vertices, no edges — all isolated + List> adj = buildGraph(5, new int[][] {}); + List bridges = TarjanBridges.findBridges(5, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testLargeCycleNoBridges() { + // Cycle: 0-1-2-3-4-5-6-7-0 + int n = 8; + int[][] edges = new int[n][2]; + for (int i = 0; i < n; i++) { + edges[i] = new int[] {i, (i + 1) % n}; + } + List> adj = buildGraph(n, edges); + List bridges = TarjanBridges.findBridges(n, adj); + assertTrue(bridges.isEmpty()); + } + + @Test + void testComplexGraphWithMultipleCyclesAndBridges() { + // Two cycles connected by a single bridge edge: + // Cycle A: 0-1-2-0 + // Cycle B: 3-4-5-3 + // Bridge: 2-3 + List> adj = buildGraph(6, new int[][] {{0, 1}, {1, 2}, {2, 0}, {3, 4}, {4, 5}, {5, 3}, {2, 3}}); + List bridges = TarjanBridges.findBridges(6, adj); + assertEquals(1, bridges.size()); + assertEquals(2, bridges.get(0)[0]); + assertEquals(3, bridges.get(0)[1]); + } + + @Test + void testNegativeVertexCountThrowsException() { + assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(-1, new ArrayList<>())); + } + + @Test + void testNullAdjacencyListThrowsException() { + assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(3, null)); + } + + @Test + void testMismatchedAdjacencyListSizeThrowsException() { + List> adj = buildGraph(2, new int[][] {{0, 1}}); + assertThrows(IllegalArgumentException.class, () -> TarjanBridges.findBridges(5, adj)); + } + + @Test + void testStarGraphAllEdgesAreBridges() { + // Star graph: center vertex 0 connected to 1, 2, 3, 4 + List> adj = buildGraph(5, new int[][] {{0, 1}, {0, 2}, {0, 3}, {0, 4}}); + List bridges = TarjanBridges.findBridges(5, adj); + assertEquals(4, bridges.size()); + } + + @Test + void testBridgeBetweenTwoCycles() { + // Two squares connected by one bridge: + // Square 1: 0-1-2-3-0 + // Square 2: 4-5-6-7-4 + // Bridge: 3-4 + List> adj = buildGraph(8, new int[][] {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {3, 4}}); + List bridges = TarjanBridges.findBridges(8, adj); + assertEquals(1, bridges.size()); + assertEquals(3, bridges.get(0)[0]); + assertEquals(4, bridges.get(0)[1]); + } +} diff --git a/src/test/java/com/thealgorithms/io/BufferedReaderTest.java b/src/test/java/com/thealgorithms/io/BufferedReaderTest.java index 891c3066058e..088e86f8f7c5 100644 --- a/src/test/java/com/thealgorithms/io/BufferedReaderTest.java +++ b/src/test/java/com/thealgorithms/io/BufferedReaderTest.java @@ -17,15 +17,15 @@ public void testPeeks() throws IOException { BufferedReader reader = new BufferedReader(input); // read the first letter - assertEquals(reader.read(), 'H'); + assertEquals('H', reader.read()); len--; - assertEquals(reader.available(), len); + assertEquals(len, reader.available()); // position: H[e]llo!\nWorld! // reader.read() will be == 'e' - assertEquals(reader.peek(1), 'l'); - assertEquals(reader.peek(2), 'l'); // second l - assertEquals(reader.peek(3), 'o'); + assertEquals('l', reader.peek(1)); + assertEquals('l', reader.peek(2)); // second l + assertEquals('o', reader.peek(3)); } @Test @@ -38,21 +38,21 @@ public void testMixes() throws IOException { BufferedReader reader = new BufferedReader(input); // read the first letter - assertEquals(reader.read(), 'H'); // first letter + assertEquals('H', reader.read()); // first letter len--; - assertEquals(reader.peek(1), 'l'); // third later (second letter after 'H') - assertEquals(reader.read(), 'e'); // second letter + assertEquals('l', reader.peek(1)); // third later (second letter after 'H') + assertEquals('e', reader.read()); // second letter len--; - assertEquals(reader.available(), len); + assertEquals(len, reader.available()); // position: H[e]llo!\nWorld! - assertEquals(reader.peek(2), 'o'); // second l - assertEquals(reader.peek(3), '!'); - assertEquals(reader.peek(4), '\n'); + assertEquals('o', reader.peek(2)); // second l + assertEquals('!', reader.peek(3)); + assertEquals('\n', reader.peek(4)); - assertEquals(reader.read(), 'l'); // third letter - assertEquals(reader.peek(1), 'o'); // fourth letter + assertEquals('l', reader.read()); // third letter + assertEquals('o', reader.peek(1)); // fourth letter for (int i = 0; i < 6; i++) { reader.read(); @@ -74,23 +74,23 @@ public void testBlockPractical() throws IOException { ByteArrayInputStream input = new ByteArrayInputStream(bytes); BufferedReader reader = new BufferedReader(input); - assertEquals(reader.peek(), 'H'); - assertEquals(reader.read(), '!'); // read the first letter + assertEquals('H', reader.peek()); + assertEquals('!', reader.read()); // read the first letter len--; // this only reads the next 5 bytes (Hello) because // the default buffer size = 5 - assertEquals(new String(reader.readBlock()), "Hello"); + assertEquals("Hello", new String(reader.readBlock())); len -= 5; assertEquals(reader.available(), len); // maybe kind of a practical demonstration / use case if (reader.read() == '\n') { - assertEquals(reader.read(), 'W'); - assertEquals(reader.read(), 'o'); + assertEquals('W', reader.read()); + assertEquals('o', reader.read()); // the rest of the blocks - assertEquals(new String(reader.readBlock()), "rld!"); + assertEquals("rld!", new String(reader.readBlock())); } else { // should not reach throw new IOException("Something not right"); diff --git a/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java b/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java new file mode 100644 index 000000000000..e8a130d0ccb2 --- /dev/null +++ b/src/test/java/com/thealgorithms/machinelearning/LinearRegressionTest.java @@ -0,0 +1,95 @@ +package com.thealgorithms.machinelearning; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class LinearRegressionTest { + + private static final double DELTA = 0.1; + + @Test + void fitLearnsCorrectSlopeAndIntercept() { + double trueM = 2.5; + double trueB = 1.5; + + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] yTrain = new double[xTrain.length]; + for (int i = 0; i < xTrain.length; i++) { + yTrain[i] = (trueM * xTrain[i]) + trueB; + } + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + assertEquals(trueM, model.getSlope(), DELTA); + assertEquals(trueB, model.getIntercept(), DELTA); + } + + @Test + void predictMatchesExpectedOnUnseenData() { + double trueM = 2.5; + double trueB = 1.5; + + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] yTrain = new double[xTrain.length]; + for (int i = 0; i < xTrain.length; i++) { + yTrain[i] = (trueM * xTrain[i]) + trueB; + } + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + double[] testInputs = {0.0, 3.5, 7.0}; + for (double testX : testInputs) { + double expectedY = (trueM * testX) + trueB; + assertEquals(expectedY, model.predict(testX), DELTA); + } + } + + @Test + void calculateMSEIsNearZeroAfterTraining() { + double[] xTrain = {1.0, 2.0, 3.0, 4.0, 5.0}; + double[] yTrain = {3.0, 5.0, 7.0, 9.0, 11.0}; // y = 2x + 1 + + LinearRegression model = new LinearRegression(0.01, 1000); + model.fit(xTrain, yTrain); + + assertEquals(0.0, model.calculateMSE(xTrain, yTrain), 0.01); + } + + @Test + void fitThrowsExceptionOnMismatchedArrayLengths() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {1.0, 2.0}; + double[] y = {1.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, y)); + } + + @Test + void fitThrowsExceptionOnEmptyArrays() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {}; + double[] y = {}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, y)); + } + + @Test + void fitThrowsExceptionOnNullX() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] y = {1.0, 2.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(null, y)); + } + + @Test + void fitThrowsExceptionOnNullY() { + LinearRegression model = new LinearRegression(0.01, 100); + double[] x = {1.0, 2.0}; + + assertThrows(IllegalArgumentException.class, () -> model.fit(x, null)); + } +} diff --git a/src/test/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifierTest.java b/src/test/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifierTest.java new file mode 100644 index 000000000000..5ccd31147ac3 --- /dev/null +++ b/src/test/java/com/thealgorithms/machinelearning/MultinomialNaiveBayesClassifierTest.java @@ -0,0 +1,141 @@ +package com.thealgorithms.machinelearning; + +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; + +class MultinomialNaiveBayesClassifierTest { + + @Test + void predictsCorrectClassOnSeparableToyDataset() { + // Class 0 samples are dominated by feature 0; class 1 samples by feature 1. + double[][] features = { + {5, 1}, + {6, 0}, + {4, 1}, + {1, 5}, + {0, 6}, + {1, 4}, + }; + int[] labels = {0, 0, 0, 1, 1, 1}; + + MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier(); + classifier.fit(features, labels); + + assertEquals(0, classifier.predict(new double[] {5, 0})); + assertEquals(1, classifier.predict(new double[] {0, 5})); + } + + @Test + void predictBatchMatchesIndividualPredictions() { + double[][] features = { + {3, 0}, + {2, 0}, + {0, 3}, + {0, 2}, + }; + int[] labels = {0, 0, 1, 1}; + + MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier(); + classifier.fit(features, labels); + + double[][] samples = {{4, 0}, {0, 4}}; + int[] predictions = classifier.predict(samples); + + assertEquals(classifier.predict(samples[0]), predictions[0]); + assertEquals(classifier.predict(samples[1]), predictions[1]); + } + + @Test + void laplaceSmoothingKeepsZeroCountFeatureLogProbabilityFinite() { + // Feature index 1 never appears for class 0 in training data. + double[][] features = { + {2, 0}, + {3, 0}, + {0, 2}, + {0, 3}, + }; + int[] labels = {0, 0, 1, 1}; + + MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier(); + classifier.fit(features, labels); + + // A sample that hits class 0's zero-count feature should still produce + // a finite, usable prediction instead of -Infinity collapsing the score. + int prediction = classifier.predict(new double[] {1, 1}); + assertTrue(prediction == 0 || prediction == 1); + } + + @Test + void predictBeforeFitThrowsIllegalStateException() { + MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier(); + assertThrows(IllegalStateException.class, () -> classifier.predict(new double[] {1, 2})); + } + + @Test + void nonPositiveAlphaThrowsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new MultinomialNaiveBayesClassifier(0.0)); + assertThrows(IllegalArgumentException.class, () -> new MultinomialNaiveBayesClassifier(-1.0)); + } + + @Test + void mismatchedSampleLengthThrowsIllegalArgumentException() { + double[][] features = { + {1, 2}, + {3, 4}, + }; + int[] labels = {0, 1}; + + MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier(); + classifier.fit(features, labels); + + assertThrows(IllegalArgumentException.class, () -> classifier.predict(new double[] {1, 2, 3})); + } + + @Test + void mismatchedFeatureAndLabelLengthsThrowsIllegalArgumentException() { + double[][] features = { + {1, 2}, + {3, 4}, + }; + int[] labels = {0}; + + MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier(); + assertThrows(IllegalArgumentException.class, () -> classifier.fit(features, labels)); + } + + @Test + void emptyFeaturesArrayThrowsIllegalArgumentException() { + double[][] features = {}; + int[] labels = {}; + MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier(); + assertThrows(IllegalArgumentException.class, () -> classifier.fit(features, labels)); + } + + @Test + void refittingReplacesPreviousModelState() { + MultinomialNaiveBayesClassifier classifier = new MultinomialNaiveBayesClassifier(); + + double[][] firstFeatures = { + {5, 0, 0}, + {0, 5, 0}, + {0, 0, 5}, + }; + int[] firstLabels = {0, 1, 2}; + classifier.fit(firstFeatures, firstLabels); + + double[][] secondFeatures = { + {5, 0}, + {0, 5}, + }; + int[] secondLabels = {0, 1}; + classifier.fit(secondFeatures, secondLabels); + + // Class 2 existed in the first fit but not the second — it must not + // survive into predictions after refitting. + int prediction = classifier.predict(new double[] {2.5, 2.5}); + assertTrue(prediction == 0 || prediction == 1); + } +} diff --git a/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java b/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java index dfca757fd877..070ff4ae3147 100644 --- a/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java +++ b/src/test/java/com/thealgorithms/maths/AbsoluteMinTest.java @@ -11,12 +11,15 @@ public class AbsoluteMinTest { void testGetMinValue() { assertEquals(0, AbsoluteMin.getMinValue(4, 0, 16)); assertEquals(-2, AbsoluteMin.getMinValue(3, -10, -2)); + assertEquals(-2, AbsoluteMin.getMinValue(-3, -10, -2)); + assertEquals(2, AbsoluteMin.getMinValue(-3, -10, 2)); + assertEquals(2, AbsoluteMin.getMinValue(-5, 2)); + assertEquals(2, AbsoluteMin.getMinValue(2, -5)); } @Test void testGetMinValueWithNoArguments() { - Exception exception = assertThrows(IllegalArgumentException.class, AbsoluteMin::getMinValue); - assertEquals("Numbers array cannot be empty", exception.getMessage()); + assertThrows(IllegalArgumentException.class, AbsoluteMin::getMinValue); } @Test @@ -24,4 +27,13 @@ void testGetMinValueWithSameAbsoluteValues() { assertEquals(-5, AbsoluteMin.getMinValue(-5, 5)); assertEquals(-5, AbsoluteMin.getMinValue(5, -5)); } + + @Test + void testIntegerMinValueOverflow() { + assertEquals(1, AbsoluteMin.getMinValue(Integer.MIN_VALUE, 1)); + assertEquals(-1, AbsoluteMin.getMinValue(Integer.MIN_VALUE, -1)); + assertEquals(0, AbsoluteMin.getMinValue(Integer.MIN_VALUE, 0)); + assertEquals(Integer.MIN_VALUE, AbsoluteMin.getMinValue(Integer.MIN_VALUE)); + assertEquals(Integer.MAX_VALUE, AbsoluteMin.getMinValue(Integer.MIN_VALUE, Integer.MAX_VALUE)); + } } diff --git a/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java b/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java new file mode 100644 index 000000000000..5b35345afd02 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/AbundantNumberTest.java @@ -0,0 +1,31 @@ +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.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class AbundantNumberTest { + @ParameterizedTest + @CsvSource({"12", "66", "222", "444", "888", "2424"}) + void abundantNumbersTest(int n) { + assertTrue(AbundantNumber.isAbundant(n)); + assertTrue(AbundantNumber.isAbundantNumber(n)); + } + + @ParameterizedTest + @CsvSource({"1", "2", "6", "111", "333", "2222"}) + void nonAbundantNumbersTest(int n) { + assertFalse(AbundantNumber.isAbundant(n)); + assertFalse(AbundantNumber.isAbundantNumber(n)); + } + + @ParameterizedTest + @CsvSource({"0", "-1"}) + void throwsNegativeNumbersNotAllowed(int n) { + assertThrows(IllegalArgumentException.class, () -> AbundantNumber.isAbundant(n)); + assertThrows(IllegalArgumentException.class, () -> AbundantNumber.isAbundantNumber(n)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/AreaTest.java b/src/test/java/com/thealgorithms/maths/AreaTest.java index b28afb85fbc3..1c2fe53ff3f3 100644 --- a/src/test/java/com/thealgorithms/maths/AreaTest.java +++ b/src/test/java/com/thealgorithms/maths/AreaTest.java @@ -16,6 +16,11 @@ void testSurfaceAreaCube() { assertEquals(6.0, Area.surfaceAreaCube(1)); } + @Test + void testSurfaceAreaCuboid() { + assertEquals(214.0, Area.surfaceAreaCuboid(5, 6, 7)); + } + @Test void testSurfaceAreaSphere() { assertEquals(12.566370614359172, Area.surfaceAreaSphere(1)); @@ -70,6 +75,12 @@ void surfaceAreaCone() { void testAllIllegalInput() { assertAll(() -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCube(0)), + () + -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(0, 1, 2)), + () + -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(1, 0, 2)), + () + -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaCuboid(1, 2, 0)), () -> assertThrows(IllegalArgumentException.class, () -> Area.surfaceAreaSphere(0)), () diff --git a/src/test/java/com/thealgorithms/maths/BellNumbersTest.java b/src/test/java/com/thealgorithms/maths/BellNumbersTest.java new file mode 100644 index 000000000000..8dd83cf0f7a9 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/BellNumbersTest.java @@ -0,0 +1,53 @@ +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; + +class BellNumbersTest { + + @Test + void testStandardCases() { + // Base cases and small numbers + assertEquals(1, BellNumbers.compute(0)); + assertEquals(1, BellNumbers.compute(1)); + assertEquals(2, BellNumbers.compute(2)); + assertEquals(5, BellNumbers.compute(3)); + assertEquals(15, BellNumbers.compute(4)); + assertEquals(52, BellNumbers.compute(5)); + } + + @Test + void testMediumNumber() { + // B10 = 115,975 + assertEquals(115975, BellNumbers.compute(10)); + // B15 = 1,382,958,545 + assertEquals(1382958545L, BellNumbers.compute(15)); + } + + @Test + void testLargeNumber() { + // B20 = 51,724,158,235,372 + // We use the 'L' suffix to tell Java this is a long literal + assertEquals(51724158235372L, BellNumbers.compute(20)); + } + + @Test + void testMaxLongCapacity() { + // B25 is the largest Bell number that fits in a Java long (signed 64-bit) + // B25 = 4,638,590,332,229,999,353 + assertEquals(4638590332229999353L, BellNumbers.compute(25)); + } + + @Test + void testNegativeInput() { + assertThrows(IllegalArgumentException.class, () -> BellNumbers.compute(-1)); + } + + @Test + void testOverflowProtection() { + // We expect an exception if the user asks for the impossible + assertThrows(IllegalArgumentException.class, () -> BellNumbers.compute(26)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java b/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java new file mode 100644 index 000000000000..02e964b53771 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ComplexNumberMultiplyTest.java @@ -0,0 +1,34 @@ +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 ComplexNumberMultiplyTest { + + @Test + void testExample() { + assertEquals("0+2i", ComplexNumberMultiply.multiply("1+1i", "1+1i")); + } + + @Test + void testNegative() { + assertEquals("0+-2i", ComplexNumberMultiply.multiply("1+-1i", "1+-1i")); + } + + @Test + void testZero() { + assertEquals("0+0i", ComplexNumberMultiply.multiply("0+0i", "5+3i")); + } + + @Test + void testInvalidFormat() { + assertThrows(IllegalArgumentException.class, () -> ComplexNumberMultiply.multiply("1+1", "1+1i")); + } + + @Test + void testNullInput() { + assertThrows(IllegalArgumentException.class, () -> ComplexNumberMultiply.multiply(null, "1+1i")); + } +} diff --git a/src/test/java/com/thealgorithms/maths/CorrelationTest.java b/src/test/java/com/thealgorithms/maths/CorrelationTest.java new file mode 100644 index 000000000000..96867d56ad5e --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/CorrelationTest.java @@ -0,0 +1,51 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test class for Correlation class + */ +public class CorrelationTest { + + public static final double DELTA = 1e-9; + + // Regular correlation test + public void testCorrelationFirst() { + double[] x = {1, 2, 3, 4}; + double[] y = {7, 1, 4, 9}; + int n = 4; + assertEquals(0.3319700011, Correlation.correlation(x, y, n), DELTA); + } + + // Regular correlation test (zero correlation) + public void testCorrelationSecond() { + double[] x = {1, 2, 3, 4}; + double[] y = {5, 0, 9, 2}; + int n = 4; + assertEquals(0, Correlation.correlation(x, y, n), DELTA); + } + + // Correlation with a constant variable is taken to be zero + public void testCorrelationConstant() { + double[] x = {1, 2, 3}; + double[] y = {4, 4, 4}; + int n = 3; + assertEquals(0, Correlation.correlation(x, y, n), DELTA); + } + + // Linear dependence gives correlation 1 + public void testCorrelationLinearDependence() { + double[] x = {1, 2, 3, 4}; + double[] y = {6, 8, 10, 12}; + int n = 4; + assertEquals(1, Correlation.correlation(x, y, n), DELTA); + } + + // Inverse linear dependence gives correlation -1 + public void testCorrelationInverseLinearDependence() { + double[] x = {1, 2, 3, 4, 5}; + double[] y = {18, 15, 12, 9, 6}; + int n = 5; + assertEquals(-1, Correlation.correlation(x, y, n), DELTA); + } +} diff --git a/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java b/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java new file mode 100644 index 000000000000..54e9e861ad44 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/DisariumNumberTest.java @@ -0,0 +1,32 @@ +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; + +class DisariumNumberTest { + + @Test + void testDisariumNumbers() { + assertTrue(DisariumNumber.isDisarium(1)); + assertTrue(DisariumNumber.isDisarium(89)); + assertTrue(DisariumNumber.isDisarium(135)); + assertTrue(DisariumNumber.isDisarium(175)); + assertTrue(DisariumNumber.isDisarium(518)); + } + + @Test + void testNonDisariumNumbers() { + assertFalse(DisariumNumber.isDisarium(10)); + assertFalse(DisariumNumber.isDisarium(100)); + assertFalse(DisariumNumber.isDisarium(200)); + } + + @Test + void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> DisariumNumber.isDisarium(0)); + assertThrows(IllegalArgumentException.class, () -> DisariumNumber.isDisarium(-5)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/DistanceBetweenTwoPointsTest.java b/src/test/java/com/thealgorithms/maths/DistanceBetweenTwoPointsTest.java new file mode 100644 index 000000000000..6bd124629740 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/DistanceBetweenTwoPointsTest.java @@ -0,0 +1,23 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class DistanceBetweenTwoPointsTest { + + @Test + void testDistanceSimple() { + assertEquals(5.0, DistanceBetweenTwoPoints.calculate(0, 0, 3, 4), 1e-9); + } + + @Test + void testDistanceNegativeCoordinates() { + assertEquals(5.0, DistanceBetweenTwoPoints.calculate(-1, -1, 2, 3), 1e-9); + } + + @Test + void testSamePoint() { + assertEquals(0.0, DistanceBetweenTwoPoints.calculate(2, 2, 2, 2), 1e-9); + } +} diff --git a/src/test/java/com/thealgorithms/maths/DistanceFormulaTest.java b/src/test/java/com/thealgorithms/maths/DistanceFormulaTest.java index 3a14b80dd4f9..66f3b7b03938 100644 --- a/src/test/java/com/thealgorithms/maths/DistanceFormulaTest.java +++ b/src/test/java/com/thealgorithms/maths/DistanceFormulaTest.java @@ -9,78 +9,78 @@ public class DistanceFormulaTest { @Test void euclideanTest1() { - Assertions.assertEquals(DistanceFormula.euclideanDistance(1, 1, 2, 2), 1.4142135623730951); + Assertions.assertEquals(1.4142135623730951, DistanceFormula.euclideanDistance(1, 1, 2, 2)); } @Test void euclideanTest2() { - Assertions.assertEquals(DistanceFormula.euclideanDistance(1, 3, 8, 0), 7.0710678118654755); + Assertions.assertEquals(7.0710678118654755, DistanceFormula.euclideanDistance(1, 3, 8, 0)); } @Test void euclideanTest3() { - Assertions.assertEquals(DistanceFormula.euclideanDistance(2.4, 9.1, 55.1, 100), 110.91911467371168); + Assertions.assertEquals(110.91911467371168, DistanceFormula.euclideanDistance(2.4, 9.1, 55.1, 100)); } @Test void euclideanTest4() { - Assertions.assertEquals(DistanceFormula.euclideanDistance(1000, 13, 20000, 84), 19022.067605809836); + Assertions.assertEquals(19022.067605809836, DistanceFormula.euclideanDistance(1000, 13, 20000, 84)); } @Test public void manhattantest1() { - assertEquals(DistanceFormula.manhattanDistance(1, 2, 3, 4), 4); + assertEquals(4, DistanceFormula.manhattanDistance(1, 2, 3, 4)); } @Test public void manhattantest2() { - assertEquals(DistanceFormula.manhattanDistance(6.5, 8.4, 20.1, 13.6), 18.8); + assertEquals(18.8, DistanceFormula.manhattanDistance(6.5, 8.4, 20.1, 13.6)); } @Test public void manhattanTest3() { - assertEquals(DistanceFormula.manhattanDistance(10.112, 50, 8, 25.67), 26.442); + assertEquals(26.442, DistanceFormula.manhattanDistance(10.112, 50, 8, 25.67)); } @Test public void hammingTest1() { int[] array1 = {1, 1, 1, 1}; int[] array2 = {0, 0, 0, 0}; - assertEquals(DistanceFormula.hammingDistance(array1, array2), 4); + assertEquals(4, DistanceFormula.hammingDistance(array1, array2)); } @Test public void hammingTest2() { int[] array1 = {1, 1, 1, 1}; int[] array2 = {1, 1, 1, 1}; - assertEquals(DistanceFormula.hammingDistance(array1, array2), 0); + assertEquals(0, DistanceFormula.hammingDistance(array1, array2)); } @Test public void hammingTest3() { int[] array1 = {1, 0, 0, 1, 1, 0, 1, 1, 0}; int[] array2 = {0, 1, 0, 0, 1, 1, 1, 0, 0}; - assertEquals(DistanceFormula.hammingDistance(array1, array2), 5); + assertEquals(5, DistanceFormula.hammingDistance(array1, array2)); } @Test public void minkowskiTest1() { double[] array1 = {1, 3, 8, 5}; double[] array2 = {4, 2, 6, 9}; - assertEquals(DistanceFormula.minkowskiDistance(array1, array2, 1), 10); + assertEquals(10, DistanceFormula.minkowskiDistance(array1, array2, 1)); } @Test public void minkowskiTest2() { double[] array1 = {1, 3, 8, 5}; double[] array2 = {4, 2, 6, 9}; - assertEquals(DistanceFormula.minkowskiDistance(array1, array2, 2), 5.477225575051661); + assertEquals(5.477225575051661, DistanceFormula.minkowskiDistance(array1, array2, 2)); } @Test public void minkowskiTest3() { double[] array1 = {1, 3, 8, 5}; double[] array2 = {4, 2, 6, 9}; - assertEquals(DistanceFormula.minkowskiDistance(array1, array2, 3), 4.641588833612778); + assertEquals(4.641588833612778, DistanceFormula.minkowskiDistance(array1, array2, 3)); } } diff --git a/src/test/java/com/thealgorithms/maths/EvilNumberTest.java b/src/test/java/com/thealgorithms/maths/EvilNumberTest.java new file mode 100644 index 000000000000..e59171fad25f --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/EvilNumberTest.java @@ -0,0 +1,28 @@ +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.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class EvilNumberTest { + @ParameterizedTest + @CsvSource({"0", "3", "10", "129", "222", "500", "777", "1198"}) + void evilNumbersTest(int n) { + assertTrue(EvilNumber.isEvilNumber(n)); + } + + @ParameterizedTest + @CsvSource({"1", "7", "100", "333", "555", "1199"}) + void odiousNumbersTest(int n) { + assertFalse(EvilNumber.isEvilNumber(n)); + } + + @ParameterizedTest + @CsvSource({"-1"}) + void throwsNegativeNumbersNotAllowed(int n) { + assertThrows(IllegalArgumentException.class, () -> EvilNumber.isEvilNumber(n)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java b/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java new file mode 100644 index 000000000000..56c005fd51ae --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ExtendedEuclideanAlgorithmTest.java @@ -0,0 +1,47 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class ExtendedEuclideanAlgorithmTest { + + /** + * Verifies that the returned values satisfy Bézout's identity: a*x + b*y = + * gcd(a, b) + */ + private void verifyBezoutIdentity(long a, long b, long[] result) { + long gcd = result[0]; + long x = result[1]; + long y = result[2]; + assertEquals(a * x + b * y, gcd, "Bézout's identity failed for gcd(" + a + ", " + b + ")"); + } + + @Test + public void testExtendedGCD() { + // Test case 1: General case gcd(30, 50) = 10 + long[] result1 = ExtendedEuclideanAlgorithm.extendedGCD(30, 50); + assertEquals(10, result1[0], "Test Case 1 Failed: gcd(30, 50) should be 10"); + verifyBezoutIdentity(30, 50, result1); + + // Test case 2: Another general case gcd(240, 46) = 2 + long[] result2 = ExtendedEuclideanAlgorithm.extendedGCD(240, 46); + assertEquals(2, result2[0], "Test Case 2 Failed: gcd(240, 46) should be 2"); + verifyBezoutIdentity(240, 46, result2); + + // Test case 3: Base case where b is 0, gcd(10, 0) = 10 + long[] result3 = ExtendedEuclideanAlgorithm.extendedGCD(10, 0); + assertEquals(10, result3[0], "Test Case 3 Failed: gcd(10, 0) should be 10"); + verifyBezoutIdentity(10, 0, result3); + + // Test case 4: Numbers are co-prime gcd(17, 13) = 1 + long[] result4 = ExtendedEuclideanAlgorithm.extendedGCD(17, 13); + assertEquals(1, result4[0], "Test Case 4 Failed: gcd(17, 13) should be 1"); + verifyBezoutIdentity(17, 13, result4); + + // Test case 5: One number is a multiple of the other gcd(100, 20) = 20 + long[] result5 = ExtendedEuclideanAlgorithm.extendedGCD(100, 20); + assertEquals(20, result5[0], "Test Case 5 Failed: gcd(100, 20) should be 20"); + verifyBezoutIdentity(100, 20, result5); + } +} diff --git a/src/test/java/com/thealgorithms/maths/FactorialTest.java b/src/test/java/com/thealgorithms/maths/FactorialTest.java index b38dc45589ee..a393136696e1 100644 --- a/src/test/java/com/thealgorithms/maths/FactorialTest.java +++ b/src/test/java/com/thealgorithms/maths/FactorialTest.java @@ -3,6 +3,7 @@ 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 FactorialTest { @@ -11,14 +12,14 @@ public class FactorialTest { @Test public void testWhenInvalidInoutProvidedShouldThrowException() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Factorial.factorial(-1)); - assertEquals(exception.getMessage(), EXCEPTION_MESSAGE); + assertEquals(EXCEPTION_MESSAGE, exception.getMessage()); } @Test public void testCorrectFactorialCalculation() { - assertEquals(1, Factorial.factorial(0)); - assertEquals(1, Factorial.factorial(1)); - assertEquals(120, Factorial.factorial(5)); - assertEquals(3628800, Factorial.factorial(10)); + assertEquals(BigInteger.ONE, Factorial.factorial(0)); + assertEquals(BigInteger.ONE, Factorial.factorial(1)); + assertEquals(BigInteger.valueOf(120), Factorial.factorial(5)); + assertEquals(BigInteger.valueOf(3628800), Factorial.factorial(10)); } } diff --git a/src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java b/src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java new file mode 100644 index 000000000000..be5ddd7ee79e --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/FriendlyNumberTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class FriendlyNumberTest { + + @Test + public void testFriendlyNumbers() { + // 6 and 28 are friendly (abundancy index = 2) + assertTrue(FriendlyNumber.areFriendly(6, 28)); + // Every number is friendly with itself + assertTrue(FriendlyNumber.areFriendly(6, 6)); + assertTrue(FriendlyNumber.areFriendly(1, 1)); + } + + @Test + public void testNonFriendlyNumbers() { + assertFalse(FriendlyNumber.areFriendly(6, 10)); + assertFalse(FriendlyNumber.areFriendly(10, 15)); + assertFalse(FriendlyNumber.areFriendly(4, 9)); + } + + @Test + public void testInvalidInputs() { + assertFalse(FriendlyNumber.areFriendly(0, 6)); + assertFalse(FriendlyNumber.areFriendly(-1, 6)); + assertFalse(FriendlyNumber.areFriendly(6, -1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/GCDTest.java b/src/test/java/com/thealgorithms/maths/GCDTest.java index bac3f8f7596c..6bc870e94df9 100644 --- a/src/test/java/com/thealgorithms/maths/GCDTest.java +++ b/src/test/java/com/thealgorithms/maths/GCDTest.java @@ -6,57 +6,77 @@ public class GCDTest { @Test - void test1() { + void testNegativeAndZeroThrowsException() { Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(-1, 0)); } @Test - void test2() { + void testPositiveAndNegativeThrowsException() { Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(10, -2)); } @Test - void test3() { + void testBothNegativeThrowsException() { Assertions.assertThrows(ArithmeticException.class, () -> GCD.gcd(-5, -3)); } @Test - void test4() { - Assertions.assertEquals(GCD.gcd(0, 2), 2); + void testZeroAndPositiveReturnsPositive() { + Assertions.assertEquals(2, GCD.gcd(0, 2)); } @Test - void test5() { - Assertions.assertEquals(GCD.gcd(10, 0), 10); + void testPositiveAndZeroReturnsPositive() { + Assertions.assertEquals(10, GCD.gcd(10, 0)); } @Test - void test6() { - Assertions.assertEquals(GCD.gcd(1, 0), 1); + void testOneAndZeroReturnsOne() { + Assertions.assertEquals(1, GCD.gcd(1, 0)); } @Test - void test7() { - Assertions.assertEquals(GCD.gcd(9, 6), 3); + void testTwoPositiveNumbers() { + Assertions.assertEquals(3, GCD.gcd(9, 6)); } @Test - void test8() { - Assertions.assertEquals(GCD.gcd(48, 18, 30, 12), 6); + void testMultipleArgumentsGcd() { + Assertions.assertEquals(6, GCD.gcd(48, 18, 30, 12)); } @Test - void testArrayGcd1() { - Assertions.assertEquals(GCD.gcd(new int[] {9, 6}), 3); + void testArrayInputGcd() { + Assertions.assertEquals(3, GCD.gcd(new int[] {9, 6})); } @Test - void testArrayGcd2() { - Assertions.assertEquals(GCD.gcd(new int[] {2 * 3 * 5 * 7, 2 * 5 * 5 * 5, 2 * 5 * 11, 5 * 5 * 5 * 13}), 5); + void testArrayWithCommonFactor() { + Assertions.assertEquals(5, GCD.gcd(new int[] {2 * 3 * 5 * 7, 2 * 5 * 5 * 5, 2 * 5 * 11, 5 * 5 * 5 * 13})); } @Test - void testArrayGcdForEmptyInput() { - Assertions.assertEquals(GCD.gcd(new int[] {}), 0); + void testEmptyArrayReturnsZero() { + Assertions.assertEquals(0, GCD.gcd(new int[] {})); + } + + @Test + void testSameNumbers() { + Assertions.assertEquals(7, GCD.gcd(7, 7)); + } + + @Test + void testPrimeNumbersHaveGcdOne() { + Assertions.assertEquals(1, GCD.gcd(13, 17)); + } + + @Test + void testSingleElementArrayReturnsElement() { + Assertions.assertEquals(42, GCD.gcd(new int[] {42})); + } + + @Test + void testLargeNumbers() { + Assertions.assertEquals(12, GCD.gcd(123456, 789012)); } } diff --git a/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java b/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java new file mode 100644 index 000000000000..19558510f916 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/JacobsthalNumberTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class JacobsthalNumberTest { + + @Test + public void testBaseCases() { + assertEquals(0, JacobsthalNumber.jacobsthal(0)); + assertEquals(1, JacobsthalNumber.jacobsthal(1)); + } + + @Test + public void testKnownValues() { + assertEquals(1, JacobsthalNumber.jacobsthal(2)); + assertEquals(3, JacobsthalNumber.jacobsthal(3)); + assertEquals(5, JacobsthalNumber.jacobsthal(4)); + assertEquals(11, JacobsthalNumber.jacobsthal(5)); + assertEquals(21, JacobsthalNumber.jacobsthal(6)); + assertEquals(43, JacobsthalNumber.jacobsthal(7)); + assertEquals(85, JacobsthalNumber.jacobsthal(8)); + assertEquals(171, JacobsthalNumber.jacobsthal(9)); + assertEquals(341, JacobsthalNumber.jacobsthal(10)); + } + + @Test + public void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> JacobsthalNumber.jacobsthal(-1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java b/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java index c4205985dbfd..885382e29ca2 100644 --- a/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java +++ b/src/test/java/com/thealgorithms/maths/LinearDiophantineEquationsSolverTest.java @@ -176,7 +176,7 @@ void testSolutionEquality() { assertEquals(solution1, solution2); assertNotEquals(solution3, solution1); assertEquals(solution1, solution1); - assertNotEquals(null, solution1); + assertNotNull(solution1); assertNotEquals("string", solution1); } @@ -217,7 +217,7 @@ void testGcdSolutionWrapperEquality() { assertEquals(wrapper1, wrapper2); assertNotEquals(wrapper3, wrapper1); assertEquals(wrapper1, wrapper1); - assertNotEquals(null, wrapper1); + assertNotNull(wrapper1); assertNotEquals("string", wrapper1); } diff --git a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java b/src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java similarity index 99% rename from src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java rename to src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java index 7c3ce6635aa0..5a3d1c64b379 100644 --- a/src/test/java/com/thealgorithms/others/LowestBasePalindromeTest.java +++ b/src/test/java/com/thealgorithms/maths/LowestBasePalindromeTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.others; +package com.thealgorithms.maths; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java b/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java new file mode 100644 index 000000000000..91904316b25c --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/LuckyNumberTest.java @@ -0,0 +1,32 @@ +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.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class LuckyNumberTest { + + @ParameterizedTest + @CsvSource({"1", "3", "13", "49", "109", "459", "949"}) + void luckyNumbersTest(int n) { + assertTrue(LuckyNumber.isLucky(n)); + assertTrue(LuckyNumber.isLuckyNumber(n)); + } + + @ParameterizedTest + @CsvSource({"2", "17", "100", "300", "700"}) + void nonLuckyNumbersTest(int n) { + assertFalse(LuckyNumber.isLucky(n)); + assertFalse(LuckyNumber.isLuckyNumber(n)); + } + + @ParameterizedTest + @CsvSource({"0", "-1"}) + void throwsNegativeNumbersNotAllowed(int n) { + assertThrows(IllegalArgumentException.class, () -> LuckyNumber.isLucky(n)); + assertThrows(IllegalArgumentException.class, () -> LuckyNumber.isLuckyNumber(n)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/MeansTest.java b/src/test/java/com/thealgorithms/maths/MeansTest.java index deee0a931910..853fdbea3963 100644 --- a/src/test/java/com/thealgorithms/maths/MeansTest.java +++ b/src/test/java/com/thealgorithms/maths/MeansTest.java @@ -172,6 +172,53 @@ void testHarmonicMeanWithLinkedList() { assertEquals(expected, Means.harmonic(numbers), EPSILON); } + // ========== Quadratic Mean Tests ========== + + @Test + void testQuadraticMeanThrowsExceptionForEmptyList() { + List numbers = new ArrayList<>(); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> Means.quadratic(numbers)); + assertTrue(exception.getMessage().contains("Empty list")); + } + + @Test + void testQuadraticMeanSingleNumber() { + LinkedHashSet numbers = new LinkedHashSet<>(Arrays.asList(2.5)); + assertEquals(2.5, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanTwoNumbers() { + List numbers = Arrays.asList(1.0, 7.0); + assertEquals(5.0, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanMultipleNumbers() { + Vector numbers = new Vector<>(Arrays.asList(1.0, 2.5, 3.0, 7.5, 10.0)); + double expected = Math.sqrt(34.5); + assertEquals(expected, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanThreeNumbers() { + List numbers = Arrays.asList(3.0, 6.0, 9.0); + double expected = Math.sqrt(42.0); + assertEquals(expected, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanIdenticalNumbers() { + List numbers = Arrays.asList(5.0, 5.0, 5.0); + assertEquals(5.0, Means.quadratic(numbers), EPSILON); + } + + @Test + void testQuadraticMeanWithLinkedList() { + LinkedList numbers = new LinkedList<>(Arrays.asList(1.0, 5.0, 11.0)); + assertEquals(7.0, Means.quadratic(numbers), EPSILON); + } + // ========== Additional Edge Case Tests ========== @Test @@ -198,21 +245,25 @@ void testAllMeansConsistencyForIdenticalValues() { double arithmetic = Means.arithmetic(numbers); double geometric = Means.geometric(numbers); double harmonic = Means.harmonic(numbers); + double quadratic = Means.quadratic(numbers); assertEquals(7.5, arithmetic, EPSILON); assertEquals(7.5, geometric, EPSILON); assertEquals(7.5, harmonic, EPSILON); + assertEquals(7.5, quadratic, EPSILON); } @Test void testMeansRelationship() { - // For positive numbers, harmonic mean ≤ geometric mean ≤ arithmetic mean + // For positive numbers, harmonic mean ≤ geometric mean ≤ arithmetic mean ≤ quadratic 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); + double quadratic = Means.quadratic(numbers); assertTrue(harmonic <= geometric, "Harmonic mean should be ≤ geometric mean"); assertTrue(geometric <= arithmetic, "Geometric mean should be ≤ arithmetic mean"); + assertTrue(arithmetic <= quadratic, "Arithmetic mean should be ≤ quadratic mean"); } } diff --git a/src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java b/src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java index 3fe58dadf8a5..1ee437b190c5 100644 --- a/src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java +++ b/src/test/java/com/thealgorithms/maths/NthUglyNumberTest.java @@ -48,22 +48,22 @@ public void testGetWithSameObject() { var uglyNumbers = new NthUglyNumber(new int[] {7, 2, 5, 3}); for (final var tc : testCases.entrySet()) { - assertEquals(uglyNumbers.get(tc.getKey()), tc.getValue()); + assertEquals(tc.getValue(), uglyNumbers.get(tc.getKey())); } - assertEquals(uglyNumbers.get(999), 385875); + assertEquals(385875, uglyNumbers.get(999)); } @Test public void testGetWithBase1() { var uglyNumbers = new NthUglyNumber(new int[] {1}); - assertEquals(uglyNumbers.get(10), 1); + assertEquals(1, uglyNumbers.get(10)); } @Test public void testGetWithBase2() { var uglyNumbers = new NthUglyNumber(new int[] {2}); - assertEquals(uglyNumbers.get(5), 32); + assertEquals(32, uglyNumbers.get(5)); } @Test diff --git a/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java b/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.java new file mode 100644 index 000000000000..b9d7f04b4d0b --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/PadovanSequenceTest.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 org.junit.jupiter.api.Test; + +/** + * @author Vraj Prajapati (@Rosander0) + */ +public class PadovanSequenceTest { + + @Test + public void testBaseCase() { + assertEquals(1, PadovanSequence.padovan(0)); + assertEquals(1, PadovanSequence.padovan(1)); + assertEquals(1, PadovanSequence.padovan(2)); + } + + @Test + public void testKnownValues() { + assertEquals(2, PadovanSequence.padovan(3)); + assertEquals(2, PadovanSequence.padovan(4)); + assertEquals(3, PadovanSequence.padovan(5)); + assertEquals(4, PadovanSequence.padovan(6)); + assertEquals(5, PadovanSequence.padovan(7)); + assertEquals(7, PadovanSequence.padovan(8)); + assertEquals(9, PadovanSequence.padovan(9)); + assertEquals(12, PadovanSequence.padovan(10)); + } + + @Test + public void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> PadovanSequence.padovan(-1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/PalindromeNumberTest.java b/src/test/java/com/thealgorithms/maths/PalindromeNumberTest.java index a70100c0b913..4e4bd85d07b5 100644 --- a/src/test/java/com/thealgorithms/maths/PalindromeNumberTest.java +++ b/src/test/java/com/thealgorithms/maths/PalindromeNumberTest.java @@ -25,6 +25,6 @@ public void testNumbersAreNotPalindromes() { @Test public void testIfNegativeInputThenExceptionExpected() { IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> PalindromeNumber.isPalindrome(-1)); - Assertions.assertEquals(exception.getMessage(), "Input parameter must not be negative!"); + Assertions.assertEquals("Input parameter must not be negative!", exception.getMessage()); } } diff --git a/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java b/src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java similarity index 98% rename from src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java rename to src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java index 130cd19b47b1..2405da558700 100644 --- a/src/test/java/com/thealgorithms/misc/PalindromePrimeTest.java +++ b/src/test/java/com/thealgorithms/maths/PalindromePrimeTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.misc; +package com.thealgorithms.maths; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/com/thealgorithms/maths/ParseIntegerTest.java b/src/test/java/com/thealgorithms/maths/ParseIntegerTest.java index 7649e21eb231..a9b78be88042 100644 --- a/src/test/java/com/thealgorithms/maths/ParseIntegerTest.java +++ b/src/test/java/com/thealgorithms/maths/ParseIntegerTest.java @@ -14,13 +14,13 @@ public class ParseIntegerTest { @Test public void testNullInput() { IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> ParseInteger.parseInt(null)); - Assertions.assertEquals(exception.getMessage(), NULL_PARAMETER_MESSAGE); + Assertions.assertEquals(NULL_PARAMETER_MESSAGE, exception.getMessage()); } @Test public void testEmptyInput() { IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, () -> ParseInteger.parseInt("")); - Assertions.assertEquals(exception.getMessage(), EMPTY_PARAMETER_MESSAGE); + Assertions.assertEquals(EMPTY_PARAMETER_MESSAGE, exception.getMessage()); } @Test diff --git a/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java b/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java new file mode 100644 index 000000000000..0ec476dc0641 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/PerrinNumberTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.maths; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class PerrinNumberTest { + + @Test + public void testBaseCases() { + assertEquals(3, PerrinNumber.perrin(0)); + assertEquals(0, PerrinNumber.perrin(1)); + assertEquals(2, PerrinNumber.perrin(2)); + } + + @Test + public void testKnownValues() { + assertEquals(3, PerrinNumber.perrin(3)); + assertEquals(2, PerrinNumber.perrin(4)); + assertEquals(5, PerrinNumber.perrin(5)); + assertEquals(5, PerrinNumber.perrin(6)); + assertEquals(7, PerrinNumber.perrin(7)); + assertEquals(10, PerrinNumber.perrin(8)); + assertEquals(12, PerrinNumber.perrin(9)); + assertEquals(17, PerrinNumber.perrin(10)); + } + + @Test + public void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> PerrinNumber.perrin(-1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/PowerOfFourTest.java b/src/test/java/com/thealgorithms/maths/PowerOfFourTest.java new file mode 100644 index 000000000000..c91f8b3cf1b5 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/PowerOfFourTest.java @@ -0,0 +1,36 @@ +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; + +class PowerOfFourTest { + + @Test + void testPowersOfFour() { + assertTrue(PowerOfFour.isPowerOfFour(1)); + assertTrue(PowerOfFour.isPowerOfFour(4)); + assertTrue(PowerOfFour.isPowerOfFour(16)); + assertTrue(PowerOfFour.isPowerOfFour(64)); + assertTrue(PowerOfFour.isPowerOfFour(256)); + assertTrue(PowerOfFour.isPowerOfFour(1024)); + } + + @Test + void testNonPowersOfFour() { + assertFalse(PowerOfFour.isPowerOfFour(2)); + assertFalse(PowerOfFour.isPowerOfFour(3)); + assertFalse(PowerOfFour.isPowerOfFour(5)); + assertFalse(PowerOfFour.isPowerOfFour(8)); + assertFalse(PowerOfFour.isPowerOfFour(15)); + assertFalse(PowerOfFour.isPowerOfFour(32)); + } + + @Test + void testEdgeCases() { + assertFalse(PowerOfFour.isPowerOfFour(0)); + assertFalse(PowerOfFour.isPowerOfFour(-1)); + assertFalse(PowerOfFour.isPowerOfFour(-4)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java b/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java index a2046511ddf5..a6552d56783c 100644 --- a/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java +++ b/src/test/java/com/thealgorithms/maths/QuadraticEquationSolverTest.java @@ -14,10 +14,10 @@ public void testSolveEquationRealRoots() { double c = 1.9; ComplexNumber[] roots = quadraticEquationSolver.solveEquation(a, b, c); - Assertions.assertEquals(roots.length, 2); - Assertions.assertEquals(roots[0].real, -0.27810465435684306); + Assertions.assertEquals(2, roots.length, 2); + Assertions.assertEquals(-0.27810465435684306, roots[0].real); Assertions.assertNull(roots[0].imaginary); - Assertions.assertEquals(roots[1].real, -1.6266572504050616); + Assertions.assertEquals(-1.6266572504050616, roots[1].real); Assertions.assertNull(roots[1].imaginary); } @@ -29,8 +29,8 @@ public void testSolveEquationEqualRoots() { double c = 1; ComplexNumber[] roots = quadraticEquationSolver.solveEquation(a, b, c); - Assertions.assertEquals(roots.length, 1); - Assertions.assertEquals(roots[0].real, -1); + Assertions.assertEquals(1, roots.length); + Assertions.assertEquals(-1, roots[0].real); } @Test @@ -41,10 +41,10 @@ public void testSolveEquationComplexRoots() { double c = 5.6; ComplexNumber[] roots = quadraticEquationSolver.solveEquation(a, b, c); - Assertions.assertEquals(roots.length, 2); - Assertions.assertEquals(roots[0].real, -0.8695652173913044); - Assertions.assertEquals(roots[0].imaginary, 1.2956229935435948); - Assertions.assertEquals(roots[1].real, -0.8695652173913044); - Assertions.assertEquals(roots[1].imaginary, -1.2956229935435948); + Assertions.assertEquals(2, roots.length); + Assertions.assertEquals(-0.8695652173913044, roots[0].real); + Assertions.assertEquals(1.2956229935435948, roots[0].imaginary); + Assertions.assertEquals(-0.8695652173913044, roots[1].real); + Assertions.assertEquals(-1.2956229935435948, roots[1].imaginary); } } diff --git a/src/test/java/com/thealgorithms/maths/SecondMinMaxTest.java b/src/test/java/com/thealgorithms/maths/SecondMinMaxTest.java index c744614e5cfa..c5d47f2213a9 100644 --- a/src/test/java/com/thealgorithms/maths/SecondMinMaxTest.java +++ b/src/test/java/com/thealgorithms/maths/SecondMinMaxTest.java @@ -29,19 +29,19 @@ public TestCase(final int[] inInputArray, final int inSecondMin, final int inSec @Test public void testForEmptyInputArray() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> SecondMinMax.findSecondMin(new int[] {})); - assertEquals(exception.getMessage(), EXP_MSG_ARR_LEN_LESS_2); + assertEquals(EXP_MSG_ARR_LEN_LESS_2, exception.getMessage()); } @Test public void testForArrayWithSingleElement() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> SecondMinMax.findSecondMax(new int[] {1})); - assertEquals(exception.getMessage(), EXP_MSG_ARR_LEN_LESS_2); + assertEquals(EXP_MSG_ARR_LEN_LESS_2, exception.getMessage()); } @Test public void testForArrayWithSameElements() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> SecondMinMax.findSecondMin(new int[] {1, 1, 1, 1})); - assertEquals(exception.getMessage(), EXP_MSG_ARR_SAME_ELE); + assertEquals(EXP_MSG_ARR_SAME_ELE, exception.getMessage()); } @ParameterizedTest diff --git a/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java b/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java index ebbd5df712fc..5d491a493ee7 100644 --- a/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java +++ b/src/test/java/com/thealgorithms/maths/SieveOfEratosthenesTest.java @@ -1,46 +1,64 @@ package com.thealgorithms.maths; -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 static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; +/** + * Test cases for Sieve of Eratosthenes algorithm + * + * @author Navadeep0007 + */ class SieveOfEratosthenesTest { + + @Test + void testPrimesUpTo10() { + List expected = Arrays.asList(2, 3, 5, 7); + assertEquals(expected, SieveOfEratosthenes.findPrimes(10)); + } + + @Test + void testPrimesUpTo30() { + List expected = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29); + assertEquals(expected, SieveOfEratosthenes.findPrimes(30)); + } + @Test - public void testfFindPrimesTill1() { - assertArrayEquals(new int[] {}, SieveOfEratosthenes.findPrimesTill(1)); + void testPrimesUpTo2() { + List expected = Arrays.asList(2); + assertEquals(expected, SieveOfEratosthenes.findPrimes(2)); } @Test - public void testfFindPrimesTill2() { - assertArrayEquals(new int[] {2}, SieveOfEratosthenes.findPrimesTill(2)); + void testPrimesUpTo1() { + assertTrue(SieveOfEratosthenes.findPrimes(1).isEmpty()); } @Test - public void testfFindPrimesTill4() { - var primesTill4 = new int[] {2, 3}; - assertArrayEquals(primesTill4, SieveOfEratosthenes.findPrimesTill(3)); - assertArrayEquals(primesTill4, SieveOfEratosthenes.findPrimesTill(4)); + void testPrimesUpTo0() { + assertTrue(SieveOfEratosthenes.findPrimes(0).isEmpty()); } @Test - public void testfFindPrimesTill40() { - var primesTill40 = new int[] {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}; - assertArrayEquals(primesTill40, SieveOfEratosthenes.findPrimesTill(37)); - assertArrayEquals(primesTill40, SieveOfEratosthenes.findPrimesTill(38)); - assertArrayEquals(primesTill40, SieveOfEratosthenes.findPrimesTill(39)); - assertArrayEquals(primesTill40, SieveOfEratosthenes.findPrimesTill(40)); + void testNegativeInput() { + assertThrows(IllegalArgumentException.class, () -> { SieveOfEratosthenes.findPrimes(-1); }); } @Test - public void testfFindPrimesTill240() { - var primesTill240 = new int[] {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239}; - assertArrayEquals(primesTill240, SieveOfEratosthenes.findPrimesTill(239)); - assertArrayEquals(primesTill240, SieveOfEratosthenes.findPrimesTill(240)); + void testCountPrimes() { + assertEquals(4, SieveOfEratosthenes.countPrimes(10)); + assertEquals(25, SieveOfEratosthenes.countPrimes(100)); } @Test - public void testFindPrimesTillThrowsExceptionForNonPositiveInput() { - assertThrows(IllegalArgumentException.class, () -> SieveOfEratosthenes.findPrimesTill(0)); + void testLargeNumber() { + List primes = SieveOfEratosthenes.findPrimes(1000); + assertEquals(168, primes.size()); // There are 168 primes up to 1000 + assertEquals(2, primes.get(0)); // First prime + assertEquals(997, primes.get(primes.size() - 1)); // Last prime up to 1000 } } diff --git a/src/test/java/com/thealgorithms/maths/SmithNumberTest.java b/src/test/java/com/thealgorithms/maths/SmithNumberTest.java new file mode 100644 index 000000000000..4e2ba0b88e33 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SmithNumberTest.java @@ -0,0 +1,22 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class SmithNumberTest { + + @ParameterizedTest + @CsvSource({"4", "22", "121", "562", "985", "4937775"}) + void positiveSmithNumbersTest(int n) { + assertTrue(SmithNumber.isSmithNumber(n)); + } + + @ParameterizedTest + @CsvSource({"2", "11", "100", "550", "999", "1234557"}) + void negativeSmithNumbersTest(int n) { + assertFalse(SmithNumber.isSmithNumber(n)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/SociableNumberTest.java b/src/test/java/com/thealgorithms/maths/SociableNumberTest.java new file mode 100644 index 000000000000..b4877cd761a7 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SociableNumberTest.java @@ -0,0 +1,56 @@ +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.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link SociableNumber}. + * + * @author Vraj Prajapati (@Rosander0) + */ +public class SociableNumberTest { + + @Test + public void testSumOfProperDivisorsEdgeCases() { + assertEquals(0, SociableNumber.sumOfProperDivisors(0)); + assertEquals(0, SociableNumber.sumOfProperDivisors(-5)); + assertEquals(0, SociableNumber.sumOfProperDivisors(1)); + assertEquals(1, SociableNumber.sumOfProperDivisors(2)); + } + + @Test + public void testSociableCycleOfLengthFive() { + assertTrue(SociableNumber.isSociable(12496, 5)); + } + + @Test + public void testAmicableNumbersAreSociableOfLengthTwo() { + assertTrue(SociableNumber.isSociable(220, 2)); + assertTrue(SociableNumber.isSociable(284, 2)); + } + + @Test + public void testNonSociableNumbers() { + assertFalse(SociableNumber.isSociable(12, 5)); + assertFalse(SociableNumber.isSociable(10, 3)); + } + + @Test + public void testEarlyCycleReturn() { + // 220 has cycle length 2; requesting a different length should return + // false because it returns to the start too early. + assertFalse(SociableNumber.isSociable(220, 3)); + assertFalse(SociableNumber.isSociable(284, 4)); + assertFalse(SociableNumber.isSociable(12496, 3)); + } + + @Test + public void testInvalidInputs() { + assertFalse(SociableNumber.isSociable(0, 5)); + assertFalse(SociableNumber.isSociable(-1, 5)); + assertFalse(SociableNumber.isSociable(220, 1)); + } +} diff --git a/src/test/java/com/thealgorithms/maths/StandardDeviationTest.java b/src/test/java/com/thealgorithms/maths/StandardDeviationTest.java index 2c10d2d14f3e..4716d389a4ca 100644 --- a/src/test/java/com/thealgorithms/maths/StandardDeviationTest.java +++ b/src/test/java/com/thealgorithms/maths/StandardDeviationTest.java @@ -8,19 +8,19 @@ public class StandardDeviationTest { @Test void test1() { double[] t1 = new double[] {1, 1, 1, 1, 1}; - Assertions.assertEquals(StandardDeviation.stdDev(t1), 0.0); + Assertions.assertEquals(0.0, StandardDeviation.stdDev(t1)); } @Test void test2() { double[] t2 = new double[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - Assertions.assertEquals(StandardDeviation.stdDev(t2), 2.8722813232690143); + Assertions.assertEquals(2.8722813232690143, StandardDeviation.stdDev(t2)); } @Test void test3() { double[] t3 = new double[] {1.1, 8.5, 20.3, 2.4, 6.2}; - Assertions.assertEquals(StandardDeviation.stdDev(t3), 6.8308125431752265); + Assertions.assertEquals(6.8308125431752265, StandardDeviation.stdDev(t3)); } @Test @@ -32,6 +32,6 @@ void test4() { 100.00045, 56.7, }; - Assertions.assertEquals(StandardDeviation.stdDev(t4), 38.506117353865775); + Assertions.assertEquals(38.506117353865775, StandardDeviation.stdDev(t4)); } } diff --git a/src/test/java/com/thealgorithms/maths/StandardScoreTest.java b/src/test/java/com/thealgorithms/maths/StandardScoreTest.java index 436b1fd011c6..6858b87ad2c6 100644 --- a/src/test/java/com/thealgorithms/maths/StandardScoreTest.java +++ b/src/test/java/com/thealgorithms/maths/StandardScoreTest.java @@ -7,21 +7,21 @@ public class StandardScoreTest { @Test void test1() { - Assertions.assertEquals(StandardScore.zScore(2, 0, 5), 0.4); + Assertions.assertEquals(0.4, StandardScore.zScore(2, 0, 5)); } @Test void test2() { - Assertions.assertEquals(StandardScore.zScore(1, 1, 1), 0.0); + Assertions.assertEquals(0.0, StandardScore.zScore(1, 1, 1)); } @Test void test3() { - Assertions.assertEquals(StandardScore.zScore(2.5, 1.8, 0.7), 1.0); + Assertions.assertEquals(1.0, StandardScore.zScore(2.5, 1.8, 0.7)); } @Test void test4() { - Assertions.assertEquals(StandardScore.zScore(8.9, 3, 4.2), 1.4047619047619049); + Assertions.assertEquals(1.4047619047619049, StandardScore.zScore(8.9, 3, 4.2)); } } diff --git a/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java b/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java index 834fe61a049e..02b3f614ca9a 100644 --- a/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java +++ b/src/test/java/com/thealgorithms/maths/SumOfSquaresTest.java @@ -1,13 +1,12 @@ 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; /** * Test class for SumOfSquares - * - * @author BEASTSHRIRAM */ class SumOfSquaresTest { @@ -65,4 +64,11 @@ void testEdgeCases() { // Test edge case assertEquals(1, SumOfSquares.minSquares(0)); // 0^2 } + + @Test + void testNegativeInput() { + // Negative inputs should throw IllegalArgumentException + assertThrows(IllegalArgumentException.class, () -> SumOfSquares.minSquares(-1)); + assertThrows(IllegalArgumentException.class, () -> SumOfSquares.minSquares(-10)); + } } diff --git a/src/test/java/com/thealgorithms/maths/VolumeTest.java b/src/test/java/com/thealgorithms/maths/VolumeTest.java index 7cd0c6716147..c0b02d6ba28e 100644 --- a/src/test/java/com/thealgorithms/maths/VolumeTest.java +++ b/src/test/java/com/thealgorithms/maths/VolumeTest.java @@ -1,6 +1,6 @@ package com.thealgorithms.maths; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; @@ -10,30 +10,39 @@ public class VolumeTest { public void volume() { /* test cube */ - assertTrue(Volume.volumeCube(7) == 343.0); + assertEquals(343.0, Volume.volumeCube(7)); /* test cuboid */ - assertTrue(Volume.volumeCuboid(2, 5, 7) == 70.0); + assertEquals(70.0, Volume.volumeCuboid(2, 5, 7)); /* test sphere */ - assertTrue(Volume.volumeSphere(7) == 1436.7550402417319); + assertEquals(1436.7550402417319, Volume.volumeSphere(7)); /* test cylinder */ - assertTrue(Volume.volumeCylinder(3, 7) == 197.92033717615698); + assertEquals(197.92033717615698, Volume.volumeCylinder(3, 7)); /* test hemisphere */ - assertTrue(Volume.volumeHemisphere(7) == 718.3775201208659); + assertEquals(718.3775201208659, Volume.volumeHemisphere(7)); /* test cone */ - assertTrue(Volume.volumeCone(3, 7) == 65.97344572538566); + assertEquals(65.97344572538566, Volume.volumeCone(3, 7)); /* test prism */ - assertTrue(Volume.volumePrism(10, 2) == 20.0); + assertEquals(20.0, Volume.volumePrism(10, 2)); /* test pyramid */ - assertTrue(Volume.volumePyramid(10, 3) == 10.0); + assertEquals(10.0, Volume.volumePyramid(10, 3)); /* test frustum */ - assertTrue(Volume.volumeFrustumOfCone(3, 5, 7) == 359.188760060433); + assertEquals(359.188760060433, Volume.volumeFrustumOfCone(3, 5, 7)); + + /* test pyramid frustum */ + assertEquals(140.0, Volume.volumeFrustumOfPyramid(6, 24, 10)); + + /* test torus */ + assertEquals(39.47841760435743, Volume.volumeTorus(2, 1)); + + /* test ellipsoid */ + assertEquals(25.1327412287183459, Volume.volumeEllipsoid(3, 2, 1)); } } diff --git a/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java b/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java new file mode 100644 index 000000000000..adc8fb717e57 --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/QRDecompositionTest.java @@ -0,0 +1,115 @@ +package com.thealgorithms.matrix; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class QRDecompositionTest { + + private static final double DELTA = 1e-9; + + @Test + public void testQRDecomposition2x2() { + double[][] matrix = {{12, -51}, {6, 167}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] q = qr.getQ(); + double[][] r = qr.getR(); + + double[][] reconstructed = multiplyMatrices(q, r); + for (int i = 0; i < matrix.length; i++) { + assertArrayEquals(matrix[i], reconstructed[i], DELTA); + } + } + + @Test + public void testQRDecomposition3x3() { + double[][] matrix = {{1, 1, 0}, {1, 0, 1}, {0, 1, 1}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] q = qr.getQ(); + double[][] r = qr.getR(); + + double[][] reconstructed = multiplyMatrices(q, r); + for (int i = 0; i < matrix.length; i++) { + assertArrayEquals(matrix[i], reconstructed[i], DELTA); + } + } + + @Test + public void testQROrthogonalColumns() { + double[][] matrix = {{1, 1, 0}, {1, 0, 1}, {0, 1, 1}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] q = qr.getQ(); + + for (int i = 0; i < q[0].length; i++) { + for (int j = i; j < q[0].length; j++) { + double dot = 0; + for (int k = 0; k < q.length; k++) { + dot += q[k][i] * q[k][j]; + } + if (i == j) { + assertArrayEquals(new double[] {1.0}, new double[] {dot}, DELTA); + } else { + assertArrayEquals(new double[] {0.0}, new double[] {dot}, DELTA); + } + } + } + } + + @Test + public void testRIsUpperTriangular() { + double[][] matrix = {{12, -51}, {6, 167}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] r = qr.getR(); + + for (int i = 1; i < r.length; i++) { + for (int j = 0; j < i; j++) { + assertArrayEquals(new double[] {0.0}, new double[] {r[i][j]}, DELTA); + } + } + } + + @Test + public void testQRDecompositionIdentityMatrix() { + double[][] matrix = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; + QRDecomposition.QR qr = QRDecomposition.decompose(matrix); + double[][] q = qr.getQ(); + double[][] r = qr.getR(); + + for (int i = 0; i < matrix.length; i++) { + assertArrayEquals(matrix[i], q[i], DELTA); + assertArrayEquals(matrix[i], r[i], DELTA); + } + } + + @Test + public void testQRDecompositionRankDeficientThrows() { + double[][] matrix = {{1, 2}, {2, 4}}; + assertThrows(ArithmeticException.class, () -> QRDecomposition.decompose(matrix)); + } + + @Test + public void testQRDecompositionNullMatrixThrows() { + assertThrows(IllegalArgumentException.class, () -> QRDecomposition.decompose(null)); + } + + @Test + public void testQRDecompositionEmptyMatrixThrows() { + assertThrows(IllegalArgumentException.class, () -> QRDecomposition.decompose(new double[0][0])); + } + + private static double[][] multiplyMatrices(double[][] a, double[][] b) { + int m = a.length; + int n = b[0].length; + int k = a[0].length; + double[][] result = new double[m][n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + for (int p = 0; p < k; p++) { + result[i][j] += a[i][p] * b[p][j]; + } + } + } + return result; + } +} diff --git a/src/test/java/com/thealgorithms/matrix/StochasticMatrixTest.java b/src/test/java/com/thealgorithms/matrix/StochasticMatrixTest.java new file mode 100644 index 000000000000..1bba918dadac --- /dev/null +++ b/src/test/java/com/thealgorithms/matrix/StochasticMatrixTest.java @@ -0,0 +1,28 @@ +package com.thealgorithms.matrix; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class StochasticMatrixTest { + + @Test + void testRowStochasticMatrix() { + double[][] matrix = {{0.2, 0.5, 0.3}, {0.1, 0.6, 0.3}}; + assertTrue(StochasticMatrix.isRowStochastic(matrix)); + assertFalse(StochasticMatrix.isColumnStochastic(matrix)); + } + + @Test + void testColumnStochasticMatrix() { + double[][] matrix = {{0.4, 0.2}, {0.6, 0.8}}; + assertTrue(StochasticMatrix.isColumnStochastic(matrix)); + } + + @Test + void testInvalidMatrix() { + double[][] matrix = {{0.5, -0.5}, {0.5, 1.5}}; + assertFalse(StochasticMatrix.isRowStochastic(matrix)); + } +} diff --git a/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java b/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java index f41953035846..c4a74af0ba8b 100644 --- a/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java +++ b/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java @@ -17,7 +17,7 @@ public class MedianOfRunningArrayTest { public void testWhenInvalidInoutProvidedShouldThrowException() { var stream = new MedianOfRunningArrayInteger(); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, stream::getMedian); - assertEquals(exception.getMessage(), EXCEPTION_MESSAGE); + assertEquals(EXCEPTION_MESSAGE, exception.getMessage()); } @Test diff --git a/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java b/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java index 915b83e376b6..c1adafa18d9f 100644 --- a/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java +++ b/src/test/java/com/thealgorithms/misc/ShuffleArrayTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.misc; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -67,7 +68,7 @@ void testShuffleRetainsElements() { ShuffleArray.shuffle(arr); // Check that the shuffled array contains the same elements - assertTrue(arr.length == 5); + assertEquals(5, arr.length); for (int i = 1; i <= 5; i++) { assertTrue(contains(arr, i)); } diff --git a/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java b/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java deleted file mode 100644 index 3b657e441b1c..000000000000 --- a/src/test/java/com/thealgorithms/others/NewManShanksPrimeTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.thealgorithms.others; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.thealgorithms.dynamicprogramming.NewManShanksPrime; -import org.junit.jupiter.api.Test; - -public class NewManShanksPrimeTest { - - @Test - void testOne() { - assertTrue(NewManShanksPrime.nthManShanksPrime(1, 1)); - } - - @Test - void testTwo() { - assertTrue(NewManShanksPrime.nthManShanksPrime(2, 3)); - } - - @Test - void testThree() { - assertTrue(NewManShanksPrime.nthManShanksPrime(3, 7)); - } - - @Test - void testFour() { - assertTrue(NewManShanksPrime.nthManShanksPrime(4, 17)); - } - - @Test - void testFive() { - assertTrue(NewManShanksPrime.nthManShanksPrime(5, 41)); - } - - @Test - void testSix() { - assertTrue(NewManShanksPrime.nthManShanksPrime(6, 99)); - } - - @Test - void testSeven() { - assertTrue(NewManShanksPrime.nthManShanksPrime(7, 239)); - } - - @Test - void testEight() { - assertTrue(NewManShanksPrime.nthManShanksPrime(8, 577)); - } -} diff --git a/src/test/java/com/thealgorithms/others/PasswordGenTest.java b/src/test/java/com/thealgorithms/others/PasswordGenTest.java index 76492556e75f..4dcdf6b9cf4f 100644 --- a/src/test/java/com/thealgorithms/others/PasswordGenTest.java +++ b/src/test/java/com/thealgorithms/others/PasswordGenTest.java @@ -17,7 +17,7 @@ public void failGenerationWithSameMinMaxLengthTest() { @Test public void generateOneCharacterPassword() { String tempPassword = PasswordGen.generatePassword(1, 2); - assertTrue(tempPassword.length() == 1); + assertEquals(1, tempPassword.length()); } @Test diff --git a/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java b/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java deleted file mode 100644 index 669f928cd247..000000000000 --- a/src/test/java/com/thealgorithms/others/cn/HammingDistanceTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.thealgorithms.others.cn; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -public class HammingDistanceTest { - @Test - public void checkForDifferentBits() { - int answer = HammingDistance.compute("000", "011"); - Assertions.assertThat(answer).isEqualTo(2); - } - - /* - - 1 0 1 0 1 - 1 1 1 1 0 - ---------- - 0 1 0 1 1 - - - */ - @Test - public void checkForDifferentBitsLength() { - int answer = HammingDistance.compute("10101", "11110"); - Assertions.assertThat(answer).isEqualTo(3); - } - - @Test - public void checkForSameBits() { - String someBits = "111"; - int answer = HammingDistance.compute(someBits, someBits); - Assertions.assertThat(answer).isEqualTo(0); - } - - @Test - public void checkForLongDataBits() { - int answer = HammingDistance.compute("10010101101010000100110100", "00110100001011001100110101"); - Assertions.assertThat(answer).isEqualTo(7); - } - - @Test - public void mismatchDataBits() { - Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("100010", "00011"); }); - - Assertions.assertThat(ex.getMessage()).contains("must have the same length"); - } - - @Test - public void mismatchDataBits2() { - Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("1", "11"); }); - - Assertions.assertThat(ex.getMessage()).contains("must have the same length"); - } - - @Test - public void checkForLongDataBitsSame() { - String someBits = "10010101101010000100110100"; - int answer = HammingDistance.compute(someBits, someBits); - Assertions.assertThat(answer).isEqualTo(0); - } - - @Test - public void checkForEmptyInput() { - String someBits = ""; - int answer = HammingDistance.compute(someBits, someBits); - Assertions.assertThat(answer).isEqualTo(0); - } - - @Test - public void checkForInputOfLength1() { - String someBits = "0"; - int answer = HammingDistance.compute(someBits, someBits); - Assertions.assertThat(answer).isEqualTo(0); - } - - @Test - public void computeThrowsExceptionWhenInputsAreNotBitStrs() { - Exception ex = org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class, () -> { HammingDistance.compute("1A", "11"); }); - - Assertions.assertThat(ex.getMessage()).contains("must be a binary string"); - } -} diff --git a/src/test/java/com/thealgorithms/physics/RelativityTest.java b/src/test/java/com/thealgorithms/physics/RelativityTest.java new file mode 100644 index 000000000000..44c17bdbd40f --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/RelativityTest.java @@ -0,0 +1,73 @@ +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; + +/** + * Unit tests for the Relativity utility class. + */ +final class RelativityTest { + + // A small tolerance (delta) for comparing floating-point numbers + private static final double DELTA = 1e-6; + private static final double C = Relativity.SPEED_OF_LIGHT; + + @Test + @DisplayName("Test the gamma parameter") + void testGamma() { + double myGamma = Relativity.gamma(0.6 * C); + assertEquals(1.25, myGamma, DELTA); + } + + @Test + @DisplayName("Test the length contraction") + void testLengthContraction() { + double myLength = Relativity.lengthContraction(5.0, 0.8 * C); + assertEquals(3.0, myLength, DELTA); + } + + @Test + @DisplayName("Test the time dilation") + void testTimeDilation() { + double myTime = Relativity.timeDilation(4.0, 0.6 * C); + assertEquals(5.0, myTime, DELTA); + } + + @Test + @DisplayName("Test the velocity addition in the same direction") + void testVelocityAdditionSameDirection() { + double myVelocity = Relativity.velocityAddition(0.8 * C, 0.75 * C); + assertEquals(0.125 * C, myVelocity, DELTA); + } + + @Test + @DisplayName("Test the velocity addition in different directions") + void testVelocityAdditionDifferentDirections() { + double myVelocity = Relativity.velocityAddition(0.8 * C, -0.75 * C); + assertEquals(0.96875 * C, myVelocity, DELTA); + } + + @Test + @DisplayName("Test the velocity addition with the speed of light") + void testVelocityAdditionWithSpeedOfLight() { + double myVelocity = Relativity.velocityAddition(C, 0.7 * C); + assertEquals(C, myVelocity, DELTA); + } + + @Test + @DisplayName("Test invalid inputs throw exception") + void testInvalidOrbitalVelocityInputs() { + assertThrows(IllegalArgumentException.class, () -> Relativity.gamma(1.2 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.gamma(-C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.lengthContraction(-1.0, 0.6 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.lengthContraction(1.0, 1.5 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.timeDilation(-5.0, -0.8 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.timeDilation(5.0, C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(0.3 * C, -C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(1.4 * C, 0.2 * C)); + assertThrows(IllegalArgumentException.class, () -> Relativity.velocityAddition(-0.4 * C, 1.2 * C)); + } +} diff --git a/src/test/java/com/thealgorithms/physics/SnellLawTest.java b/src/test/java/com/thealgorithms/physics/SnellLawTest.java new file mode 100644 index 000000000000..ddd5fb1d5af7 --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/SnellLawTest.java @@ -0,0 +1,41 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class SnellLawTest { + + @Test + public void testRefractedAngle() { + double n1 = 1.0; // air + double n2 = 1.5; // glass + double theta1 = Math.toRadians(30); + + double theta2 = SnellLaw.refractedAngle(n1, n2, theta1); + + double expected = Math.asin(n1 / n2 * Math.sin(theta1)); + + assertEquals(expected, theta2, 1e-12); + } + + @Test + public void testTotalInternalReflection() { + double n1 = 1.5; + double n2 = 1.0; + double theta1 = Math.toRadians(60); // large angle + + assertThrows(IllegalArgumentException.class, () -> SnellLaw.refractedAngle(n1, n2, theta1)); + } + + @Test + public void testNoTotalInternalReflectionAtLowAngles() { + double n1 = 1.5; + double n2 = 1.0; + double theta1 = Math.toRadians(10); + + assertDoesNotThrow(() -> SnellLaw.refractedAngle(n1, n2, theta1)); + } +} diff --git a/src/test/java/com/thealgorithms/physics/ThinLensTest.java b/src/test/java/com/thealgorithms/physics/ThinLensTest.java new file mode 100644 index 000000000000..cf7e94676819 --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/ThinLensTest.java @@ -0,0 +1,19 @@ +package com.thealgorithms.physics; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class ThinLensTest { + + @Test + void testConvexLensRealImage() { + double v = ThinLens.imageDistance(10, 20); + assertEquals(20, v, 1e-6); + } + + @Test + void testMagnification() { + assertEquals(2.0, ThinLens.magnification(20, 10), 1e-6); + } +} diff --git a/src/test/java/com/thealgorithms/prefixsum/DifferenceArrayTest.java b/src/test/java/com/thealgorithms/prefixsum/DifferenceArrayTest.java new file mode 100644 index 000000000000..88a480f25f1a --- /dev/null +++ b/src/test/java/com/thealgorithms/prefixsum/DifferenceArrayTest.java @@ -0,0 +1,110 @@ +package com.thealgorithms.prefixsum; + +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; + +class DifferenceArrayTest { + + @Test + void testStandardRangeUpdate() { + int[] input = {10, 20, 30, 40, 50}; + DifferenceArray da = new DifferenceArray(input); + + da.update(1, 3, 5); + + long[] expected = {10, 25, 35, 45, 50}; + assertArrayEquals(expected, da.getResultArray()); + } + + @Test + void testMultipleOverlappingUpdates() { + int[] input = {10, 10, 10, 10, 10}; + DifferenceArray da = new DifferenceArray(input); + + da.update(0, 2, 10); + da.update(2, 4, 20); + + long[] expected = {20, 20, 40, 30, 30}; + assertArrayEquals(expected, da.getResultArray()); + } + + @Test + void testIntegerOverflowSafety() { + int[] input = {Integer.MAX_VALUE, 100}; + DifferenceArray da = new DifferenceArray(input); + + da.update(0, 0, 100); + + long[] result = da.getResultArray(); + long expectedVal = (long) Integer.MAX_VALUE + 100; + + assertEquals(expectedVal, result[0]); + } + + @Test + void testFullRangeUpdate() { + int[] input = {1, 2, 3}; + DifferenceArray da = new DifferenceArray(input); + + da.update(0, 2, 100); + + long[] expected = {101, 102, 103}; + assertArrayEquals(expected, da.getResultArray()); + } + + @Test + void testBoundaryWriteOptimization() { + int[] input = {5, 5}; + DifferenceArray da = new DifferenceArray(input); + + da.update(1, 1, 5); + + long[] expected = {5, 10}; + + assertArrayEquals(expected, da.getResultArray()); + } + + @Test + void testLargeMassiveUpdate() { + int[] input = {0}; + DifferenceArray da = new DifferenceArray(input); + + int iterations = 100000; + for (int i = 0; i < iterations; i++) { + da.update(0, 0, 1); + } + + assertEquals(100000L, da.getResultArray()[0]); + } + + @Test + void testNullInputThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new DifferenceArray(null)); + } + + @Test + void testEmptyInputThrowsException() { + assertThrows(IllegalArgumentException.class, () -> new DifferenceArray(new int[] {})); + } + + @Test + void testInvalidRangeNegativeIndex() { + DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3}); + assertThrows(IllegalArgumentException.class, () -> da.update(-1, 1, 5)); + } + + @Test + void testInvalidRangeOutOfBounds() { + DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3}); + assertThrows(IllegalArgumentException.class, () -> da.update(0, 3, 5)); + } + + @Test + void testInvalidRangeStartGreaterThanEnd() { + DifferenceArray da = new DifferenceArray(new int[] {1, 2, 3}); + assertThrows(IllegalArgumentException.class, () -> da.update(2, 1, 5)); + } +} diff --git a/src/test/java/com/thealgorithms/prefixsum/PrefixSum2DTest.java b/src/test/java/com/thealgorithms/prefixsum/PrefixSum2DTest.java new file mode 100644 index 000000000000..87feff859356 --- /dev/null +++ b/src/test/java/com/thealgorithms/prefixsum/PrefixSum2DTest.java @@ -0,0 +1,92 @@ +package com.thealgorithms.prefixsum; + +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 PrefixSum2DTest { + + @Test + @DisplayName("Test basic 3x3 square matrix") + void testStandardSquare() { + int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + PrefixSum2D ps = new PrefixSum2D(matrix); + + // Sum of top-left 2x2: {1,2, 4,5} -> 12 + assertEquals(12L, ps.sumRegion(0, 0, 1, 1)); + // Sum of bottom-right 2x2: {5,6, 8,9} -> 28 + assertEquals(28L, ps.sumRegion(1, 1, 2, 2)); + // Full matrix -> 45 + assertEquals(45L, ps.sumRegion(0, 0, 2, 2)); + } + + @Test + @DisplayName("Test rectangular matrix (more cols than rows)") + void testRectangularWide() { + int[][] matrix = {{1, 1, 1, 1}, {2, 2, 2, 2}}; + PrefixSum2D ps = new PrefixSum2D(matrix); + + // Sum of first 3 columns of both rows -> (1*3) + (2*3) = 9 + assertEquals(9L, ps.sumRegion(0, 0, 1, 2)); + } + + @Test + @DisplayName("Test rectangular matrix (more rows than cols)") + void testRectangularTall() { + int[][] matrix = {{1}, {2}, {3}, {4}}; + PrefixSum2D ps = new PrefixSum2D(matrix); + + // Sum of middle two elements -> 2+3 = 5 + assertEquals(5L, ps.sumRegion(1, 0, 2, 0)); + } + + @Test + @DisplayName("Test single element matrix") + void testSingleElement() { + int[][] matrix = {{100}}; + PrefixSum2D ps = new PrefixSum2D(matrix); + + assertEquals(100L, ps.sumRegion(0, 0, 0, 0)); + } + + @Test + @DisplayName("Test large numbers for overflow (Integer -> Long)") + void testLargeNumbers() { + // 2 billion. Two of these sum to > MAX_INT + int val = 2_000_000_000; + int[][] matrix = {{val, val}, {val, val}}; + PrefixSum2D ps = new PrefixSum2D(matrix); + + // 4 * 2B = 8 Billion + assertEquals(8_000_000_000L, ps.sumRegion(0, 0, 1, 1)); + } + + @Test + @DisplayName("Test invalid inputs") + void testInvalidInputs() { + assertThrows(IllegalArgumentException.class, () -> new PrefixSum2D(null)); + assertThrows(IllegalArgumentException.class, () -> new PrefixSum2D(new int[][] {})); // empty + assertThrows(IllegalArgumentException.class, () -> new PrefixSum2D(new int[][] {{}})); // empty row + } + + @Test + @DisplayName("Test invalid query ranges") + void testInvalidRanges() { + int[][] matrix = {{1, 2}, {3, 4}}; + PrefixSum2D ps = new PrefixSum2D(matrix); + + // Negative indices + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(-1, 0, 0, 0)); + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(0, -1, 0, 0)); + + // Out of bounds + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(0, 0, 2, 0)); // row2 too big + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(0, 0, 0, 2)); // col2 too big + + // Inverted ranges (start > end) + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(1, 0, 0, 0)); // row1 > row2 + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRegion(0, 1, 0, 0)); // col1 > col2 + } +} diff --git a/src/test/java/com/thealgorithms/prefixsum/PrefixSumTest.java b/src/test/java/com/thealgorithms/prefixsum/PrefixSumTest.java new file mode 100644 index 000000000000..a421b62e9306 --- /dev/null +++ b/src/test/java/com/thealgorithms/prefixsum/PrefixSumTest.java @@ -0,0 +1,80 @@ +package com.thealgorithms.prefixsum; + +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 PrefixSumTest { + + @Test + @DisplayName("Test basic sum with positive integers") + void testStandardCase() { + int[] input = {1, 2, 3, 4, 5}; + PrefixSum ps = new PrefixSum(input); + + // Sum of range [0, 4] -> 15 + assertEquals(15L, ps.sumRange(0, 4)); + + // Sum of range [1, 3] -> 9 + assertEquals(9L, ps.sumRange(1, 3)); + } + + @Test + @DisplayName("Test array with negative numbers and zeros") + void testNegativeAndZeros() { + int[] input = {-2, 0, 3, -5, 2, -1}; + PrefixSum ps = new PrefixSum(input); + + assertEquals(1L, ps.sumRange(0, 2)); + assertEquals(-1L, ps.sumRange(2, 5)); + assertEquals(0L, ps.sumRange(1, 1)); + } + + @Test + @DisplayName("Test with large integers to verify overflow handling") + void testLargeNumbers() { + // Two values that fit in int, but their sum exceeds Integer.MAX_VALUE + // Integer.MAX_VALUE is approx 2.14 billion. + int val = 2_000_000_000; + int[] input = {val, val, val}; + PrefixSum ps = new PrefixSum(input); + + // Sum of three 2 billion values is 6 billion (fits in long, overflows int) + assertEquals(6_000_000_000L, ps.sumRange(0, 2)); + } + + @Test + @DisplayName("Test single element array") + void testSingleElement() { + int[] input = {42}; + PrefixSum ps = new PrefixSum(input); + assertEquals(42L, ps.sumRange(0, 0)); + } + + @Test + @DisplayName("Test constructor with null input") + void testNullInput() { + assertThrows(IllegalArgumentException.class, () -> new PrefixSum(null)); + } + + @Test + @DisplayName("Test empty array behavior") + void testEmptyArray() { + int[] input = {}; + PrefixSum ps = new PrefixSum(input); + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRange(0, 0)); + } + + @Test + @DisplayName("Test invalid range indices") + void testInvalidIndices() { + int[] input = {10, 20, 30}; + PrefixSum ps = new PrefixSum(input); + + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRange(-1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRange(0, 3)); + assertThrows(IndexOutOfBoundsException.class, () -> ps.sumRange(2, 1)); + } +} diff --git a/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java b/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java new file mode 100644 index 000000000000..12072318ac74 --- /dev/null +++ b/src/test/java/com/thealgorithms/prefixsum/RangeSumQueryTest.java @@ -0,0 +1,73 @@ +package com.thealgorithms.prefixsum; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link RangeSumQuery}. + */ +class RangeSumQueryTest { + + @Test + void testBasicExample() { + int[] nums = {1, 2, 3, 4, 5}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertEquals(6, RangeSumQuery.sumRange(prefixSum, 0, 2)); // 1+2+3 + assertEquals(9, RangeSumQuery.sumRange(prefixSum, 1, 3)); // 2+3+4 + assertEquals(15, RangeSumQuery.sumRange(prefixSum, 0, 4)); // 1+2+3+4+5 + assertEquals(12, RangeSumQuery.sumRange(prefixSum, 2, 4)); // 3+4+5 + } + + @Test + void testSingleElement() { + int[] nums = {7}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertEquals(7, RangeSumQuery.sumRange(prefixSum, 0, 0)); + } + + @Test + void testAllZeros() { + int[] nums = {0, 0, 0, 0}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertEquals(0, RangeSumQuery.sumRange(prefixSum, 0, 3)); + assertEquals(0, RangeSumQuery.sumRange(prefixSum, 1, 2)); + } + + @Test + void testNegativeNumbers() { + int[] nums = {-1, 2, -3, 4}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertEquals(-2, RangeSumQuery.sumRange(prefixSum, 0, 2)); // -1+2-3 + assertEquals(3, RangeSumQuery.sumRange(prefixSum, 1, 3)); // 2-3+4 + } + + @Test + void testEmptyArrayThrowsException() { + int[] nums = {}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 0, 0)); + } + + @Test + void testNullArrayThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.buildPrefixSum(null)); + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(null, 0, 0)); + } + + @Test + void testInvalidIndicesThrowsException() { + int[] nums = {1, 2, 3}; + int[] prefixSum = RangeSumQuery.buildPrefixSum(nums); + + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, -1, 2)); + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 1, 5)); + assertThrows(IllegalArgumentException.class, () -> RangeSumQuery.sumRange(prefixSum, 2, 1)); + } +} diff --git a/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java b/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java new file mode 100644 index 000000000000..68f85b713046 --- /dev/null +++ b/src/test/java/com/thealgorithms/prefixsum/SubarraySumEqualskTest.java @@ -0,0 +1,59 @@ +package com.thealgorithms.prefixsum; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link SubarraySumEqualsK}. + */ +class SubarraySumEqualsKTest { + + @Test + void testBasicExample() { + int[] nums = {1, 1, 1}; + int k = 2; + assertEquals(2, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testWithNegativeNumbers() { + int[] nums = {1, -1, 0}; + int k = 0; + assertEquals(3, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testSingleElementEqualToK() { + int[] nums = {5}; + int k = 5; + assertEquals(1, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testSingleElementNotEqualToK() { + int[] nums = {5}; + int k = 3; + assertEquals(0, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testAllZeros() { + int[] nums = {0, 0, 0}; + int k = 0; + assertEquals(6, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testEmptyArray() { + int[] nums = {}; + int k = 0; + assertEquals(0, SubarraySumEqualsK.countSubarrays(nums, k)); + } + + @Test + void testNullArrayThrowsException() { + assertThrows(IllegalArgumentException.class, () -> SubarraySumEqualsK.countSubarrays(null, 0)); + } +} diff --git a/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java b/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java deleted file mode 100644 index 7fb96dcf805f..000000000000 --- a/src/test/java/com/thealgorithms/puzzlesandgames/SudokuTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.thealgorithms.puzzlesandgames; - -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; - -public class SudokuTest { - - @Test - void testIsSafe2() { - int[][] board = {{3, 0, 6, 5, 0, 8, 4, 0, 0}, {5, 2, 0, 0, 0, 0, 0, 0, 0}, {0, 8, 7, 0, 0, 0, 0, 3, 1}, {0, 0, 3, 0, 1, 0, 0, 8, 0}, {9, 0, 0, 8, 6, 3, 0, 0, 5}, {0, 5, 0, 0, 9, 0, 6, 0, 0}, {1, 3, 0, 0, 0, 0, 2, 5, 0}, {0, 0, 0, 0, 0, 0, 0, 7, 4}, {0, 0, 5, 2, 0, 6, 3, 0, 0}}; - - assertFalse(Sudoku.isSafe(board, 0, 1, 3)); - assertTrue(Sudoku.isSafe(board, 1, 2, 1)); - assertThrows(ArrayIndexOutOfBoundsException.class, () -> { Sudoku.isSafe(board, 10, 10, 5); }); - assertThrows(ArrayIndexOutOfBoundsException.class, () -> { Sudoku.isSafe(board, -1, 0, 5); }); - } - - @Test - void testSolveSudoku() { - int[][] board = {{3, 0, 6, 5, 0, 8, 4, 0, 0}, {5, 2, 0, 0, 0, 0, 0, 0, 0}, {0, 8, 7, 0, 0, 0, 0, 3, 1}, {0, 0, 3, 0, 1, 0, 0, 8, 0}, {9, 0, 0, 8, 6, 3, 0, 0, 5}, {0, 5, 0, 0, 9, 0, 6, 0, 0}, {1, 3, 0, 0, 0, 0, 2, 5, 0}, {0, 0, 0, 0, 0, 0, 0, 7, 4}, {0, 0, 5, 2, 0, 6, 3, 0, 0}}; - - assertTrue(Sudoku.solveSudoku(board, board.length)); - assertEquals(1, board[0][1]); - assertThrows(ArrayIndexOutOfBoundsException.class, () -> { Sudoku.solveSudoku(board, 10); }); - assertTrue(Sudoku.solveSudoku(board, -1)); - } - - @Test - void testUnsolvableSudoku() { - int[][] unsolvableBoard = {{5, 1, 6, 8, 4, 9, 7, 3, 2}, {3, 0, 7, 6, 0, 5, 0, 0, 0}, {8, 0, 9, 7, 0, 0, 0, 6, 5}, {1, 3, 5, 0, 6, 0, 9, 0, 7}, {4, 7, 2, 5, 9, 1, 0, 0, 6}, {9, 6, 8, 3, 7, 0, 0, 5, 0}, {2, 5, 3, 1, 8, 6, 0, 7, 4}, {6, 8, 4, 2, 5, 7, 3, 9, 0}, {7, 9, 1, 4, 3, 0, 5, 0, 0}}; - - assertFalse(Sudoku.solveSudoku(unsolvableBoard, unsolvableBoard.length)); - } -} diff --git a/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java b/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java index 42669eb03bb4..f0a2686d3e4b 100644 --- a/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java +++ b/src/test/java/com/thealgorithms/puzzlesandgames/TowerOfHanoiTest.java @@ -1,14 +1,31 @@ package com.thealgorithms.puzzlesandgames; 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.List; +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 TowerOfHanoiTest { + @ParameterizedTest + @MethodSource("diskCountAndMoveCount") + void testMoveCountMatchesFormula(int disks, int expectedMoves) { + List result = new ArrayList<>(); + TowerOfHanoi.shift(disks, "A", "B", "C", result); + assertEquals(expectedMoves, result.size()); + } + + private static Stream diskCountAndMoveCount() { + return Stream.of(Arguments.of(1, 1), Arguments.of(2, 3), Arguments.of(3, 7), Arguments.of(4, 15), Arguments.of(5, 31), Arguments.of(10, 1023)); + } + @Test public void testHanoiWithOneDisc() { List result = new ArrayList<>(); @@ -39,6 +56,15 @@ public void testHanoiWithThreeDiscs() { assertEquals(expected, result); } + @Test + public void testHanoiWithDifferentPoles() { + List result = new ArrayList<>(); + TowerOfHanoi.shift(2, "X", "Y", "Z", result); + + List expected = List.of("Move 1 from X to Y", "Move 2 from X to Z", "Move 1 from Y to Z"); + assertEquals(expected, result); + } + @Test public void testHanoiWithZeroDiscs() { List result = new ArrayList<>(); @@ -47,4 +73,10 @@ public void testHanoiWithZeroDiscs() { // There should be no moves if there are 0 discs assertTrue(result.isEmpty()); } + + @Test + public void testHanoiWithNegativeDiscsThrows() { + List result = new ArrayList<>(); + assertThrows(IllegalArgumentException.class, () -> TowerOfHanoi.shift(-1, "Pole1", "Pole2", "Pole3", result)); + } } diff --git a/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java b/src/test/java/com/thealgorithms/recursion/FactorialRecursionTest.java similarity index 96% rename from src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java rename to src/test/java/com/thealgorithms/recursion/FactorialRecursionTest.java index db18b46356b4..198fcd558f63 100644 --- a/src/test/java/com/thealgorithms/maths/FactorialRecursionTest.java +++ b/src/test/java/com/thealgorithms/recursion/FactorialRecursionTest.java @@ -1,4 +1,4 @@ -package com.thealgorithms.maths; +package com.thealgorithms.recursion; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/src/test/java/com/thealgorithms/searches/BinarySearch2dArrayTest.java b/src/test/java/com/thealgorithms/searches/BinarySearch2dArrayTest.java index 18f0afc6a0a6..dec2c86de9c7 100644 --- a/src/test/java/com/thealgorithms/searches/BinarySearch2dArrayTest.java +++ b/src/test/java/com/thealgorithms/searches/BinarySearch2dArrayTest.java @@ -117,7 +117,7 @@ public void binarySearch2dArrayTestTargetInMiddle() { int target = 8; // Assert that the requirement, that the target is in the middle row and middle column, is // fulfilled. - assertEquals(arr[arr.length / 2][arr[0].length / 2], target); + assertEquals(target, arr[arr.length / 2][arr[0].length / 2]); int[] ans = BinarySearch2dArray.binarySearch(arr, target); System.out.println(Arrays.toString(ans)); assertEquals(1, ans[0]); @@ -135,8 +135,8 @@ public void binarySearch2dArrayTestTargetAboveMiddleRowInMiddleColumn() { // Assert that the requirement, that he target is in the middle column, // in an array with an even number of columns, and on the row "above" the middle row. - assertEquals(arr[0].length % 2, 0); - assertEquals(arr[arr.length / 2 - 1][arr[0].length / 2], target); + assertEquals(0, arr[0].length % 2); + assertEquals(target, arr[arr.length / 2 - 1][arr[0].length / 2]); int[] ans = BinarySearch2dArray.binarySearch(arr, target); System.out.println(Arrays.toString(ans)); assertEquals(0, ans[0]); diff --git a/src/test/java/com/thealgorithms/searches/BinarySearchTest.java b/src/test/java/com/thealgorithms/searches/BinarySearchTest.java index bd4620a7fa7d..00bed165734e 100644 --- a/src/test/java/com/thealgorithms/searches/BinarySearchTest.java +++ b/src/test/java/com/thealgorithms/searches/BinarySearchTest.java @@ -105,4 +105,90 @@ void testBinarySearchLargeArray() { int expectedIndex = 9999; // Index of the last element assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the last element should be 9999."); } + + /** + * Test for binary search with null array. + */ + @Test + void testBinarySearchNullArray() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = null; + int key = 5; // Key to search + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in a null array."); + } + + /** + * Test for binary search with duplicate elements. + */ + @Test + void testBinarySearchWithDuplicates() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {1, 2, 2, 2, 3}; + int key = 2; // Element present multiple times + + int result = binarySearch.find(array, key); + assertEquals(2, array[result], "The returned index should contain the searched element."); + } + + /** + * Test for binary search where all elements are the same. + */ + @Test + void testBinarySearchAllElementsSame() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {5, 5, 5, 5, 5}; + int key = 5; // All elements match + + int result = binarySearch.find(array, key); + assertEquals(5, array[result], "The returned index should contain the searched element."); + } + + /** + * Test for binary search with negative numbers. + */ + @Test + void testBinarySearchNegativeNumbers() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {-10, -5, 0, 5, 10}; + int key = -5; // Element present + int expectedIndex = 1; // Index of the element + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the element should be 1."); + } + + /** + * Test for binary search when key is smaller than all elements. + */ + @Test + void testBinarySearchKeySmallerThanAll() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {10, 20, 30}; + int key = 5; // Smaller than all elements + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for binary search when key is larger than all elements. + */ + @Test + void testBinarySearchKeyLargerThanAll() { + BinarySearch binarySearch = new BinarySearch(); + Integer[] array = {10, 20, 30}; + int key = 40; // Larger than all elements + int expectedIndex = -1; // Key not found + assertEquals(expectedIndex, binarySearch.find(array, key), "The element should not be found in the array."); + } + + /** + * Test for binary search with String array. + */ + @Test + void testBinarySearchStrings() { + BinarySearch binarySearch = new BinarySearch(); + String[] array = {"apple", "banana", "cherry", "date"}; + String key = "cherry"; // Element present + int expectedIndex = 2; // Index of the element + assertEquals(expectedIndex, binarySearch.find(array, key), "The index of the element should be 2."); + } } diff --git a/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java b/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java index c84da531e8a4..c6b07ca2b4d5 100644 --- a/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java +++ b/src/test/java/com/thealgorithms/searches/ExponentialSearchTest.java @@ -81,4 +81,46 @@ void testExponentialSearchLargeArray() { int expectedIndex = 9999; assertEquals(expectedIndex, exponentialSearch.find(array, key), "The index of the last element should be 9999."); } + + /** + * An element sitting exactly on the doubling boundary used to be reported as missing, because + * the binary search was handed {@code range} as its exclusive upper bound instead of + * {@code range + 1}. + */ + @Test + void testExponentialSearchElementOnRangeBoundary() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + Integer[] array = {-25, -9, 8, 21}; + assertEquals(2, exponentialSearch.find(array, 8), "The index of the found element should be 2."); + } + + /** + * Every element must be found regardless of the array length. + */ + @Test + void testExponentialSearchFindsEveryElement() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + for (int length = 1; length <= 50; length++) { + Integer[] array = new Integer[length]; + for (int i = 0; i < length; i++) { + array[i] = i * 2; + } + for (int i = 0; i < length; i++) { + assertEquals(i, exponentialSearch.find(array, i * 2), "Element at index " + i + " should be found for length " + length + "."); + } + } + } + + /** + * A missing key has to yield -1 rather than the negative insertion point that + * {@link java.util.Arrays#binarySearch} returns. + */ + @Test + void testExponentialSearchNotFoundReturnsMinusOne() { + ExponentialSearch exponentialSearch = new ExponentialSearch(); + Integer[] array = {1, 3, 5, 7, 9, 11}; + assertEquals(-1, exponentialSearch.find(array, 4), "A key inside the range but absent should give -1."); + assertEquals(-1, exponentialSearch.find(array, 0), "A key below the minimum should give -1."); + assertEquals(-1, exponentialSearch.find(array, 12), "A key above the maximum should give -1."); + } } diff --git a/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java b/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java index 801c33b1d09a..04a270864223 100644 --- a/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java +++ b/src/test/java/com/thealgorithms/searches/FibonacciSearchTest.java @@ -121,4 +121,53 @@ void testFibonacciSearchLargeArray() { int expectedIndex = 9999; assertEquals(expectedIndex, fibonacciSearch.find(array, key), "The index of the last element should be 9999."); } + + /** + * A key greater than every element used to throw {@link ArrayIndexOutOfBoundsException}, + * because the final probe read {@code array[offset + 1]} without checking the bound. + */ + @Test + void testFibonacciSearchKeyGreaterThanLastElement() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + for (int length = 1; length <= 50; length++) { + Integer[] array = new Integer[length]; + for (int i = 0; i < length; i++) { + array[i] = i; + } + assertEquals(-1, fibonacciSearch.find(array, length), "A key above the maximum should not be found for length " + length + "."); + } + } + + /** + * The final probe used reference equality, so a key that is equal but not identical to the + * stored element was reported as missing. Values above 127 are outside the {@link Integer} + * cache and therefore are not the same object as the boxed array element. + */ + @Test + void testFibonacciSearchFindsEqualButNotIdenticalKey() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + Integer[] array = {10, 20, 300}; + assertEquals(2, fibonacciSearch.find(array, Integer.valueOf(300)), "The index of the found element should be 2."); + + String[] words = {"a", "b", "c"}; + String equalButDistinct = new StringBuilder("c").toString(); + assertEquals(2, fibonacciSearch.find(words, equalButDistinct), "The index of the found element should be 2."); + } + + /** + * Every element must be found regardless of the array length. + */ + @Test + void testFibonacciSearchFindsEveryElement() { + FibonacciSearch fibonacciSearch = new FibonacciSearch(); + for (int length = 1; length <= 50; length++) { + Integer[] array = new Integer[length]; + for (int i = 0; i < length; i++) { + array[i] = 1000 + i * 2; + } + for (int i = 0; i < length; i++) { + assertEquals(i, fibonacciSearch.find(array, Integer.valueOf(1000 + i * 2)), "Element at index " + i + " should be found for length " + length + "."); + } + } + } } diff --git a/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java b/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java index b3b7e7ef129c..b7ef64125da8 100644 --- a/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java +++ b/src/test/java/com/thealgorithms/searches/InterpolationSearchTest.java @@ -87,4 +87,15 @@ void testInterpolationSearchLargeNonUniformArray() { int key = 21; // Present in the array assertEquals(6, interpolationSearch.find(array, key), "The index of the found element should be 6."); } + + /** + * Test for interpolation search with specific sorted arrays that previously caused division by zero. + */ + @Test + void testInterpolationSearchDivisionByZeroEdgeCases() { + InterpolationSearch interpolationSearch = new InterpolationSearch(); + assertEquals(3, interpolationSearch.find(new int[] {0, 0, 0, 2}, 2)); + assertEquals(0, interpolationSearch.find(new int[] {2, 2, 2, 2}, 2)); + assertEquals(3, interpolationSearch.find(new int[] {0, 1, 2, 4}, 4)); + } } diff --git a/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java index b2e121ac1ba0..f291610b298b 100644 --- a/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java +++ b/src/test/java/com/thealgorithms/searches/IterativeBinarySearchTest.java @@ -87,6 +87,26 @@ void testBinarySearchEmptyArray() { assertEquals(-1, binarySearch.find(array, key), "The element should not be found in an empty array."); } + /** + * Test for binary search with a null array. + */ + @Test + void testBinarySearchNullArray() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer key = 1; + assertEquals(-1, binarySearch.find(null, key), "The element should not be found in a null array."); + } + + /** + * Test for binary search with a null key. + */ + @Test + void testBinarySearchNullKey() { + IterativeBinarySearch binarySearch = new IterativeBinarySearch(); + Integer[] array = {1, 2, 4, 8, 16}; + assertEquals(-1, binarySearch.find(array, null), "A null search key should return -1."); + } + /** * Test for binary search on a large array. */ diff --git a/src/test/java/com/thealgorithms/searches/JumpSearchTest.java b/src/test/java/com/thealgorithms/searches/JumpSearchTest.java index 3fa319b66a41..a5ce93b8a3af 100644 --- a/src/test/java/com/thealgorithms/searches/JumpSearchTest.java +++ b/src/test/java/com/thealgorithms/searches/JumpSearchTest.java @@ -2,7 +2,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; /** * Unit tests for the JumpSearch class. @@ -91,4 +93,50 @@ void testJumpSearchLargeArrayNotFound() { Integer key = 999; // Key not present assertEquals(-1, jumpSearch.find(array, key), "The element should not be found in the array."); } + + /** + * A key greater than every element used to make the jumping loop spin forever, because the + * cursor was clamped to the last index and therefore stopped advancing. + */ + @Test + @Timeout(value = 5, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) + void testJumpSearchKeyGreaterThanLastElement() { + JumpSearch jumpSearch = new JumpSearch(); + Integer[] array = {1, 2, 3, 4}; + assertEquals(-1, jumpSearch.find(array, 5), "A key above the maximum should not be found."); + } + + /** + * The same regression across several lengths, since the jump size depends on the array length. + */ + @Test + @Timeout(value = 5, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) + void testJumpSearchKeyGreaterThanLastElementForEveryLength() { + JumpSearch jumpSearch = new JumpSearch(); + for (int length = 1; length <= 50; length++) { + Integer[] array = new Integer[length]; + for (int i = 0; i < length; i++) { + array[i] = i; + } + assertEquals(-1, jumpSearch.find(array, length), "A key above the maximum should not be found for length " + length + "."); + } + } + + /** + * Every element must be found regardless of the array length, including the ones that sit + * exactly on a jump boundary. + */ + @Test + void testJumpSearchFindsEveryElement() { + JumpSearch jumpSearch = new JumpSearch(); + for (int length = 1; length <= 50; length++) { + Integer[] array = new Integer[length]; + for (int i = 0; i < length; i++) { + array[i] = i * 2; + } + for (int i = 0; i < length; i++) { + assertEquals(i, jumpSearch.find(array, i * 2), "Element at index " + i + " should be found for length " + length + "."); + } + } + } } diff --git a/src/test/java/com/thealgorithms/searches/KMPSearchTest.java b/src/test/java/com/thealgorithms/searches/KMPSearchTest.java index cb804ac6a6a3..216c5fcd7d2c 100644 --- a/src/test/java/com/thealgorithms/searches/KMPSearchTest.java +++ b/src/test/java/com/thealgorithms/searches/KMPSearchTest.java @@ -14,7 +14,7 @@ public void kmpSearchTestLast() { KMPSearch kmpSearch = new KMPSearch(); int value = kmpSearch.kmpSearch(pat, txt); System.out.println(value); - assertEquals(value, 10); + assertEquals(10, value); } @Test @@ -25,7 +25,7 @@ public void kmpSearchTestFront() { KMPSearch kmpSearch = new KMPSearch(); int value = kmpSearch.kmpSearch(pat, txt); System.out.println(value); - assertEquals(value, 0); + assertEquals(0, value); } @Test @@ -36,7 +36,7 @@ public void kmpSearchTestMiddle() { KMPSearch kmpSearch = new KMPSearch(); int value = kmpSearch.kmpSearch(pat, txt); System.out.println(value); - assertEquals(value, 4); + assertEquals(4, value); } @Test @@ -47,7 +47,7 @@ public void kmpSearchTestNotFound() { KMPSearch kmpSearch = new KMPSearch(); int value = kmpSearch.kmpSearch(pat, txt); System.out.println(value); - assertEquals(value, 4); + assertEquals(4, value); } @Test @@ -58,6 +58,6 @@ public void kmpSearchTest4() { KMPSearch kmpSearch = new KMPSearch(); int value = kmpSearch.kmpSearch(pat, txt); System.out.println(value); - assertEquals(value, -1); + assertEquals(-1, value); } } diff --git a/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java b/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java index 534c2a4487b2..c0d82489209f 100644 --- a/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java +++ b/src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Random; import org.junit.jupiter.api.Test; class LinearSearchThreadTest { @@ -62,10 +63,11 @@ void testSearcherEmptySegment() throws InterruptedException { void testSearcherRandomNumbers() throws InterruptedException { int size = 200; int[] array = new int[size]; + Random random = new Random(); for (int i = 0; i < size; i++) { - array[i] = (int) (Math.random() * 100); + array[i] = random.nextInt(100); } - int target = array[(int) (Math.random() * size)]; // Randomly select a target that is present + final int target = array[random.nextInt(size)]; // Randomly select a target that is present Searcher searcher = new Searcher(array, 0, size, target); searcher.start(); searcher.join(); diff --git a/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java deleted file mode 100644 index 6eab20f45467..000000000000 --- a/src/test/java/com/thealgorithms/searches/PerfectBinarySearchTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.thealgorithms.searches; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -/** - * @author D Sunil (https://github.com/sunilnitdgp) - * @see PerfectBinarySearch - */ -public class PerfectBinarySearchTest { - - @Test - public void testIntegerBinarySearch() { - Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - PerfectBinarySearch binarySearch = new PerfectBinarySearch<>(); - - // Test cases for elements present in the array - assertEquals(0, binarySearch.find(array, 1)); // First element - assertEquals(4, binarySearch.find(array, 5)); // Middle element - assertEquals(9, binarySearch.find(array, 10)); // Last element - assertEquals(6, binarySearch.find(array, 7)); // Element in the middle - - // Test cases for elements not in the array - assertEquals(-1, binarySearch.find(array, 0)); // Element before the array - assertEquals(-1, binarySearch.find(array, 11)); // Element after the array - assertEquals(-1, binarySearch.find(array, 100)); // Element not in the array - } - - @Test - public void testStringBinarySearch() { - String[] array = {"apple", "banana", "cherry", "date", "fig"}; - PerfectBinarySearch binarySearch = new PerfectBinarySearch<>(); - - // Test cases for elements not in the array - assertEquals(-1, binarySearch.find(array, "apricot")); // Element not in the array - assertEquals(-1, binarySearch.find(array, "bananaa")); // Element not in the array - - // Test cases for elements present in the array - assertEquals(0, binarySearch.find(array, "apple")); // First element - assertEquals(2, binarySearch.find(array, "cherry")); // Middle element - assertEquals(4, binarySearch.find(array, "fig")); // Last element - } -} diff --git a/src/test/java/com/thealgorithms/searches/QuickSelectTest.java b/src/test/java/com/thealgorithms/searches/QuickSelectTest.java index cf160b0ff4b5..4c96be76861a 100644 --- a/src/test/java/com/thealgorithms/searches/QuickSelectTest.java +++ b/src/test/java/com/thealgorithms/searches/QuickSelectTest.java @@ -172,7 +172,7 @@ void quickSelect70thPercentileOfManyElements() { void quickSelectMedianOfThreeCharacters() { List elements = Arrays.asList('X', 'Z', 'Y'); char actual = QuickSelect.select(elements, 1); - assertEquals(actual, 'Y'); + assertEquals('Y', actual); } @Test diff --git a/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java new file mode 100644 index 000000000000..1e6ab4c37fcc --- /dev/null +++ b/src/test/java/com/thealgorithms/searches/RotatedBinarySearchTest.java @@ -0,0 +1,53 @@ +package com.thealgorithms.searches; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class RotatedBinarySearchTest { + + @Test + void shouldFindElementInRotatedArrayLeftSide() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7}; + assertEquals(2, search.find(array, 10)); + } + + @Test + void shouldFindElementInRotatedArrayRightSide() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7}; + assertEquals(6, search.find(array, 2)); + } + + @Test + void shouldFindElementInNotRotatedArray() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {1, 2, 3, 4, 5, 6, 7}; + assertEquals(4, search.find(array, 5)); + } + + @Test + void shouldReturnMinusOneWhenNotFound() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {4, 5, 6, 7, 0, 1, 2}; + assertEquals(-1, search.find(array, 3)); + } + + @Test + void shouldHandleWhenMiddleIsGreaterThanKeyInRightSortedHalf() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {6, 7, 0, 1, 2, 3, 4, 5}; + assertEquals(2, search.find(array, 0)); + } + + @Test + void shouldHandleDuplicates() { + RotatedBinarySearch search = new RotatedBinarySearch(); + Integer[] array = {2, 2, 2, 3, 4, 2}; + int index = search.find(array, 3); + assertTrue(index >= 0 && index < array.length); + assertEquals(3, array[index]); + } +} diff --git a/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java b/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java deleted file mode 100644 index e2917733d1d9..000000000000 --- a/src/test/java/com/thealgorithms/searches/SortOrderAgnosticBinarySearchTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.thealgorithms.searches; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -public class SortOrderAgnosticBinarySearchTest { - - @Test - public void testAscending() { - int[] arr = {1, 2, 3, 4, 5}; // for ascending order. - int target = 2; - int ans = SortOrderAgnosticBinarySearch.find(arr, target); - int excepted = 1; - assertEquals(excepted, ans); - } - - @Test - public void testDescending() { - int[] arr = {5, 4, 3, 2, 1}; // for descending order. - int target = 2; - int ans = SortOrderAgnosticBinarySearch.find(arr, target); - int excepted = 3; - assertEquals(excepted, ans); - } -} diff --git a/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java b/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java new file mode 100644 index 000000000000..a6931bca99d2 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/CountDistinctElementsInWindowTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class CountDistinctElementsInWindowTest { + + @Test + public void testBasicCase() { + assertArrayEquals(new int[] {3, 2, 2}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 2, 3}, 3)); + } + + @Test + public void testAllSame() { + assertArrayEquals(new int[] {1, 1, 1}, CountDistinctElementsInWindow.countDistinct(new int[] {2, 2, 2, 2}, 2)); + } + + @Test + public void testAllDistinct() { + assertArrayEquals(new int[] {3, 3}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 4}, 3)); + } + + @Test + public void testWindowSizeEqualsArray() { + assertArrayEquals(new int[] {4}, CountDistinctElementsInWindow.countDistinct(new int[] {1, 2, 3, 4}, 4)); + } + + @Test + public void testInvalidInput() { + assertThrows(IllegalArgumentException.class, () -> CountDistinctElementsInWindow.countDistinct(new int[] {}, 2)); + } +} diff --git a/src/test/java/com/thealgorithms/slidingwindow/CountNiceSubarraysTest.java b/src/test/java/com/thealgorithms/slidingwindow/CountNiceSubarraysTest.java new file mode 100644 index 000000000000..71bf24cc9e30 --- /dev/null +++ b/src/test/java/com/thealgorithms/slidingwindow/CountNiceSubarraysTest.java @@ -0,0 +1,55 @@ +package com.thealgorithms.slidingwindow; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class CountNiceSubarraysTest { + @Test + void testExampleCase() { + int[] nums = {1, 1, 2, 1, 1}; + assertEquals(2, CountNiceSubarrays.countNiceSubarrays(nums, 3)); + } + + @Test + void testAllEvenNumbers() { + int[] nums = {2, 4, 6, 8}; + assertEquals(0, CountNiceSubarrays.countNiceSubarrays(nums, 1)); + } + + @Test + void testSingleOdd() { + int[] nums = {1}; + assertEquals(1, CountNiceSubarrays.countNiceSubarrays(nums, 1)); + } + + @Test + void testMultipleChoices() { + int[] nums = {2, 2, 1, 2, 2, 1, 2}; + assertEquals(6, CountNiceSubarrays.countNiceSubarrays(nums, 2)); + } + + @Test + void testTrailingEvenNumbers() { + int[] nums = {1, 2, 2, 2}; + assertEquals(4, CountNiceSubarrays.countNiceSubarrays(nums, 1)); + } + + @Test + void testMultipleWindowShrinks() { + int[] nums = {1, 1, 1, 1}; + assertEquals(3, CountNiceSubarrays.countNiceSubarrays(nums, 2)); + } + + @Test + void testEvensBetweenOdds() { + int[] nums = {2, 1, 2, 1, 2}; + assertEquals(4, CountNiceSubarrays.countNiceSubarrays(nums, 2)); + } + + @Test + void testShrinkWithTrailingEvens() { + int[] nums = {2, 2, 1, 2, 2, 1, 2, 2}; + assertEquals(9, CountNiceSubarrays.countNiceSubarrays(nums, 2)); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java b/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java index 60c4bbe9d342..2ee30c44e282 100644 --- a/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/BitonicSortTest.java @@ -1,8 +1,17 @@ package com.thealgorithms.sorts; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + public class BitonicSortTest extends SortingAlgorithmTest { @Override SortAlgorithm getSortAlgorithm() { return new BitonicSort(); } + + @Test + void shouldRejectNullArray() { + assertThrows(IllegalArgumentException.class, () -> getSortAlgorithm().sort((Integer[]) null)); + } } diff --git a/src/test/java/com/thealgorithms/sorts/ConcurrentMergeSortTest.java b/src/test/java/com/thealgorithms/sorts/ConcurrentMergeSortTest.java new file mode 100644 index 000000000000..454d0bd26929 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/ConcurrentMergeSortTest.java @@ -0,0 +1,93 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.Arrays; +import java.util.Random; +import org.junit.jupiter.api.Test; + +/** + * JUnit 5 test class for {@link ConcurrentMergeSort}. + */ +public class ConcurrentMergeSortTest { + + @Test + public void testAlreadySortedArray() { + int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + ConcurrentMergeSort.sort(array); + + assertArrayEquals(expected, array, "Already sorted array should remain unchanged."); + } + + @Test + public void testReverseSortedArray() { + int[] array = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; + int[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + ConcurrentMergeSort.sort(array); + + assertArrayEquals(expected, array, "Reverse sorted array should be sorted correctly."); + } + + @Test + public void testIdenticalElementsArray() { + int[] array = {5, 5, 5, 5, 5, 5, 5}; + int[] expected = {5, 5, 5, 5, 5, 5, 5}; + + ConcurrentMergeSort.sort(array); + + assertArrayEquals(expected, array, "Array with identical elements should be sorted correctly (unchanged)."); + } + + @Test + public void testLargeRandomArray() { + int size = 100_000; + int[] array = new int[size]; + int[] expected = new int[size]; + // Using a fixed seed for deterministic testing + Random random = new Random(42); + + for (int i = 0; i < size; i++) { + int value = random.nextInt(); + array[i] = value; + expected[i] = value; + } + + // Generate the expected result using Java's highly optimized built-in sort + Arrays.sort(expected); + + // This will easily trigger the concurrency threshold (8192) in the implementation + ConcurrentMergeSort.sort(array); + + assertArrayEquals(expected, array, "Large random array should be sorted correctly utilizing concurrency."); + } + + @Test + public void testEmptyArray() { + int[] array = {}; + int[] expected = {}; + + ConcurrentMergeSort.sort(array); + + assertArrayEquals(expected, array, "Empty array should be handled without errors."); + } + + @Test + public void testSingleElementArray() { + int[] array = {42}; + int[] expected = {42}; + + ConcurrentMergeSort.sort(array); + + assertArrayEquals(expected, array, "Single element array should be handled without errors."); + } + + @Test + public void testNullArray() { + int[] array = null; + ConcurrentMergeSort.sort(array); + org.junit.jupiter.api.Assertions.assertNull(array, "Null array should be handled without errors."); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java new file mode 100644 index 000000000000..49783f332e75 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/LibrarySortTest.java @@ -0,0 +1,70 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class LibrarySortTest { + + @Test + public void testBasicSort() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {5, 3, 1, 4, 2})); + } + + @Test + public void testAlreadySorted() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {1, 2, 3, 4, 5})); + } + + @Test + public void testReverseSorted() { + assertArrayEquals(new int[] {1, 2, 3, 4, 5}, LibrarySort.sort(new int[] {5, 4, 3, 2, 1})); + } + + @Test + public void testDuplicates() { + assertArrayEquals(new int[] {1, 2, 2, 3, 3}, LibrarySort.sort(new int[] {3, 2, 1, 3, 2})); + } + + @Test + public void testSingleElement() { + assertArrayEquals(new int[] {1}, LibrarySort.sort(new int[] {1})); + } + + @Test + public void testEmptyArray() { + assertArrayEquals(new int[] {}, LibrarySort.sort(new int[] {})); + } + + @Test + public void testNullArray() { + assertThrows(IllegalArgumentException.class, () -> LibrarySort.sort(null)); + } + + // --- Added to cover branches the tests above never reach --- + + @Test + public void testShiftLeftWhenRightSideIsFull() { + // Right side of the target slot is completely occupied, forcing a left shift. + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6}, LibrarySort.sort(new int[] {0, 1, 2, 6, 4, 5, 3})); + } + + @Test + public void testTieBreakPrefersRightWhenDistancesEqual() { + // A gap exists on both sides at equal distance; algorithm should favor the right shift. + assertArrayEquals(new int[] {0, 1, 2, 3}, LibrarySort.sort(new int[] {0, 1, 3, 2})); + } + + @Test + public void testRightSearchRunsOffTheEnd() { + // No gap anywhere to the right of the target slot, all the way to the array's end. + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6, 7}, LibrarySort.sort(new int[] {0, 1, 2, 3, 4, 5, 7, 6})); + } + + @Test + public void testInsertAtEndWithNoTrailingGap() { + // A new global maximum arrives with no trailing gap left, forcing insertAtEnd(). + assertArrayEquals(new int[] {0, 1, 2, 3, 4, 5, 6, 7}, LibrarySort.sort(new int[] {0, 1, 2, 3, 4, 5, 6, 7})); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java b/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java new file mode 100644 index 000000000000..8df0502e80e7 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/SmoothSortTest.java @@ -0,0 +1,8 @@ +package com.thealgorithms.sorts; + +public class SmoothSortTest extends SortingAlgorithmTest { + @Override + SortAlgorithm getSortAlgorithm() { + return new SmoothSort(); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java b/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java index d5588b2b968e..e19f5b928263 100644 --- a/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/TopologicalSortTest.java @@ -58,7 +58,7 @@ public void failureTest() { Exception exception = assertThrows(RuntimeException.class, () -> TopologicalSort.sort(graph)); String expected = "This graph contains a cycle. No linear ordering is possible. " + "Back edge: 6 -> 2"; - assertEquals(exception.getMessage(), expected); + assertEquals(expected, exception.getMessage()); } @Test void testEmptyGraph() { diff --git a/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java b/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java new file mode 100644 index 000000000000..91da746447a8 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/TournamentSortTest.java @@ -0,0 +1,19 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +public class TournamentSortTest extends SortingAlgorithmTest { + + @Test + void shouldAcceptWhenNullArrayIsPassed() { + Integer[] array = null; + assertNull(getSortAlgorithm().sort(array)); + } + + @Override + SortAlgorithm getSortAlgorithm() { + return new TournamentSort(); + } +} diff --git a/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java b/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java index c5d57d63cf38..0d8b6acf9043 100644 --- a/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java +++ b/src/test/java/com/thealgorithms/sorts/WiggleSortTest.java @@ -1,6 +1,7 @@ package com.thealgorithms.sorts; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; import org.junit.jupiter.api.Test; @@ -70,4 +71,22 @@ void wiggleTestStrings() { wiggleSort.sort(values); assertArrayEquals(values, result); } + + @Test + void wiggleTestNonWiggleSortableOddArrayThrows() { + // [1, 2, 2] is not wiggle-sortable: the median 2 appears ceil(3 / 2) = 2 times + // but is not the smallest value, so sorting must fail instead of returning + // a wrongly ordered array + WiggleSort wiggleSort = new WiggleSort(); + Integer[] values = {1, 2, 2}; + assertThrows(IllegalArgumentException.class, () -> wiggleSort.sort(values)); + } + + @Test + void wiggleTestTooManyDuplicatesThrows() { + // more than half of the values are the same, which can never be wiggle-sorted + WiggleSort wiggleSort = new WiggleSort(); + Integer[] values = {2, 2, 2, 1}; + assertThrows(IllegalArgumentException.class, () -> wiggleSort.sort(values)); + } } diff --git a/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java b/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java new file mode 100644 index 000000000000..2e4ea74691da --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/StockSpanProblemTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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 StockSpanProblemTest { + + @ParameterizedTest + @MethodSource("validTestCases") + void testCalculateSpan(int[] prices, int[] expectedSpans) { + assertArrayEquals(expectedSpans, StockSpanProblem.calculateSpan(prices)); + } + + private static Stream validTestCases() { + return Stream.of(Arguments.of(new int[] {10, 4, 5, 90, 120, 80}, new int[] {1, 1, 2, 4, 5, 1}), Arguments.of(new int[] {100, 50, 60, 70, 80, 90}, new int[] {1, 1, 2, 3, 4, 5}), Arguments.of(new int[] {5, 4, 3, 2, 1}, new int[] {1, 1, 1, 1, 1}), + Arguments.of(new int[] {1, 2, 3, 4, 5}, new int[] {1, 2, 3, 4, 5}), Arguments.of(new int[] {10, 20, 30, 40, 50}, new int[] {1, 2, 3, 4, 5}), Arguments.of(new int[] {100, 80, 60, 70, 60, 75, 85}, new int[] {1, 1, 1, 2, 1, 4, 6}), + Arguments.of(new int[] {7, 7, 7, 7}, new int[] {1, 2, 3, 4}), Arguments.of(new int[] {}, new int[] {}), Arguments.of(new int[] {42}, new int[] {1})); + } + + @ParameterizedTest + @MethodSource("invalidTestCases") + void testCalculateSpanInvalidInput(int[] prices) { + assertThrows(IllegalArgumentException.class, () -> StockSpanProblem.calculateSpan(prices)); + } + + private static Stream invalidTestCases() { + return Stream.of(Arguments.of((int[]) null)); + } +} diff --git a/src/test/java/com/thealgorithms/stacks/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/stacks/ValidParenthesesTest.java new file mode 100644 index 000000000000..39014780caa9 --- /dev/null +++ b/src/test/java/com/thealgorithms/stacks/ValidParenthesesTest.java @@ -0,0 +1,32 @@ +package com.thealgorithms.stacks; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ValidParenthesesTest { + + @Test + void testValidParentheses() { + assertTrue(ValidParentheses.isValid("()")); + assertTrue(ValidParentheses.isValid("()[]{}")); + assertTrue(ValidParentheses.isValid("{[]}")); + assertTrue(ValidParentheses.isValid("")); + } + + @Test + void testInvalidParentheses() { + assertFalse(ValidParentheses.isValid("(]")); + assertFalse(ValidParentheses.isValid("([)]")); + assertFalse(ValidParentheses.isValid("{{{")); + assertFalse(ValidParentheses.isValid("}")); + assertFalse(ValidParentheses.isValid("(")); + } + + @Test + void testNullAndOddLength() { + assertFalse(ValidParentheses.isValid(null)); + assertFalse(ValidParentheses.isValid("(()")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java b/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java index 7b41e11ef22f..0c7d7e4701cf 100644 --- a/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java +++ b/src/test/java/com/thealgorithms/strings/AlphabeticalTest.java @@ -1,15 +1,45 @@ package com.thealgorithms.strings; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; +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.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -public class AlphabeticalTest { +@DisplayName("Alphabetical.isAlphabetical()") +class AlphabeticalTest { - @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)); + static Stream testCases() { + // Workaround for SpotBugs false positive (NAB_NEEDLESS_BOOLEAN_CONSTANT_CONVERSION) + // due to JUnit Arguments.of(Object...) auto-boxing + return Stream.of(arguments("", Boolean.FALSE, "Should return false for empty string"), arguments(" ", Boolean.FALSE, "Should return false for blank string"), arguments("a1b2", Boolean.FALSE, "Should return false when string contains numbers"), + arguments("abc!DEF", Boolean.FALSE, "Should return false when string contains symbols"), arguments("#abc", Boolean.FALSE, "Should return false when first character is not a letter"), arguments("abc", Boolean.TRUE, "Should return true for non-decreasing order"), + arguments("aBcD", Boolean.TRUE, "Should return true for mixed case increasing sequence"), arguments("a", Boolean.TRUE, "Should return true for single letter"), arguments("'", Boolean.FALSE, "Should return false for single symbol"), + arguments("aabbcc", Boolean.TRUE, "Should return true for repeated letters"), arguments("cba", Boolean.FALSE, "Should return false when order decreases"), arguments("abzba", Boolean.FALSE, "Should return false when middle breaks order")); + } + + private void assertAlphabetical(String input, boolean expected, String message) { + // Arrange & Act + boolean result = Alphabetical.isAlphabetical(input); + + // Assert + assertEquals(expected, result, message); + } + + @Test + @DisplayName("Should return false for null input") + void nullInputTest() { + assertAlphabetical(null, false, "Should return false for null input"); + } + + @ParameterizedTest(name = "{2}") + @MethodSource("testCases") + @DisplayName("Alphabetical cases") + void isAlphabeticalTest(String input, boolean expected, String message) { + assertAlphabetical(input, expected, message); } } diff --git a/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java index 9e8ae9e9f153..4cd55a4d7410 100644 --- a/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java +++ b/src/test/java/com/thealgorithms/strings/AlternativeStringArrangeTest.java @@ -1,9 +1,11 @@ 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.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class AlternativeStringArrangeTest { @@ -20,4 +22,15 @@ private static Stream provideTestData() { void arrangeTest(String input1, String input2, String expected) { assertEquals(expected, AlternativeStringArrange.arrange(input1, input2)); } + + @ParameterizedTest(name = "null input ({0}, {1}) should throw IllegalArgumentException") + @MethodSource("provideNullInputs") + void arrangeThrowsOnNullInput(String input1, String input2) { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> AlternativeStringArrange.arrange(input1, input2)); + assertEquals("Input strings must not be null", ex.getMessage()); + } + + private static Stream provideNullInputs() { + return Stream.of(Arguments.of(null, "abc"), Arguments.of("abc", null), Arguments.of(null, null)); + } } diff --git a/src/test/java/com/thealgorithms/strings/KMPTest.java b/src/test/java/com/thealgorithms/strings/KMPTest.java new file mode 100644 index 000000000000..9fa5f398d420 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/KMPTest.java @@ -0,0 +1,29 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class KMPTest { + + @Test + public void testNullInputs() { + assertEquals(List.of(), KMP.kmpMatcher(null, "A")); + assertEquals(List.of(), KMP.kmpMatcher("A", null)); + assertEquals(List.of(), KMP.kmpMatcher(null, null)); + } + + @Test + public void testKMPMatcher() { + assertEquals(List.of(0, 1), KMP.kmpMatcher("AAAAABAAABA", "AAAA")); + assertEquals(List.of(0, 3), KMP.kmpMatcher("ABCABC", "ABC")); + assertEquals(List.of(10), KMP.kmpMatcher("ABABDABACDABABCABAB", "ABABCABAB")); + assertEquals(List.of(), KMP.kmpMatcher("ABCDE", "FGH")); + assertEquals(List.of(), KMP.kmpMatcher("A", "AA")); + assertEquals(List.of(0, 1, 2), KMP.kmpMatcher("AAA", "A")); + assertEquals(List.of(0), KMP.kmpMatcher("A", "A")); + assertEquals(List.of(), KMP.kmpMatcher("", "A")); + assertEquals(List.of(), KMP.kmpMatcher("A", "")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java b/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java new file mode 100644 index 000000000000..c22cc77df18a --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/KasaiAlgorithmTest.java @@ -0,0 +1,75 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class KasaiAlgorithmTest { + + @Test + public void testKasaiBanana() { + String text = "banana"; + // Suffixes: + // 0: banana + // 1: anana + // 2: nana + // 3: ana + // 4: na + // 5: a + // + // Sorted Suffixes: + // 5: a + // 3: ana + // 1: anana + // 0: banana + // 4: na + // 2: nana + int[] suffixArr = {5, 3, 1, 0, 4, 2}; + + int[] expectedLcp = {1, 3, 0, 0, 2, 0}; + + assertArrayEquals(expectedLcp, KasaiAlgorithm.kasai(text, suffixArr)); + } + + @Test + public void testKasaiAaaa() { + String text = "aaaa"; + // Sorted Suffixes: + // 3: a + // 2: aa + // 1: aaa + // 0: aaaa + int[] suffixArr = {3, 2, 1, 0}; + int[] expectedLcp = {1, 2, 3, 0}; + + assertArrayEquals(expectedLcp, KasaiAlgorithm.kasai(text, suffixArr)); + } + + @Test + public void testKasaiEmptyString() { + assertArrayEquals(new int[0], KasaiAlgorithm.kasai("", new int[0])); + } + + @Test + public void testKasaiSingleChar() { + assertArrayEquals(new int[] {0}, KasaiAlgorithm.kasai("A", new int[] {0})); + } + + @Test + public void testKasaiNullTextOrSuffixArray() { + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai(null, new int[] {0})); + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", null)); + } + + @Test + public void testKasaiInvalidSuffixArrayLength() { + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {0, 1})); + } + + @Test + public void testKasaiInvalidSuffixArrayIndex() { + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {1})); // Out of bounds + assertThrows(IllegalArgumentException.class, () -> KasaiAlgorithm.kasai("A", new int[] {-1})); // Out of bounds + } +} diff --git a/src/test/java/com/thealgorithms/strings/LengthOfLastWordTest.java b/src/test/java/com/thealgorithms/strings/LengthOfLastWordTest.java new file mode 100644 index 000000000000..46a0a6eb0008 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/LengthOfLastWordTest.java @@ -0,0 +1,18 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class LengthOfLastWordTest { + @Test + public void testLengthOfLastWord() { + assertEquals(5, new LengthOfLastWord().lengthOfLastWord("Hello World")); + assertEquals(4, new LengthOfLastWord().lengthOfLastWord(" fly me to the moon ")); + assertEquals(6, new LengthOfLastWord().lengthOfLastWord("luffy is still joyboy")); + assertEquals(5, new LengthOfLastWord().lengthOfLastWord("Hello")); + assertEquals(0, new LengthOfLastWord().lengthOfLastWord(" ")); + assertEquals(0, new LengthOfLastWord().lengthOfLastWord("")); + assertEquals(3, new LengthOfLastWord().lengthOfLastWord("JUST LIE ")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java new file mode 100644 index 000000000000..e54abcf2f1f3 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java @@ -0,0 +1,36 @@ +package com.thealgorithms.strings; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class LongestCommonSubstringTest { + + @Test + public void testNullOrEmptyInputs() { + assertEquals("", LongestCommonSubstring.longestCommonSubstring(null, "abc")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", null)); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("", "abc")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", "")); + } + + @Test + public void testNormalSubstrings() { + assertEquals("cde", LongestCommonSubstring.longestCommonSubstring("abcdef", "zcdemf")); + assertEquals("abc", LongestCommonSubstring.longestCommonSubstring("abc", "abc")); + assertEquals("cdef", LongestCommonSubstring.longestCommonSubstring("abcdef", "cdefgh")); + } + + @Test + public void testSingleCharacterAndNoMatch() { + assertEquals("a", LongestCommonSubstring.longestCommonSubstring("a", "a")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", "xyz")); + } + + @Test + public void testMultipleMatchesFirstLongest() { + // Keeps the first matched longest substring when lengths are tied + assertEquals("abc", LongestCommonSubstring.longestCommonSubstring("abcXdef", "abcYdef")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java deleted file mode 100644 index aa13c0f4a474..000000000000 --- a/src/test/java/com/thealgorithms/strings/LongestPalindromicSubstringTest.java +++ /dev/null @@ -1,21 +0,0 @@ -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.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -class LongestPalindromicSubstringTest { - - @ParameterizedTest - @MethodSource("provideTestCasesForLongestPalindrome") - void testLongestPalindrome(String input, String expected) { - assertEquals(expected, LongestPalindromicSubstring.longestPalindrome(input)); - } - - private static Stream provideTestCasesForLongestPalindrome() { - return Stream.of(Arguments.of("babad", "bab"), Arguments.of("cbbd", "bb"), Arguments.of("a", "a"), Arguments.of("", ""), Arguments.of("abc", "a"), Arguments.of(null, ""), Arguments.of("aaaaa", "aaaaa")); - } -} diff --git a/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java new file mode 100644 index 000000000000..366f6863340d --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/LongestRepeatedSubstringTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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; + +class LongestRepeatedSubstringTest { + + @ParameterizedTest(name = "\"{0}\" -> \"{1}\"") + @MethodSource("provideTestCases") + void testLongestRepeatedSubstring(String input, String expected) { + assertEquals(expected, LongestRepeatedSubstring.longestRepeatedSubstring(input)); + } + + private static Stream provideTestCases() { + return Stream.of(Arguments.of("banana", "ana"), Arguments.of("abcabc", "abc"), Arguments.of("aaaa", "aaa"), Arguments.of("abcd", ""), Arguments.of("a", ""), Arguments.of("", ""), Arguments.of(null, ""), Arguments.of("aab", "a"), Arguments.of("aa", "a"), Arguments.of("mississippi", "issi")); + } + + @ParameterizedTest(name = "\"{0}\" -> LCP={1}") + @MethodSource("provideLcpTestCases") + void testBuildLcpArray(String input, int[] expectedLcp) { + int[] suffixArray = SuffixArray.buildSuffixArray(input); + assertArrayEquals(expectedLcp, LongestRepeatedSubstring.buildLcpArray(input, suffixArray)); + } + + private static Stream provideLcpTestCases() { + return Stream.of(Arguments.of("banana", new int[] {1, 3, 0, 0, 2}), Arguments.of("ab", new int[] {0})); + } +} diff --git a/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java b/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java new file mode 100644 index 000000000000..016f72ff9d7f --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/MoveHashToEndTest.java @@ -0,0 +1,54 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +public class MoveHashToEndTest { + + @Test + void testBasicCase() { + assertEquals("helllo###", MoveHashToEnd.moveHashToEnd("h#e#l#llo")); + } + + @Test + void testNoHash() { + assertEquals("hello", MoveHashToEnd.moveHashToEnd("hello")); + } + + @Test + void testAllHashes() { + assertEquals("###", MoveHashToEnd.moveHashToEnd("###")); + } + + @Test + void testHashAtEnd() { + assertEquals("hello#", MoveHashToEnd.moveHashToEnd("hello#")); + } + + @Test + void testHashAtStart() { + assertEquals("hello#", MoveHashToEnd.moveHashToEnd("#hello")); + } + + @Test + void testEmptyString() { + assertEquals("", MoveHashToEnd.moveHashToEnd("")); + } + + @Test + void testNullInput() { + assertNull(MoveHashToEnd.moveHashToEnd(null)); + } + + @Test + void testSingleHash() { + assertEquals("#", MoveHashToEnd.moveHashToEnd("#")); + } + + @Test + void testSingleNonHashChar() { + assertEquals("a", MoveHashToEnd.moveHashToEnd("a")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/RabinKarpTest.java b/src/test/java/com/thealgorithms/strings/RabinKarpTest.java new file mode 100644 index 000000000000..6dfd099e9bca --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/RabinKarpTest.java @@ -0,0 +1,46 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import org.junit.jupiter.api.Test; + +public class RabinKarpTest { + + @Test + public void testNullInputs() { + assertEquals(List.of(), RabinKarp.search(null, "A")); + assertEquals(List.of(), RabinKarp.search("A", null)); + assertEquals(List.of(), RabinKarp.search(null, null)); + } + + @Test + public void testHashCollision() { + // 'a' = 97. (char)198 % 101 = 97. + // For length 1, h = 1. p = 97. t = 198 % 101 = 97. + // Collision occurs, loop checks characters: 198 != 97, breaks. + char collisionChar = (char) 198; + String text = String.valueOf(collisionChar); + String pattern = "a"; + assertEquals(List.of(), RabinKarp.search(text, pattern)); + } + + @Test + public void testSearchWithCustomQ() { + // Using a different prime + assertEquals(List.of(0, 1), RabinKarp.search("AAAA", "AAA", 13)); + } + + @Test + public void testRabinKarpSearch() { + assertEquals(List.of(0, 1), RabinKarp.search("AAAAABAAABA", "AAAA")); + assertEquals(List.of(0, 3), RabinKarp.search("ABCABC", "ABC")); + assertEquals(List.of(10), RabinKarp.search("ABABDABACDABABCABAB", "ABABCABAB")); + assertEquals(List.of(), RabinKarp.search("ABCDE", "FGH")); + assertEquals(List.of(), RabinKarp.search("A", "AA")); + assertEquals(List.of(0, 1, 2), RabinKarp.search("AAA", "A")); + assertEquals(List.of(0), RabinKarp.search("A", "A")); + assertEquals(List.of(), RabinKarp.search("", "A")); + assertEquals(List.of(), RabinKarp.search("A", "")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java b/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java new file mode 100644 index 000000000000..3beb2e83399b --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/RemoveStarsTest.java @@ -0,0 +1,28 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class RemoveStarsTest { + + @Test + void testExampleCase() { + assertEquals("lecoe", RemoveStars.removeStars("leet**cod*e")); + } + + @Test + void testAllStars() { + assertEquals("", RemoveStars.removeStars("abc***")); + } + + @Test + void testNoStars() { + assertEquals("hello", RemoveStars.removeStars("hello")); + } + + @Test + void testSingleCharacter() { + assertEquals("", RemoveStars.removeStars("a*")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/TitleCaseTest.java b/src/test/java/com/thealgorithms/strings/TitleCaseTest.java new file mode 100644 index 000000000000..37bb44ed2c7b --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/TitleCaseTest.java @@ -0,0 +1,39 @@ +package com.thealgorithms.strings; +// author: Vraj Prajapati @Rosander0 + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TitleCaseTest { + + @Test + public void testNullOrEmptyInputs() { + assertEquals("", TitleCase.toTitleCase(null)); + assertEquals("", TitleCase.toTitleCase("")); + } + + @Test + public void testSingleWord() { + assertEquals("Hello", TitleCase.toTitleCase("hello")); + assertEquals("Hello", TitleCase.toTitleCase("HELLO")); + assertEquals("A", TitleCase.toTitleCase("a")); + } + + @Test + public void testMultipleWords() { + assertEquals("The Quick Brown Fox", TitleCase.toTitleCase("the quick brown fox")); + assertEquals("The Quick Brown Fox", TitleCase.toTitleCase("THE QUICK BROWN FOX")); + assertEquals("Already Title Case", TitleCase.toTitleCase("already Title Case")); + } + + @Test + public void testWhitespace() { + assertEquals(" Spaces ", TitleCase.toTitleCase(" spaces ")); + } + + @Test + public void testQuotedWords() { + assertEquals("\"Hello\"", TitleCase.toTitleCase("\"hello\"")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java b/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java new file mode 100644 index 000000000000..42b2d04ff265 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/TopKFrequentWordsTest.java @@ -0,0 +1,34 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +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 TopKFrequentWordsTest { + + @ParameterizedTest + @MethodSource("validTestCases") + void testFindTopKFrequentWords(String[] words, int k, List expected) { + assertEquals(expected, TopKFrequentWords.findTopKFrequentWords(words, k)); + } + + static Stream validTestCases() { + return Stream.of(Arguments.of(new String[] {"i", "love", "leetcode", "i", "love", "coding"}, 2, List.of("i", "love")), Arguments.of(new String[] {"the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"}, 4, List.of("the", "is", "sunny", "day")), + Arguments.of(new String[] {"bbb", "aaa", "bbb", "aaa", "ccc"}, 2, List.of("aaa", "bbb")), Arguments.of(new String[] {"one", "two", "three"}, 10, List.of("one", "three", "two")), Arguments.of(new String[] {}, 3, List.of()), Arguments.of(new String[] {"x", "x", "y"}, 0, List.of())); + } + + @ParameterizedTest + @MethodSource("invalidTestCases") + void testFindTopKFrequentWordsInvalidInput(String[] words, int k) { + assertThrows(IllegalArgumentException.class, () -> TopKFrequentWords.findTopKFrequentWords(words, k)); + } + + static Stream invalidTestCases() { + return Stream.of(Arguments.of((String[]) null, 1), Arguments.of(new String[] {"a", null, "b"}, 2), Arguments.of(new String[] {"a"}, -1)); + } +} diff --git a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java b/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java deleted file mode 100644 index 411b11e743b8..000000000000 --- a/src/test/java/com/thealgorithms/strings/ValidParenthesesTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.thealgorithms.strings; - -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 { - - @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 testNullInputThrows() { - IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ValidParentheses.isValid(null)); - assertEquals("Input string cannot be null", ex.getMessage()); - } - - @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/strings/WordLadderTest.java b/src/test/java/com/thealgorithms/strings/WordLadderTest.java index 221953411da7..c029940abfb0 100644 --- a/src/test/java/com/thealgorithms/strings/WordLadderTest.java +++ b/src/test/java/com/thealgorithms/strings/WordLadderTest.java @@ -24,7 +24,7 @@ public class WordLadderTest { public void testWordLadder() { List wordList1 = Arrays.asList("hot", "dot", "dog", "lot", "log", "cog"); - assertEquals(WordLadder.ladderLength("hit", "cog", wordList1), 5); + assertEquals(5, WordLadder.ladderLength("hit", "cog", wordList1)); } /** @@ -39,7 +39,7 @@ public void testWordLadder() { public void testWordLadder2() { List wordList2 = Arrays.asList("hot", "dot", "dog", "lot", "log"); - assertEquals(WordLadder.ladderLength("hit", "cog", wordList2), 0); + assertEquals(0, WordLadder.ladderLength("hit", "cog", wordList2)); } /** @@ -54,7 +54,7 @@ public void testWordLadder2() { public void testWordLadder3() { List wordList3 = emptyList(); - assertEquals(WordLadder.ladderLength("hit", "cog", wordList3), 0); + assertEquals(0, WordLadder.ladderLength("hit", "cog", wordList3)); } @ParameterizedTest diff --git a/src/test/java/com/thealgorithms/strings/ZAlgorithmTest.java b/src/test/java/com/thealgorithms/strings/ZAlgorithmTest.java new file mode 100644 index 000000000000..df749ed9a8b5 --- /dev/null +++ b/src/test/java/com/thealgorithms/strings/ZAlgorithmTest.java @@ -0,0 +1,25 @@ +package com.thealgorithms.strings; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class ZAlgorithmTest { + + @Test + void testZFunction() { + int[] z = ZAlgorithm.zFunction("aaaaa"); + assertArrayEquals(new int[] {0, 4, 3, 2, 1}, z); + } + + @Test + void testSearchFound() { + assertEquals(2, ZAlgorithm.search("abcabca", "cab")); + } + + @Test + void testSearchNotFound() { + assertEquals(-1, ZAlgorithm.search("abcdef", "gh")); + } +} diff --git a/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java b/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java index 2cbbfe3d2dd8..9bf118c9b844 100644 --- a/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java +++ b/src/test/java/com/thealgorithms/strings/zigZagPattern/ZigZagPatternTest.java @@ -9,8 +9,8 @@ public class ZigZagPatternTest { public void testZigZagPattern() { String input1 = "HelloWorldFromJava"; String input2 = "javaIsAProgrammingLanguage"; - Assertions.assertEquals(ZigZagPattern.encode(input1, 4), "HooeWrrmalolFJvlda"); - Assertions.assertEquals(ZigZagPattern.encode(input2, 4), "jAaLgasPrmgaaevIrgmnnuaoig"); + Assertions.assertEquals("HooeWrrmalolFJvlda", ZigZagPattern.encode(input1, 4)); + Assertions.assertEquals("jAaLgasPrmgaaevIrgmnnuaoig", ZigZagPattern.encode(input2, 4)); // Edge cases Assertions.assertEquals("ABC", ZigZagPattern.encode("ABC", 1)); // Single row Assertions.assertEquals("A", ZigZagPattern.encode("A", 2)); // numRows > length of string